#include "s2_amalgamation.h"
#if S2_INTERNAL_MINIZ
/* start of file miniz-sgb.c */
#undef MINIZ_NO_STDIO
#define MINIZ_NO_STDIO
#undef MINIZ_NO_ARCHIVE_APIS
#define MINIZ_NO_ARCHIVE_APIS
/* miniz.c 2.0.6 beta - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
   See "unlicense" statement at the end of this file.
   Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013
   Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt

   Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define
   MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros).

   * Low-level Deflate/Inflate implementation notes:

     Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or
     greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses
     approximately as well as zlib.

     Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function
     coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory
     block large enough to hold the entire file.

     The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation.

   * zlib-style API notes:

     miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in
     zlib replacement in many apps:
        The z_stream struct, optional memory allocation callbacks
        deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound
        inflateInit/inflateInit2/inflate/inflateEnd
        compress, compress2, compressBound, uncompress
        CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines.
        Supports raw deflate streams or standard zlib streams with adler-32 checking.

     Limitations:
      The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries.
      I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but
      there are no guarantees that miniz.c pulls this off perfectly.

   * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by
     Alex Evans. Supports 1-4 bytes/pixel images.

   * ZIP archive API notes:

     The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to
     get the job done with minimal fuss. There are simple API's to retrieve file information, read files from
     existing archives, create new archives, append new files to existing archives, or clone archive data from
     one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h),
     or you can specify custom file read/write callbacks.

     - Archive reading: Just call this function to read a single file from a disk archive:

      void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name,
        size_t *pSize, mz_uint zip_flags);

     For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central
     directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files.

     - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file:

     int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);

     The locate operation can optionally check file comments too, which (as one example) can be used to identify
     multiple versions of the same file in an archive. This function uses a simple linear search through the central
     directory, so it's not very fast.

     Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and
     retrieve detailed info on each file by calling mz_zip_reader_file_stat().

     - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data
     to disk and builds an exact image of the central directory in memory. The central directory image is written
     all at once at the end of the archive file when the archive is finalized.

     The archive writer can optionally align each file's local header and file data to any power of 2 alignment,
     which can be useful when the archive will be read from optical media. Also, the writer supports placing
     arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still
     readable by any ZIP tool.

     - Archive appending: The simple way to add a single file to an archive is to call this function:

      mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name,
        const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);

     The archive will be created if it doesn't already exist, otherwise it'll be appended to.
     Note the appending is done in-place and is not an atomic operation, so if something goes wrong
     during the operation it's possible the archive could be left without a central directory (although the local
     file headers and file data will be fine, so the archive will be recoverable).

     For more complex archive modification scenarios:
     1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to
     preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the
     compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and
     you're done. This is safe but requires a bunch of temporary disk space or heap memory.

     2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(),
     append new files as needed, then finalize the archive which will write an updated central directory to the
     original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a
     possibility that the archive's central directory could be lost with this method if anything goes wrong, though.

     - ZIP archive support limitations:
     No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files.
     Requires streams capable of seeking.

   * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the
     below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it.

   * Important: For best perf. be sure to customize the below macros for your target platform:
     #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
     #define MINIZ_LITTLE_ENDIAN 1
     #define MINIZ_HAS_64BIT_REGISTERS 1

   * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz
     uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files
     (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes).
*/





/* Defines to completely disable specific portions of miniz.c: 
   If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */

/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */
/*#define MINIZ_NO_STDIO */

/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */
/* get/set file times, and the C run-time funcs that get/set times won't be called. */
/* The current downside is the times written to your archives will be from 1979. */
/*#define MINIZ_NO_TIME */

/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */
/*#define MINIZ_NO_ARCHIVE_APIS */

/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */
/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */

/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */
/*#define MINIZ_NO_ZLIB_APIS */

/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */
/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */

/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. 
   Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc
   callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user
   functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */
/*#define MINIZ_NO_MALLOC */

#if defined(__TINYC__) && (defined(__linux) || defined(__linux__))
/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */
#define MINIZ_NO_TIME
#endif

#include <stddef.h>

#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS)
#include <time.h>
#endif

#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */
#define MINIZ_X86_OR_X64_CPU 1
#else
#define MINIZ_X86_OR_X64_CPU 0
#endif

#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU
/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */
#define MINIZ_LITTLE_ENDIAN 1
#else
#define MINIZ_LITTLE_ENDIAN 0
#endif

#if MINIZ_X86_OR_X64_CPU
/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
#else
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0
#endif

#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__)
/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */
#define MINIZ_HAS_64BIT_REGISTERS 1
#else
#define MINIZ_HAS_64BIT_REGISTERS 0
#endif

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- zlib-style API Definitions. */

/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */
typedef unsigned long mz_ulong;

/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */
void mz_free(void *p);

#define MZ_ADLER32_INIT (1)
/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */
mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len);

#define MZ_CRC32_INIT (0)
/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */
mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len);

/* Compression strategies. */
enum
{
    MZ_DEFAULT_STRATEGY = 0,
    MZ_FILTERED = 1,
    MZ_HUFFMAN_ONLY = 2,
    MZ_RLE = 3,
    MZ_FIXED = 4
};

/* Method */
#define MZ_DEFLATED 8

/* Heap allocation callbacks.
Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */
typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size);
typedef void (*mz_free_func)(void *opaque, void *address);
typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size);

/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */
enum
{
    MZ_NO_COMPRESSION = 0,
    MZ_BEST_SPEED = 1,
    MZ_BEST_COMPRESSION = 9,
    MZ_UBER_COMPRESSION = 10,
    MZ_DEFAULT_LEVEL = 6,
    MZ_DEFAULT_COMPRESSION = -1
};

#define MZ_VERSION "10.0.1"
#define MZ_VERNUM 0xA010
#define MZ_VER_MAJOR 10
#define MZ_VER_MINOR 0
#define MZ_VER_REVISION 1
#define MZ_VER_SUBREVISION 0

#ifndef MINIZ_NO_ZLIB_APIS

/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */
enum
{
    MZ_NO_FLUSH = 0,
    MZ_PARTIAL_FLUSH = 1,
    MZ_SYNC_FLUSH = 2,
    MZ_FULL_FLUSH = 3,
    MZ_FINISH = 4,
    MZ_BLOCK = 5
};

/* Return status codes. MZ_PARAM_ERROR is non-standard. */
enum
{
    MZ_OK = 0,
    MZ_STREAM_END = 1,
    MZ_NEED_DICT = 2,
    MZ_ERRNO = -1,
    MZ_STREAM_ERROR = -2,
    MZ_DATA_ERROR = -3,
    MZ_MEM_ERROR = -4,
    MZ_BUF_ERROR = -5,
    MZ_VERSION_ERROR = -6,
    MZ_PARAM_ERROR = -10000
};

/* Window bits */
#define MZ_DEFAULT_WINDOW_BITS 15

struct mz_internal_state;

/* Compression/decompression stream struct. */
typedef struct mz_stream_s
{
    const unsigned char *next_in; /* pointer to next byte to read */
    unsigned int avail_in;        /* number of bytes available at next_in */
    mz_ulong total_in;            /* total number of bytes consumed so far */

    unsigned char *next_out; /* pointer to next byte to write */
    unsigned int avail_out;  /* number of bytes that can be written to next_out */
    mz_ulong total_out;      /* total number of bytes produced so far */

    char *msg;                       /* error msg (unused) */
    struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */

    mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */
    mz_free_func zfree;   /* optional heap free function (defaults to free) */
    void *opaque;         /* heap alloc function user pointer */

    int data_type;     /* data_type (unused) */
    mz_ulong adler;    /* adler32 of the source or uncompressed data */
    mz_ulong reserved; /* not used */
} mz_stream;

typedef mz_stream *mz_streamp;

/* Returns the version string of miniz.c. */
const char *mz_version(void);

/* mz_deflateInit() initializes a compressor with default options: */
/* Parameters: */
/*  pStream must point to an initialized mz_stream struct. */
/*  level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */
/*  level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */
/*  (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */
/* Return values: */
/*  MZ_OK on success. */
/*  MZ_STREAM_ERROR if the stream is bogus. */
/*  MZ_PARAM_ERROR if the input parameters are bogus. */
/*  MZ_MEM_ERROR on out of memory. */
int mz_deflateInit(mz_streamp pStream, int level);

/* mz_deflateInit2() is like mz_deflate(), except with more control: */
/* Additional parameters: */
/*   method must be MZ_DEFLATED */
/*   window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */
/*   mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */
int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy);

/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */
int mz_deflateReset(mz_streamp pStream);

/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */
/* Parameters: */
/*   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */
/*   flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */
/* Return values: */
/*   MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */
/*   MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */
/*   MZ_STREAM_ERROR if the stream is bogus. */
/*   MZ_PARAM_ERROR if one of the parameters is invalid. */
/*   MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */
int mz_deflate(mz_streamp pStream, int flush);

/* mz_deflateEnd() deinitializes a compressor: */
/* Return values: */
/*  MZ_OK on success. */
/*  MZ_STREAM_ERROR if the stream is bogus. */
int mz_deflateEnd(mz_streamp pStream);

/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */
mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len);

/* Single-call compression functions mz_compress() and mz_compress2(): */
/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */
int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level);

/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */
mz_ulong mz_compressBound(mz_ulong source_len);

/* Initializes a decompressor. */
int mz_inflateInit(mz_streamp pStream);

/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */
/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */
int mz_inflateInit2(mz_streamp pStream, int window_bits);

/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */
/* Parameters: */
/*   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */
/*   flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */
/*   On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */
/*   MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */
/* Return values: */
/*   MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */
/*   MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */
/*   MZ_STREAM_ERROR if the stream is bogus. */
/*   MZ_DATA_ERROR if the deflate stream is invalid. */
/*   MZ_PARAM_ERROR if one of the parameters is invalid. */
/*   MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */
/*   with more input data, or with more room in the output buffer (except when using single call decompression, described above). */
int mz_inflate(mz_streamp pStream, int flush);

/* Deinitializes a decompressor. */
int mz_inflateEnd(mz_streamp pStream);

/* Single-call decompression. */
/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */
int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);

/* Returns a string description of the specified error code, or NULL if the error code is invalid. */
const char *mz_error(int err);

/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */
/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */
#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES
typedef unsigned char Byte;
typedef unsigned int uInt;
typedef mz_ulong uLong;
typedef Byte Bytef;
typedef uInt uIntf;
typedef char charf;
typedef int intf;
typedef void *voidpf;
typedef uLong uLongf;
typedef void *voidp;
typedef void *const voidpc;
#define Z_NULL 0
#define Z_NO_FLUSH MZ_NO_FLUSH
#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH
#define Z_SYNC_FLUSH MZ_SYNC_FLUSH
#define Z_FULL_FLUSH MZ_FULL_FLUSH
#define Z_FINISH MZ_FINISH
#define Z_BLOCK MZ_BLOCK
#define Z_OK MZ_OK
#define Z_STREAM_END MZ_STREAM_END
#define Z_NEED_DICT MZ_NEED_DICT
#define Z_ERRNO MZ_ERRNO
#define Z_STREAM_ERROR MZ_STREAM_ERROR
#define Z_DATA_ERROR MZ_DATA_ERROR
#define Z_MEM_ERROR MZ_MEM_ERROR
#define Z_BUF_ERROR MZ_BUF_ERROR
#define Z_VERSION_ERROR MZ_VERSION_ERROR
#define Z_PARAM_ERROR MZ_PARAM_ERROR
#define Z_NO_COMPRESSION MZ_NO_COMPRESSION
#define Z_BEST_SPEED MZ_BEST_SPEED
#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION
#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION
#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY
#define Z_FILTERED MZ_FILTERED
#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY
#define Z_RLE MZ_RLE
#define Z_FIXED MZ_FIXED
#define Z_DEFLATED MZ_DEFLATED
#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS
#define alloc_func mz_alloc_func
#define free_func mz_free_func
#define internal_state mz_internal_state
#define z_stream mz_stream
#define deflateInit mz_deflateInit
#define deflateInit2 mz_deflateInit2
#define deflateReset mz_deflateReset
#define deflate mz_deflate
#define deflateEnd mz_deflateEnd
#define deflateBound mz_deflateBound
#define compress mz_compress
#define compress2 mz_compress2
#define compressBound mz_compressBound
#define inflateInit mz_inflateInit
#define inflateInit2 mz_inflateInit2
#define inflate mz_inflate
#define inflateEnd mz_inflateEnd
#define uncompress mz_uncompress
#define crc32 mz_crc32
#define adler32 mz_adler32
#define MAX_WBITS 15
#define MAX_MEM_LEVEL 9
#define zError mz_error
#define ZLIB_VERSION MZ_VERSION
#define ZLIB_VERNUM MZ_VERNUM
#define ZLIB_VER_MAJOR MZ_VER_MAJOR
#define ZLIB_VER_MINOR MZ_VER_MINOR
#define ZLIB_VER_REVISION MZ_VER_REVISION
#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION
#define zlibVersion mz_version
#define zlib_version mz_version()
#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */

#endif /* MINIZ_NO_ZLIB_APIS */

#ifdef __cplusplus
}
#endif
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

/* ------------------- Types and macros */
typedef unsigned char mz_uint8;
typedef signed short mz_int16;
typedef unsigned short mz_uint16;
typedef unsigned int mz_uint32;
typedef unsigned int mz_uint;
typedef int64_t mz_int64;
typedef uint64_t mz_uint64;
typedef int mz_bool;

#define MZ_FALSE (0)
#define MZ_TRUE (1)

/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */
#ifdef _MSC_VER
#define MZ_MACRO_END while (0, 0)
#else
#define MZ_MACRO_END while (0)
#endif

#ifdef MINIZ_NO_STDIO
#define MZ_FILE void *
#else
#include <stdio.h>
#define MZ_FILE FILE
#endif /* #ifdef MINIZ_NO_STDIO */

#ifdef MINIZ_NO_TIME
typedef struct mz_dummy_time_t_tag
{
    int m_dummy;
} mz_dummy_time_t;
#define MZ_TIME_T mz_dummy_time_t
#else
#define MZ_TIME_T time_t
#endif

#define MZ_ASSERT(x) assert(x)

#ifdef MINIZ_NO_MALLOC
#define MZ_MALLOC(x) NULL
#define MZ_FREE(x) (void)x, ((void)0)
#define MZ_REALLOC(p, x) NULL
#else
#define MZ_MALLOC(x) malloc(x)
#define MZ_FREE(x) free(x)
#define MZ_REALLOC(p, x) realloc(p, x)
#endif

#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
#define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
#define MZ_READ_LE32(p) *((const mz_uint32 *)(p))
#else
#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U))
#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))
#endif

#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U))

#ifdef _MSC_VER
#define MZ_FORCEINLINE __forceinline
#elif defined(__GNUC__)
#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__))
#else
#define MZ_FORCEINLINE inline
#endif

#ifdef __cplusplus
extern "C" {
#endif

extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size);
extern void miniz_def_free_func(void *opaque, void *address);
extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size);

#define MZ_UINT16_MAX (0xFFFFU)
#define MZ_UINT32_MAX (0xFFFFFFFFU)

#ifdef __cplusplus
}
#endif


#ifdef __cplusplus
extern "C" {
#endif
/* ------------------- Low-level Compression API Definitions */

/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */
#define TDEFL_LESS_MEMORY 0

/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */
/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */
enum
{
    TDEFL_HUFFMAN_ONLY = 0,
    TDEFL_DEFAULT_MAX_PROBES = 128,
    TDEFL_MAX_PROBES_MASK = 0xFFF
};

/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */
/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */
/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */
/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */
/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */
/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */
/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */
/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */
/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */
enum
{
    TDEFL_WRITE_ZLIB_HEADER = 0x01000,
    TDEFL_COMPUTE_ADLER32 = 0x02000,
    TDEFL_GREEDY_PARSING_FLAG = 0x04000,
    TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000,
    TDEFL_RLE_MATCHES = 0x10000,
    TDEFL_FILTER_MATCHES = 0x20000,
    TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000,
    TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000
};

/* High level compression functions: */
/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */
/* On entry: */
/*  pSrc_buf, src_buf_len: Pointer and size of source block to compress. */
/*  flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */
/* On return: */
/*  Function returns a pointer to the compressed data, or NULL on failure. */
/*  *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */
/*  The caller must free() the returned block when it's no longer needed. */
void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);

/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */
/* Returns 0 on failure. */
size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);

/* Compresses an image to a compressed PNG file in memory. */
/* On entry: */
/*  pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */
/*  The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */
/*  level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */
/*  If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */
/* On return: */
/*  Function returns a pointer to the compressed data, or NULL on failure. */
/*  *pLen_out will be set to the size of the PNG image file. */
/*  The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */
void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip);
void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out);

/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */
typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);

/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */
mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

enum
{
    TDEFL_MAX_HUFF_TABLES = 3,
    TDEFL_MAX_HUFF_SYMBOLS_0 = 288,
    TDEFL_MAX_HUFF_SYMBOLS_1 = 32,
    TDEFL_MAX_HUFF_SYMBOLS_2 = 19,
    TDEFL_LZ_DICT_SIZE = 32768,
    TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1,
    TDEFL_MIN_MATCH_LEN = 3,
    TDEFL_MAX_MATCH_LEN = 258
};

/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */
#if TDEFL_LESS_MEMORY
enum
{
    TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024,
    TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,
    TDEFL_MAX_HUFF_SYMBOLS = 288,
    TDEFL_LZ_HASH_BITS = 12,
    TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,
    TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,
    TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS
};
#else
enum
{
    TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024,
    TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,
    TDEFL_MAX_HUFF_SYMBOLS = 288,
    TDEFL_LZ_HASH_BITS = 15,
    TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,
    TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,
    TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS
};
#endif

/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */
typedef enum {
    TDEFL_STATUS_BAD_PARAM = -2,
    TDEFL_STATUS_PUT_BUF_FAILED = -1,
    TDEFL_STATUS_OKAY = 0,
    TDEFL_STATUS_DONE = 1
} tdefl_status;

/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */
typedef enum {
    TDEFL_NO_FLUSH = 0,
    TDEFL_SYNC_FLUSH = 2,
    TDEFL_FULL_FLUSH = 3,
    TDEFL_FINISH = 4
} tdefl_flush;

/* tdefl's compression state structure. */
typedef struct
{
    tdefl_put_buf_func_ptr m_pPut_buf_func;
    void *m_pPut_buf_user;
    mz_uint m_flags, m_max_probes[2];
    int m_greedy_parsing;
    mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size;
    mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end;
    mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer;
    mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish;
    tdefl_status m_prev_return_status;
    const void *m_pIn_buf;
    void *m_pOut_buf;
    size_t *m_pIn_buf_size, *m_pOut_buf_size;
    tdefl_flush m_flush;
    const mz_uint8 *m_pSrc;
    size_t m_src_buf_left, m_out_buf_ofs;
    mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1];
    mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
    mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
    mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
    mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE];
    mz_uint16 m_next[TDEFL_LZ_DICT_SIZE];
    mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE];
    mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE];
} tdefl_compressor;

/* Initializes the compressor. */
/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */
/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */
/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */
/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */
tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */
tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush);

/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */
/* tdefl_compress_buffer() always consumes the entire input buffer. */
tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush);

tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d);
mz_uint32 tdefl_get_adler32(tdefl_compressor *d);

/* Create tdefl_compress() flags given zlib-style compression parameters. */
/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */
/* window_bits may be -15 (raw deflate) or 15 (zlib) */
/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */
mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy);

/* Allocate the tdefl_compressor structure in C so that */
/* non-C language bindings to tdefl_ API don't need to worry about */
/* structure size and allocation mechanism. */
tdefl_compressor *tdefl_compressor_alloc();
void tdefl_compressor_free(tdefl_compressor *pComp);

#ifdef __cplusplus
}
#endif

/* ------------------- Low-level Decompression API Definitions */

#ifdef __cplusplus
extern "C" {
#endif
/* Decompression flags used by tinfl_decompress(). */
/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */
/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */
/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */
/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */
enum
{
    TINFL_FLAG_PARSE_ZLIB_HEADER = 1,
    TINFL_FLAG_HAS_MORE_INPUT = 2,
    TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,
    TINFL_FLAG_COMPUTE_ADLER32 = 8
};

/* High level decompression functions: */
/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */
/* On entry: */
/*  pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */
/* On return: */
/*  Function returns a pointer to the decompressed data, or NULL on failure. */
/*  *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */
/*  The caller must call mz_free() on the returned block when it's no longer needed. */
void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);

/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */
/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */
#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))
size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);

/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */
/* Returns 1 on success or 0 on failure. */
typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);
int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

struct tinfl_decompressor_tag;
typedef struct tinfl_decompressor_tag tinfl_decompressor;

/* Allocate the tinfl_decompressor structure in C so that */
/* non-C language bindings to tinfl_ API don't need to worry about */
/* structure size and allocation mechanism. */

tinfl_decompressor *tinfl_decompressor_alloc();
void tinfl_decompressor_free(tinfl_decompressor *pDecomp);

/* Max size of LZ dictionary. */
#define TINFL_LZ_DICT_SIZE 32768

/* Return status. */
typedef enum {
    /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */
    /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */
    /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */
    TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4,

    /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */
    TINFL_STATUS_BAD_PARAM = -3,

    /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */
    TINFL_STATUS_ADLER32_MISMATCH = -2,

    /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */
    TINFL_STATUS_FAILED = -1,

    /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */

    /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */
    /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */
    TINFL_STATUS_DONE = 0,

    /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */
    /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */
    /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */
    TINFL_STATUS_NEEDS_MORE_INPUT = 1,

    /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */
    /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */
    /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */
    /* so I may need to add some code to address this. */
    TINFL_STATUS_HAS_MORE_OUTPUT = 2
} tinfl_status;

/* Initializes the decompressor to its initial state. */
#define tinfl_init(r)     \
    do                    \
    {                     \
        (r)->m_state = 0; \
    }                     \
    MZ_MACRO_END
#define tinfl_get_adler32(r) (r)->m_check_adler32

/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */
/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */
tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags);

/* Internal/private bits follow. */
enum
{
    TINFL_MAX_HUFF_TABLES = 3,
    TINFL_MAX_HUFF_SYMBOLS_0 = 288,
    TINFL_MAX_HUFF_SYMBOLS_1 = 32,
    TINFL_MAX_HUFF_SYMBOLS_2 = 19,
    TINFL_FAST_LOOKUP_BITS = 10,
    TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS
};

typedef struct
{
    mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0];
    mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2];
} tinfl_huff_table;

#if MINIZ_HAS_64BIT_REGISTERS
#define TINFL_USE_64BIT_BITBUF 1
#else
#define TINFL_USE_64BIT_BITBUF 0
#endif

#if TINFL_USE_64BIT_BITBUF
typedef mz_uint64 tinfl_bit_buf_t;
#define TINFL_BITBUF_SIZE (64)
#else
typedef mz_uint32 tinfl_bit_buf_t;
#define TINFL_BITBUF_SIZE (32)
#endif

struct tinfl_decompressor_tag
{
    mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES];
    tinfl_bit_buf_t m_bit_buf;
    size_t m_dist_from_out_buf_start;
    tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES];
    mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137];
};

#ifdef __cplusplus
}
#endif



/* ------------------- ZIP archive reading/writing */

#ifndef MINIZ_NO_ARCHIVE_APIS

#ifdef __cplusplus
extern "C" {
#endif

enum
{
    /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */
    MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024,
    MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512,
    MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512
};

typedef struct
{
    /* Central directory file index. */
    mz_uint32 m_file_index;

    /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */
    mz_uint64 m_central_dir_ofs;

    /* These fields are copied directly from the zip's central dir. */
    mz_uint16 m_version_made_by;
    mz_uint16 m_version_needed;
    mz_uint16 m_bit_flag;
    mz_uint16 m_method;

#ifndef MINIZ_NO_TIME
    MZ_TIME_T m_time;
#endif

    /* CRC-32 of uncompressed data. */
    mz_uint32 m_crc32;

    /* File's compressed size. */
    mz_uint64 m_comp_size;

    /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */
    mz_uint64 m_uncomp_size;

    /* Zip internal and external file attributes. */
    mz_uint16 m_internal_attr;
    mz_uint32 m_external_attr;

    /* Entry's local header file offset in bytes. */
    mz_uint64 m_local_header_ofs;

    /* Size of comment in bytes. */
    mz_uint32 m_comment_size;

    /* MZ_TRUE if the entry appears to be a directory. */
    mz_bool m_is_directory;

    /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */
    mz_bool m_is_encrypted;

    /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */
    mz_bool m_is_supported;

    /* Filename. If string ends in '/' it's a subdirectory entry. */
    /* Guaranteed to be zero terminated, may be truncated to fit. */
    char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];

    /* Comment field. */
    /* Guaranteed to be zero terminated, may be truncated to fit. */
    char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE];

} mz_zip_archive_file_stat;

typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n);
typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n);
typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque);

struct mz_zip_internal_state_tag;
typedef struct mz_zip_internal_state_tag mz_zip_internal_state;

typedef enum {
    MZ_ZIP_MODE_INVALID = 0,
    MZ_ZIP_MODE_READING = 1,
    MZ_ZIP_MODE_WRITING = 2,
    MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3
} mz_zip_mode;

typedef enum {
    MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100,
    MZ_ZIP_FLAG_IGNORE_PATH = 0x0200,
    MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400,
    MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800,
    MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */
    MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000,     /* validate the local headers, but don't decompress the entire file and check the crc32 */
    MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000,               /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */
    MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000,
    MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000
} mz_zip_flags;

typedef enum {
    MZ_ZIP_TYPE_INVALID = 0,
    MZ_ZIP_TYPE_USER,
    MZ_ZIP_TYPE_MEMORY,
    MZ_ZIP_TYPE_HEAP,
    MZ_ZIP_TYPE_FILE,
    MZ_ZIP_TYPE_CFILE,
    MZ_ZIP_TOTAL_TYPES
} mz_zip_type;

/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */
typedef enum {
    MZ_ZIP_NO_ERROR = 0,
    MZ_ZIP_UNDEFINED_ERROR,
    MZ_ZIP_TOO_MANY_FILES,
    MZ_ZIP_FILE_TOO_LARGE,
    MZ_ZIP_UNSUPPORTED_METHOD,
    MZ_ZIP_UNSUPPORTED_ENCRYPTION,
    MZ_ZIP_UNSUPPORTED_FEATURE,
    MZ_ZIP_FAILED_FINDING_CENTRAL_DIR,
    MZ_ZIP_NOT_AN_ARCHIVE,
    MZ_ZIP_INVALID_HEADER_OR_CORRUPTED,
    MZ_ZIP_UNSUPPORTED_MULTIDISK,
    MZ_ZIP_DECOMPRESSION_FAILED,
    MZ_ZIP_COMPRESSION_FAILED,
    MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE,
    MZ_ZIP_CRC_CHECK_FAILED,
    MZ_ZIP_UNSUPPORTED_CDIR_SIZE,
    MZ_ZIP_ALLOC_FAILED,
    MZ_ZIP_FILE_OPEN_FAILED,
    MZ_ZIP_FILE_CREATE_FAILED,
    MZ_ZIP_FILE_WRITE_FAILED,
    MZ_ZIP_FILE_READ_FAILED,
    MZ_ZIP_FILE_CLOSE_FAILED,
    MZ_ZIP_FILE_SEEK_FAILED,
    MZ_ZIP_FILE_STAT_FAILED,
    MZ_ZIP_INVALID_PARAMETER,
    MZ_ZIP_INVALID_FILENAME,
    MZ_ZIP_BUF_TOO_SMALL,
    MZ_ZIP_INTERNAL_ERROR,
    MZ_ZIP_FILE_NOT_FOUND,
    MZ_ZIP_ARCHIVE_TOO_LARGE,
    MZ_ZIP_VALIDATION_FAILED,
    MZ_ZIP_WRITE_CALLBACK_FAILED,
    MZ_ZIP_TOTAL_ERRORS
} mz_zip_error;

typedef struct
{
    mz_uint64 m_archive_size;
    mz_uint64 m_central_directory_file_ofs;

    /* We only support up to UINT32_MAX files in zip64 mode. */
    mz_uint32 m_total_files;
    mz_zip_mode m_zip_mode;
    mz_zip_type m_zip_type;
    mz_zip_error m_last_error;

    mz_uint64 m_file_offset_alignment;

    mz_alloc_func m_pAlloc;
    mz_free_func m_pFree;
    mz_realloc_func m_pRealloc;
    void *m_pAlloc_opaque;

    mz_file_read_func m_pRead;
    mz_file_write_func m_pWrite;
    mz_file_needs_keepalive m_pNeeds_keepalive;
    void *m_pIO_opaque;

    mz_zip_internal_state *m_pState;

} mz_zip_archive;

typedef struct
{
    mz_zip_archive *pZip;
    mz_uint flags;

    int status;
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
    mz_uint file_crc32;
#endif
    mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs;
    mz_zip_archive_file_stat file_stat;
    void *pRead_buf;
    void *pWrite_buf;

    size_t out_blk_remain;

    tinfl_decompressor inflator;

} mz_zip_reader_extract_iter_state;

/* -------- ZIP reading */

/* Inits a ZIP archive reader. */
/* These functions read and validate the archive's central directory. */
mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags);

mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags);

#ifndef MINIZ_NO_STDIO
/* Read a archive from a disk file. */
/* file_start_ofs is the file offset where the archive actually begins, or 0. */
/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */
mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags);
mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size);

/* Read an archive from an already opened FILE, beginning at the current file position. */
/* The archive is assumed to be archive_size bytes long. If archive_size is < 0, then the entire rest of the file is assumed to contain the archive. */
/* The FILE will NOT be closed when mz_zip_reader_end() is called. */
mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags);
#endif

/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */
mz_bool mz_zip_reader_end(mz_zip_archive *pZip);

/* -------- ZIP reading or writing */

/* Clears a mz_zip_archive struct to all zeros. */
/* Important: This must be done before passing the struct to any mz_zip functions. */
void mz_zip_zero_struct(mz_zip_archive *pZip);

mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip);
mz_zip_type mz_zip_get_type(mz_zip_archive *pZip);

/* Returns the total number of files in the archive. */
mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip);

mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip);
mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip);
MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip);

/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */
size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n);

/* Attempts to locates a file in the archive's central directory. */
/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */
/* Returns -1 if the file cannot be found. */
int mz_zip_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
/* Returns MZ_FALSE if the file cannot be found. */
mz_bool mz_zip_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex);

/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */
/* Note that the m_last_error functionality is not thread safe. */
mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num);
mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip);
mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip);
mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip);
const char *mz_zip_get_error_string(mz_zip_error mz_err);

/* MZ_TRUE if the archive file entry is a directory entry. */
mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index);

/* MZ_TRUE if the file is encrypted/strong encrypted. */
mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index);

/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */
mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index);

/* Retrieves the filename of an archive file entry. */
/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */
mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size);

/* Attempts to locates a file in the archive's central directory. */
/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */
/* Returns -1 if the file cannot be found. */
int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index);

/* Returns detailed information about an archive file entry. */
mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);

/* MZ_TRUE if the file is in zip64 format. */
/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */
mz_bool mz_zip_is_zip64(mz_zip_archive *pZip);

/* Returns the total central directory size in bytes. */
/* The current max supported size is <= MZ_UINT32_MAX. */
size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip);

/* Extracts a archive file to a memory buffer using no memory allocation. */
/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */
mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);

/* Extracts a archive file to a memory buffer. */
mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags);
mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags);

/* Extracts a archive file to a dynamically allocated heap buffer. */
/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */
/* Returns NULL and sets the last error on failure. */
void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags);
void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags);

/* Extracts a archive file using a callback function to output the file's data. */
mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);
mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);

/* Extract a file iteratively */
mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);
mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);
size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size);
mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState);

#ifndef MINIZ_NO_STDIO
/* Extracts a archive file to a disk file and sets its last accessed and modified times. */
/* This function only extracts files, not archive directory records. */
mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags);
mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags);

/* Extracts a archive file starting at the current position in the destination FILE stream. */
mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags);
mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags);
#endif

#if 0
/* TODO */
	typedef void *mz_zip_streaming_extract_state_ptr;
	mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);
	uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
	uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
	mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs);
	size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size);
	mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
#endif

/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */
/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */
mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);

/* Validates an entire archive by calling mz_zip_validate_file() on each file. */
mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags);

/* Misc utils/helpers, valid for ZIP reading or writing */
mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr);
mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr);

/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */
mz_bool mz_zip_end(mz_zip_archive *pZip);

/* -------- ZIP writing */

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

/* Inits a ZIP archive writer. */
/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/
/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/
mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size);
mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags);

mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size);
mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags);

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning);
mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags);
mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags);
#endif

/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */
/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */
/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */
/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */
/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */
/* the archive is finalized the file's central directory will be hosed. */
mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename);
mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);

/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */
/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */
/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags);

/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */
/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */
mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                 mz_uint64 uncomp_size, mz_uint32 uncomp_crc32);

mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                    mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
                                    const char *user_extra_data_central, mz_uint user_extra_data_central_len);

#ifndef MINIZ_NO_STDIO
/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */
/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);

/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */
mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add,
                                const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
                                const char *user_extra_data_central, mz_uint user_extra_data_central_len);
#endif

/* Adds a file to an archive by fully cloning the data from another archive. */
/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */
mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index);

/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */
/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */
/* An archive must be manually finalized by calling this function for it to be valid. */
mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip);

/* Finalizes a heap archive, returning a poiner to the heap block and its size. */
/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */
mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize);

/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */
/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */
mz_bool mz_zip_writer_end(mz_zip_archive *pZip);

/* -------- Misc. high-level helper functions: */

/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */
/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */
/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */
mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr);

/* Reads a single file from an archive into a heap block. */
/* If pComment is not NULL, only the file with the specified comment will be extracted. */
/* Returns NULL on failure. */
void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags);
void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr);

#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */

#ifdef __cplusplus
}
#endif

#endif /* MINIZ_NO_ARCHIVE_APIS */
/**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * All Rights Reserved.
 *
 * 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.
 *
 **************************************************************************/


typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1];
typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1];
typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1];

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- zlib-style API's */

mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len)
{
    mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16);
    size_t block_len = buf_len % 5552;
    if (!ptr)
        return MZ_ADLER32_INIT;
    while (buf_len)
    {
        for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
        {
            s1 += ptr[0], s2 += s1;
            s1 += ptr[1], s2 += s1;
            s1 += ptr[2], s2 += s1;
            s1 += ptr[3], s2 += s1;
            s1 += ptr[4], s2 += s1;
            s1 += ptr[5], s2 += s1;
            s1 += ptr[6], s2 += s1;
            s1 += ptr[7], s2 += s1;
        }
        for (; i < block_len; ++i)
            s1 += *ptr++, s2 += s1;
        s1 %= 65521U, s2 %= 65521U;
        buf_len -= block_len;
        block_len = 5552;
    }
    return (s2 << 16) + s1;
}

/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */
#if 0
    mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
    {
        static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
                                               0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
        mz_uint32 crcu32 = (mz_uint32)crc;
        if (!ptr)
            return MZ_CRC32_INIT;
        crcu32 = ~crcu32;
        while (buf_len--)
        {
            mz_uint8 b = *ptr++;
            crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)];
            crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)];
        }
        return ~crcu32;
    }
#else
/* Faster, but larger CPU cache footprint.
 */
mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
{
    static const mz_uint32 s_crc_table[256] =
        {
          0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
          0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
          0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
          0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
          0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
          0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
          0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
          0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
          0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
          0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
          0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB,
          0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
          0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
          0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE,
          0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
          0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
          0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409,
          0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
          0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739,
          0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
          0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268,
          0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0,
          0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,
          0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
          0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
          0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,
          0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
          0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
          0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE,
          0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
          0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6,
          0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
          0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,
          0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,
          0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
          0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
          0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
        };

    mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF;
    const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr;

    while (buf_len >= 4)
    {
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF];
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF];
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF];
        pByte_buf += 4;
        buf_len -= 4;
    }

    while (buf_len)
    {
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
        ++pByte_buf;
        --buf_len;
    }

    return ~crc32;
}
#endif

void mz_free(void *p)
{
    MZ_FREE(p);
}

void *miniz_def_alloc_func(void *opaque, size_t items, size_t size)
{
    (void)opaque, (void)items, (void)size;
    return MZ_MALLOC(items * size);
}
void miniz_def_free_func(void *opaque, void *address)
{
    (void)opaque, (void)address;
    MZ_FREE(address);
}
void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size)
{
    (void)opaque, (void)address, (void)items, (void)size;
    return MZ_REALLOC(address, items * size);
}

const char *mz_version(void)
{
    return MZ_VERSION;
}

#ifndef MINIZ_NO_ZLIB_APIS

int mz_deflateInit(mz_streamp pStream, int level)
{
    return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY);
}

int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)
{
    tdefl_compressor *pComp;
    mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);

    if (!pStream)
        return MZ_STREAM_ERROR;
    if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)))
        return MZ_PARAM_ERROR;

    pStream->data_type = 0;
    pStream->adler = MZ_ADLER32_INIT;
    pStream->msg = NULL;
    pStream->reserved = 0;
    pStream->total_in = 0;
    pStream->total_out = 0;
    if (!pStream->zalloc)
        pStream->zalloc = miniz_def_alloc_func;
    if (!pStream->zfree)
        pStream->zfree = miniz_def_free_func;

    pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor));
    if (!pComp)
        return MZ_MEM_ERROR;

    pStream->state = (struct mz_internal_state *)pComp;

    if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)
    {
        mz_deflateEnd(pStream);
        return MZ_PARAM_ERROR;
    }

    return MZ_OK;
}

int mz_deflateReset(mz_streamp pStream)
{
    if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree))
        return MZ_STREAM_ERROR;
    pStream->total_in = pStream->total_out = 0;
    tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags);
    return MZ_OK;
}

int mz_deflate(mz_streamp pStream, int flush)
{
    size_t in_bytes, out_bytes;
    mz_ulong orig_total_in, orig_total_out;
    int mz_status = MZ_OK;

    if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out))
        return MZ_STREAM_ERROR;
    if (!pStream->avail_out)
        return MZ_BUF_ERROR;

    if (flush == MZ_PARTIAL_FLUSH)
        flush = MZ_SYNC_FLUSH;

    if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)
        return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;

    orig_total_in = pStream->total_in;
    orig_total_out = pStream->total_out;
    for (;;)
    {
        tdefl_status defl_status;
        in_bytes = pStream->avail_in;
        out_bytes = pStream->avail_out;

        defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);
        pStream->next_in += (mz_uint)in_bytes;
        pStream->avail_in -= (mz_uint)in_bytes;
        pStream->total_in += (mz_uint)in_bytes;
        pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state);

        pStream->next_out += (mz_uint)out_bytes;
        pStream->avail_out -= (mz_uint)out_bytes;
        pStream->total_out += (mz_uint)out_bytes;

        if (defl_status < 0)
        {
            mz_status = MZ_STREAM_ERROR;
            break;
        }
        else if (defl_status == TDEFL_STATUS_DONE)
        {
            mz_status = MZ_STREAM_END;
            break;
        }
        else if (!pStream->avail_out)
            break;
        else if ((!pStream->avail_in) && (flush != MZ_FINISH))
        {
            if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))
                break;
            return MZ_BUF_ERROR; /* Can't make forward progress without some input.
 */
        }
    }
    return mz_status;
}

int mz_deflateEnd(mz_streamp pStream)
{
    if (!pStream)
        return MZ_STREAM_ERROR;
    if (pStream->state)
    {
        pStream->zfree(pStream->opaque, pStream->state);
        pStream->state = NULL;
    }
    return MZ_OK;
}

mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len)
{
    (void)pStream;
    /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */
    return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5);
}

int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level)
{
    int status;
    mz_stream stream;
    memset(&stream, 0, sizeof(stream));

    /* In case mz_ulong is 64-bits (argh I hate longs). */
    if ((source_len | *pDest_len) > 0xFFFFFFFFU)
        return MZ_PARAM_ERROR;

    stream.next_in = pSource;
    stream.avail_in = (mz_uint32)source_len;
    stream.next_out = pDest;
    stream.avail_out = (mz_uint32)*pDest_len;

    status = mz_deflateInit(&stream, level);
    if (status != MZ_OK)
        return status;

    status = mz_deflate(&stream, MZ_FINISH);
    if (status != MZ_STREAM_END)
    {
        mz_deflateEnd(&stream);
        return (status == MZ_OK) ? MZ_BUF_ERROR : status;
    }

    *pDest_len = stream.total_out;
    return mz_deflateEnd(&stream);
}

int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
    return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION);
}

mz_ulong mz_compressBound(mz_ulong source_len)
{
    return mz_deflateBound(NULL, source_len);
}

typedef struct
{
    tinfl_decompressor m_decomp;
    mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed;
    int m_window_bits;
    mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
    tinfl_status m_last_status;
} inflate_state;

int mz_inflateInit2(mz_streamp pStream, int window_bits)
{
    inflate_state *pDecomp;
    if (!pStream)
        return MZ_STREAM_ERROR;
    if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))
        return MZ_PARAM_ERROR;

    pStream->data_type = 0;
    pStream->adler = 0;
    pStream->msg = NULL;
    pStream->total_in = 0;
    pStream->total_out = 0;
    pStream->reserved = 0;
    if (!pStream->zalloc)
        pStream->zalloc = miniz_def_alloc_func;
    if (!pStream->zfree)
        pStream->zfree = miniz_def_free_func;

    pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));
    if (!pDecomp)
        return MZ_MEM_ERROR;

    pStream->state = (struct mz_internal_state *)pDecomp;

    tinfl_init(&pDecomp->m_decomp);
    pDecomp->m_dict_ofs = 0;
    pDecomp->m_dict_avail = 0;
    pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
    pDecomp->m_first_call = 1;
    pDecomp->m_has_flushed = 0;
    pDecomp->m_window_bits = window_bits;

    return MZ_OK;
}

int mz_inflateInit(mz_streamp pStream)
{
    return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS);
}

int mz_inflate(mz_streamp pStream, int flush)
{
    inflate_state *pState;
    mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
    size_t in_bytes, out_bytes, orig_avail_in;
    tinfl_status status;

    if ((!pStream) || (!pStream->state))
        return MZ_STREAM_ERROR;
    if (flush == MZ_PARTIAL_FLUSH)
        flush = MZ_SYNC_FLUSH;
    if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH))
        return MZ_STREAM_ERROR;

    pState = (inflate_state *)pStream->state;
    if (pState->m_window_bits > 0)
        decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
    orig_avail_in = pStream->avail_in;

    first_call = pState->m_first_call;
    pState->m_first_call = 0;
    if (pState->m_last_status < 0)
        return MZ_DATA_ERROR;

    if (pState->m_has_flushed && (flush != MZ_FINISH))
        return MZ_STREAM_ERROR;
    pState->m_has_flushed |= (flush == MZ_FINISH);

    if ((flush == MZ_FINISH) && (first_call))
    {
        /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */
        decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
        in_bytes = pStream->avail_in;
        out_bytes = pStream->avail_out;
        status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);
        pState->m_last_status = status;
        pStream->next_in += (mz_uint)in_bytes;
        pStream->avail_in -= (mz_uint)in_bytes;
        pStream->total_in += (mz_uint)in_bytes;
        pStream->adler = tinfl_get_adler32(&pState->m_decomp);
        pStream->next_out += (mz_uint)out_bytes;
        pStream->avail_out -= (mz_uint)out_bytes;
        pStream->total_out += (mz_uint)out_bytes;

        if (status < 0)
            return MZ_DATA_ERROR;
        else if (status != TINFL_STATUS_DONE)
        {
            pState->m_last_status = TINFL_STATUS_FAILED;
            return MZ_BUF_ERROR;
        }
        return MZ_STREAM_END;
    }
    /* flush != MZ_FINISH then we must assume there's more input. */
    if (flush != MZ_FINISH)
        decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;

    if (pState->m_dict_avail)
    {
        n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
        memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
        pStream->next_out += n;
        pStream->avail_out -= n;
        pStream->total_out += n;
        pState->m_dict_avail -= n;
        pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
        return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
    }

    for (;;)
    {
        in_bytes = pStream->avail_in;
        out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;

        status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
        pState->m_last_status = status;

        pStream->next_in += (mz_uint)in_bytes;
        pStream->avail_in -= (mz_uint)in_bytes;
        pStream->total_in += (mz_uint)in_bytes;
        pStream->adler = tinfl_get_adler32(&pState->m_decomp);

        pState->m_dict_avail = (mz_uint)out_bytes;

        n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
        memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
        pStream->next_out += n;
        pStream->avail_out -= n;
        pStream->total_out += n;
        pState->m_dict_avail -= n;
        pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);

        if (status < 0)
            return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */
        else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
            return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */
        else if (flush == MZ_FINISH)
        {
            /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */
            if (status == TINFL_STATUS_DONE)
                return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
            /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */
            else if (!pStream->avail_out)
                return MZ_BUF_ERROR;
        }
        else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))
            break;
    }

    return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
}

int mz_inflateEnd(mz_streamp pStream)
{
    if (!pStream)
        return MZ_STREAM_ERROR;
    if (pStream->state)
    {
        pStream->zfree(pStream->opaque, pStream->state);
        pStream->state = NULL;
    }
    return MZ_OK;
}

int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
    mz_stream stream;
    int status;
    memset(&stream, 0, sizeof(stream));

    /* In case mz_ulong is 64-bits (argh I hate longs). */
    if ((source_len | *pDest_len) > 0xFFFFFFFFU)
        return MZ_PARAM_ERROR;

    stream.next_in = pSource;
    stream.avail_in = (mz_uint32)source_len;
    stream.next_out = pDest;
    stream.avail_out = (mz_uint32)*pDest_len;

    status = mz_inflateInit(&stream);
    if (status != MZ_OK)
        return status;

    status = mz_inflate(&stream, MZ_FINISH);
    if (status != MZ_STREAM_END)
    {
        mz_inflateEnd(&stream);
        return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status;
    }
    *pDest_len = stream.total_out;

    return mz_inflateEnd(&stream);
}

const char *mz_error(int err)
{
    static struct
    {
        int m_err;
        const char *m_pDesc;
    } s_error_descs[] =
        {
          { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" }
        };
    mz_uint i;
    for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i)
        if (s_error_descs[i].m_err == err)
            return s_error_descs[i].m_pDesc;
    return NULL;
}

#endif /*MINIZ_NO_ZLIB_APIS */

#ifdef __cplusplus
}
#endif

/*
  This is free and unencumbered software released into the public domain.

  Anyone is free to copy, modify, publish, use, compile, sell, or
  distribute this software, either in source code form or as a compiled
  binary, for any purpose, commercial or non-commercial, and by any
  means.

  In jurisdictions that recognize copyright laws, the author or authors
  of this software dedicate any and all copyright interest in the
  software to the public domain. We make this dedication for the benefit
  of the public at large and to the detriment of our heirs and
  successors. We intend this dedication to be an overt act of
  relinquishment in perpetuity of all present and future rights to this
  software under copyright law.

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

  For more information, please refer to <http://unlicense.org/>
*/
/**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * All Rights Reserved.
 *
 * 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.
 *
 **************************************************************************/




#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- Low-level Compression (independent from all decompression API's) */

/* Purposely making these tables static for faster init and thread safety. */
static const mz_uint16 s_tdefl_len_sym[256] =
    {
      257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272,
      273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276,
      277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
      279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280,
      281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281,
      282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282,
      283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283,
      284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285
    };

static const mz_uint8 s_tdefl_len_extra[256] =
    {
      0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0
    };

static const mz_uint8 s_tdefl_small_dist_sym[512] =
    {
      0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11,
      11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13,
      13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
      14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
      14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
      15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17
    };

static const mz_uint8 s_tdefl_small_dist_extra[512] =
    {
      0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
      6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
      6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      7, 7, 7, 7, 7, 7, 7, 7
    };

static const mz_uint8 s_tdefl_large_dist_sym[128] =
    {
      0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
      26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
      28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
    };

static const mz_uint8 s_tdefl_large_dist_extra[128] =
    {
      0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
      12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
      13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13
    };

/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */
typedef struct
{
    mz_uint16 m_key, m_sym_index;
} tdefl_sym_freq;
static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1)
{
    mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2];
    tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1;
    MZ_CLEAR_OBJ(hist);
    for (i = 0; i < num_syms; i++)
    {
        mz_uint freq = pSyms0[i].m_key;
        hist[freq & 0xFF]++;
        hist[256 + ((freq >> 8) & 0xFF)]++;
    }
    while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256]))
        total_passes--;
    for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)
    {
        const mz_uint32 *pHist = &hist[pass << 8];
        mz_uint offsets[256], cur_ofs = 0;
        for (i = 0; i < 256; i++)
        {
            offsets[i] = cur_ofs;
            cur_ofs += pHist[i];
        }
        for (i = 0; i < num_syms; i++)
            pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];
        {
            tdefl_sym_freq *t = pCur_syms;
            pCur_syms = pNew_syms;
            pNew_syms = t;
        }
    }
    return pCur_syms;
}

/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */
static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n)
{
    int root, leaf, next, avbl, used, dpth;
    if (n == 0)
        return;
    else if (n == 1)
    {
        A[0].m_key = 1;
        return;
    }
    A[0].m_key += A[1].m_key;
    root = 0;
    leaf = 2;
    for (next = 1; next < n - 1; next++)
    {
        if (leaf >= n || A[root].m_key < A[leaf].m_key)
        {
            A[next].m_key = A[root].m_key;
            A[root++].m_key = (mz_uint16)next;
        }
        else
            A[next].m_key = A[leaf++].m_key;
        if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key))
        {
            A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key);
            A[root++].m_key = (mz_uint16)next;
        }
        else
            A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key);
    }
    A[n - 2].m_key = 0;
    for (next = n - 3; next >= 0; next--)
        A[next].m_key = A[A[next].m_key].m_key + 1;
    avbl = 1;
    used = dpth = 0;
    root = n - 2;
    next = n - 1;
    while (avbl > 0)
    {
        while (root >= 0 && (int)A[root].m_key == dpth)
        {
            used++;
            root--;
        }
        while (avbl > used)
        {
            A[next--].m_key = (mz_uint16)(dpth);
            avbl--;
        }
        avbl = 2 * used;
        dpth++;
        used = 0;
    }
}

/* Limits canonical Huffman code table's max code size. */
enum
{
    TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32
};
static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size)
{
    int i;
    mz_uint32 total = 0;
    if (code_list_len <= 1)
        return;
    for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++)
        pNum_codes[max_code_size] += pNum_codes[i];
    for (i = max_code_size; i > 0; i--)
        total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i));
    while (total != (1UL << max_code_size))
    {
        pNum_codes[max_code_size]--;
        for (i = max_code_size - 1; i > 0; i--)
            if (pNum_codes[i])
            {
                pNum_codes[i]--;
                pNum_codes[i + 1] += 2;
                break;
            }
        total--;
    }
}

static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table)
{
    int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE];
    mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1];
    MZ_CLEAR_OBJ(num_codes);
    if (static_table)
    {
        for (i = 0; i < table_len; i++)
            num_codes[d->m_huff_code_sizes[table_num][i]]++;
    }
    else
    {
        tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms;
        int num_used_syms = 0;
        const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0];
        for (i = 0; i < table_len; i++)
            if (pSym_count[i])
            {
                syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i];
                syms0[num_used_syms++].m_sym_index = (mz_uint16)i;
            }

        pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1);
        tdefl_calculate_minimum_redundancy(pSyms, num_used_syms);

        for (i = 0; i < num_used_syms; i++)
            num_codes[pSyms[i].m_key]++;

        tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);

        MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]);
        MZ_CLEAR_OBJ(d->m_huff_codes[table_num]);
        for (i = 1, j = num_used_syms; i <= code_size_limit; i++)
            for (l = num_codes[i]; l > 0; l--)
                d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i);
    }

    next_code[1] = 0;
    for (j = 0, i = 2; i <= code_size_limit; i++)
        next_code[i] = j = ((j + num_codes[i - 1]) << 1);

    for (i = 0; i < table_len; i++)
    {
        mz_uint rev_code = 0, code, code_size;
        if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0)
            continue;
        code = next_code[code_size]++;
        for (l = code_size; l > 0; l--, code >>= 1)
            rev_code = (rev_code << 1) | (code & 1);
        d->m_huff_codes[table_num][i] = (mz_uint16)rev_code;
    }
}

#define TDEFL_PUT_BITS(b, l)                                       \
    do                                                             \
    {                                                              \
        mz_uint bits = b;                                          \
        mz_uint len = l;                                           \
        MZ_ASSERT(bits <= ((1U << len) - 1U));                     \
        d->m_bit_buffer |= (bits << d->m_bits_in);                 \
        d->m_bits_in += len;                                       \
        while (d->m_bits_in >= 8)                                  \
        {                                                          \
            if (d->m_pOutput_buf < d->m_pOutput_buf_end)           \
                *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \
            d->m_bit_buffer >>= 8;                                 \
            d->m_bits_in -= 8;                                     \
        }                                                          \
    }                                                              \
    MZ_MACRO_END

#define TDEFL_RLE_PREV_CODE_SIZE()                                                                                       \
    {                                                                                                                    \
        if (rle_repeat_count)                                                                                            \
        {                                                                                                                \
            if (rle_repeat_count < 3)                                                                                    \
            {                                                                                                            \
                d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \
                while (rle_repeat_count--)                                                                               \
                    packed_code_sizes[num_packed_code_sizes++] = prev_code_size;                                         \
            }                                                                                                            \
            else                                                                                                         \
            {                                                                                                            \
                d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1);                                        \
                packed_code_sizes[num_packed_code_sizes++] = 16;                                                         \
                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3);                           \
            }                                                                                                            \
            rle_repeat_count = 0;                                                                                        \
        }                                                                                                                \
    }

#define TDEFL_RLE_ZERO_CODE_SIZE()                                                         \
    {                                                                                      \
        if (rle_z_count)                                                                   \
        {                                                                                  \
            if (rle_z_count < 3)                                                           \
            {                                                                              \
                d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count);  \
                while (rle_z_count--)                                                      \
                    packed_code_sizes[num_packed_code_sizes++] = 0;                        \
            }                                                                              \
            else if (rle_z_count <= 10)                                                    \
            {                                                                              \
                d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1);          \
                packed_code_sizes[num_packed_code_sizes++] = 17;                           \
                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3);  \
            }                                                                              \
            else                                                                           \
            {                                                                              \
                d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1);          \
                packed_code_sizes[num_packed_code_sizes++] = 18;                           \
                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \
            }                                                                              \
            rle_z_count = 0;                                                               \
        }                                                                                  \
    }

static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };

static void tdefl_start_dynamic_block(tdefl_compressor *d)
{
    int num_lit_codes, num_dist_codes, num_bit_lengths;
    mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;
    mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;

    d->m_huff_count[0][256] = 1;

    tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE);
    tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE);

    for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--)
        if (d->m_huff_code_sizes[0][num_lit_codes - 1])
            break;
    for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--)
        if (d->m_huff_code_sizes[1][num_dist_codes - 1])
            break;

    memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);
    memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);
    total_code_sizes_to_pack = num_lit_codes + num_dist_codes;
    num_packed_code_sizes = 0;
    rle_z_count = 0;
    rle_repeat_count = 0;

    memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2);
    for (i = 0; i < total_code_sizes_to_pack; i++)
    {
        mz_uint8 code_size = code_sizes_to_pack[i];
        if (!code_size)
        {
            TDEFL_RLE_PREV_CODE_SIZE();
            if (++rle_z_count == 138)
            {
                TDEFL_RLE_ZERO_CODE_SIZE();
            }
        }
        else
        {
            TDEFL_RLE_ZERO_CODE_SIZE();
            if (code_size != prev_code_size)
            {
                TDEFL_RLE_PREV_CODE_SIZE();
                d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1);
                packed_code_sizes[num_packed_code_sizes++] = code_size;
            }
            else if (++rle_repeat_count == 6)
            {
                TDEFL_RLE_PREV_CODE_SIZE();
            }
        }
        prev_code_size = code_size;
    }
    if (rle_repeat_count)
    {
        TDEFL_RLE_PREV_CODE_SIZE();
    }
    else
    {
        TDEFL_RLE_ZERO_CODE_SIZE();
    }

    tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE);

    TDEFL_PUT_BITS(2, 2);

    TDEFL_PUT_BITS(num_lit_codes - 257, 5);
    TDEFL_PUT_BITS(num_dist_codes - 1, 5);

    for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--)
        if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]])
            break;
    num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1));
    TDEFL_PUT_BITS(num_bit_lengths - 4, 4);
    for (i = 0; (int)i < num_bit_lengths; i++)
        TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3);

    for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;)
    {
        mz_uint code = packed_code_sizes[packed_code_sizes_index++];
        MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2);
        TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);
        if (code >= 16)
            TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]);
    }
}

static void tdefl_start_static_block(tdefl_compressor *d)
{
    mz_uint i;
    mz_uint8 *p = &d->m_huff_code_sizes[0][0];

    for (i = 0; i <= 143; ++i)
        *p++ = 8;
    for (; i <= 255; ++i)
        *p++ = 9;
    for (; i <= 279; ++i)
        *p++ = 7;
    for (; i <= 287; ++i)
        *p++ = 8;

    memset(d->m_huff_code_sizes[1], 5, 32);

    tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE);
    tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE);

    TDEFL_PUT_BITS(1, 2);
}

static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS
static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
{
    mz_uint flags;
    mz_uint8 *pLZ_codes;
    mz_uint8 *pOutput_buf = d->m_pOutput_buf;
    mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf;
    mz_uint64 bit_buffer = d->m_bit_buffer;
    mz_uint bits_in = d->m_bits_in;

#define TDEFL_PUT_BITS_FAST(b, l)                    \
    {                                                \
        bit_buffer |= (((mz_uint64)(b)) << bits_in); \
        bits_in += (l);                              \
    }

    flags = 1;
    for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1)
    {
        if (flags == 1)
            flags = *pLZ_codes++ | 0x100;

        if (flags & 1)
        {
            mz_uint s0, s1, n0, n1, sym, num_extra_bits;
            mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1);
            pLZ_codes += 3;

            MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);

            /* This sequence coaxes MSVC into using cmov's vs. jmp's. */
            s0 = s_tdefl_small_dist_sym[match_dist & 511];
            n0 = s_tdefl_small_dist_extra[match_dist & 511];
            s1 = s_tdefl_large_dist_sym[match_dist >> 8];
            n1 = s_tdefl_large_dist_extra[match_dist >> 8];
            sym = (match_dist < 512) ? s0 : s1;
            num_extra_bits = (match_dist < 512) ? n0 : n1;

            MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
        }
        else
        {
            mz_uint lit = *pLZ_codes++;
            MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
            TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);

            if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
            {
                flags >>= 1;
                lit = *pLZ_codes++;
                MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
                TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);

                if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
                {
                    flags >>= 1;
                    lit = *pLZ_codes++;
                    MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
                    TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
                }
            }
        }

        if (pOutput_buf >= d->m_pOutput_buf_end)
            return MZ_FALSE;

        *(mz_uint64 *)pOutput_buf = bit_buffer;
        pOutput_buf += (bits_in >> 3);
        bit_buffer >>= (bits_in & ~7);
        bits_in &= 7;
    }

#undef TDEFL_PUT_BITS_FAST

    d->m_pOutput_buf = pOutput_buf;
    d->m_bits_in = 0;
    d->m_bit_buffer = 0;

    while (bits_in)
    {
        mz_uint32 n = MZ_MIN(bits_in, 16);
        TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n);
        bit_buffer >>= n;
        bits_in -= n;
    }

    TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);

    return (d->m_pOutput_buf < d->m_pOutput_buf_end);
}
#else
static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
{
    mz_uint flags;
    mz_uint8 *pLZ_codes;

    flags = 1;
    for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1)
    {
        if (flags == 1)
            flags = *pLZ_codes++ | 0x100;
        if (flags & 1)
        {
            mz_uint sym, num_extra_bits;
            mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8));
            pLZ_codes += 3;

            MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);

            if (match_dist < 512)
            {
                sym = s_tdefl_small_dist_sym[match_dist];
                num_extra_bits = s_tdefl_small_dist_extra[match_dist];
            }
            else
            {
                sym = s_tdefl_large_dist_sym[match_dist >> 8];
                num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8];
            }
            MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
        }
        else
        {
            mz_uint lit = *pLZ_codes++;
            MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
            TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
        }
    }

    TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);

    return (d->m_pOutput_buf < d->m_pOutput_buf_end);
}
#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */

static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block)
{
    if (static_block)
        tdefl_start_static_block(d);
    else
        tdefl_start_dynamic_block(d);
    return tdefl_compress_lz_codes(d);
}

static int tdefl_flush_block(tdefl_compressor *d, int flush)
{
    mz_uint saved_bit_buf, saved_bits_in;
    mz_uint8 *pSaved_output_buf;
    mz_bool comp_block_succeeded = MZ_FALSE;
    int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size;
    mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf;

    d->m_pOutput_buf = pOutput_buf_start;
    d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16;

    MZ_ASSERT(!d->m_output_flush_remaining);
    d->m_output_flush_ofs = 0;
    d->m_output_flush_remaining = 0;

    *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left);
    d->m_pLZ_code_buf -= (d->m_num_flags_left == 8);

    if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index))
    {
        TDEFL_PUT_BITS(0x78, 8);
        TDEFL_PUT_BITS(0x01, 8);
    }

    TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1);

    pSaved_output_buf = d->m_pOutput_buf;
    saved_bit_buf = d->m_bit_buffer;
    saved_bits_in = d->m_bits_in;

    if (!use_raw_block)
        comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48));

    /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */
    if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) &&
        ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size))
    {
        mz_uint i;
        d->m_pOutput_buf = pSaved_output_buf;
        d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
        TDEFL_PUT_BITS(0, 2);
        if (d->m_bits_in)
        {
            TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
        }
        for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF)
        {
            TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16);
        }
        for (i = 0; i < d->m_total_lz_bytes; ++i)
        {
            TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8);
        }
    }
    /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */
    else if (!comp_block_succeeded)
    {
        d->m_pOutput_buf = pSaved_output_buf;
        d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
        tdefl_compress_block(d, MZ_TRUE);
    }

    if (flush)
    {
        if (flush == TDEFL_FINISH)
        {
            if (d->m_bits_in)
            {
                TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
            }
            if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER)
            {
                mz_uint i, a = d->m_adler32;
                for (i = 0; i < 4; i++)
                {
                    TDEFL_PUT_BITS((a >> 24) & 0xFF, 8);
                    a <<= 8;
                }
            }
        }
        else
        {
            mz_uint i, z = 0;
            TDEFL_PUT_BITS(0, 3);
            if (d->m_bits_in)
            {
                TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
            }
            for (i = 2; i; --i, z ^= 0xFFFF)
            {
                TDEFL_PUT_BITS(z & 0xFFFF, 16);
            }
        }
    }

    MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end);

    memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
    memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);

    d->m_pLZ_code_buf = d->m_lz_code_buf + 1;
    d->m_pLZ_flags = d->m_lz_code_buf;
    d->m_num_flags_left = 8;
    d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes;
    d->m_total_lz_bytes = 0;
    d->m_block_index++;

    if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0)
    {
        if (d->m_pPut_buf_func)
        {
            *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
            if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user))
                return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED);
        }
        else if (pOutput_buf_start == d->m_output_buf)
        {
            int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs));
            memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy);
            d->m_out_buf_ofs += bytes_to_copy;
            if ((n -= bytes_to_copy) != 0)
            {
                d->m_output_flush_ofs = bytes_to_copy;
                d->m_output_flush_remaining = n;
            }
        }
        else
        {
            d->m_out_buf_ofs += n;
        }
    }

    return d->m_output_flush_remaining;
}

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
#ifdef MINIZ_UNALIGNED_USE_MEMCPY
static inline mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p)
{
	mz_uint16 ret;
	memcpy(&ret, p, sizeof(mz_uint16));
	return ret;
}
static inline mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p)
{
	mz_uint16 ret;
	memcpy(&ret, p, sizeof(mz_uint16));
	return ret;
}
#else
#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p)
#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p)
#endif
static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
{
    mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
    mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
    const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q;
    mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s);
    MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);
    if (max_match_len <= match_len)
        return;
    for (;;)
    {
        for (;;)
        {
            if (--num_probes_left == 0)
                return;
#define TDEFL_PROBE                                                                             \
    next_probe_pos = d->m_next[probe_pos];                                                      \
    if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \
        return;                                                                                 \
    probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK;                                       \
    if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01)                \
        break;
            TDEFL_PROBE;
            TDEFL_PROBE;
            TDEFL_PROBE;
        }
        if (!dist)
            break;
        q = (const mz_uint16 *)(d->m_dict + probe_pos);
        if (TDEFL_READ_UNALIGNED_WORD2(q) != s01)
            continue;
        p = s;
        probe_len = 32;
        do
        {
        } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&
                 (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));
        if (!probe_len)
        {
            *pMatch_dist = dist;
            *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN);
            break;
        }
        else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len)
        {
            *pMatch_dist = dist;
            if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len)
                break;
            c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]);
        }
    }
}
#else
static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
{
    mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
    mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
    const mz_uint8 *s = d->m_dict + pos, *p, *q;
    mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1];
    MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);
    if (max_match_len <= match_len)
        return;
    for (;;)
    {
        for (;;)
        {
            if (--num_probes_left == 0)
                return;
#define TDEFL_PROBE                                                                               \
    next_probe_pos = d->m_next[probe_pos];                                                        \
    if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist))   \
        return;                                                                                   \
    probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK;                                         \
    if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \
        break;
            TDEFL_PROBE;
            TDEFL_PROBE;
            TDEFL_PROBE;
        }
        if (!dist)
            break;
        p = s;
        q = d->m_dict + probe_pos;
        for (probe_len = 0; probe_len < max_match_len; probe_len++)
            if (*p++ != *q++)
                break;
        if (probe_len > match_len)
        {
            *pMatch_dist = dist;
            if ((*pMatch_len = match_len = probe_len) == max_match_len)
                return;
            c0 = d->m_dict[pos + match_len];
            c1 = d->m_dict[pos + match_len - 1];
        }
    }
}
#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
static mz_bool tdefl_compress_fast(tdefl_compressor *d)
{
    /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */
    mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left;
    mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags;
    mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;

    while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size)))
    {
        const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096;
        mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
        mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size);
        d->m_src_buf_left -= num_bytes_to_process;
        lookahead_size += num_bytes_to_process;

        while (num_bytes_to_process)
        {
            mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process);
            memcpy(d->m_dict + dst_pos, d->m_pSrc, n);
            if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
                memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos));
            d->m_pSrc += n;
            dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK;
            num_bytes_to_process -= n;
        }

        dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size);
        if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE))
            break;

        while (lookahead_size >= 4)
        {
            mz_uint cur_match_dist, cur_match_len = 1;
            mz_uint8 *pCur_dict = d->m_dict + cur_pos;
            mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF;
            mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK;
            mz_uint probe_pos = d->m_hash[hash];
            d->m_hash[hash] = (mz_uint16)lookahead_pos;

            if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram))
            {
                const mz_uint16 *p = (const mz_uint16 *)pCur_dict;
                const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos);
                mz_uint32 probe_len = 32;
                do
                {
                } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&
                         (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));
                cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q);
                if (!probe_len)
                    cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0;

                if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)))
                {
                    cur_match_len = 1;
                    *pLZ_code_buf++ = (mz_uint8)first_trigram;
                    *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
                    d->m_huff_count[0][(mz_uint8)first_trigram]++;
                }
                else
                {
                    mz_uint32 s0, s1;
                    cur_match_len = MZ_MIN(cur_match_len, lookahead_size);

                    MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE));

                    cur_match_dist--;

                    pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN);
                    *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist;
                    pLZ_code_buf += 3;
                    *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80);

                    s0 = s_tdefl_small_dist_sym[cur_match_dist & 511];
                    s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8];
                    d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++;

                    d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++;
                }
            }
            else
            {
                *pLZ_code_buf++ = (mz_uint8)first_trigram;
                *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
                d->m_huff_count[0][(mz_uint8)first_trigram]++;
            }

            if (--num_flags_left == 0)
            {
                num_flags_left = 8;
                pLZ_flags = pLZ_code_buf++;
            }

            total_lz_bytes += cur_match_len;
            lookahead_pos += cur_match_len;
            dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE);
            cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK;
            MZ_ASSERT(lookahead_size >= cur_match_len);
            lookahead_size -= cur_match_len;

            if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
            {
                int n;
                d->m_lookahead_pos = lookahead_pos;
                d->m_lookahead_size = lookahead_size;
                d->m_dict_size = dict_size;
                d->m_total_lz_bytes = total_lz_bytes;
                d->m_pLZ_code_buf = pLZ_code_buf;
                d->m_pLZ_flags = pLZ_flags;
                d->m_num_flags_left = num_flags_left;
                if ((n = tdefl_flush_block(d, 0)) != 0)
                    return (n < 0) ? MZ_FALSE : MZ_TRUE;
                total_lz_bytes = d->m_total_lz_bytes;
                pLZ_code_buf = d->m_pLZ_code_buf;
                pLZ_flags = d->m_pLZ_flags;
                num_flags_left = d->m_num_flags_left;
            }
        }

        while (lookahead_size)
        {
            mz_uint8 lit = d->m_dict[cur_pos];

            total_lz_bytes++;
            *pLZ_code_buf++ = lit;
            *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
            if (--num_flags_left == 0)
            {
                num_flags_left = 8;
                pLZ_flags = pLZ_code_buf++;
            }

            d->m_huff_count[0][lit]++;

            lookahead_pos++;
            dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE);
            cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
            lookahead_size--;

            if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
            {
                int n;
                d->m_lookahead_pos = lookahead_pos;
                d->m_lookahead_size = lookahead_size;
                d->m_dict_size = dict_size;
                d->m_total_lz_bytes = total_lz_bytes;
                d->m_pLZ_code_buf = pLZ_code_buf;
                d->m_pLZ_flags = pLZ_flags;
                d->m_num_flags_left = num_flags_left;
                if ((n = tdefl_flush_block(d, 0)) != 0)
                    return (n < 0) ? MZ_FALSE : MZ_TRUE;
                total_lz_bytes = d->m_total_lz_bytes;
                pLZ_code_buf = d->m_pLZ_code_buf;
                pLZ_flags = d->m_pLZ_flags;
                num_flags_left = d->m_num_flags_left;
            }
        }
    }

    d->m_lookahead_pos = lookahead_pos;
    d->m_lookahead_size = lookahead_size;
    d->m_dict_size = dict_size;
    d->m_total_lz_bytes = total_lz_bytes;
    d->m_pLZ_code_buf = pLZ_code_buf;
    d->m_pLZ_flags = pLZ_flags;
    d->m_num_flags_left = num_flags_left;
    return MZ_TRUE;
}
#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */

static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit)
{
    d->m_total_lz_bytes++;
    *d->m_pLZ_code_buf++ = lit;
    *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1);
    if (--d->m_num_flags_left == 0)
    {
        d->m_num_flags_left = 8;
        d->m_pLZ_flags = d->m_pLZ_code_buf++;
    }
    d->m_huff_count[0][lit]++;
}

static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist)
{
    mz_uint32 s0, s1;

    MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE));

    d->m_total_lz_bytes += match_len;

    d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN);

    match_dist -= 1;
    d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF);
    d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8);
    d->m_pLZ_code_buf += 3;

    *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80);
    if (--d->m_num_flags_left == 0)
    {
        d->m_num_flags_left = 8;
        d->m_pLZ_flags = d->m_pLZ_code_buf++;
    }

    s0 = s_tdefl_small_dist_sym[match_dist & 511];
    s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127];
    d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++;

    if (match_len >= TDEFL_MIN_MATCH_LEN)
        d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++;
}

static mz_bool tdefl_compress_normal(tdefl_compressor *d)
{
    const mz_uint8 *pSrc = d->m_pSrc;
    size_t src_buf_left = d->m_src_buf_left;
    tdefl_flush flush = d->m_flush;

    while ((src_buf_left) || ((flush) && (d->m_lookahead_size)))
    {
        mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos;
        /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */
        if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1))
        {
            mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2;
            mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK];
            mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size);
            const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process;
            src_buf_left -= num_bytes_to_process;
            d->m_lookahead_size += num_bytes_to_process;
            while (pSrc != pSrc_end)
            {
                mz_uint8 c = *pSrc++;
                d->m_dict[dst_pos] = c;
                if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
                    d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
                hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
                d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];
                d->m_hash[hash] = (mz_uint16)(ins_pos);
                dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
                ins_pos++;
            }
        }
        else
        {
            while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
            {
                mz_uint8 c = *pSrc++;
                mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
                src_buf_left--;
                d->m_dict[dst_pos] = c;
                if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
                    d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
                if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN)
                {
                    mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2;
                    mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
                    d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];
                    d->m_hash[hash] = (mz_uint16)(ins_pos);
                }
            }
        }
        d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size);
        if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
            break;

        /* Simple lazy/greedy parsing state machine. */
        len_to_move = 1;
        cur_match_dist = 0;
        cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1);
        cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;
        if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS))
        {
            if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))
            {
                mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK];
                cur_match_len = 0;
                while (cur_match_len < d->m_lookahead_size)
                {
                    if (d->m_dict[cur_pos + cur_match_len] != c)
                        break;
                    cur_match_len++;
                }
                if (cur_match_len < TDEFL_MIN_MATCH_LEN)
                    cur_match_len = 0;
                else
                    cur_match_dist = 1;
            }
        }
        else
        {
            tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len);
        }
        if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5)))
        {
            cur_match_dist = cur_match_len = 0;
        }
        if (d->m_saved_match_len)
        {
            if (cur_match_len > d->m_saved_match_len)
            {
                tdefl_record_literal(d, (mz_uint8)d->m_saved_lit);
                if (cur_match_len >= 128)
                {
                    tdefl_record_match(d, cur_match_len, cur_match_dist);
                    d->m_saved_match_len = 0;
                    len_to_move = cur_match_len;
                }
                else
                {
                    d->m_saved_lit = d->m_dict[cur_pos];
                    d->m_saved_match_dist = cur_match_dist;
                    d->m_saved_match_len = cur_match_len;
                }
            }
            else
            {
                tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist);
                len_to_move = d->m_saved_match_len - 1;
                d->m_saved_match_len = 0;
            }
        }
        else if (!cur_match_dist)
            tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]);
        else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128))
        {
            tdefl_record_match(d, cur_match_len, cur_match_dist);
            len_to_move = cur_match_len;
        }
        else
        {
            d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)];
            d->m_saved_match_dist = cur_match_dist;
            d->m_saved_match_len = cur_match_len;
        }
        /* Move the lookahead forward by len_to_move bytes. */
        d->m_lookahead_pos += len_to_move;
        MZ_ASSERT(d->m_lookahead_size >= len_to_move);
        d->m_lookahead_size -= len_to_move;
        d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE);
        /* Check if it's time to flush the current LZ codes to the internal output buffer. */
        if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) ||
            ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))))
        {
            int n;
            d->m_pSrc = pSrc;
            d->m_src_buf_left = src_buf_left;
            if ((n = tdefl_flush_block(d, 0)) != 0)
                return (n < 0) ? MZ_FALSE : MZ_TRUE;
        }
    }

    d->m_pSrc = pSrc;
    d->m_src_buf_left = src_buf_left;
    return MZ_TRUE;
}

static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d)
{
    if (d->m_pIn_buf_size)
    {
        *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
    }

    if (d->m_pOut_buf_size)
    {
        size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining);
        memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n);
        d->m_output_flush_ofs += (mz_uint)n;
        d->m_output_flush_remaining -= (mz_uint)n;
        d->m_out_buf_ofs += n;

        *d->m_pOut_buf_size = d->m_out_buf_ofs;
    }

    return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY;
}

tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush)
{
    if (!d)
    {
        if (pIn_buf_size)
            *pIn_buf_size = 0;
        if (pOut_buf_size)
            *pOut_buf_size = 0;
        return TDEFL_STATUS_BAD_PARAM;
    }

    d->m_pIn_buf = pIn_buf;
    d->m_pIn_buf_size = pIn_buf_size;
    d->m_pOut_buf = pOut_buf;
    d->m_pOut_buf_size = pOut_buf_size;
    d->m_pSrc = (const mz_uint8 *)(pIn_buf);
    d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0;
    d->m_out_buf_ofs = 0;
    d->m_flush = flush;

    if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) ||
        (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf))
    {
        if (pIn_buf_size)
            *pIn_buf_size = 0;
        if (pOut_buf_size)
            *pOut_buf_size = 0;
        return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM);
    }
    d->m_wants_to_finish |= (flush == TDEFL_FINISH);

    if ((d->m_output_flush_remaining) || (d->m_finished))
        return (d->m_prev_return_status = tdefl_flush_output_buffer(d));

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
    if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) &&
        ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) &&
        ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0))
    {
        if (!tdefl_compress_fast(d))
            return d->m_prev_return_status;
    }
    else
#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */
    {
        if (!tdefl_compress_normal(d))
            return d->m_prev_return_status;
    }

    if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf))
        d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf);

    if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining))
    {
        if (tdefl_flush_block(d, flush) < 0)
            return d->m_prev_return_status;
        d->m_finished = (flush == TDEFL_FINISH);
        if (flush == TDEFL_FULL_FLUSH)
        {
            MZ_CLEAR_OBJ(d->m_hash);
            MZ_CLEAR_OBJ(d->m_next);
            d->m_dict_size = 0;
        }
    }

    return (d->m_prev_return_status = tdefl_flush_output_buffer(d));
}

tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush)
{
    MZ_ASSERT(d->m_pPut_buf_func);
    return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush);
}

tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
    d->m_pPut_buf_func = pPut_buf_func;
    d->m_pPut_buf_user = pPut_buf_user;
    d->m_flags = (mz_uint)(flags);
    d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3;
    d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0;
    d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3;
    if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
        MZ_CLEAR_OBJ(d->m_hash);
    d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0;
    d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0;
    d->m_pLZ_code_buf = d->m_lz_code_buf + 1;
    d->m_pLZ_flags = d->m_lz_code_buf;
    d->m_num_flags_left = 8;
    d->m_pOutput_buf = d->m_output_buf;
    d->m_pOutput_buf_end = d->m_output_buf;
    d->m_prev_return_status = TDEFL_STATUS_OKAY;
    d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0;
    d->m_adler32 = 1;
    d->m_pIn_buf = NULL;
    d->m_pOut_buf = NULL;
    d->m_pIn_buf_size = NULL;
    d->m_pOut_buf_size = NULL;
    d->m_flush = TDEFL_NO_FLUSH;
    d->m_pSrc = NULL;
    d->m_src_buf_left = 0;
    d->m_out_buf_ofs = 0;
    if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
        MZ_CLEAR_OBJ(d->m_dict);
    memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
    memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
    return TDEFL_STATUS_OKAY;
}

tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d)
{
    return d->m_prev_return_status;
}

mz_uint32 tdefl_get_adler32(tdefl_compressor *d)
{
    return d->m_adler32;
}

mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
    tdefl_compressor *pComp;
    mz_bool succeeded;
    if (((buf_len) && (!pBuf)) || (!pPut_buf_func))
        return MZ_FALSE;
    pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
    if (!pComp)
        return MZ_FALSE;
    succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY);
    succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE);
    MZ_FREE(pComp);
    return succeeded;
}

typedef struct
{
    size_t m_size, m_capacity;
    mz_uint8 *m_pBuf;
    mz_bool m_expandable;
} tdefl_output_buffer;

static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser)
{
    tdefl_output_buffer *p = (tdefl_output_buffer *)pUser;
    size_t new_size = p->m_size + len;
    if (new_size > p->m_capacity)
    {
        size_t new_capacity = p->m_capacity;
        mz_uint8 *pNew_buf;
        if (!p->m_expandable)
            return MZ_FALSE;
        do
        {
            new_capacity = MZ_MAX(128U, new_capacity << 1U);
        } while (new_size > new_capacity);
        pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity);
        if (!pNew_buf)
            return MZ_FALSE;
        p->m_pBuf = pNew_buf;
        p->m_capacity = new_capacity;
    }
    memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len);
    p->m_size = new_size;
    return MZ_TRUE;
}

void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
{
    tdefl_output_buffer out_buf;
    MZ_CLEAR_OBJ(out_buf);
    if (!pOut_len)
        return MZ_FALSE;
    else
        *pOut_len = 0;
    out_buf.m_expandable = MZ_TRUE;
    if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))
        return NULL;
    *pOut_len = out_buf.m_size;
    return out_buf.m_pBuf;
}

size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
{
    tdefl_output_buffer out_buf;
    MZ_CLEAR_OBJ(out_buf);
    if (!pOut_buf)
        return 0;
    out_buf.m_pBuf = (mz_uint8 *)pOut_buf;
    out_buf.m_capacity = out_buf_len;
    if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))
        return 0;
    return out_buf.m_size;
}

static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };

/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */
mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy)
{
    mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
    if (window_bits > 0)
        comp_flags |= TDEFL_WRITE_ZLIB_HEADER;

    if (!level)
        comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
    else if (strategy == MZ_FILTERED)
        comp_flags |= TDEFL_FILTER_MATCHES;
    else if (strategy == MZ_HUFFMAN_ONLY)
        comp_flags &= ~TDEFL_MAX_PROBES_MASK;
    else if (strategy == MZ_FIXED)
        comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;
    else if (strategy == MZ_RLE)
        comp_flags |= TDEFL_RLE_MATCHES;

    return comp_flags;
}

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */
#endif

/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at
 http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.
 This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */
void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip)
{
    /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */
    static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
    tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
    tdefl_output_buffer out_buf;
    int i, bpl = w * num_chans, y, z;
    mz_uint32 c;
    *pLen_out = 0;
    if (!pComp)
        return NULL;
    MZ_CLEAR_OBJ(out_buf);
    out_buf.m_expandable = MZ_TRUE;
    out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h);
    if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity)))
    {
        MZ_FREE(pComp);
        return NULL;
    }
    /* write dummy header */
    for (z = 41; z; --z)
        tdefl_output_buffer_putter(&z, 1, &out_buf);
    /* compress image data */
    tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER);
    for (y = 0; y < h; ++y)
    {
        tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH);
        tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH);
    }
    if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE)
    {
        MZ_FREE(pComp);
        MZ_FREE(out_buf.m_pBuf);
        return NULL;
    }
    /* write real header */
    *pLen_out = out_buf.m_size - 41;
    {
        static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 };
        mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d,
                                0x0a, 0x1a, 0x0a, 0x00, 0x00,
                                0x00, 0x0d, 0x49, 0x48, 0x44,
                                0x52, 0x00, 0x00, 0x00, 0x00,
                                0x00, 0x00, 0x00, 0x00, 0x08,
                                0x00, 0x00, 0x00, 0x00, 0x00,
                                0x00, 0x00, 0x00, 0x00, 0x00,
                                0x00, 0x00, 0x49, 0x44, 0x41,
                                0x54 };
        pnghdr[18] = (mz_uint8)(w >> 8);
        pnghdr[19] = (mz_uint8)w;
        pnghdr[22] = (mz_uint8)(h >> 8);
        pnghdr[23] = (mz_uint8)h;
        pnghdr[25] = chans[num_chans];
        pnghdr[33] = (mz_uint8)(*pLen_out >> 24);
        pnghdr[34] = (mz_uint8)(*pLen_out >> 16);
        pnghdr[35] = (mz_uint8)(*pLen_out >> 8);
        pnghdr[36] = (mz_uint8)*pLen_out;
        c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17);
        for (i = 0; i < 4; ++i, c <<= 8)
            ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24);
        memcpy(out_buf.m_pBuf, pnghdr, 41);
    }
    /* write footer (IDAT CRC-32, followed by IEND chunk) */
    if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf))
    {
        *pLen_out = 0;
        MZ_FREE(pComp);
        MZ_FREE(out_buf.m_pBuf);
        return NULL;
    }
    c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4);
    for (i = 0; i < 4; ++i, c <<= 8)
        (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24);
    /* compute final size of file, grab compressed data buffer and return */
    *pLen_out += 57;
    MZ_FREE(pComp);
    return out_buf.m_pBuf;
}
void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out)
{
    /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */
    return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE);
}

/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */
/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */
/* structure size and allocation mechanism. */
tdefl_compressor *tdefl_compressor_alloc()
{
    return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
}

void tdefl_compressor_free(tdefl_compressor *pComp)
{
    MZ_FREE(pComp);
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#ifdef __cplusplus
}
#endif
/**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * All Rights Reserved.
 *
 * 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.
 *
 **************************************************************************/



#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- Low-level Decompression (completely independent from all compression API's) */

#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l)
#define TINFL_MEMSET(p, c, l) memset(p, c, l)

#define TINFL_CR_BEGIN  \
    switch (r->m_state) \
    {                   \
        case 0:
#define TINFL_CR_RETURN(state_index, result) \
    do                                       \
    {                                        \
        status = result;                     \
        r->m_state = state_index;            \
        goto common_exit;                    \
        case state_index:;                   \
    }                                        \
    MZ_MACRO_END
#define TINFL_CR_RETURN_FOREVER(state_index, result) \
    do                                               \
    {                                                \
        for (;;)                                     \
        {                                            \
            TINFL_CR_RETURN(state_index, result);    \
        }                                            \
    }                                                \
    MZ_MACRO_END
#define TINFL_CR_FINISH }

#define TINFL_GET_BYTE(state_index, c)                                                                                                                           \
    do                                                                                                                                                           \
    {                                                                                                                                                            \
        while (pIn_buf_cur >= pIn_buf_end)                                                                                                                       \
        {                                                                                                                                                        \
            TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \
        }                                                                                                                                                        \
        c = *pIn_buf_cur++;                                                                                                                                      \
    }                                                                                                                                                            \
    MZ_MACRO_END

#define TINFL_NEED_BITS(state_index, n)                \
    do                                                 \
    {                                                  \
        mz_uint c;                                     \
        TINFL_GET_BYTE(state_index, c);                \
        bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \
        num_bits += 8;                                 \
    } while (num_bits < (mz_uint)(n))
#define TINFL_SKIP_BITS(state_index, n)      \
    do                                       \
    {                                        \
        if (num_bits < (mz_uint)(n))         \
        {                                    \
            TINFL_NEED_BITS(state_index, n); \
        }                                    \
        bit_buf >>= (n);                     \
        num_bits -= (n);                     \
    }                                        \
    MZ_MACRO_END
#define TINFL_GET_BITS(state_index, b, n)    \
    do                                       \
    {                                        \
        if (num_bits < (mz_uint)(n))         \
        {                                    \
            TINFL_NEED_BITS(state_index, n); \
        }                                    \
        b = bit_buf & ((1 << (n)) - 1);      \
        bit_buf >>= (n);                     \
        num_bits -= (n);                     \
    }                                        \
    MZ_MACRO_END

/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */
/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */
/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */
/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */
#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff)                             \
    do                                                                         \
    {                                                                          \
        temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)];     \
        if (temp >= 0)                                                         \
        {                                                                      \
            code_len = temp >> 9;                                              \
            if ((code_len) && (num_bits >= code_len))                          \
                break;                                                         \
        }                                                                      \
        else if (num_bits > TINFL_FAST_LOOKUP_BITS)                            \
        {                                                                      \
            code_len = TINFL_FAST_LOOKUP_BITS;                                 \
            do                                                                 \
            {                                                                  \
                temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
            } while ((temp < 0) && (num_bits >= (code_len + 1)));              \
            if (temp >= 0)                                                     \
                break;                                                         \
        }                                                                      \
        TINFL_GET_BYTE(state_index, c);                                        \
        bit_buf |= (((tinfl_bit_buf_t)c) << num_bits);                         \
        num_bits += 8;                                                         \
    } while (num_bits < 15);

/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */
/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */
/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */
/* The slow path is only executed at the very end of the input buffer. */
/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */
/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */
#define TINFL_HUFF_DECODE(state_index, sym, pHuff)                                                                                  \
    do                                                                                                                              \
    {                                                                                                                               \
        int temp;                                                                                                                   \
        mz_uint code_len, c;                                                                                                        \
        if (num_bits < 15)                                                                                                          \
        {                                                                                                                           \
            if ((pIn_buf_end - pIn_buf_cur) < 2)                                                                                    \
            {                                                                                                                       \
                TINFL_HUFF_BITBUF_FILL(state_index, pHuff);                                                                         \
            }                                                                                                                       \
            else                                                                                                                    \
            {                                                                                                                       \
                bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \
                pIn_buf_cur += 2;                                                                                                   \
                num_bits += 16;                                                                                                     \
            }                                                                                                                       \
        }                                                                                                                           \
        if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)                                               \
            code_len = temp >> 9, temp &= 511;                                                                                      \
        else                                                                                                                        \
        {                                                                                                                           \
            code_len = TINFL_FAST_LOOKUP_BITS;                                                                                      \
            do                                                                                                                      \
            {                                                                                                                       \
                temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)];                                                      \
            } while (temp < 0);                                                                                                     \
        }                                                                                                                           \
        sym = temp;                                                                                                                 \
        bit_buf >>= code_len;                                                                                                       \
        num_bits -= code_len;                                                                                                       \
    }                                                                                                                               \
    MZ_MACRO_END

tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags)
{
    static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 };
    static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 };
    static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 };
    static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 };
    static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
    static const int s_min_table_sizes[3] = { 257, 1, 4 };

    tinfl_status status = TINFL_STATUS_FAILED;
    mz_uint32 num_bits, dist, counter, num_extra;
    tinfl_bit_buf_t bit_buf;
    const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size;
    mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size;
    size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start;

    /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */
    if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start))
    {
        *pIn_buf_size = *pOut_buf_size = 0;
        return TINFL_STATUS_BAD_PARAM;
    }

    num_bits = r->m_num_bits;
    bit_buf = r->m_bit_buf;
    dist = r->m_dist;
    counter = r->m_counter;
    num_extra = r->m_num_extra;
    dist_from_out_buf_start = r->m_dist_from_out_buf_start;
    TINFL_CR_BEGIN

    bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0;
    r->m_z_adler32 = r->m_check_adler32 = 1;
    if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
    {
        TINFL_GET_BYTE(1, r->m_zhdr0);
        TINFL_GET_BYTE(2, r->m_zhdr1);
        counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8));
        if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
            counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4)))));
        if (counter)
        {
            TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED);
        }
    }

    do
    {
        TINFL_GET_BITS(3, r->m_final, 3);
        r->m_type = r->m_final >> 1;
        if (r->m_type == 0)
        {
            TINFL_SKIP_BITS(5, num_bits & 7);
            for (counter = 0; counter < 4; ++counter)
            {
                if (num_bits)
                    TINFL_GET_BITS(6, r->m_raw_header[counter], 8);
                else
                    TINFL_GET_BYTE(7, r->m_raw_header[counter]);
            }
            if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8))))
            {
                TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED);
            }
            while ((counter) && (num_bits))
            {
                TINFL_GET_BITS(51, dist, 8);
                while (pOut_buf_cur >= pOut_buf_end)
                {
                    TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT);
                }
                *pOut_buf_cur++ = (mz_uint8)dist;
                counter--;
            }
            while (counter)
            {
                size_t n;
                while (pOut_buf_cur >= pOut_buf_end)
                {
                    TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT);
                }
                while (pIn_buf_cur >= pIn_buf_end)
                {
                    TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS);
                }
                n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);
                TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n);
                pIn_buf_cur += n;
                pOut_buf_cur += n;
                counter -= (mz_uint)n;
            }
        }
        else if (r->m_type == 3)
        {
            TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED);
        }
        else
        {
            if (r->m_type == 1)
            {
                mz_uint8 *p = r->m_tables[0].m_code_size;
                mz_uint i;
                r->m_table_sizes[0] = 288;
                r->m_table_sizes[1] = 32;
                TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32);
                for (i = 0; i <= 143; ++i)
                    *p++ = 8;
                for (; i <= 255; ++i)
                    *p++ = 9;
                for (; i <= 279; ++i)
                    *p++ = 7;
                for (; i <= 287; ++i)
                    *p++ = 8;
            }
            else
            {
                for (counter = 0; counter < 3; counter++)
                {
                    TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]);
                    r->m_table_sizes[counter] += s_min_table_sizes[counter];
                }
                MZ_CLEAR_OBJ(r->m_tables[2].m_code_size);
                for (counter = 0; counter < r->m_table_sizes[2]; counter++)
                {
                    mz_uint s;
                    TINFL_GET_BITS(14, s, 3);
                    r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s;
                }
                r->m_table_sizes[2] = 19;
            }
            for (; (int)r->m_type >= 0; r->m_type--)
            {
                int tree_next, tree_cur;
                tinfl_huff_table *pTable;
                mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16];
                pTable = &r->m_tables[r->m_type];
                MZ_CLEAR_OBJ(total_syms);
                MZ_CLEAR_OBJ(pTable->m_look_up);
                MZ_CLEAR_OBJ(pTable->m_tree);
                for (i = 0; i < r->m_table_sizes[r->m_type]; ++i)
                    total_syms[pTable->m_code_size[i]]++;
                used_syms = 0, total = 0;
                next_code[0] = next_code[1] = 0;
                for (i = 1; i <= 15; ++i)
                {
                    used_syms += total_syms[i];
                    next_code[i + 1] = (total = ((total + total_syms[i]) << 1));
                }
                if ((65536 != total) && (used_syms > 1))
                {
                    TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED);
                }
                for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index)
                {
                    mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index];
                    if (!code_size)
                        continue;
                    cur_code = next_code[code_size]++;
                    for (l = code_size; l > 0; l--, cur_code >>= 1)
                        rev_code = (rev_code << 1) | (cur_code & 1);
                    if (code_size <= TINFL_FAST_LOOKUP_BITS)
                    {
                        mz_int16 k = (mz_int16)((code_size << 9) | sym_index);
                        while (rev_code < TINFL_FAST_LOOKUP_SIZE)
                        {
                            pTable->m_look_up[rev_code] = k;
                            rev_code += (1 << code_size);
                        }
                        continue;
                    }
                    if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)]))
                    {
                        pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next;
                        tree_cur = tree_next;
                        tree_next -= 2;
                    }
                    rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1);
                    for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--)
                    {
                        tree_cur -= ((rev_code >>= 1) & 1);
                        if (!pTable->m_tree[-tree_cur - 1])
                        {
                            pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next;
                            tree_cur = tree_next;
                            tree_next -= 2;
                        }
                        else
                            tree_cur = pTable->m_tree[-tree_cur - 1];
                    }
                    tree_cur -= ((rev_code >>= 1) & 1);
                    pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index;
                }
                if (r->m_type == 2)
                {
                    for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);)
                    {
                        mz_uint s;
                        TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]);
                        if (dist < 16)
                        {
                            r->m_len_codes[counter++] = (mz_uint8)dist;
                            continue;
                        }
                        if ((dist == 16) && (!counter))
                        {
                            TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED);
                        }
                        num_extra = "\02\03\07"[dist - 16];
                        TINFL_GET_BITS(18, s, num_extra);
                        s += "\03\03\013"[dist - 16];
                        TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s);
                        counter += s;
                    }
                    if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)
                    {
                        TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED);
                    }
                    TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]);
                    TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]);
                }
            }
            for (;;)
            {
                mz_uint8 *pSrc;
                for (;;)
                {
                    if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))
                    {
                        TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]);
                        if (counter >= 256)
                            break;
                        while (pOut_buf_cur >= pOut_buf_end)
                        {
                            TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT);
                        }
                        *pOut_buf_cur++ = (mz_uint8)counter;
                    }
                    else
                    {
                        int sym2;
                        mz_uint code_len;
#if TINFL_USE_64BIT_BITBUF
                        if (num_bits < 30)
                        {
                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits);
                            pIn_buf_cur += 4;
                            num_bits += 32;
                        }
#else
                        if (num_bits < 15)
                        {
                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);
                            pIn_buf_cur += 2;
                            num_bits += 16;
                        }
#endif
                        if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
                            code_len = sym2 >> 9;
                        else
                        {
                            code_len = TINFL_FAST_LOOKUP_BITS;
                            do
                            {
                                sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)];
                            } while (sym2 < 0);
                        }
                        counter = sym2;
                        bit_buf >>= code_len;
                        num_bits -= code_len;
                        if (counter & 256)
                            break;

#if !TINFL_USE_64BIT_BITBUF
                        if (num_bits < 15)
                        {
                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);
                            pIn_buf_cur += 2;
                            num_bits += 16;
                        }
#endif
                        if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
                            code_len = sym2 >> 9;
                        else
                        {
                            code_len = TINFL_FAST_LOOKUP_BITS;
                            do
                            {
                                sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)];
                            } while (sym2 < 0);
                        }
                        bit_buf >>= code_len;
                        num_bits -= code_len;

                        pOut_buf_cur[0] = (mz_uint8)counter;
                        if (sym2 & 256)
                        {
                            pOut_buf_cur++;
                            counter = sym2;
                            break;
                        }
                        pOut_buf_cur[1] = (mz_uint8)sym2;
                        pOut_buf_cur += 2;
                    }
                }
                if ((counter &= 511) == 256)
                    break;

                num_extra = s_length_extra[counter - 257];
                counter = s_length_base[counter - 257];
                if (num_extra)
                {
                    mz_uint extra_bits;
                    TINFL_GET_BITS(25, extra_bits, num_extra);
                    counter += extra_bits;
                }

                TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]);
                num_extra = s_dist_extra[dist];
                dist = s_dist_base[dist];
                if (num_extra)
                {
                    mz_uint extra_bits;
                    TINFL_GET_BITS(27, extra_bits, num_extra);
                    dist += extra_bits;
                }

                dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start;
                if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
                {
                    TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED);
                }

                pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);

                if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)
                {
                    while (counter--)
                    {
                        while (pOut_buf_cur >= pOut_buf_end)
                        {
                            TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT);
                        }
                        *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];
                    }
                    continue;
                }
#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
                else if ((counter >= 9) && (counter <= dist))
                {
                    const mz_uint8 *pSrc_end = pSrc + (counter & ~7);
                    do
                    {
                        ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0];
                        ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1];
                        pOut_buf_cur += 8;
                    } while ((pSrc += 8) < pSrc_end);
                    if ((counter &= 7) < 3)
                    {
                        if (counter)
                        {
                            pOut_buf_cur[0] = pSrc[0];
                            if (counter > 1)
                                pOut_buf_cur[1] = pSrc[1];
                            pOut_buf_cur += counter;
                        }
                        continue;
                    }
                }
#endif
                do
                {
                    pOut_buf_cur[0] = pSrc[0];
                    pOut_buf_cur[1] = pSrc[1];
                    pOut_buf_cur[2] = pSrc[2];
                    pOut_buf_cur += 3;
                    pSrc += 3;
                } while ((int)(counter -= 3) > 2);
                if ((int)counter > 0)
                {
                    pOut_buf_cur[0] = pSrc[0];
                    if ((int)counter > 1)
                        pOut_buf_cur[1] = pSrc[1];
                    pOut_buf_cur += counter;
                }
            }
        }
    } while (!(r->m_final & 1));

    /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */
    /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */
    TINFL_SKIP_BITS(32, num_bits & 7);
    while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))
    {
        --pIn_buf_cur;
        num_bits -= 8;
    }
    bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1);
    MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */

    if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
    {
        for (counter = 0; counter < 4; ++counter)
        {
            mz_uint s;
            if (num_bits)
                TINFL_GET_BITS(41, s, 8);
            else
                TINFL_GET_BYTE(42, s);
            r->m_z_adler32 = (r->m_z_adler32 << 8) | s;
        }
    }
    TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE);

    TINFL_CR_FINISH

common_exit:
    /* As long as we aren't telling the caller that we NEED more input to make forward progress: */
    /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */
    /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */
    if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS))
    {
        while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))
        {
            --pIn_buf_cur;
            num_bits -= 8;
        }
    }
    r->m_num_bits = num_bits;
    r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1);
    r->m_dist = dist;
    r->m_counter = counter;
    r->m_num_extra = num_extra;
    r->m_dist_from_out_buf_start = dist_from_out_buf_start;
    *pIn_buf_size = pIn_buf_cur - pIn_buf_next;
    *pOut_buf_size = pOut_buf_cur - pOut_buf_next;
    if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))
    {
        const mz_uint8 *ptr = pOut_buf_next;
        size_t buf_len = *pOut_buf_size;
        mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16;
        size_t block_len = buf_len % 5552;
        while (buf_len)
        {
            for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
            {
                s1 += ptr[0], s2 += s1;
                s1 += ptr[1], s2 += s1;
                s1 += ptr[2], s2 += s1;
                s1 += ptr[3], s2 += s1;
                s1 += ptr[4], s2 += s1;
                s1 += ptr[5], s2 += s1;
                s1 += ptr[6], s2 += s1;
                s1 += ptr[7], s2 += s1;
            }
            for (; i < block_len; ++i)
                s1 += *ptr++, s2 += s1;
            s1 %= 65521U, s2 %= 65521U;
            buf_len -= block_len;
            block_len = 5552;
        }
        r->m_check_adler32 = (s2 << 16) + s1;
        if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32))
            status = TINFL_STATUS_ADLER32_MISMATCH;
    }
    return status;
}

/* Higher level helper functions. */
void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
{
    tinfl_decompressor decomp;
    void *pBuf = NULL, *pNew_buf;
    size_t src_buf_ofs = 0, out_buf_capacity = 0;
    *pOut_len = 0;
    tinfl_init(&decomp);
    for (;;)
    {
        size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity;
        tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size,
                                               (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
        if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT))
        {
            MZ_FREE(pBuf);
            *pOut_len = 0;
            return NULL;
        }
        src_buf_ofs += src_buf_size;
        *pOut_len += dst_buf_size;
        if (status == TINFL_STATUS_DONE)
            break;
        new_out_buf_capacity = out_buf_capacity * 2;
        if (new_out_buf_capacity < 128)
            new_out_buf_capacity = 128;
        pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity);
        if (!pNew_buf)
        {
            MZ_FREE(pBuf);
            *pOut_len = 0;
            return NULL;
        }
        pBuf = pNew_buf;
        out_buf_capacity = new_out_buf_capacity;
    }
    return pBuf;
}

size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
{
    tinfl_decompressor decomp;
    tinfl_status status;
    tinfl_init(&decomp);
    status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
    return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len;
}

int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
    int result = 0;
    tinfl_decompressor decomp;
    mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE);
    size_t in_buf_ofs = 0, dict_ofs = 0;
    if (!pDict)
        return TINFL_STATUS_FAILED;
    tinfl_init(&decomp);
    for (;;)
    {
        size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs;
        tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size,
                                               (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)));
        in_buf_ofs += in_buf_size;
        if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user)))
            break;
        if (status != TINFL_STATUS_HAS_MORE_OUTPUT)
        {
            result = (status == TINFL_STATUS_DONE);
            break;
        }
        dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1);
    }
    MZ_FREE(pDict);
    *pIn_buf_size = in_buf_ofs;
    return result;
}

tinfl_decompressor *tinfl_decompressor_alloc()
{
    tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor));
    if (pDecomp)
        tinfl_init(pDecomp);
    return pDecomp;
}

void tinfl_decompressor_free(tinfl_decompressor *pDecomp)
{
    MZ_FREE(pDecomp);
}

#ifdef __cplusplus
}
#endif
/**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * Copyright 2016 Martin Raiber
 * All Rights Reserved.
 *
 * 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.
 *
 **************************************************************************/


#ifndef MINIZ_NO_ARCHIVE_APIS

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- .ZIP archive reading */

#ifdef MINIZ_NO_STDIO
#define MZ_FILE void *
#else
#include <sys/stat.h>

#if defined(_MSC_VER) || defined(__MINGW64__)
static FILE *mz_fopen(const char *pFilename, const char *pMode)
{
    FILE *pFile = NULL;
    fopen_s(&pFile, pFilename, pMode);
    return pFile;
}
static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)
{
    FILE *pFile = NULL;
    if (freopen_s(&pFile, pPath, pMode, pStream))
        return NULL;
    return pFile;
}
#ifndef MINIZ_NO_TIME
#include <sys/utime.h>
#endif
#define MZ_FOPEN mz_fopen
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 _ftelli64
#define MZ_FSEEK64 _fseeki64
#define MZ_FILE_STAT_STRUCT _stat
#define MZ_FILE_STAT _stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN mz_freopen
#define MZ_DELETE_FILE remove
#elif defined(__MINGW32__)
#ifndef MINIZ_NO_TIME
#include <sys/utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 ftello64
#define MZ_FSEEK64 fseeko64
#define MZ_FILE_STAT_STRUCT _stat
#define MZ_FILE_STAT _stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
#define MZ_DELETE_FILE remove
#elif defined(__TINYC__)
#ifndef MINIZ_NO_TIME
#include <sys/utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 ftell
#define MZ_FSEEK64 fseek
#define MZ_FILE_STAT_STRUCT stat
#define MZ_FILE_STAT stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
#define MZ_DELETE_FILE remove
#elif defined(__GNUC__) && _LARGEFILE64_SOURCE
#ifndef MINIZ_NO_TIME
#include <utime.h>
#endif
#define MZ_FOPEN(f, m) fopen64(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 ftello64
#define MZ_FSEEK64 fseeko64
#define MZ_FILE_STAT_STRUCT stat64
#define MZ_FILE_STAT stat64
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
#define MZ_DELETE_FILE remove
#elif defined(__APPLE__) && _LARGEFILE64_SOURCE
#ifndef MINIZ_NO_TIME
#include <utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 ftello
#define MZ_FSEEK64 fseeko
#define MZ_FILE_STAT_STRUCT stat
#define MZ_FILE_STAT stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(p, m, s) freopen(p, m, s)
#define MZ_DELETE_FILE remove

#else
#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.")
#ifndef MINIZ_NO_TIME
#include <utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#ifdef __STRICT_ANSI__
#define MZ_FTELL64 ftell
#define MZ_FSEEK64 fseek
#else
#define MZ_FTELL64 ftello
#define MZ_FSEEK64 fseeko
#endif
#define MZ_FILE_STAT_STRUCT stat
#define MZ_FILE_STAT stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
#define MZ_DELETE_FILE remove
#endif /* #ifdef _MSC_VER */
#endif /* #ifdef MINIZ_NO_STDIO */

#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))

/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */
enum
{
    /* ZIP archive identifiers and record sizes */
    MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50,
    MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50,
    MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50,
    MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30,
    MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46,
    MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,

    /* ZIP64 archive identifier and record sizes */
    MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50,
    MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50,
    MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56,
    MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20,
    MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001,
    MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50,
    MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24,
    MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16,

    /* Central directory header record offsets */
    MZ_ZIP_CDH_SIG_OFS = 0,
    MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4,
    MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6,
    MZ_ZIP_CDH_BIT_FLAG_OFS = 8,
    MZ_ZIP_CDH_METHOD_OFS = 10,
    MZ_ZIP_CDH_FILE_TIME_OFS = 12,
    MZ_ZIP_CDH_FILE_DATE_OFS = 14,
    MZ_ZIP_CDH_CRC32_OFS = 16,
    MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20,
    MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24,
    MZ_ZIP_CDH_FILENAME_LEN_OFS = 28,
    MZ_ZIP_CDH_EXTRA_LEN_OFS = 30,
    MZ_ZIP_CDH_COMMENT_LEN_OFS = 32,
    MZ_ZIP_CDH_DISK_START_OFS = 34,
    MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36,
    MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38,
    MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42,

    /* Local directory header offsets */
    MZ_ZIP_LDH_SIG_OFS = 0,
    MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4,
    MZ_ZIP_LDH_BIT_FLAG_OFS = 6,
    MZ_ZIP_LDH_METHOD_OFS = 8,
    MZ_ZIP_LDH_FILE_TIME_OFS = 10,
    MZ_ZIP_LDH_FILE_DATE_OFS = 12,
    MZ_ZIP_LDH_CRC32_OFS = 14,
    MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18,
    MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22,
    MZ_ZIP_LDH_FILENAME_LEN_OFS = 26,
    MZ_ZIP_LDH_EXTRA_LEN_OFS = 28,
    MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3,

    /* End of central directory offsets */
    MZ_ZIP_ECDH_SIG_OFS = 0,
    MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4,
    MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6,
    MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8,
    MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10,
    MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12,
    MZ_ZIP_ECDH_CDIR_OFS_OFS = 16,
    MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,

    /* ZIP64 End of central directory locator offsets */
    MZ_ZIP64_ECDL_SIG_OFS = 0,                    /* 4 bytes */
    MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4,          /* 4 bytes */
    MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8,  /* 8 bytes */
    MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */

    /* ZIP64 End of central directory header offsets */
    MZ_ZIP64_ECDH_SIG_OFS = 0,                       /* 4 bytes */
    MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4,            /* 8 bytes */
    MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12,          /* 2 bytes */
    MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14,           /* 2 bytes */
    MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16,            /* 4 bytes */
    MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20,            /* 4 bytes */
    MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */
    MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32,       /* 8 bytes */
    MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40,                /* 8 bytes */
    MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48,                 /* 8 bytes */
    MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0,
    MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11
};

typedef struct
{
    void *m_p;
    size_t m_size, m_capacity;
    mz_uint m_element_size;
} mz_zip_array;

struct mz_zip_internal_state_tag
{
    mz_zip_array m_central_dir;
    mz_zip_array m_central_dir_offsets;
    mz_zip_array m_sorted_central_dir_offsets;

    /* The flags passed in when the archive is initially opened. */
    uint32_t m_init_flags;

    /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */
    mz_bool m_zip64;

    /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */
    mz_bool m_zip64_has_extended_info_fields;

    /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */
    MZ_FILE *m_pFile;
    mz_uint64 m_file_archive_start_ofs;

    void *m_pMem;
    size_t m_mem_size;
    size_t m_mem_capacity;
};

#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size

#if defined(DEBUG) || defined(_DEBUG) || defined(NDEBUG)
static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index)
{
    MZ_ASSERT(index < pArray->m_size);
    return index;
}
#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)]
#else
#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index]
#endif

static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size)
{
    memset(pArray, 0, sizeof(mz_zip_array));
    pArray->m_element_size = element_size;
}

static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray)
{
    pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p);
    memset(pArray, 0, sizeof(mz_zip_array));
}

static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing)
{
    void *pNew_p;
    size_t new_capacity = min_new_capacity;
    MZ_ASSERT(pArray->m_element_size);
    if (pArray->m_capacity >= min_new_capacity)
        return MZ_TRUE;
    if (growing)
    {
        new_capacity = MZ_MAX(1, pArray->m_capacity);
        while (new_capacity < min_new_capacity)
            new_capacity *= 2;
    }
    if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity)))
        return MZ_FALSE;
    pArray->m_p = pNew_p;
    pArray->m_capacity = new_capacity;
    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing)
{
    if (new_capacity > pArray->m_capacity)
    {
        if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing))
            return MZ_FALSE;
    }
    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing)
{
    if (new_size > pArray->m_capacity)
    {
        if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing))
            return MZ_FALSE;
    }
    pArray->m_size = new_size;
    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n)
{
    return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE);
}

static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n)
{
    size_t orig_size = pArray->m_size;
    if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE))
        return MZ_FALSE;
    memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size);
    return MZ_TRUE;
}

#ifndef MINIZ_NO_TIME
static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date)
{
    struct tm tm;
    memset(&tm, 0, sizeof(tm));
    tm.tm_isdst = -1;
    tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900;
    tm.tm_mon = ((dos_date >> 5) & 15) - 1;
    tm.tm_mday = dos_date & 31;
    tm.tm_hour = (dos_time >> 11) & 31;
    tm.tm_min = (dos_time >> 5) & 63;
    tm.tm_sec = (dos_time << 1) & 62;
    return mktime(&tm);
}

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)
{
#ifdef _MSC_VER
    struct tm tm_struct;
    struct tm *tm = &tm_struct;
    errno_t err = localtime_s(tm, &time);
    if (err)
    {
        *pDOS_date = 0;
        *pDOS_time = 0;
        return;
    }
#else
    struct tm *tm = localtime(&time);
#endif /* #ifdef _MSC_VER */

    *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));
    *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);
}
#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */

#ifndef MINIZ_NO_STDIO
#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime)
{
    struct MZ_FILE_STAT_STRUCT file_stat;

    /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */
    if (MZ_FILE_STAT(pFilename, &file_stat) != 0)
        return MZ_FALSE;

    *pTime = file_stat.st_mtime;

    return MZ_TRUE;
}
#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/

static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time)
{
    struct utimbuf t;

    memset(&t, 0, sizeof(t));
    t.actime = access_time;
    t.modtime = modified_time;

    return !utime(pFilename, &t);
}
#endif /* #ifndef MINIZ_NO_STDIO */
#endif /* #ifndef MINIZ_NO_TIME */

static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num)
{
    if (pZip)
        pZip->m_last_error = err_num;
    return MZ_FALSE;
}

static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags)
{
    (void)flags;
    if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!pZip->m_pAlloc)
        pZip->m_pAlloc = miniz_def_alloc_func;
    if (!pZip->m_pFree)
        pZip->m_pFree = miniz_def_free_func;
    if (!pZip->m_pRealloc)
        pZip->m_pRealloc = miniz_def_realloc_func;

    pZip->m_archive_size = 0;
    pZip->m_central_directory_file_ofs = 0;
    pZip->m_total_files = 0;
    pZip->m_last_error = MZ_ZIP_NO_ERROR;

    if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
    pZip->m_pState->m_init_flags = flags;
    pZip->m_pState->m_zip64 = MZ_FALSE;
    pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE;

    pZip->m_zip_mode = MZ_ZIP_MODE_READING;

    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index)
{
    const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
    const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index));
    mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    mz_uint8 l = 0, r = 0;
    pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
    pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
    pE = pL + MZ_MIN(l_len, r_len);
    while (pL < pE)
    {
        if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
            break;
        pL++;
        pR++;
    }
    return (pL == pE) ? (l_len < r_len) : (l < r);
}

#define MZ_SWAP_UINT32(a, b) \
    do                       \
    {                        \
        mz_uint32 t = a;     \
        a = b;               \
        b = t;               \
    }                        \
    MZ_MACRO_END

/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */
static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip)
{
    mz_zip_internal_state *pState = pZip->m_pState;
    const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
    const mz_zip_array *pCentral_dir = &pState->m_central_dir;
    mz_uint32 *pIndices;
    mz_uint32 start, end;
    const mz_uint32 size = pZip->m_total_files;

    if (size <= 1U)
        return;

    pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);

    start = (size - 2U) >> 1U;
    for (;;)
    {
        mz_uint64 child, root = start;
        for (;;)
        {
            if ((child = (root << 1U) + 1U) >= size)
                break;
            child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])));
            if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
                break;
            MZ_SWAP_UINT32(pIndices[root], pIndices[child]);
            root = child;
        }
        if (!start)
            break;
        start--;
    }

    end = size - 1;
    while (end > 0)
    {
        mz_uint64 child, root = 0;
        MZ_SWAP_UINT32(pIndices[end], pIndices[0]);
        for (;;)
        {
            if ((child = (root << 1U) + 1U) >= end)
                break;
            child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]));
            if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
                break;
            MZ_SWAP_UINT32(pIndices[root], pIndices[child]);
            root = child;
        }
        end--;
    }
}

static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs)
{
    mz_int64 cur_file_ofs;
    mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
    mz_uint8 *pBuf = (mz_uint8 *)buf_u32;

    /* Basic sanity checks - reject files which are too small */
    if (pZip->m_archive_size < record_size)
        return MZ_FALSE;

    /* Find the record by scanning the file from the end towards the beginning. */
    cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0);
    for (;;)
    {
        int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);

        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n)
            return MZ_FALSE;

        for (i = n - 4; i >= 0; --i)
        {
            mz_uint s = MZ_READ_LE32(pBuf + i);
            if (s == record_sig)
            {
                if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size)
                    break;
            }
        }

        if (i >= 0)
        {
            cur_file_ofs += i;
            break;
        }

        /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */
        if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size)))
            return MZ_FALSE;

        cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);
    }

    *pOfs = cur_file_ofs;
    return MZ_TRUE;
}

static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags)
{
    mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0;
    mz_uint64 cdir_ofs = 0;
    mz_int64 cur_file_ofs = 0;
    const mz_uint8 *p;

    mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
    mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
    mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
    mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32;

    mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32;

    mz_uint64 zip64_end_of_central_dir_ofs = 0;

    /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */
    if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

    if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs))
        return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR);

    /* Read and verify the end of central directory record. */
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

    if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
    {
        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)
        {
            if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG)
            {
                zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS);
                if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
                    return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

                if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)
                {
                    if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG)
                    {
                        pZip->m_pState->m_zip64 = MZ_TRUE;
                    }
                }
            }
        }
    }

    pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS);
    cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
    num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
    cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);
    cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS);
    cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);

    if (pZip->m_pState->m_zip64)
    {
        mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS);
        mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS);
        mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
        mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS);
        mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS);

        if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12))
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

        if (zip64_total_num_of_disks != 1U)
            return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

        /* Check for miniz's practical limits */
        if (zip64_cdir_total_entries > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);

        pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries;

        if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);

        cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk;

        /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */
        if (zip64_size_of_central_directory > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

        cdir_size = (mz_uint32)zip64_size_of_central_directory;

        num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS);

        cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS);

        cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS);
    }

    if (pZip->m_total_files != cdir_entries_on_this_disk)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

    if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1)))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

    if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    pZip->m_central_directory_file_ofs = cdir_ofs;

    if (pZip->m_total_files)
    {
        mz_uint i, n;
        /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */
        if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) ||
            (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE)))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        if (sort_central_dir)
        {
            if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE))
                return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

        /* Now create an index into the central directory file records, do some basic sanity checking on each record */
        p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p;
        for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i)
        {
            mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size;
            mz_uint64 comp_size, decomp_size, local_header_ofs;

            if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG))
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p);

            if (sort_central_dir)
                MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i;

            comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
            decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
            local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
            filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
            ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);

            if ((!pZip->m_pState->m_zip64_has_extended_info_fields) &&
                (ext_data_size) &&
                (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX))
            {
                /* Attempt to find zip64 extended information field in the entry's extra data */
                mz_uint32 extra_size_remaining = ext_data_size;

                if (extra_size_remaining)
                {
                    const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size;

                    do
                    {
                        mz_uint32 field_id;
                        mz_uint32 field_data_size;

                        if (extra_size_remaining < (sizeof(mz_uint16) * 2))
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        field_id = MZ_READ_LE16(pExtra_data);
                        field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));

                        if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
                        {
                            /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */
                            pZip->m_pState->m_zip64 = MZ_TRUE;
                            pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE;
                            break;
                        }

                        pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;
                        extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;
                    } while (extra_size_remaining);
                }
            }

            /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */
            if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX))
            {
                if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size))
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS);
            if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1)))
                return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

            if (comp_size != MZ_UINT32_MAX)
            {
                if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
            if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED)
                return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

            if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n)
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            n -= total_header_size;
            p += total_header_size;
        }
    }

    if (sort_central_dir)
        mz_zip_reader_sort_central_dir_offsets_by_filename(pZip);

    return MZ_TRUE;
}

void mz_zip_zero_struct(mz_zip_archive *pZip)
{
    if (pZip)
        MZ_CLEAR_OBJ(*pZip);
}

static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)
{
    mz_bool status = MZ_TRUE;

    if (!pZip)
        return MZ_FALSE;

    if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
    {
        if (set_last_error)
            pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER;

        return MZ_FALSE;
    }

    if (pZip->m_pState)
    {
        mz_zip_internal_state *pState = pZip->m_pState;
        pZip->m_pState = NULL;

        mz_zip_array_clear(pZip, &pState->m_central_dir);
        mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
        mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);

#ifndef MINIZ_NO_STDIO
        if (pState->m_pFile)
        {
            if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
            {
                if (MZ_FCLOSE(pState->m_pFile) == EOF)
                {
                    if (set_last_error)
                        pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED;
                    status = MZ_FALSE;
                }
            }
            pState->m_pFile = NULL;
        }
#endif /* #ifndef MINIZ_NO_STDIO */

        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
    }
    pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;

    return status;
}

mz_bool mz_zip_reader_end(mz_zip_archive *pZip)
{
    return mz_zip_reader_end_internal(pZip, MZ_TRUE);
}
mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags)
{
    if ((!pZip) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_reader_init_internal(pZip, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_USER;
    pZip->m_archive_size = size;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n);
    memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s);
    return s;
}

mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags)
{
    if (!pMem)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

    if (!mz_zip_reader_init_internal(pZip, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY;
    pZip->m_archive_size = size;
    pZip->m_pRead = mz_zip_mem_read_func;
    pZip->m_pIO_opaque = pZip;
    pZip->m_pNeeds_keepalive = NULL;

#ifdef __cplusplus
    pZip->m_pState->m_pMem = const_cast<void *>(pMem);
#else
    pZip->m_pState->m_pMem = (void *)pMem;
#endif

    pZip->m_pState->m_mem_size = size;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);

    file_ofs += pZip->m_pState->m_file_archive_start_ofs;

    if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
        return 0;

    return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile);
}

mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags)
{
    return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0);
}

mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size)
{
    mz_uint64 file_size;
    MZ_FILE *pFile;

    if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pFile = MZ_FOPEN(pFilename, "rb");
    if (!pFile)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    file_size = archive_size;
    if (!file_size)
    {
        if (MZ_FSEEK64(pFile, 0, SEEK_END))
        {
            MZ_FCLOSE(pFile);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);
        }

        file_size = MZ_FTELL64(pFile);
    }

    /* TODO: Better sanity check archive_size and the # of actual remaining bytes */

    if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
    {
	MZ_FCLOSE(pFile);
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
    }

    if (!mz_zip_reader_init_internal(pZip, flags))
    {
        MZ_FCLOSE(pFile);
        return MZ_FALSE;
    }

    pZip->m_zip_type = MZ_ZIP_TYPE_FILE;
    pZip->m_pRead = mz_zip_file_read_func;
    pZip->m_pIO_opaque = pZip;
    pZip->m_pState->m_pFile = pFile;
    pZip->m_archive_size = file_size;
    pZip->m_pState->m_file_archive_start_ofs = file_start_ofs;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags)
{
    mz_uint64 cur_file_ofs;

    if ((!pZip) || (!pFile))
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    cur_file_ofs = MZ_FTELL64(pFile);

    if (!archive_size)
    {
        if (MZ_FSEEK64(pFile, 0, SEEK_END))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);

        archive_size = MZ_FTELL64(pFile) - cur_file_ofs;

        if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
            return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
    }

    if (!mz_zip_reader_init_internal(pZip, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;
    pZip->m_pRead = mz_zip_file_read_func;

    pZip->m_pIO_opaque = pZip;
    pZip->m_pState->m_pFile = pFile;
    pZip->m_archive_size = archive_size;
    pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

#endif /* #ifndef MINIZ_NO_STDIO */

static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index)
{
    if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files))
        return NULL;
    return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
}

mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index)
{
    mz_uint m_bit_flag;
    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
    return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0;
}

mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index)
{
    mz_uint bit_flag;
    mz_uint method;

    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
    bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);

    if ((method != 0) && (method != MZ_DEFLATED))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
        return MZ_FALSE;
    }

    if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
        return MZ_FALSE;
    }

    if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index)
{
    mz_uint filename_len, attribute_mapping_id, external_attr;
    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    if (filename_len)
    {
        if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/')
            return MZ_TRUE;
    }

    /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */
    /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */
    /* FIXME: Remove this check? Is it necessary - we already check the filename. */
    attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8;
    (void)attribute_mapping_id;

    external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
    if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0)
    {
        return MZ_TRUE;
    }

    return MZ_FALSE;
}

static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data)
{
    mz_uint n;
    const mz_uint8 *p = pCentral_dir_header;

    if (pFound_zip64_extra_data)
        *pFound_zip64_extra_data = MZ_FALSE;

    if ((!p) || (!pStat))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* Extract fields from the central directory record. */
    pStat->m_file_index = file_index;
    pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index);
    pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS);
    pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS);
    pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
    pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
#ifndef MINIZ_NO_TIME
    pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS));
#endif
    pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS);
    pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
    pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
    pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS);
    pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
    pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);

    /* Copy as much of the filename and comment as possible. */
    n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1);
    memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
    pStat->m_filename[n] = '\0';

    n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS);
    n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1);
    pStat->m_comment_size = n;
    memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n);
    pStat->m_comment[n] = '\0';

    /* Set some flags for convienance */
    pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index);
    pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index);
    pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index);

    /* See if we need to read any zip64 extended information fields. */
    /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */
    if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX)
    {
        /* Attempt to find zip64 extended information field in the entry's extra data */
        mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);

        if (extra_size_remaining)
        {
            const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);

            do
            {
                mz_uint32 field_id;
                mz_uint32 field_data_size;

                if (extra_size_remaining < (sizeof(mz_uint16) * 2))
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                field_id = MZ_READ_LE16(pExtra_data);
                field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));

                if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
                {
                    const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2;
                    mz_uint32 field_data_remaining = field_data_size;

                    if (pFound_zip64_extra_data)
                        *pFound_zip64_extra_data = MZ_TRUE;

                    if (pStat->m_uncomp_size == MZ_UINT32_MAX)
                    {
                        if (field_data_remaining < sizeof(mz_uint64))
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        pStat->m_uncomp_size = MZ_READ_LE64(pField_data);
                        pField_data += sizeof(mz_uint64);
                        field_data_remaining -= sizeof(mz_uint64);
                    }

                    if (pStat->m_comp_size == MZ_UINT32_MAX)
                    {
                        if (field_data_remaining < sizeof(mz_uint64))
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        pStat->m_comp_size = MZ_READ_LE64(pField_data);
                        pField_data += sizeof(mz_uint64);
                        field_data_remaining -= sizeof(mz_uint64);
                    }

                    if (pStat->m_local_header_ofs == MZ_UINT32_MAX)
                    {
                        if (field_data_remaining < sizeof(mz_uint64))
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        pStat->m_local_header_ofs = MZ_READ_LE64(pField_data);
                        pField_data += sizeof(mz_uint64);
                        field_data_remaining -= sizeof(mz_uint64);
                    }

                    break;
                }

                pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;
                extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;
            } while (extra_size_remaining);
        }
    }

    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags)
{
    mz_uint i;
    if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE)
        return 0 == memcmp(pA, pB, len);
    for (i = 0; i < len; ++i)
        if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i]))
            return MZ_FALSE;
    return MZ_TRUE;
}

static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len)
{
    const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
    mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    mz_uint8 l = 0, r = 0;
    pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
    pE = pL + MZ_MIN(l_len, r_len);
    while (pL < pE)
    {
        if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
            break;
        pL++;
        pR++;
    }
    return (pL == pE) ? (int)(l_len - r_len) : (l - r);
}

static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex)
{
    mz_zip_internal_state *pState = pZip->m_pState;
    const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
    const mz_zip_array *pCentral_dir = &pState->m_central_dir;
    mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
    const uint32_t size = pZip->m_total_files;
    const mz_uint filename_len = (mz_uint)strlen(pFilename);

    if (pIndex)
        *pIndex = 0;

    if (size)
    {
        /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */
        /* honestly the major expense here on 32-bit CPU's will still be the filename compare */
        mz_int64 l = 0, h = (mz_int64)size - 1;

        while (l <= h)
        {
            mz_int64 m = l + ((h - l) >> 1);
            uint32_t file_index = pIndices[(uint32_t)m];

            int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len);
            if (!comp)
            {
                if (pIndex)
                    *pIndex = file_index;
                return MZ_TRUE;
            }
            else if (comp < 0)
                l = m + 1;
            else
                h = m - 1;
        }
    }

    return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);
}

int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags)
{
    mz_uint32 index;
    if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index))
        return -1;
    else
        return (int)index;
}

mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex)
{
    mz_uint file_index;
    size_t name_len, comment_len;

    if (pIndex)
        *pIndex = 0;

    if ((!pZip) || (!pZip->m_pState) || (!pName))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* See if we can use a binary search */
    if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) &&
        (pZip->m_zip_mode == MZ_ZIP_MODE_READING) &&
        ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size))
    {
        return mz_zip_locate_file_binary_search(pZip, pName, pIndex);
    }

    /* Locate the entry by scanning the entire central directory */
    name_len = strlen(pName);
    if (name_len > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    comment_len = pComment ? strlen(pComment) : 0;
    if (comment_len > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    for (file_index = 0; file_index < pZip->m_total_files; file_index++)
    {
        const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
        mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS);
        const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
        if (filename_len < name_len)
            continue;
        if (comment_len)
        {
            mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS);
            const char *pFile_comment = pFilename + filename_len + file_extra_len;
            if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags)))
                continue;
        }
        if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len))
        {
            int ofs = filename_len - 1;
            do
            {
                if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':'))
                    break;
            } while (--ofs >= 0);
            ofs++;
            pFilename += ofs;
            filename_len -= ofs;
        }
        if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags)))
        {
            if (pIndex)
                *pIndex = file_index;
            return MZ_TRUE;
        }
    }

    return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);
}

mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
{
    int status = TINFL_STATUS_DONE;
    mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail;
    mz_zip_archive_file_stat file_stat;
    void *pRead_buf;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
    tinfl_decompressor inflator;

    if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    /* A directory or zero length file */
    if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))
        return MZ_TRUE;

    /* Encryption and patch files are not supported. */
    if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

    /* This function only supports decompressing stored and deflate. */
    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);

    /* Ensure supplied output buffer is large enough. */
    needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;
    if (buf_size < needed_size)
        return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL);

    /* Read and parse the local directory entry. */
    cur_file_ofs = file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
    {
        /* The file is stored or the caller has requested the compressed data. */
        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0)
        {
            if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)
                return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);
        }
#endif

        return MZ_TRUE;
    }

    /* Decompress the file either directly from memory or from a file input buffer. */
    tinfl_init(&inflator);

    if (pZip->m_pState->m_pMem)
    {
        /* Read directly from the archive in memory. */
        pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
        read_buf_size = read_buf_avail = file_stat.m_comp_size;
        comp_remaining = 0;
    }
    else if (pUser_read_buf)
    {
        /* Use a user provided read buffer. */
        if (!user_read_buf_size)
            return MZ_FALSE;
        pRead_buf = (mz_uint8 *)pUser_read_buf;
        read_buf_size = user_read_buf_size;
        read_buf_avail = 0;
        comp_remaining = file_stat.m_comp_size;
    }
    else
    {
        /* Temporarily allocate a read buffer. */
        read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
        if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        read_buf_avail = 0;
        comp_remaining = file_stat.m_comp_size;
    }

    do
    {
        /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */
        size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs);
        if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
        {
            read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
            if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
            {
                status = TINFL_STATUS_FAILED;
                mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
                break;
            }
            cur_file_ofs += read_buf_avail;
            comp_remaining -= read_buf_avail;
            read_buf_ofs = 0;
        }
        in_buf_size = (size_t)read_buf_avail;
        status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0));
        read_buf_avail -= in_buf_size;
        read_buf_ofs += in_buf_size;
        out_buf_ofs += out_buf_size;
    } while (status == TINFL_STATUS_NEEDS_MORE_INPUT);

    if (status == TINFL_STATUS_DONE)
    {
        /* Make sure the entire file was decompressed, and check its CRC. */
        if (out_buf_ofs != file_stat.m_uncomp_size)
        {
            mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
            status = TINFL_STATUS_FAILED;
        }
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)
        {
            mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);
            status = TINFL_STATUS_FAILED;
        }
#endif
    }

    if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf))
        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);

    return status == TINFL_STATUS_DONE;
}

mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
        return MZ_FALSE;
    return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size);
}

mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags)
{
    return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0);
}

mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags)
{
    return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0);
}

void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags)
{
    mz_uint64 comp_size, uncomp_size, alloc_size;
    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    void *pBuf;

    if (pSize)
        *pSize = 0;

    if (!p)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return NULL;
    }

    comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
    uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);

    alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size;
    if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))
    {
        mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
        return NULL;
    }

    if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size)))
    {
        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        return NULL;
    }

    if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
        return NULL;
    }

    if (pSize)
        *pSize = (size_t)alloc_size;
    return pBuf;
}

void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
    {
        if (pSize)
            *pSize = 0;
        return MZ_FALSE;
    }
    return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags);
}

mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
{
    int status = TINFL_STATUS_DONE;
    mz_uint file_crc32 = MZ_CRC32_INIT;
    mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs;
    mz_zip_archive_file_stat file_stat;
    void *pRead_buf = NULL;
    void *pWrite_buf = NULL;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;

    if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    /* A directory or zero length file */
    if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))
        return MZ_TRUE;

    /* Encryption and patch files are not supported. */
    if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

    /* This function only supports decompressing stored and deflate. */
    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);

    /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */
    cur_file_ofs = file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    /* Decompress the file either directly from memory or from a file input buffer. */
    if (pZip->m_pState->m_pMem)
    {
        pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
        read_buf_size = read_buf_avail = file_stat.m_comp_size;
        comp_remaining = 0;
    }
    else
    {
        read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
        if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        read_buf_avail = 0;
        comp_remaining = file_stat.m_comp_size;
    }

    if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
    {
        /* The file is stored or the caller has requested the compressed data. */
        if (pZip->m_pState->m_pMem)
        {
            if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX))
                return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

            if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size)
            {
                mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
                status = TINFL_STATUS_FAILED;
            }
            else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
            {
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size);
#endif
            }

            cur_file_ofs += file_stat.m_comp_size;
            out_buf_ofs += file_stat.m_comp_size;
            comp_remaining = 0;
        }
        else
        {
            while (comp_remaining)
            {
                read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
                if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                    status = TINFL_STATUS_FAILED;
                    break;
                }

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
                {
                    file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail);
                }
#endif

                if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
                    status = TINFL_STATUS_FAILED;
                    break;
                }

                cur_file_ofs += read_buf_avail;
                out_buf_ofs += read_buf_avail;
                comp_remaining -= read_buf_avail;
            }
        }
    }
    else
    {
        tinfl_decompressor inflator;
        tinfl_init(&inflator);

        if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
        {
            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            status = TINFL_STATUS_FAILED;
        }
        else
        {
            do
            {
                mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
                size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
                if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
                {
                    read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
                    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
                    {
                        mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                        status = TINFL_STATUS_FAILED;
                        break;
                    }
                    cur_file_ofs += read_buf_avail;
                    comp_remaining -= read_buf_avail;
                    read_buf_ofs = 0;
                }

                in_buf_size = (size_t)read_buf_avail;
                status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
                read_buf_avail -= in_buf_size;
                read_buf_ofs += in_buf_size;

                if (out_buf_size)
                {
                    if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size)
                    {
                        mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
                        status = TINFL_STATUS_FAILED;
                        break;
                    }

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                    file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size);
#endif
                    if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size)
                    {
                        mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
                        status = TINFL_STATUS_FAILED;
                        break;
                    }
                }
            } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT));
        }
    }

    if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
    {
        /* Make sure the entire file was decompressed, and check its CRC. */
        if (out_buf_ofs != file_stat.m_uncomp_size)
        {
            mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
            status = TINFL_STATUS_FAILED;
        }
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        else if (file_crc32 != file_stat.m_crc32)
        {
            mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
            status = TINFL_STATUS_FAILED;
        }
#endif
    }

    if (!pZip->m_pState->m_pMem)
        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);

    if (pWrite_buf)
        pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf);

    return status == TINFL_STATUS_DONE;
}

mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
        return MZ_FALSE;

    return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags);
}

mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)
{
    mz_zip_reader_extract_iter_state *pState;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;

    /* Argument sanity check */
    if ((!pZip) || (!pZip->m_pState))
        return NULL;

    /* Allocate an iterator status structure */
    pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state));
    if (!pState)
    {
        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        return NULL;
    }

    /* Fetch file details */
    if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* Encryption and patch files are not supported. */
    if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* This function only supports decompressing stored and deflate. */
    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* Init state - save args */
    pState->pZip = pZip;
    pState->flags = flags;

    /* Init state - reset variables to defaults */
    pState->status = TINFL_STATUS_DONE;
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
    pState->file_crc32 = MZ_CRC32_INIT;
#endif
    pState->read_buf_ofs = 0;
    pState->out_buf_ofs = 0;
    pState->pRead_buf = NULL;
    pState->pWrite_buf = NULL;
    pState->out_blk_remain = 0;

    /* Read and parse the local directory entry. */
    pState->cur_file_ofs = pState->file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
    {
        mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* Decompress the file either directly from memory or from a file input buffer. */
    if (pZip->m_pState->m_pMem)
    {
        pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs;
        pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size;
        pState->comp_remaining = pState->file_stat.m_comp_size;
    }
    else
    {
        if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))
        {
            /* Decompression required, therefore intermediate read buffer required */
            pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE);
            if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size)))
            {
                mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
                pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
                return NULL;
            }
        }
        else
        {
            /* Decompression not required - we will be reading directly into user buffer, no temp buf required */
            pState->read_buf_size = 0;
        }
        pState->read_buf_avail = 0;
        pState->comp_remaining = pState->file_stat.m_comp_size;
    }

    if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))
    {
        /* Decompression required, init decompressor */
        tinfl_init( &pState->inflator );

        /* Allocate write buffer */
        if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
        {
            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            if (pState->pRead_buf)
                pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf);
            pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
            return NULL;
        }
    }

    return pState;
}

mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)
{
    mz_uint32 file_index;

    /* Locate file index by name */
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
        return NULL;

    /* Construct iterator */
    return mz_zip_reader_extract_iter_new(pZip, file_index, flags);
}

size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size)
{
    size_t copied_to_caller = 0;

    /* Argument sanity check */
    if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf))
        return 0;

    if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))
    {
        /* The file is stored or the caller has requested the compressed data, calc amount to return. */
        copied_to_caller = MZ_MIN( buf_size, pState->comp_remaining );

        /* Zip is in memory....or requires reading from a file? */
        if (pState->pZip->m_pState->m_pMem)
        {
            /* Copy data to caller's buffer */
            memcpy( pvBuf, pState->pRead_buf, copied_to_caller );
            pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller;
        }
        else
        {
            /* Read directly into caller's buffer */
            if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller)
            {
                /* Failed to read all that was asked for, flag failure and alert user */
                mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);
                pState->status = TINFL_STATUS_FAILED;
                copied_to_caller = 0;
            }
        }

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        /* Compute CRC if not returning compressed data only */
        if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
            pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller);
#endif

        /* Advance offsets, dec counters */
        pState->cur_file_ofs += copied_to_caller;
        pState->out_buf_ofs += copied_to_caller;
        pState->comp_remaining -= copied_to_caller;
    }
    else
    {
        do
        {
            /* Calc ptr to write buffer - given current output pos and block size */
            mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));

            /* Calc max output size - given current output pos and block size */
            size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));

            if (!pState->out_blk_remain)
            {
                /* Read more data from file if none available (and reading from file) */
                if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem))
                {
                    /* Calc read size */
                    pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining);
                    if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail)
                    {
                        mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);
                        pState->status = TINFL_STATUS_FAILED;
                        break;
                    }

                    /* Advance offsets, dec counters */
                    pState->cur_file_ofs += pState->read_buf_avail;
                    pState->comp_remaining -= pState->read_buf_avail;
                    pState->read_buf_ofs = 0;
                }

                /* Perform decompression */
                in_buf_size = (size_t)pState->read_buf_avail;
                pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
                pState->read_buf_avail -= in_buf_size;
                pState->read_buf_ofs += in_buf_size;

                /* Update current output block size remaining */
                pState->out_blk_remain = out_buf_size;
            }

            if (pState->out_blk_remain)
            {
                /* Calc amount to return. */
                size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain );

                /* Copy data to caller's buffer */
                memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy );

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                /* Perform CRC */
                pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy);
#endif

                /* Decrement data consumed from block */
                pState->out_blk_remain -= to_copy;

                /* Inc output offset, while performing sanity check */
                if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size)
                {
                    mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);
                    pState->status = TINFL_STATUS_FAILED;
                    break;
                }

                /* Increment counter of data copied to caller */
                copied_to_caller += to_copy;
            }
        } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) );
    }

    /* Return how many bytes were copied into user buffer */
    return copied_to_caller;
}

mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState)
{
    int status;

    /* Argument sanity check */
    if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState))
        return MZ_FALSE;

    /* Was decompression completed and requested? */
    if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
    {
        /* Make sure the entire file was decompressed, and check its CRC. */
        if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size)
        {
            mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
            pState->status = TINFL_STATUS_FAILED;
        }
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        else if (pState->file_crc32 != pState->file_stat.m_crc32)
        {
            mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);
            pState->status = TINFL_STATUS_FAILED;
        }
#endif
    }

    /* Free buffers */
    if (!pState->pZip->m_pState->m_pMem)
        pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf);
    if (pState->pWrite_buf)
        pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf);

    /* Save status */
    status = pState->status;

    /* Free context */
    pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState);

    return status == TINFL_STATUS_DONE;
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n)
{
    (void)ofs;

    return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque);
}

mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags)
{
    mz_bool status;
    mz_zip_archive_file_stat file_stat;
    MZ_FILE *pFile;

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);

    pFile = MZ_FOPEN(pDst_filename, "wb");
    if (!pFile)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);

    if (MZ_FCLOSE(pFile) == EOF)
    {
        if (status)
            mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);

        status = MZ_FALSE;
    }

#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)
    if (status)
        mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time);
#endif

    return status;
}

mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))
        return MZ_FALSE;

    return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags);
}

mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags)
{
    mz_zip_archive_file_stat file_stat;

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);

    return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);
}

mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))
        return MZ_FALSE;

    return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags);
}
#endif /* #ifndef MINIZ_NO_STDIO */

static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
    mz_uint32 *p = (mz_uint32 *)pOpaque;
    (void)file_ofs;
    *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n);
    return n;
}

mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)
{
    mz_zip_archive_file_stat file_stat;
    mz_zip_internal_state *pState;
    const mz_uint8 *pCentral_dir_header;
    mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE;
    mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
    mz_uint64 local_header_ofs = 0;
    mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32;
    mz_uint64 local_header_comp_size, local_header_uncomp_size;
    mz_uint32 uncomp_crc32 = MZ_CRC32_INIT;
    mz_bool has_data_descriptor;
    mz_uint32 local_header_bit_flags;

    mz_zip_array file_data_array;
    mz_zip_array_init(&file_data_array, 1);

    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (file_index > pZip->m_total_files)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    pCentral_dir_header = mz_zip_get_cdh(pZip, file_index);

    if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir))
        return MZ_FALSE;

    /* A directory or zero length file */
    if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size))
        return MZ_TRUE;

    /* Encryption and patch files are not supported. */
    if (file_stat.m_is_encrypted)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

    /* This function only supports stored and deflate. */
    if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);

    if (!file_stat.m_is_supported)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);

    /* Read and parse the local directory entry. */
    local_header_ofs = file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);
    local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);
    local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);
    local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS);
    local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
    has_data_descriptor = (local_header_bit_flags & 8) != 0;

    if (local_header_filename_len != strlen(file_stat.m_filename))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    if (local_header_filename_len)
    {
        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len)
        {
            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            goto handle_failure;
        }

        /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */
        if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0)
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            goto handle_failure;
        }
    }

    if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))
    {
        mz_uint32 extra_size_remaining = local_header_extra_len;
        const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p;

        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)
        {
            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            goto handle_failure;
        }

        do
        {
            mz_uint32 field_id, field_data_size, field_total_size;

            if (extra_size_remaining < (sizeof(mz_uint16) * 2))
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            field_id = MZ_READ_LE16(pExtra_data);
            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
            field_total_size = field_data_size + sizeof(mz_uint16) * 2;

            if (field_total_size > extra_size_remaining)
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
            {
                const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);

                if (field_data_size < sizeof(mz_uint64) * 2)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
                    goto handle_failure;
                }

                local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);
                local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64));

                found_zip64_ext_data_in_ldir = MZ_TRUE;
                break;
            }

            pExtra_data += field_total_size;
            extra_size_remaining -= field_total_size;
        } while (extra_size_remaining);
    }

    /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */
    /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */
    if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32))
    {
        mz_uint8 descriptor_buf[32];
        mz_bool has_id;
        const mz_uint8 *pSrc;
        mz_uint32 file_crc32;
        mz_uint64 comp_size = 0, uncomp_size = 0;

        mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4;

        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s))
        {
            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            goto handle_failure;
        }

        has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID);
        pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf;

        file_crc32 = MZ_READ_LE32(pSrc);

        if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir))
        {
            comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32));
            uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64));
        }
        else
        {
            comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32));
            uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32));
        }

        if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size))
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            goto handle_failure;
        }
    }
    else
    {
        if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size))
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            goto handle_failure;
        }
    }

    mz_zip_array_clear(pZip, &file_data_array);

    if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0)
    {
        if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0))
            return MZ_FALSE;

        /* 1 more check to be sure, although the extract checks too. */
        if (uncomp_crc32 != file_stat.m_crc32)
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            return MZ_FALSE;
        }
    }

    return MZ_TRUE;

handle_failure:
    mz_zip_array_clear(pZip, &file_data_array);
    return MZ_FALSE;
}

mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags)
{
    mz_zip_internal_state *pState;
    uint32_t i;

    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    /* Basic sanity checks */
    if (!pState->m_zip64)
    {
        if (pZip->m_total_files > MZ_UINT16_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

        if (pZip->m_archive_size > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
    }
    else
    {
        if (pZip->m_total_files >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

        if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
    }

    for (i = 0; i < pZip->m_total_files; i++)
    {
        if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags)
        {
            mz_uint32 found_index;
            mz_zip_archive_file_stat stat;

            if (!mz_zip_reader_file_stat(pZip, i, &stat))
                return MZ_FALSE;

            if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index))
                return MZ_FALSE;

            /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */
            if (found_index != i)
                return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
        }

        if (!mz_zip_validate_file(pZip, i, flags))
            return MZ_FALSE;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr)
{
    mz_bool success = MZ_TRUE;
    mz_zip_archive zip;
    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;

    if ((!pMem) || (!size))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;
        return MZ_FALSE;
    }

    mz_zip_zero_struct(&zip);

    if (!mz_zip_reader_init_mem(&zip, pMem, size, flags))
    {
        if (pErr)
            *pErr = zip.m_last_error;
        return MZ_FALSE;
    }

    if (!mz_zip_validate_archive(&zip, flags))
    {
        actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (!mz_zip_reader_end_internal(&zip, success))
    {
        if (!actual_err)
            actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (pErr)
        *pErr = actual_err;

    return success;
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr)
{
    mz_bool success = MZ_TRUE;
    mz_zip_archive zip;
    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;

    if (!pFilename)
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;
        return MZ_FALSE;
    }

    mz_zip_zero_struct(&zip);

    if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0))
    {
        if (pErr)
            *pErr = zip.m_last_error;
        return MZ_FALSE;
    }

    if (!mz_zip_validate_archive(&zip, flags))
    {
        actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (!mz_zip_reader_end_internal(&zip, success))
    {
        if (!actual_err)
            actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (pErr)
        *pErr = actual_err;

    return success;
}
#endif /* #ifndef MINIZ_NO_STDIO */

/* ------------------- .ZIP archive writing */

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v)
{
    p[0] = (mz_uint8)v;
    p[1] = (mz_uint8)(v >> 8);
}
static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v)
{
    p[0] = (mz_uint8)v;
    p[1] = (mz_uint8)(v >> 8);
    p[2] = (mz_uint8)(v >> 16);
    p[3] = (mz_uint8)(v >> 24);
}
static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v)
{
    mz_write_le32(p, (mz_uint32)v);
    mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32));
}

#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v))
#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v))
#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v))

static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    mz_zip_internal_state *pState = pZip->m_pState;
    mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size);

    if (!n)
        return 0;

    /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */
    if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))
    {
        mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
        return 0;
    }

    if (new_size > pState->m_mem_capacity)
    {
        void *pNew_block;
        size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity);

        while (new_capacity < new_size)
            new_capacity *= 2;

        if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity)))
        {
            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            return 0;
        }

        pState->m_pMem = pNew_block;
        pState->m_mem_capacity = new_capacity;
    }
    memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n);
    pState->m_mem_size = (size_t)new_size;
    return n;
}

static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)
{
    mz_zip_internal_state *pState;
    mz_bool status = MZ_TRUE;

    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)))
    {
        if (set_last_error)
            mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    pState = pZip->m_pState;
    pZip->m_pState = NULL;
    mz_zip_array_clear(pZip, &pState->m_central_dir);
    mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
    mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);

#ifndef MINIZ_NO_STDIO
    if (pState->m_pFile)
    {
        if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
        {
            if (MZ_FCLOSE(pState->m_pFile) == EOF)
            {
                if (set_last_error)
                    mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);
                status = MZ_FALSE;
            }
        }

        pState->m_pFile = NULL;
    }
#endif /* #ifndef MINIZ_NO_STDIO */

    if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem);
        pState->m_pMem = NULL;
    }

    pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
    pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;
    return status;
}

mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags)
{
    mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0;

    if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
    {
        if (!pZip->m_pRead)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    if (pZip->m_file_offset_alignment)
    {
        /* Ensure user specified file offset alignment is a power of 2. */
        if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1))
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    if (!pZip->m_pAlloc)
        pZip->m_pAlloc = miniz_def_alloc_func;
    if (!pZip->m_pFree)
        pZip->m_pFree = miniz_def_free_func;
    if (!pZip->m_pRealloc)
        pZip->m_pRealloc = miniz_def_realloc_func;

    pZip->m_archive_size = existing_size;
    pZip->m_central_directory_file_ofs = 0;
    pZip->m_total_files = 0;

    if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));

    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));

    pZip->m_pState->m_zip64 = zip64;
    pZip->m_pState->m_zip64_has_extended_info_fields = zip64;

    pZip->m_zip_type = MZ_ZIP_TYPE_USER;
    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size)
{
    return mz_zip_writer_init_v2(pZip, existing_size, 0);
}

mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags)
{
    pZip->m_pWrite = mz_zip_heap_write_func;
    pZip->m_pNeeds_keepalive = NULL;

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
        pZip->m_pRead = mz_zip_mem_read_func;

    pZip->m_pIO_opaque = pZip;

    if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_HEAP;

    if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning)))
    {
        if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size)))
        {
            mz_zip_writer_end_internal(pZip, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }
        pZip->m_pState->m_mem_capacity = initial_allocation_size;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size)
{
    return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0);
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);

    file_ofs += pZip->m_pState->m_file_archive_start_ofs;

    if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
    {
        mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);
        return 0;
    }

    return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile);
}

mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning)
{
    return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0);
}

mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags)
{
    MZ_FILE *pFile;

    pZip->m_pWrite = mz_zip_file_write_func;
    pZip->m_pNeeds_keepalive = NULL;

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
        pZip->m_pRead = mz_zip_file_read_func;

    pZip->m_pIO_opaque = pZip;

    if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))
        return MZ_FALSE;

    if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb")))
    {
        mz_zip_writer_end(pZip);
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
    }

    pZip->m_pState->m_pFile = pFile;
    pZip->m_zip_type = MZ_ZIP_TYPE_FILE;

    if (size_to_reserve_at_beginning)
    {
        mz_uint64 cur_ofs = 0;
        char buf[4096];

        MZ_CLEAR_OBJ(buf);

        do
        {
            size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning);
            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n)
            {
                mz_zip_writer_end(pZip);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
            }
            cur_ofs += n;
            size_to_reserve_at_beginning -= n;
        } while (size_to_reserve_at_beginning);
    }

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags)
{
    pZip->m_pWrite = mz_zip_file_write_func;
    pZip->m_pNeeds_keepalive = NULL;

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
        pZip->m_pRead = mz_zip_file_read_func;

    pZip->m_pIO_opaque = pZip;

    if (!mz_zip_writer_init_v2(pZip, 0, flags))
        return MZ_FALSE;

    pZip->m_pState->m_pFile = pFile;
    pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
    pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;

    return MZ_TRUE;
}
#endif /* #ifndef MINIZ_NO_STDIO */

mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)
{
    mz_zip_internal_state *pState;

    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (flags & MZ_ZIP_FLAG_WRITE_ZIP64)
    {
        /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */
        if (!pZip->m_pState->m_zip64)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    /* No sense in trying to write to an archive that's already at the support max size */
    if (pZip->m_pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);

        if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
    }

    pState = pZip->m_pState;

    if (pState->m_pFile)
    {
#ifdef MINIZ_NO_STDIO
        (void)pFilename;
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
#else
        if (pZip->m_pIO_opaque != pZip)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

        if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
        {
            if (!pFilename)
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

            /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */
            if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile)))
            {
                /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */
                mz_zip_reader_end_internal(pZip, MZ_FALSE);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
            }
        }

        pZip->m_pWrite = mz_zip_file_write_func;
        pZip->m_pNeeds_keepalive = NULL;
#endif /* #ifdef MINIZ_NO_STDIO */
    }
    else if (pState->m_pMem)
    {
        /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */
        if (pZip->m_pIO_opaque != pZip)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

        pState->m_mem_capacity = pState->m_mem_size;
        pZip->m_pWrite = mz_zip_heap_write_func;
        pZip->m_pNeeds_keepalive = NULL;
    }
    /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */
    else if (!pZip->m_pWrite)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* Start writing new files at the archive's current central directory location. */
    /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */
    pZip->m_archive_size = pZip->m_central_directory_file_ofs;
    pZip->m_central_directory_file_ofs = 0;

    /* Clear the sorted central dir offsets, they aren't useful or maintained now. */
    /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */
    /* TODO: We could easily maintain the sorted central directory offsets. */
    mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets);

    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename)
{
    return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0);
}

/* TODO: pArchive_name is a terrible name here! */
mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags)
{
    return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0);
}

typedef struct
{
    mz_zip_archive *m_pZip;
    mz_uint64 m_cur_archive_file_ofs;
    mz_uint64 m_comp_size;
} mz_zip_writer_add_state;

static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser)
{
    mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser;
    if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len)
        return MZ_FALSE;

    pState->m_cur_archive_file_ofs += len;
    pState->m_comp_size += len;
    return MZ_TRUE;
}

#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2)
#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3)
static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs)
{
    mz_uint8 *pDst = pBuf;
    mz_uint32 field_size = 0;

    MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);
    MZ_WRITE_LE16(pDst + 2, 0);
    pDst += sizeof(mz_uint16) * 2;

    if (pUncomp_size)
    {
        MZ_WRITE_LE64(pDst, *pUncomp_size);
        pDst += sizeof(mz_uint64);
        field_size += sizeof(mz_uint64);
    }

    if (pComp_size)
    {
        MZ_WRITE_LE64(pDst, *pComp_size);
        pDst += sizeof(mz_uint64);
        field_size += sizeof(mz_uint64);
    }

    if (pLocal_header_ofs)
    {
        MZ_WRITE_LE64(pDst, *pLocal_header_ofs);
        pDst += sizeof(mz_uint64);
        field_size += sizeof(mz_uint64);
    }

    MZ_WRITE_LE16(pBuf + 2, field_size);

    return (mz_uint32)(pDst - pBuf);
}

static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date)
{
    (void)pZip;
    memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE);
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date);
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32);
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size);
    return MZ_TRUE;
}

static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst,
                                                       mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size,
                                                       mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,
                                                       mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,
                                                       mz_uint64 local_header_ofs, mz_uint32 ext_attributes)
{
    (void)pZip;
    memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX));
    return MZ_TRUE;
}

static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size,
                                                const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size,
                                                mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,
                                                mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,
                                                mz_uint64 local_header_ofs, mz_uint32 ext_attributes,
                                                const char *user_extra_data, mz_uint user_extra_data_len)
{
    mz_zip_internal_state *pState = pZip->m_pState;
    mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size;
    size_t orig_central_dir_size = pState->m_central_dir.m_size;
    mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];

    if (!pZip->m_pState->m_zip64)
    {
        if (local_header_ofs > 0xFFFFFFFF)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
    }

    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size + user_extra_data_len, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes))
        return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

    if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &central_dir_ofs, 1)))
    {
        /* Try to resize the central directory array back into its original state. */
        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    return MZ_TRUE;
}

static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name)
{
    /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */
    if (*pArchive_name == '/')
        return MZ_FALSE;

    while (*pArchive_name)
    {
        if ((*pArchive_name == '\\') || (*pArchive_name == ':'))
            return MZ_FALSE;

        pArchive_name++;
    }

    return MZ_TRUE;
}

static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip)
{
    mz_uint32 n;
    if (!pZip->m_file_offset_alignment)
        return 0;
    n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1));
    return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1));
}

static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n)
{
    char buf[4096];
    memset(buf, 0, MZ_MIN(sizeof(buf), n));
    while (n)
    {
        mz_uint32 s = MZ_MIN(sizeof(buf), n);
        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_file_ofs += s;
        n -= s;
    }
    return MZ_TRUE;
}

mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                 mz_uint64 uncomp_size, mz_uint32 uncomp_crc32)
{
    return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0);
}

mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size,
                                    mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified,
                                    const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
{
    mz_uint16 method = 0, dos_time = 0, dos_date = 0;
    mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;
    mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0;
    size_t archive_name_size;
    mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
    tdefl_compressor *pComp = NULL;
    mz_bool store_data_uncompressed;
    mz_zip_internal_state *pState;
    mz_uint8 *pExtra_data = NULL;
    mz_uint32 extra_size = 0;
    mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
    mz_uint16 bit_flags = 0;

    if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
        bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;

    if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
        bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;

    if ((int)level_and_flags < 0)
        level_and_flags = MZ_DEFAULT_LEVEL;
    level = level_and_flags & 0xF;
    store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));

    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    if (pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */
        }
        if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF))
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        }
    }

    if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_writer_validate_archive_name(pArchive_name))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

#ifndef MINIZ_NO_TIME
    if (last_modified != NULL)
    {
        mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date);
    }
    else
    {
        MZ_TIME_T cur_time;
        time(&cur_time);
        mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date);
    }
#endif /* #ifndef MINIZ_NO_TIME */

    archive_name_size = strlen(pArchive_name);
    if (archive_name_size > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    if (!pState->m_zip64)
    {
        /* Bail early if the archive would obviously become too large */
        if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size 
			+ MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + 
			pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len
			+ MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        }
    }

    if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/'))
    {
        /* Set DOS Subdirectory attribute bit. */
        ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG;

        /* Subdirectories cannot contain data. */
        if ((buf_size) || (uncomp_size))
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */
    if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1)))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    if ((!store_data_uncompressed) && (buf_size))
    {
        if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor))))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
        return MZ_FALSE;
    }

    local_dir_header_ofs += num_alignment_padding_bytes;
    if (pZip->m_file_offset_alignment)
    {
        MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
    }
    cur_archive_file_ofs += num_alignment_padding_bytes;

    MZ_CLEAR_OBJ(local_dir_header);

    if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
    {
        method = MZ_DEFLATED;
    }

    if (pState->m_zip64)
    {
        if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)
        {
            pExtra_data = extra_data;
            extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                               (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
        }

        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, extra_size + user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }
        cur_archive_file_ofs += archive_name_size;

        if (pExtra_data != NULL)
        {
            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

            cur_archive_file_ofs += extra_size;
        }
    }
    else
    {
        if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }
        cur_archive_file_ofs += archive_name_size;
    }

    if (user_extra_data_len > 0)
    {
        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += user_extra_data_len;
    }

    if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
    {
        uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size);
        uncomp_size = buf_size;
        if (uncomp_size <= 3)
        {
            level = 0;
            store_data_uncompressed = MZ_TRUE;
        }
    }

    if (store_data_uncompressed)
    {
        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_archive_file_ofs += buf_size;
        comp_size = buf_size;
    }
    else if (buf_size)
    {
        mz_zip_writer_add_state state;

        state.m_pZip = pZip;
        state.m_cur_archive_file_ofs = cur_archive_file_ofs;
        state.m_comp_size = 0;

        if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) ||
            (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE))
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);
        }

        comp_size = state.m_comp_size;
        cur_archive_file_ofs = state.m_cur_archive_file_ofs;
    }

    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
    pComp = NULL;

    if (uncomp_size)
    {
        mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];
        mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;

        MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR);

        MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);
        MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);
        if (pExtra_data == NULL)
        {
            if (comp_size > MZ_UINT32_MAX)
                return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

            MZ_WRITE_LE32(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);
        }
        else
        {
            MZ_WRITE_LE64(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);
            local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;
        }

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)
            return MZ_FALSE;

        cur_archive_file_ofs += local_dir_footer_size;
    }

    if (pExtra_data != NULL)
    {
        extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                           (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
    }

    if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, extra_size, pComment,
                                          comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,
                                          user_extra_data_central, user_extra_data_central_len))
        return MZ_FALSE;

    pZip->m_total_files++;
    pZip->m_archive_size = cur_archive_file_ofs;

    return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
{
    mz_uint16 gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;
    mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes;
    mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0;
    mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = size_to_add, comp_size = 0;
    size_t archive_name_size;
    mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
    mz_uint8 *pExtra_data = NULL;
    mz_uint32 extra_size = 0;
    mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
    mz_zip_internal_state *pState;

    if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
        gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;

    if ((int)level_and_flags < 0)
        level_and_flags = MZ_DEFAULT_LEVEL;
    level = level_and_flags & 0xF;

    /* Sanity checks */
    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    if ((!pState->m_zip64) && (uncomp_size > MZ_UINT32_MAX))
    {
        /* Source file is too large for non-zip64 */
        /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        pState->m_zip64 = MZ_TRUE;
    }

    /* We could support this, but why? */
    if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_writer_validate_archive_name(pArchive_name))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

    if (pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */
        }
    }

    archive_name_size = strlen(pArchive_name);
    if (archive_name_size > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    if (!pState->m_zip64)
    {
        /* Bail early if the archive would obviously become too large */
        if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE 
			+ archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024
			+ MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        }
    }

#ifndef MINIZ_NO_TIME
    if (pFile_time)
    {
        mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date);
    }
#endif

    if (uncomp_size <= 3)
        level = 0;

    if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))
    {
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
    }

    cur_archive_file_ofs += num_alignment_padding_bytes;
    local_dir_header_ofs = cur_archive_file_ofs;

    if (pZip->m_file_offset_alignment)
    {
        MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
    }

    if (uncomp_size && level)
    {
        method = MZ_DEFLATED;
    }

    MZ_CLEAR_OBJ(local_dir_header);
    if (pState->m_zip64)
    {
        if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)
        {
            pExtra_data = extra_data;
            extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                               (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
        }

        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, extra_size + user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_archive_file_ofs += archive_name_size;

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += extra_size;
    }
    else
    {
        if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_archive_file_ofs += archive_name_size;
    }

    if (user_extra_data_len > 0)
    {
        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += user_extra_data_len;
    }

    if (uncomp_size)
    {
        mz_uint64 uncomp_remaining = uncomp_size;
        void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE);
        if (!pRead_buf)
        {
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!level)
        {
            while (uncomp_remaining)
            {
                mz_uint n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining);
                if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n))
                {
                    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                    return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                }
                uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);
                uncomp_remaining -= n;
                cur_archive_file_ofs += n;
            }
            comp_size = uncomp_size;
        }
        else
        {
            mz_bool result = MZ_FALSE;
            mz_zip_writer_add_state state;
            tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor));
            if (!pComp)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            }

            state.m_pZip = pZip;
            state.m_cur_archive_file_ofs = cur_archive_file_ofs;
            state.m_comp_size = 0;

            if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
            }

            for (;;)
            {
                size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
                tdefl_status status;
                tdefl_flush flush = TDEFL_NO_FLUSH;

                if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                    break;
                }

                uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size);
                uncomp_remaining -= in_buf_size;

                if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque))
                    flush = TDEFL_FULL_FLUSH;

                status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? flush : TDEFL_FINISH);
                if (status == TDEFL_STATUS_DONE)
                {
                    result = MZ_TRUE;
                    break;
                }
                else if (status != TDEFL_STATUS_OKAY)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);
                    break;
                }
            }

            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);

            if (!result)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                return MZ_FALSE;
            }

            comp_size = state.m_comp_size;
            cur_archive_file_ofs = state.m_cur_archive_file_ofs;
        }

        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
    }

    {
        mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];
        mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;

        MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);
        MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);
        if (pExtra_data == NULL)
        {
            if (comp_size > MZ_UINT32_MAX)
                return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

            MZ_WRITE_LE32(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);
        }
        else
        {
            MZ_WRITE_LE64(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);
            local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;
        }

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)
            return MZ_FALSE;

        cur_archive_file_ofs += local_dir_footer_size;
    }

    if (pExtra_data != NULL)
    {
        extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                           (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
    }

    if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, extra_size, pComment, comment_size,
                                          uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,
                                          user_extra_data_central, user_extra_data_central_len))
        return MZ_FALSE;

    pZip->m_total_files++;
    pZip->m_archive_size = cur_archive_file_ofs;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
{
    MZ_FILE *pSrc_file = NULL;
    mz_uint64 uncomp_size = 0;
    MZ_TIME_T file_modified_time;
    MZ_TIME_T *pFile_time = NULL;
    mz_bool status;

    memset(&file_modified_time, 0, sizeof(file_modified_time));

#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)
    pFile_time = &file_modified_time;
    if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time))
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED);
#endif

    pSrc_file = MZ_FOPEN(pSrc_filename, "rb");
    if (!pSrc_file)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    MZ_FSEEK64(pSrc_file, 0, SEEK_END);
    uncomp_size = MZ_FTELL64(pSrc_file);
    MZ_FSEEK64(pSrc_file, 0, SEEK_SET);

    status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0);

    MZ_FCLOSE(pSrc_file);

    return status;
}
#endif /* #ifndef MINIZ_NO_STDIO */

static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start)
{
    /* + 64 should be enough for any new zip64 data */
    if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE);

    if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start))
    {
        mz_uint8 new_ext_block[64];
        mz_uint8 *pDst = new_ext_block;
        mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);
        mz_write_le16(pDst + sizeof(mz_uint16), 0);
        pDst += sizeof(mz_uint16) * 2;

        if (pUncomp_size)
        {
            mz_write_le64(pDst, *pUncomp_size);
            pDst += sizeof(mz_uint64);
        }

        if (pComp_size)
        {
            mz_write_le64(pDst, *pComp_size);
            pDst += sizeof(mz_uint64);
        }

        if (pLocal_header_ofs)
        {
            mz_write_le64(pDst, *pLocal_header_ofs);
            pDst += sizeof(mz_uint64);
        }

        if (pDisk_start)
        {
            mz_write_le32(pDst, *pDisk_start);
            pDst += sizeof(mz_uint32);
        }

        mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2));

        if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    if ((pExt) && (ext_len))
    {
        mz_uint32 extra_size_remaining = ext_len;
        const mz_uint8 *pExtra_data = pExt;

        do
        {
            mz_uint32 field_id, field_data_size, field_total_size;

            if (extra_size_remaining < (sizeof(mz_uint16) * 2))
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            field_id = MZ_READ_LE16(pExtra_data);
            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
            field_total_size = field_data_size + sizeof(mz_uint16) * 2;

            if (field_total_size > extra_size_remaining)
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
            {
                if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size))
                    return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            }

            pExtra_data += field_total_size;
            extra_size_remaining -= field_total_size;
        } while (extra_size_remaining);
    }

    return MZ_TRUE;
}

/* TODO: This func is now pretty freakin complex due to zip64, split it up? */
mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index)
{
    mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size;
    mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs;
    mz_uint64 cur_src_file_ofs, cur_dst_file_ofs;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
    mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];
    size_t orig_central_dir_size;
    mz_zip_internal_state *pState;
    void *pBuf;
    const mz_uint8 *pSrc_central_header;
    mz_zip_archive_file_stat src_file_stat;
    mz_uint32 src_filename_len, src_comment_len, src_ext_len;
    mz_uint32 local_header_filename_size, local_header_extra_len;
    mz_uint64 local_header_comp_size, local_header_uncomp_size;
    mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;

    /* Sanity checks */
    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */
    if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* Get pointer to the source central dir header and crack it */
    if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index)))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS);
    src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS);
    src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len;

    /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */
    if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

    if (!pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }

    if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL))
        return MZ_FALSE;

    cur_src_file_ofs = src_file_stat.m_local_header_ofs;
    cur_dst_file_ofs = pZip->m_archive_size;

    /* Read the source archive's local dir header */
    if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;

    /* Compute the total size we need to copy (filename+extra data+compressed data) */
    local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);
    local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);
    local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);
    src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size;

    /* Try to find a zip64 extended information field */
    if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))
    {
        mz_zip_array file_data_array;
        const mz_uint8 *pExtra_data;
        mz_uint32 extra_size_remaining = local_header_extra_len;

        mz_zip_array_init(&file_data_array, 1);
        if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE))
        {
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)
        {
            mz_zip_array_clear(pZip, &file_data_array);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
        }

        pExtra_data = (const mz_uint8 *)file_data_array.m_p;

        do
        {
            mz_uint32 field_id, field_data_size, field_total_size;

            if (extra_size_remaining < (sizeof(mz_uint16) * 2))
            {
                mz_zip_array_clear(pZip, &file_data_array);
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            field_id = MZ_READ_LE16(pExtra_data);
            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
            field_total_size = field_data_size + sizeof(mz_uint16) * 2;

            if (field_total_size > extra_size_remaining)
            {
                mz_zip_array_clear(pZip, &file_data_array);
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
            {
                const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);

                if (field_data_size < sizeof(mz_uint64) * 2)
                {
                    mz_zip_array_clear(pZip, &file_data_array);
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
                }

                local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);
                local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */

                found_zip64_ext_data_in_ldir = MZ_TRUE;
                break;
            }

            pExtra_data += field_total_size;
            extra_size_remaining -= field_total_size;
        } while (extra_size_remaining);

        mz_zip_array_clear(pZip, &file_data_array);
    }

    if (!pState->m_zip64)
    {
        /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */
        /* We also check when the archive is finalized so this doesn't need to be perfect. */
        mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) +
                                            pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64;

        if (approx_new_archive_size >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
    }

    /* Write dest archive padding */
    if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes))
        return MZ_FALSE;

    cur_dst_file_ofs += num_alignment_padding_bytes;

    local_dir_header_ofs = cur_dst_file_ofs;
    if (pZip->m_file_offset_alignment)
    {
        MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
    }

    /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */
    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

    cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;

    /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */
    if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining)))))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    while (src_archive_bytes_remaining)
    {
        n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining);
        if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
        }
        cur_src_file_ofs += n;

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }
        cur_dst_file_ofs += n;

        src_archive_bytes_remaining -= n;
    }

    /* Now deal with the optional data descriptor */
    bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
    if (bit_flags & 8)
    {
        /* Copy data descriptor */
        if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir))
        {
            /* src is zip64, dest must be zip64 */

            /* name			uint32_t's */
            /* id				1 (optional in zip64?) */
            /* crc			1 */
            /* comp_size	2 */
            /* uncomp_size 2 */
            if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6))
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            }

            n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5);
        }
        else
        {
            /* src is NOT zip64 */
            mz_bool has_id;

            if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            }

            has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID);

            if (pZip->m_pState->m_zip64)
            {
                /* dest is zip64, so upgrade the data descriptor */
                const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0));
                const mz_uint32 src_crc32 = pSrc_descriptor[0];
                const mz_uint64 src_comp_size = pSrc_descriptor[1];
                const mz_uint64 src_uncomp_size = pSrc_descriptor[2];

                mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID);
                mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32);
                mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size);
                mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size);

                n = sizeof(mz_uint32) * 6;
            }
            else
            {
                /* dest is NOT zip64, just copy it as-is */
                n = sizeof(mz_uint32) * (has_id ? 4 : 3);
            }
        }

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_src_file_ofs += n;
        cur_dst_file_ofs += n;
    }
    pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);

    /* Finally, add the new central dir header */
    orig_central_dir_size = pState->m_central_dir.m_size;

    memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);

    if (pState->m_zip64)
    {
        /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */
        const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len;
        mz_zip_array new_ext_block;

        mz_zip_array_init(&new_ext_block, sizeof(mz_uint8));

        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX);
        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX);
        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX);

        if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            return MZ_FALSE;
        }

        MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size);

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        mz_zip_array_clear(pZip, &new_ext_block);
    }
    else
    {
        /* sanity checks */
        if (cur_dst_file_ofs > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

        if (local_dir_header_ofs >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs);

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size))
        {
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }
    }

    /* This shouldn't trigger unless we screwed up during the initial sanity checks */
    if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)
    {
        /* TODO: Support central dirs >= 32-bits in size */
        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
    }

    n = (mz_uint32)orig_central_dir_size;
    if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1))
    {
        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    pZip->m_total_files++;
    pZip->m_archive_size = cur_dst_file_ofs;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip)
{
    mz_zip_internal_state *pState;
    mz_uint64 central_dir_ofs, central_dir_size;
    mz_uint8 hdr[256];

    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    if (pState->m_zip64)
    {
        if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX))
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX))
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }

    central_dir_ofs = 0;
    central_dir_size = 0;
    if (pZip->m_total_files)
    {
        /* Write central directory */
        central_dir_ofs = pZip->m_archive_size;
        central_dir_size = pState->m_central_dir.m_size;
        pZip->m_central_directory_file_ofs = central_dir_ofs;
        if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        pZip->m_archive_size += central_dir_size;
    }

    if (pState->m_zip64)
    {
        /* Write zip64 end of central directory header */
        mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size;

        MZ_CLEAR_OBJ(hdr);
        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64));
        MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */
        MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs);
        if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE;

        /* Write zip64 end of central directory locator */
        MZ_CLEAR_OBJ(hdr);
        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr);
        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1);
        if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE;
    }

    /* Write end of central directory record */
    MZ_CLEAR_OBJ(hdr);
    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG);
    MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));
    MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));
    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size));
    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs));

    if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

#ifndef MINIZ_NO_STDIO
    if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF))
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);
#endif /* #ifndef MINIZ_NO_STDIO */

    pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE;

    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;
    return MZ_TRUE;
}

mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize)
{
    if ((!ppBuf) || (!pSize))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    *ppBuf = NULL;
    *pSize = 0;

    if ((!pZip) || (!pZip->m_pState))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (pZip->m_pWrite != mz_zip_heap_write_func)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_writer_finalize_archive(pZip))
        return MZ_FALSE;

    *ppBuf = pZip->m_pState->m_pMem;
    *pSize = pZip->m_pState->m_mem_size;
    pZip->m_pState->m_pMem = NULL;
    pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_end(mz_zip_archive *pZip)
{
    return mz_zip_writer_end_internal(pZip, MZ_TRUE);
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
{
    return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL);
}

mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr)
{
    mz_bool status, created_new_archive = MZ_FALSE;
    mz_zip_archive zip_archive;
    struct MZ_FILE_STAT_STRUCT file_stat;
    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;

    mz_zip_zero_struct(&zip_archive);
    if ((int)level_and_flags < 0)
        level_and_flags = MZ_DEFAULT_LEVEL;

    if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;
        return MZ_FALSE;
    }

    if (!mz_zip_writer_validate_archive_name(pArchive_name))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_FILENAME;
        return MZ_FALSE;
    }

    /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */
    /* So be sure to compile with _LARGEFILE64_SOURCE 1 */
    if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0)
    {
        /* Create a new archive. */
        if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags))
        {
            if (pErr)
                *pErr = zip_archive.m_last_error;
            return MZ_FALSE;
        }

        created_new_archive = MZ_TRUE;
    }
    else
    {
        /* Append to an existing archive. */
        if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))
        {
            if (pErr)
                *pErr = zip_archive.m_last_error;
            return MZ_FALSE;
        }

        if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags))
        {
            if (pErr)
                *pErr = zip_archive.m_last_error;

            mz_zip_reader_end_internal(&zip_archive, MZ_FALSE);

            return MZ_FALSE;
        }
    }

    status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0);
    actual_err = zip_archive.m_last_error;

    /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */
    if (!mz_zip_writer_finalize_archive(&zip_archive))
    {
        if (!actual_err)
            actual_err = zip_archive.m_last_error;

        status = MZ_FALSE;
    }

    if (!mz_zip_writer_end_internal(&zip_archive, status))
    {
        if (!actual_err)
            actual_err = zip_archive.m_last_error;

        status = MZ_FALSE;
    }

    if ((!status) && (created_new_archive))
    {
        /* It's a new archive and something went wrong, so just delete it. */
        int ignoredStatus = MZ_DELETE_FILE(pZip_filename);
        (void)ignoredStatus;
    }

    if (pErr)
        *pErr = actual_err;

    return status;
}

void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr)
{
    mz_uint32 file_index;
    mz_zip_archive zip_archive;
    void *p = NULL;

    if (pSize)
        *pSize = 0;

    if ((!pZip_filename) || (!pArchive_name))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;

        return NULL;
    }

    mz_zip_zero_struct(&zip_archive);
    if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))
    {
        if (pErr)
            *pErr = zip_archive.m_last_error;

        return NULL;
    }

    if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index))
    {
        p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags);
    }

    mz_zip_reader_end_internal(&zip_archive, p != NULL);

    if (pErr)
        *pErr = zip_archive.m_last_error;

    return p;
}

void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags)
{
    return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL);
}

#endif /* #ifndef MINIZ_NO_STDIO */

#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */

/* ------------------- Misc utils */

mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip)
{
    return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID;
}

mz_zip_type mz_zip_get_type(mz_zip_archive *pZip)
{
    return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID;
}

mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num)
{
    mz_zip_error prev_err;

    if (!pZip)
        return MZ_ZIP_INVALID_PARAMETER;

    prev_err = pZip->m_last_error;

    pZip->m_last_error = err_num;
    return prev_err;
}

mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip)
{
    if (!pZip)
        return MZ_ZIP_INVALID_PARAMETER;

    return pZip->m_last_error;
}

mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip)
{
    return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR);
}

mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip)
{
    mz_zip_error prev_err;

    if (!pZip)
        return MZ_ZIP_INVALID_PARAMETER;

    prev_err = pZip->m_last_error;

    pZip->m_last_error = MZ_ZIP_NO_ERROR;
    return prev_err;
}

const char *mz_zip_get_error_string(mz_zip_error mz_err)
{
    switch (mz_err)
    {
        case MZ_ZIP_NO_ERROR:
            return "no error";
        case MZ_ZIP_UNDEFINED_ERROR:
            return "undefined error";
        case MZ_ZIP_TOO_MANY_FILES:
            return "too many files";
        case MZ_ZIP_FILE_TOO_LARGE:
            return "file too large";
        case MZ_ZIP_UNSUPPORTED_METHOD:
            return "unsupported method";
        case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
            return "unsupported encryption";
        case MZ_ZIP_UNSUPPORTED_FEATURE:
            return "unsupported feature";
        case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:
            return "failed finding central directory";
        case MZ_ZIP_NOT_AN_ARCHIVE:
            return "not a ZIP archive";
        case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
            return "invalid header or archive is corrupted";
        case MZ_ZIP_UNSUPPORTED_MULTIDISK:
            return "unsupported multidisk archive";
        case MZ_ZIP_DECOMPRESSION_FAILED:
            return "decompression failed or archive is corrupted";
        case MZ_ZIP_COMPRESSION_FAILED:
            return "compression failed";
        case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:
            return "unexpected decompressed size";
        case MZ_ZIP_CRC_CHECK_FAILED:
            return "CRC-32 check failed";
        case MZ_ZIP_UNSUPPORTED_CDIR_SIZE:
            return "unsupported central directory size";
        case MZ_ZIP_ALLOC_FAILED:
            return "allocation failed";
        case MZ_ZIP_FILE_OPEN_FAILED:
            return "file open failed";
        case MZ_ZIP_FILE_CREATE_FAILED:
            return "file create failed";
        case MZ_ZIP_FILE_WRITE_FAILED:
            return "file write failed";
        case MZ_ZIP_FILE_READ_FAILED:
            return "file read failed";
        case MZ_ZIP_FILE_CLOSE_FAILED:
            return "file close failed";
        case MZ_ZIP_FILE_SEEK_FAILED:
            return "file seek failed";
        case MZ_ZIP_FILE_STAT_FAILED:
            return "file stat failed";
        case MZ_ZIP_INVALID_PARAMETER:
            return "invalid parameter";
        case MZ_ZIP_INVALID_FILENAME:
            return "invalid filename";
        case MZ_ZIP_BUF_TOO_SMALL:
            return "buffer too small";
        case MZ_ZIP_INTERNAL_ERROR:
            return "internal error";
        case MZ_ZIP_FILE_NOT_FOUND:
            return "file not found";
        case MZ_ZIP_ARCHIVE_TOO_LARGE:
            return "archive is too large";
        case MZ_ZIP_VALIDATION_FAILED:
            return "validation failed";
        case MZ_ZIP_WRITE_CALLBACK_FAILED:
            return "write calledback failed";
        default:
            break;
    }

    return "unknown error";
}

/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */
mz_bool mz_zip_is_zip64(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return MZ_FALSE;

    return pZip->m_pState->m_zip64;
}

size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return 0;

    return pZip->m_pState->m_central_dir.m_size;
}

mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip)
{
    return pZip ? pZip->m_total_files : 0;
}

mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip)
{
    if (!pZip)
        return 0;
    return pZip->m_archive_size;
}

mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return 0;
    return pZip->m_pState->m_file_archive_start_ofs;
}

MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return 0;
    return pZip->m_pState->m_pFile;
}

size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n)
{
    if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n);
}

mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size)
{
    mz_uint n;
    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        if (filename_buf_size)
            pFilename[0] = '\0';
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return 0;
    }
    n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    if (filename_buf_size)
    {
        n = MZ_MIN(n, filename_buf_size - 1);
        memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
        pFilename[n] = '\0';
    }
    return n + 1;
}

mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
{
    return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL);
}

mz_bool mz_zip_end(mz_zip_archive *pZip)
{
    if (!pZip)
        return MZ_FALSE;

    if (pZip->m_zip_mode == MZ_ZIP_MODE_READING)
        return mz_zip_reader_end(pZip);
#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
    else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))
        return mz_zip_writer_end(pZip);
#endif

    return MZ_FALSE;
}

#ifdef __cplusplus
}
#endif

#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/
/* end of file miniz-sgb.c */
#endif
/* ^^^ S2_INTERNAL_MINIZ */
/* start of file /home/stephan/fossil/cwal/cwal_amalgamation.c */
#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

/** @internal

  CWAL_UNUSED_VAR exists only to squelch, in non-debug builds,
  warnings about the existence of vars and function parameters which
  are only used in assert() expressions (and thus get filtered out in
  non-debug builds).
*/
#define CWAL_UNUSED_VAR __attribute__((__unused__)) /* avoiding unused var in non-debug build */

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

   Hypothetically, it is legal for a visitor to overwrite *elem, but
   that feature is not used in cwal's internals and might or might not
   work (or be semantically legal) in any other context.
*/
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 {
    /** Indicates that cwal_ptr_table_op() should remove the requested
        item. */
    CWAL_PTR_TABLE_OP_REMOVE = -1,
    /** Indicates that cwal_ptr_table_op() should search for the
        requested item. */
    CWAL_PTR_TABLE_OP_SEARCH = 0,
    /** Indicates that cwal_ptr_table_op() should insert the requested
        item. */
    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.
  Returns CWAL_RC_NOT_FOUND, and has no side effects, if no matching
  entry is found.
  
  CWAL_PTR_TABLE_OP_SEARCH: If a match is found returns 0, else
  returns CWAL_RC_NOT_FOUND.

  CWAL_PTR_TABLE_OP_INSERT: insert the given key into the
  table. Returns CWAL_RC_ALREADY_EXISTS, and has no side effects, 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(), passing it (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(), passing it (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(), passing it (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 t, 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, in bytes, 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 3 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), z-strings (whose memory comes from cwal_malloc() but is
   allocated separately from the string instance (we use this for
   "taking" memory from buffer instances), and (as a performance
   optimization in some algorithms) ASCII-only strings.
*/
struct cwal_string {
    cwal_size_t length;
};

/** @internal

    Internal state flags. Keep these at 16 bits (and use only the
    bottom 8 unless you know a given type supports it!) or adjust
    various flags types accordingly! Note that some flags apply only
    to particular types, and thus certain flags might have the same
    values to conserve bit-space.
*/
enum cwal_internal_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) in a single block. See
     cwal_value_new() for the many examples.
                
     - Its concrete value type has a cwal_obase struct as its first
     member. This, in combination with the first requirement, allows
     us (due to an interesting 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.

     Reminder to self: we can re-use this slot for flags on
     cwal_engine, cwal_obase, and cwal_scope :).
  */
  CWAL_F_ISA_OBASE = 0x01,

  /**
     Set on cwal_engine instances during init if their memcap config
     is enabled, to speed up some if/else checking in the allocator.
  */
  CWAL_F_TRACK_MEM_SIZE = 0x02,
  
  /**
     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).

     Used in cwal_engine::flags and cwal_scope::flags. Values use a
     different flag with similar semantics.
  */
  CWAL_F_IS_DESTRUCTING = 0x04,

  /**
     Used for marking cwal_scope::props (via its cwal_obase::flags) to
     avoid some weird corner cases involving the vacuum-safe flag
     when/if the variable storage Objects are exposed to
     clients/scripts. It's needed to be able to differentiate this
     case from vacuum-safe in order to avoid a corner case if the
     client explicitly sets/unsets the vacuum-safe flag on a prop
     storage container.
  */
  CWAL_F_IS_PROP_STORAGE = 0x10,

  /**
     A flag to briefly note that a value is temporarily locked, e.g.
     currently being sorted, and must not be traversed or modified.
     As of this writing (20191211), only arrays are/need to be locked
     in a way distinct from the is-visiting flag, so we we "could"
     re-use this flag's value with other semantics for non-array
     types, if needed.
  */
  CWAL_F_LOCKED = 0x20
};

   
/** @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.
 */
struct cwal_obase {
    /**
       Linked list of key/value pairs held by this object.
    */
    cwal_kvp * kvp;
    /**
       The "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 on 64-bit, but
       increasing either flags field on 32-bit increases the sizeof
       (4 bytes if we increase both fields to 32 bits). Internally we
       currently need 16 bits for flags.

       As of 20141205, we are using the top few bits of these via
       cwal_container_flags, exposing/modifying the upper byte of
       these flags via cwal_container_flags_set() and
       cwal_container_flags_get().

       As of 20191212, we desperately need more room for flags, but
       cannot do so without increasing the sizeof by up to 4 on 32-bit
       builds, from 12 to 16. We may just have to eat that cost :/.
       On 64-bit builds that change wouldn't change the sizeof().
    */
    cwal_flags16_t flags;
    /**
       Engine-internal flags specifically for container-type-specific
       behaviour.
    */
    cwal_flags16_t containerFlags;

    /**
       Holds client-specified flags.
    */
    cwal_flags16_t clientFlags;

    cwal_flags16_t reservedPadding;
};

/** @internal
    Empty-initialized cwal_obase object. */
#define cwal_obase_empty_m { NULL/*kvp*/, NULL/*prototype*/, CWAL_F_NONE/*flags*/, 0/*containerFlags*/, 0/*clientFlags*/, 0/*reservedPadding*/ }

/** @internal

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

/** @internal

    The metadata for concrete Object values (type CWAL_TYPE_OBJECT).
 */
struct cwal_object {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
};
/**
   Empty-initialized cwal_object object.
*/
#define cwal_object_empty_m { cwal_obase_empty_m }
/**
   Empty-initialized cwal_object object.
*/
extern const cwal_object cwal_object_empty;

/**
   Information for binding a native function to the script engine in
   the form of a Function value (type CWAL_TYPE_FUNCTION).
*/
struct cwal_function {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    /**
       Client-defined state for the function.
    */
    cwal_state state;

    /**
       The concrete callback implementation.
    */
    cwal_callback_f callback;

    /**
       "Rescoper" for the function. Can be set via
       cwal_function_set_rescoper(), and gives the client a way to
       rescope any function-private data (stored in this struct's
       state member) if needed.

       Use case: s2's s2_func_state wants to hold/manage "static"
       script state at the function level without using a hidden
       property. i.e. we don't want cwal_props_clear() to be able to
       nuke that state.
    */
    cwal_value_rescoper_f rescoper;
};

/**
   Empty-initialized cwal_function object.
*/
#define cwal_function_empty_m { cwal_obase_empty_m, cwal_state_empty_m, NULL, 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;
};
/**
   Empty-initialized cwal_exception object.
*/
#define cwal_exception_empty_m { cwal_obase_empty_m, -1 }
/**
   Empty-initialized cwal_exception object.
*/
extern const cwal_exception cwal_exception_empty;

/**
   A key/value pair of cwal_values. While key can be an arbitrary
   cwal_value, the engine requires that the key be of a type with a
   stable hash code AND stable comparison semantics (as of 201811,
   cwal_tuple and cwal_buffer are the only types which are "most
   definitely not recommended) for use as keys because modification of
   their contents changes how they compare to other values, which
   means that they can get "out of place" within a sorted property
   list (e.g. cwal_obase::kvp).

   Each of these objects owns its key/value pointers, and they
   are cleaned up by cwal_kvp_clean(). A KVP holds a reference
   to each of its parts.
*/
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_obase and cwal_hash
       use this. Nobody else should.
    */
    cwal_kvp * right;
    /**
       We need this for intepreter-level flags like "const" and
       "hidden/non-iterable." This was increased from 16 to 32 bits on
       20191210, which does not change the sizeof(), because of
       padding, but the public APIs were left as-is (exposing only
       cwal_flags16_t). We can still, on 64-bit builds, stuff another
       4(?) bytes in here without increasing the sizeof(), which would
       allow us to add a refcount to KVPs, though that wouldn't be as
       useful as it may initially sound because we'd need to implement
       Copy-on-Write for shared KVPs, which has some backfire cases
       for how we use these objects.

       It might be interesting to expose the top 16 of these bits for
       use by clients, but a concrete use case for such flags (which
       isn't covered by existing cwal-level flags) eludes me
    */
    cwal_flags16_t flags;
};

/**
   Empty-initialized cwal_kvp object.
*/
#define cwal_kvp_empty_m {NULL,NULL,NULL,0U/*flags*/}
/**
   Empty-initialized cwal_kvp object.
*/
extern const cwal_kvp cwal_kvp_empty;

/** @internal

   Semantically allocates a new cwal_kvp object, owned by e, though it
   may pull a recycled kvp from e's internal recycler. Returns 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_kvp * kvp );
    
/** @internal

   Calls cwal_kvp_clean(e,kvp) and then either adds kvp to e's 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.
*/
void cwal_kvp_free( cwal_engine * e, 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 * const v );

    
/**
   Typedef for cwal_value comparison functions. Has memcmp()
   semantics. Ordering of mismatched types (e.g. comparing an array to
   an object) 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 property lists and
   hashtables), and are not strictly required to follow the semantics
   of any given scripting environment or specificiation. (That said,
   the public cwal_value_compare() interface uses these, so the
   behaviour must remain stable.)

   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. Comparisons
   are prohibited (by convention) from allocating any memory, and the
   API is not set up to be able to report an OOM error to the caller.
*/
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 data 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 (though, in fact, they are in used that way).
     */
    char const * typeName;

    /**
       Internal flags.
    */
    cwal_flags16_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). Implementations must not do anything
       with child values other than unref them, as they may very well
       already be dead and in the gc-queue (or recycler) by the time
       this is called. The engine delays (via the gc-queue) any
       free()ing of those children while a cleanup pass is running, so
       handling the memory of a child value is legal, but any usage
       other than an unref is semantically strictly illegal.
    */
    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 upscope 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 which are "unmanaged" (e.g. they're not
       stored in cwal_object properties), 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 API only calls
       this when upscoping is necessary.

       The framework guarantees the following:

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

       - v is of the value type represented by this vtab instance.

       - 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. 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 and assert()ing). 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 (via cwal_value_vtab) and many
   concrete types have high-level handle representations
   (e.g. cwal_object and cwal_array).

   @see cwal_new_VALUE()
   @see cwal_value_vtab
*/
struct cwal_value {
    /**
       The "vtbl" of type-specific operations. All instances of a
       given logical value type share a single vtab 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). It is, on rare
       occasion, necessary for code (sometimes even client-side) to
       cwal cwal_value_rescope() to re-parent a value.
     */
    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 array-style 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 a refcount and certain flags.

       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() 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_value_unhand() decrements the refcount (if it is not
       already 0) but does not destroy the value if it reaches 0. If
       it reaches 0, "unhanding" re-sets the value into a so-called
       "probationary" state, making it subject to being swept up if no
       reference is taken before the next cwal_engine_sweep() (or
       similar).

       - This number HAS FLAGS ENCODED IN IT, so don't use this value
       as-is. How many flags, where they are, and what they mean, are
       internal details. Search cwal.c for RCFLAGS for the gory
       details.
    */
    cwal_refcount_t rcflags;

    /*
      Historical notes, no longer entirely relevant but perhaps
      interesting:

      ========
      
      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.

      16 bits (64k) of refcount might be a feasible upper limit, even
      for large apps. And nobody will ever need more than 640kb of
      RAM, either.

      We could... use a 32-bit type, reserve the bottom 24 bits (16M)
      for the refcount, and the top 8 bits for client-side flags.

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

      Ideas of what clients could use flags for:

      - tagging values which want special handling. e.g. if scripts
      can override get/set operations, that may be too costly if
      performed on all values. A flag could indicate whether a given
      value has such an override. Tagging constructor/factory
      functions for special handling with the 'new' keyword is
      something we could possibly use in s2.

      - in s2, we could use this to tag Classes and instances of classes,
      such that we could change property lookup on them (and potentially
      reject the addition of new properties to classes).

      Problems:

      - Built in constant values cannot be flagged. The use of the
      numeric (-1, 0, 1) values as built-in constants saves up to 50%
      of numeric-type allocations in many test scripts, so i don't
      want to drop those. If we allow flags only on container types
      (plus buffers), we can move the cost/handling there (and maybe
      get more flag space). Or we add a way for clients to create
      mutable instances of builtin values, such that they can be
      tagged. That would require some fixes/changes in other bits
      which make assumptions about the uniqueness of, e.g. boolean
      values.

    */
};


/**
   Empty-initialized cwal_value object.
*/
#define cwal_value_empty_m { \
    &cwal_value_vtab_empty/*api*/,\
    0/*scope*/,\
    0/*left*/,\
    0/*right*/,                             \
    0/*rcflags*/ \
}
/**
   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_value_rescoper_f rescoper;
};
#define cwal_native_empty_m {\
        cwal_obase_empty_m, \
        0/*native*/,\
        0/*typeID*/,\
        0/*finalize*/, \
        0/*rescoper*/ \
        }
extern const cwal_native cwal_native_empty;

/** @internal
   Hash table Value type.
*/
struct cwal_hash {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    /**
       Array (hash table) of (cwal_kvp*) values.
       Its .count property holds the current total
       number of entries. Its .alloced property holds
       the length (in entries) of the table. Its .list
       member is the raw array storage.
    */
    cwal_list table;
    /**
       Size effect of the chunk recycler: we need to
       be able to differentiate between the hashtable
       size and the amount of memory allocated for it
       (which might be larger).
     */
    cwal_size_t hashSize;
};
#define cwal_hash_empty_m {            \
    cwal_obase_empty_m/*base*/,        \
    cwal_list_empty_m/*table*/,    \
    0/*hashSize*/\
    }
extern const cwal_hash cwal_hash_empty;


/** @internal

    An object-style representation for cwal_buffer. This type is
    strictly internal, not exposed to clients.
*/
struct cwal_buffer_obj {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    /**
       The buffer owned/tracked by this object.
    */
    cwal_buffer buf;
};
typedef struct cwal_buffer_obj cwal_buffer_obj;
#define cwal_buffer_obj_empty_m {\
    cwal_obase_empty_m/*base*/, \
    cwal_buffer_empty_m/*buf*/ \
}
extern const cwal_buffer_obj cwal_buffer_obj_empty;

/**
   Internal state for CWAL_TYPE_TUPLE-typed cwal_values.

*/
struct cwal_tuple {
    /**
       Number of entries in the list (set at init-time and
       never changes).
    */
    uint16_t n;
    /**
       Not yet used. Note that removing this does not shrink this
       type's sizeof(), due to padding.
    */
    uint16_t flags;
    /**
       List of this->n entries.
    */
    cwal_value ** list;
};
#define cwal_tuple_empty_m {0U,0U,NULL}

/** @internal

    Internal impl of the weak reference class.
*/
struct cwal_weak_ref {
    void * value;
    cwal_type_id typeID;
    cwal_refcount_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 * const v );


/** @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 Value 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->gcInitiator 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

/** @internal

   If the library is built with tracing enabled and tracing is enabled
   for e, this function outputs the state of e->trace and then clears
   that state. The intention is that various macros initialize the
   trace state, then call this to output it.
*/
void cwal_trace( cwal_engine * e );

/** @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), and there is no API for creating/managing non-scope-managed
   values from client code.

   Each allocated value is owned by exactly one scope, and this
   function effectively steals the value from the owning scope. 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, and may assert() that v's is in the
   expected state).

   In all cases, if this function returns 0 the caller effectively
   takes over ownership of v and its memory, and the value IS NOT
   VALID for use with most of the API because, after calling this, it
   has no owning scope, and many APIs assert() that a value has an
   owning scope.

   For built-in values this is a harmless no-op.
       
   Error conditions:

   CWAL_RC_MISUE: either e or v are NULL.

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

/** @internal

    An internal helper for routines (specifically JSON)
    to traverse an object tree and detect cycles.

    Passed the object which is about to be traversed and a pointer
    to an opaque state value.

    If this function returns 0, the value is either not capable of
    participating in acyclic traversal or it is and was flagged as now
    being in an acyclic traversal. If non-0 is returned, the value was
    already flagged as being in an acylic traversal and was traversed
    again (by this function), indicating a cyclic case (i.e. an
    error).

    If it returns CWAL_RC_CYCLES_DETECTED: the value is already in the
    process of acyclic traversal. The caller should fail the operation
    with the result code returned by this function
    (CWAL_RC_CYCLES_DETECTED) or a semantic equivalent for the given
    operation.

    If, and only if, the return code is 0, the caller is obligated to
    call cwal_visit_acyclic_end(), passing it the same second
    argument. The caller MAY call cwal_visit_acyclic_end() if this
    function returns non-0, but is not obligated to.

    @see cwal_visit_props_begin()
    @see cwal_visit_list_begin()
    @see cwal_visit_acyclic_end()
*/
int cwal_visit_acyclic_begin( cwal_value * const v, int * const opaque );

/** @internal

    MUST be called if, and only if, the previous call to
    cwal_visit_acyclic_begin() returned 0. MAY be called if the
    previous call to cwal_visit_acyclic_begin() returned non-0, but it
    is not required.

    @see cwal_visit_acyclic_begin()
*/
void cwal_visit_acyclic_end( cwal_value * const v, int opaque );

/** @internal

    Internal helper to flag property visitation/traversal. If this is
    called, the value stored in *opaque MUST be passed to
    cwal_visit_props_end() in the same logical code block.

    v MUST be a type capable of property iteration (and not a
    builtin).

    Calling this obliges the caller to pass the value of *opaque
    to cwal_visit_props_end() when visitation is complete.

    The API guarantees that this routine will set *opaque to a non-0
    value, so that callers may use 0 for their own purposes (e.g.
    determining whether or not they need to call
    cwal_visit_props_end()).

    @see cwal_visit_props_end()
*/
void cwal_visit_props_begin( cwal_value * const v, int * const opaque );

/** @internal

    If, and only if, cwal_visit_props_begin() was passed v, the
    resulting integer value from that call MUST be passed to this
    function when property traversal is complete.

    @see cwal_visit_props_begin()
*/
void cwal_visit_props_end( cwal_value * const v, int opaque );

/** @internal

    Internal helper to flag property visitation. If this is called,
    the value stored in *opaque MUST be passed to
    cwal_visit_list_end() in the same logical code block.

    v MUST be a type capable of list iteration (and not a builtin).

    Calling this obliges the caller to pass the value of *opaque
    to cwal_visit_list_end() when visitation is complete.

    The API guarantees that this routine will set *opaque to a non-0
    value, so that callers may use 0 for their own purposes (e.g.
    determining whether or not they need to call
    cwal_visit_props_end()).

    @see cwal_visit_list_end()
*/
void cwal_visit_list_begin( cwal_value * const v, int * const opaque );

/** @internal

    If, and only if, cwal_visit_list_begin() was passed v, the
    resulting integer value from that call MUST be passed to this
    function when property traversal is complete.

    @see cwal_visit_list_begin()
*/
void cwal_visit_list_end( cwal_value * const v, int opaque );

/* 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-2018 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-2018 Stephan G. Beal
(https://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 */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 
/* vim: set ts=4 et sw=4 tw=80: */
#include <assert.h>
#include <stdlib.h> /* malloc(), free(), qsort() */
#include <string.h>
#include <errno.h>
#include <stdarg.h>

/**
   Tells the internals whether to keep Object properties sorted or
   not. Sorting speeds up searches. It is/should be enabled as of
   20170320.
 */
#define CWAL_KVP_TRY_SORTING 1

#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;
const cwal_memchunk_config cwal_memchunk_config_empty = cwal_memchunk_config_empty_m;
static const cwal_memchunk_overlay cwal_memchunk_overlay_empty = {0,0};
const cwal_memcap_config cwal_memcap_config_empty = cwal_memcap_config_empty_m;
const cwal_buffer_obj cwal_buffer_obj_empty = cwal_buffer_obj_empty_m;
const cwal_tuple cwal_tuple_empty = cwal_tuple_empty_m;

#define E_IS_DEAD(E) ((E)->fatalCode)

/**
   An experiment in padding all allocations up to a pointer size so
   that the recycler (which might (or might not)) can optimize a
   bit. Similar to the string padding, but it's not clear whether we
   will get as much benefit here.

   Initial tests show padding to take up notably more memory (~2k in
   s2's amalgamated unit tests) and not saving any (or trivially few)
   allocations.

   20160206: enabling this gives, in the s2 unit tests, a microscopic
   reduction (<500b) in peak mem (via list and buffer memory) but
   shows promise in chunk recycling hits (roughly 4%
   improvement). This depends entirely on other options, though: if
   value/chunk recycling and string interning are enabled, plus
   cwal_memcap_config::forceAllocSizeTracking is enabled, alloc counts
   are completely unaffected and the byte difference between using
   this and not is in the 2-digit range.
*/
#define CWAL_ALLOC_DO_PAD 1
#if 0
/* Round N to next ptr size, even if it is on a ptr size boundary. */
/* ==> valgrind errors (uninvestigated) */
#define CWAL_MEMSZ_PAD(N) \
    ((CWAL_ALLOC_DO_PAD)                                     \
     ? ((N) + sizeof(void*) - ((N) % sizeof(void*)))         \
     : (N))
#else
/* Round to next ptr size if N is not on a ptr size boundary. */
#define CWAL_MEMSZ_PAD(N) \
    ((CWAL_ALLOC_DO_PAD)                                     \
    ? (((N)%sizeof(void*))                                     \
       ? ((N) + sizeof(void*) - ((N) % sizeof(void*)))         \
       : (N))                                                  \
   : (N))
#endif

/**
   CWAL_RCFLAGS_BITS = Number of bits to steal from
   cwal_value::refcount for use in per-value-instance flags.

   i could possibly be convinced to go up to 8 bits (16M on 32-bit,
   noting that cwal_config.h uses 32-bit int for cwal_refcount_t on
   16-bit builds). If we go any higher than 10 bits (~4M refcount max
   on 32-bit) it would make sense to switch to 64-bit
   cwal_value::refcount on 32-bit platforms, in which case we can
   comfortably use as many of these flags as we need (at the cost of
   bit-flipping performance on 32-bit platforms).
*/
#define CWAL_RCFLAGS_BITS 8
/** CWAL_RCFLAGS_BITS_MASK = lower-byte mask of CWAL_RCFLAGS_BITS bits. */
#define CWAL_RCFLAGS_BITS_MASK 0xFF

/**
   CWAL_RCFLAG_MAXRC is a mask of bits of cwal_value::refcount
   which are usable for reference counting. We actually use the bottom
   bits for flags, not the top, just to help provoke any internal
   misuse faster. This mask is used to check for overflow when
   incrementing the refcount.
*/
#define CWAL_RCFLAG_MAXRC (((cwal_refcount_t)-1)>>CWAL_RCFLAGS_BITS)

/**
   CWAL_REFCOUNT(V) requires V to be a non-null
   (cwal_value*). Evaluates to the (V)->rcflags normalized as a
   refcount counter, stripped of the mask bits.

   Warning: the compiler likes to complain about expressions with no
   effect, and it's easy to use this macro in such a context (when
   combined with other macros from its family). It's also easy to
   reformulate such uses so that they don't cause that.
*/
#define CWAL_REFCOUNT(V) ((V)->rcflags >> CWAL_RCFLAGS_BITS)

/**
   CWAL_REFCOUNT_SHIFTED(V) requires V to be a non-null
   (cwal_value*). Evaluates to the (V)->rcflags, shifted and/or masked
   into position so that it can be masked with flags.

   e.g. if the reference count is currently 1, then this will be (1
   <<CWAL_RCFLAGS_BITS).
*/
#define CWAL_REFCOUNT_SHIFTED(V) ((V)->rcflags & ~CWAL_RCFLAGS_BITS_MASK)

/**
   CWAL_RCFLAGS(V) requires V to be a non-null (cwal_value*). Evaluates to the
   flag bits of (V)->rcflags, stripped of the refcount part.
*/
#define CWAL_RCFLAGS(V) ((V)->rcflags & CWAL_RCFLAGS_BITS_MASK)

/** Adjusts (cwal_value*) V's refcount by N, retaining flags stashed there. Evals
    to the new refcount, INCLUDING the flags bits. */
#define CWAL_RCADJ(V,N) ((V)->rcflags = CWAL_RCFLAGS(V) | ((CWAL_REFCOUNT(V)+(N)) << CWAL_RCFLAGS_BITS))

/** Decrements (cwal_value*) V's refcount by 1, retaining flags stashed there. Evals to
    the new refcount value, sans flags. */
#define CWAL_RCDECR(V) (CWAL_RCADJ(V,-1), CWAL_REFCOUNT(V))

/** Increments (cwal_value*) V's refcount by 1, retaining flags stashed there. Evals to
    the new refcount value, sans flags. */
#define CWAL_RCINCR(V) (CWAL_RCADJ(V,1), CWAL_REFCOUNT(V))

/*#define CWAL_RCFLAGS_SET(V,F) ((V)->rcflags = CWAL_REFCOUNT_SHIFTED(V) | (CWAL_RCFLAGS_BITS_MASK & (F)))*/
#define CWAL_RCFLAG_ON(V,F) ((V)->rcflags = CWAL_REFCOUNT_SHIFTED(V) | (CWAL_RCFLAGS_BITS_MASK & (CWAL_RCFLAGS(V) | (F))))

/*Disable the given flag in the given (cwal_value*). */
#define CWAL_RCFLAG_OFF(V,F) ((V)->rcflags = CWAL_REFCOUNT_SHIFTED(V) | (CWAL_RCFLAGS_BITS_MASK & (CWAL_RCFLAGS(V) & ~(F))))

/*True if the given flag is on in the given (cwal_value*), else false. */
#define CWAL_RCFLAG_HAS(V,F) ((CWAL_RCFLAGS(V) & (F)) ? 1 : 0)

/*True if the (cwal_value*) V is not NULL and has any rc-flags
  set which indicate that it is in the process of being cleaned
  up, e.g. during its own destruction or its scope's cleanup. */
#define CWAL_V_IS_IN_CLEANUP(V)                                     \
    ((V) && (CWAL_RCFLAG_HAS((V),CWAL_RCF_IS_DESTRUCTING)           \
             || CWAL_RCFLAG_HAS((V), CWAL_RCF_IS_GC_QUEUED)         \
             /* || CWAL_RCFLAG_HAS((V), CWAL_RCF_IS_RECYCLED)) */))


/**
   Evaluates to true if the non-NULL (cwal_value*) V is suitable for storage
   in cwal_scope::mine::headObj (else it goes in headPod, which has finalization
   repercussions).
*/
#if 0
#define CWAL_V_GOES_IN_HEADOBJ(V) (CWAL_VOBASE(V)                       \
                                   || CWAL_TYPE_TUPLE==(V)->vtab->typeID \
                                   || CWAL_TYPE_UNIQUE==(V)->vtab->typeID)
#else
#  define CWAL_V_GOES_IN_HEADOBJ(V) (!!CWAL_VOBASE(V))
#endif

/**
   Flags for use with CWAL_REFCOUNT() and friends. Reminder: every bit
   we steal halves the maximum refcount value!

   See also: CWAL_RCFLAGS_BITS
*/
enum {
CWAL_RCF_NONE = 0x0,
/**
   Set on values which are being destroyed, so that finalization can
   DTRT when encounting it multiple times along the way (cycles).
*/
CWAL_RCF_IS_DESTRUCTING = 0x1,
/**
   Sanity-checking flag which tells us that a given Value is in the
   delayed-gc queue.
*/
CWAL_RCF_IS_GC_QUEUED = 0x2,
/**
   Sanity-checking flag which tells us that a given Value is in a
   recycling bin.
*/
CWAL_RCF_IS_RECYCLED = 0x4,
/**
   Flag set only when a value is in the process of rescoping, so that
   cyclic rescoping can break cycles.
*/
CWAL_RCF_IS_RESCOPING = 0x8,
/**
   Flags an object that its properties are being iterated over or are
   otherwise locked from being modified.
*/
CWAL_RCF_IS_VISITING = 0x10,
/**
   Flags an object that one of the following parts is being iterated
   over or is otherwise locked from being modified: array list, tuple
   list, hashtable (also a list).
*/
CWAL_RCF_IS_VISITING_LIST = 0x20,

/**
   A variant of the visiting-related flags intended to be able to
   restrict recursion by catching cycles. Specifically added to
   re-enable cycle detection in the JSON bits. This flag is
   specifically not intended to be used recursively (it's intended to
   catch recursion) and it explicitly is not specific to iteration
   over either object-level properties or list entries: it's intended
   to be used for both. In the contexts it's used for (JSON), those
   two uses are never combined in the same operation, so there is no
   semantic ambiguity.
*/
CWAL_RCF_IS_VISITING_ACYCLIC = 0x40,

/**
   This flag tells the engine whether or not a given value is
   "vacuum-proof" (immune to cwal_engine_vacuum()). This has to be in
   cwal_value::rcflags instead of cwal_obase::flags so that
   CWAL_TYPE_UNIQUE and CWAL_TYPE_TUPLE (and similar
   not-full-fledged-container types) can be made vacuum-proof.
*/
CWAL_RCF_IS_VACUUM_PROOF = 0x80
/* CWAL_RCF_IS_LOCKED = 0x100 - we'll need this if we want to lock
   tuples the same way as arrays and hashtables, but we can also
   justify not giving tuples full-fledged superpowers. */
};

#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,
        {/*state (cwal_state) */
          NULL/*data*/,
          NULL/*typeID*/,
          cwal_finalizer_f_fclose/*finalize*/
        }
    },
    cwal_engine_tracer_empty_m,
    cwal_state_empty_m/*state*/,
    {/*hook*/
        NULL/*on_init()*/,
        NULL/*init_state*/,
        NULL/*scope_push()*/,
        NULL/*scope_pop()*/,
        NULL/*scope_state*/
    },
    {/*interning*/
        cwal_cstr_internable_predicate_f_default/*is_internable()*/,
        NULL/*state*/
    },
    cwal_memcap_config_empty_m
};

/**
   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 only recycle strings for use with
       "close" size matches (within 1 increment of
       CwalConsts.StringPadSize).
    */
    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;

    /**
       Initial size for cwal_scope::props hashtables.
    */
    cwal_size_t ScopePropsHashSize;
} 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*/,
    11/*ScopePropsHashSize*/
};

const cwal_weak_ref cwal_weak_ref_empty = cwal_weak_ref_empty_m;

/**
   CWAL_BUILTIN_INT_FIRST is the lowest-numbered built-in integer
   value, and it must have a value of 0 or
   lower. CWAL_BUILTIN_INT_LAST is the highest, and it must have a
   value of 0 or higher. CWAL_BUILTIN_INT_COUNT is how many of those
   values there are.

   The library currently won't build if CWAL_BUILTIN_INT_COUNT is 0,
   but both FIRST and LAST may be 0, in which case the COUNT will be 1
   (the number 0 will be built in).

   The real limit to how many values we "could" build in is the
   sizeof(CWAL_BUILTIN_VALS): we don't want it to be 64k+. 100
   built-in integers take up approximately 4k of space on 64-bit.

   Historical tests showed the single biggest gains to be had (in
   terms of malloc saving) when then inclusive range [-1,1] is built
   in. A range of [-1,10] provides marginally higher results. Above
   that tends to make little difference, at least in generic test
   scripts. The values lower than -1 are not used nearly as often, and
   thus don't benefit as much from building them in.
*/
#define CWAL_BUILTIN_INT_FIRST (-10)
#define CWAL_BUILTIN_INT_LAST (20)
#define CWAL_BUILTIN_INT_COUNT (CWAL_BUILTIN_INT_LAST + -(CWAL_BUILTIN_INT_FIRST) + 1/*zero*/)

char const * cwal_version_string(cwal_size_t * length){
    if(length) *length = (cwal_size_t)(sizeof(CWAL_VERSION_STRING)
                                       -sizeof(CWAL_VERSION_STRING[0]
                                               /*NUL byte*/));
    return CWAL_VERSION_STRING;
}

char const * cwal_cppflags(cwal_size_t * length){
    if(length) *length = (cwal_size_t)(sizeof(CWAL_CPPFLAGS)
                                       -sizeof(CWAL_CPPFLAGS[0]
                                               /*NUL byte*/));
    return CWAL_CPPFLAGS;
}

char const * cwal_cflags(cwal_size_t * length){
    if(length) *length = (cwal_size_t)(sizeof(CWAL_CFLAGS)
                                       -sizeof(CWAL_CFLAGS[0]
                                               /*NUL byte*/));
    return CWAL_CFLAGS;
}

char const * cwal_cxxflags(cwal_size_t * length){
    if(length) *length = (cwal_size_t)(sizeof(CWAL_CXXFLAGS)
                                       -sizeof(CWAL_CXXFLAGS[0]
                                               /*NUL byte*/));
    return CWAL_CXXFLAGS;
}

/**
   Internal impl for cwal_value_unref(). Neither e nor v may be NULL
   (may trigger an assert()).  This is a no-op if v is a builtin.
*/
static int cwal_value_unref2(cwal_engine * e, cwal_value *v );

/**
   Was intended to be an internal impl for cwal_value_ref(), but it
   didn't quite work out that way.
*/
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 );

/**
   Requires that self is-a cwal_unique. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_unique( cwal_engine * e, void * V );

/**
   Requires that self is-a cwal_tuple. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_tuple( cwal_engine * e, void * V );

/**
   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_rw(cwal_string *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 cwal_hash_t cwal_value_hash_tuple( cwal_value const * v );
static int cwal_value_cmp_bool( cwal_value const * lhs, cwal_value const * rhs );
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 );
static int cwal_value_cmp_tuple( 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_function( cwal_value * v );
static int cwal_rescope_children_native( cwal_value * v );
static int cwal_rescope_children_hash( cwal_value * v );
static int cwal_rescope_children_unique( cwal_value * v );
static int cwal_rescope_children_tuple( 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,
      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,
      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_ISA_OBASE,
      cwal_value_cleanup_buffer,
      cwal_value_hash_ptr,
      cwal_value_cmp_buffer,
      cwal_rescope_children_obase
    };
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_function
    };
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
    };
static const cwal_value_vtab cwal_value_vtab_unique =
    { CWAL_TYPE_UNIQUE, "unique",
      CWAL_F_NONE,
      cwal_value_cleanup_unique,
      cwal_value_hash_ptr,
      cwal_value_cmp_ptr_only,
      cwal_rescope_children_unique
    };
static const cwal_value_vtab cwal_value_vtab_tuple =
    { CWAL_TYPE_TUPLE, "tuple",
      CWAL_F_NONE,
      cwal_value_cleanup_tuple,
      cwal_value_hash_tuple,
      cwal_value_cmp_tuple,
      cwal_rescope_children_tuple
    };

/**
   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 };
static const cwal_value cwal_value_unique_empty = { &cwal_value_vtab_unique, NULL, NULL, NULL, 0 };
static const cwal_value cwal_tuple_value_empty = { &cwal_value_vtab_tuple, 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) ((T*)((V) ? ((T*)((V)+1)) : (T*)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))

/*
  workarounds for false gcc warning:
  
  https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47214

  Summary: gcc's nonnull warning triggers incorrectly
  for cases like somefunc( X ? Y : NULL ) because it cannot
  know that X will never be false in that context.

  The _NONULL variants work like the non-_NONULL variants
  but MUST NOT be passed a NULL value.
*/
#define CWAL_VVPCAST_NONULL(T,V) ((T*)((V)+1))
#define CWAL_DBL_NONULL(V) CWAL_VVPCAST_NONULL(cwal_double_t,(V))
#define CWAL_INT_NONULL(V) (CWAL_VVPCAST_NONULL(cwal_int_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 three bits and encode the length in the remaining ones. So the
   maximum length of a given String value is 2^(CWAL_SIZE_T_BITS-3).
*/
#if 16 == CWAL_SIZE_T_BITS
    /* Max string length: 8k */
#  define CWAL_STRLEN_MASK    ((cwal_size_t)0x1FFFU)
#  define CWAL_STR_XMASK      ((cwal_size_t)0x8000U)
#  define CWAL_STR_ZMASK      ((cwal_size_t)0x4000U)
#  define CWAL_STR_ASCII_MASK ((cwal_size_t)0x2000U)
#elif 32 == CWAL_SIZE_T_BITS
    /* Max string length: 0.5GB */
#  define CWAL_STRLEN_MASK    ((cwal_size_t)0x1FFFFFFFU)
#  define CWAL_STR_XMASK      ((cwal_size_t)0x80000000U)
#  define CWAL_STR_ZMASK      ((cwal_size_t)0x40000000U)
#  define CWAL_STR_ASCII_MASK ((cwal_size_t)0x20000000U)
#elif 64 == CWAL_SIZE_T_BITS
#if 0
    /* Portability: stick with 32-bit lengths. */
#  define CWAL_STRLEN_MASK    ((cwal_size_t)0x1FFFFFFFU)
#  define CWAL_STR_XMASK      ((cwal_size_t)0x80000000U)
#  define CWAL_STR_ZMASK      ((cwal_size_t)0x40000000U)
#  define CWAL_STR_ASCII_MASK ((cwal_size_t)0x20000000U)
#else
    /* Max string length: You don't have that much memory. */
#  define CWAL_STRLEN_MASK    ((cwal_size_t)0x1FFFFFFFFFFFFFFFU)
#  define CWAL_STR_XMASK      ((cwal_size_t)0x8000000000000000U)
#  define CWAL_STR_ZMASK      ((cwal_size_t)0x4000000000000000U)
#  define CWAL_STR_ASCII_MASK ((cwal_size_t)0x2000000000000000U)
#endif
#endif

/*
  CWAL_STR_ASCII_MASK is a tag for strings which are pure ASCII (all
  bytes are in the inclusive range [0,127]), in which case we can
  speed up many operations which currently always have to read UTF8
  char-by-char. To disable this feature, set CWAL_STR_ASCII_MASK to 0
  and make sure all test code which assert()s that a given string is
  ASCII is disabled.
*/

/* #define CWAL_STRLEN_MASK ((cwal_size_t)~(CWAL_STR_XMASK \
   | CWAL_STR_ZMASK | CWAL_STR_ASCII_MASK)) */


/**
   CWAL_STR(V) casts CWAL_TYPE_STRING (cwal_value*) V to a (cwal_string*).
   If V is NULL or not-a String it evals to 0.
*/
#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) ? 1 : 0)
/**
   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) ? 1 : 0)
/**
   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_STR_ISX(S) || CWAL_STR_ISZ(S))

/**
   Evaluates to true if S (which must be a valid (cwal_string*)) has been
   marked as being ASCII.
*/
#define CWAL_STR_ISASCII(S) ((CWAL_STR_ASCII_MASK & (S)->length) ? 1 : 0)

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

/** Evaluates to non-0 if the cwal_size_t value LEN is too long for a string length. */
#define CWAL_STRLEN_TOO_LONG(LEN) (((cwal_size_t)(LEN)) & ~((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 is not 0 and 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)) ? 1 : 0)

/**
   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_BUFOBJ(V) casts CWAL_TYPE_BUFFER (cwal_value*) V to a (cwal_buffer_obj*).
*/
#define CWAL_BUFOBJ(V) (((V) && (V)->vtab && (CWAL_TYPE_BUFFER==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_buffer_obj,(V)) : 0)

/**
   CWAL_UNIQUE_VALPP(V) casts gets the wrapped (cwal_value**) part of
   CWAL_TYPE_UNIQUE (cwal_value*) V. If V is-not-a Unique, it evals
   to 0.
*/
#define CWAL_UNIQUE_VALPP(V) (((V) && (V)->vtab && (CWAL_TYPE_UNIQUE==(V)->vtab->typeID)) ? (CWAL_VVPCAST(cwal_value*,(V))) : 0)

/**
   CWAL_TUPLE(V) casts (cwal_value*) V to (cwal_tuple*).
   If V is-not-a Tuple, it evals to 0.
*/
#define CWAL_TUPLE(V) (((V) && (V)->vtab && (CWAL_TYPE_TUPLE==(V)->vtab->typeID)) ? (CWAL_VVPCAST(cwal_tuple,(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)

/**
   Evaluates to true if (cwal_value [const] *)V is currently flagged
   as being visited (object-level properties).
*/
#define CWAL_V_IS_VISITING(V) CWAL_RCFLAG_HAS((V),CWAL_RCF_IS_VISITING)
/**
   Evaluates to true if (cwal_value [const] *)V is currently flagged
   as being list-visited (arrays and hashtable entries).
*/
#define CWAL_V_IS_VISITING_LIST(V) CWAL_RCFLAG_HAS((V),CWAL_RCF_IS_VISITING_LIST)

/**
   CWAL_OB_xxx(OBTYPE) all require OBTYPE to be a pointer to a
   cwal_obase-deriving type, typically an array or hashtable.

   Semantic ambiguity here: this flag is currently only used to lock
   array/list parts. If we also want to lock properties, this flag
   becomes ambiguous for list-using types. It would be conceivable to
   have the list part locked while the properties are not, and vice
   versa.
*/
#define CWAL_OB_LOCK(OBTYPE) (OBTYPE)->base.flags |= CWAL_F_LOCKED
#define CWAL_OB_UNLOCK(OBTYPE) (OBTYPE)->base.flags &= ~CWAL_F_LOCKED
#define CWAL_OB_IS_LOCKED(OBTYPE) (CWAL_F_LOCKED & (OBTYPE)->base.flags)

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

#define CWAL_V_IS_VACUUM_SAFE(V) \
    (CWAL_RCFLAG_HAS((V), CWAL_RCF_IS_VACUUM_PROOF))

/**
   Must only be used when (cwal_engine::flags &
   CWAL_F_TRACK_MEM_SIZE). This returns an address sizeof(void*)
   bytes before void pointer M, cast to a (cwal_memsize_t*).
 */
#define MEMSZ_PTR_FROM_MEM(M) (cwal_memsize_t*)((unsigned char *)(M) - sizeof(void*))

/**
   Returns a (cwal_value*) to the given index from
   CWAL_BUILTIN_VALS.memInt.
 */
#define CWAL_BUILTIN_INT_VAL(NDX) ((cwal_value*)&CWAL_BUILTIN_VALS.memInt[NDX])

/**
  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_builtin_values(), to ensure that we only initialize
       this once. Pedantic side-note: it's potentially possible that
       two engine instances in different threads, being initialized at
       the same time, try to initialize this data concurrently. That's
       okay, as each initialization will set the data to the exact
       same state and same addresses, so there's no real harm done. We
       intentionally don't update the 'inited' member until the end up
       of the init process, as its harmless if it's inited multiple
       times concurrently but not harmless if we update this flag at
       the start of the process and another thread tries to use this
       data before it's completely initialized by the thread which set
       that flag.
    */
    int inited;
    /**
       Each of the memXXX entries holds the raw block of memory
       intended for (cwal_value + concrete_type). These are
       initialized by cwal_init_builtin_values().
    */
#define sz_int sizeof(cwal_value)+sizeof(cwal_int_t)
    unsigned char memInt[CWAL_BUILTIN_INT_COUNT
                         ? CWAL_BUILTIN_INT_COUNT
                         : 1/*dummy build placeholder*/][sz_int];
    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 CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS
    unsigned char memAsciiPrintable[128/* one entry each for ASCII [0,127] */]
    [(sizeof(cwal_value)+sizeof(cwal_string)+2)
     /* ==> "X\0", where X is an ASCII char 0..127. */];
#else
    unsigned char memAsciiPrintable[1][1/*dummy placeholder*/];
#endif
    unsigned char memTuple0[sizeof(cwal_value)+sizeof(cwal_tuple)];
#undef sz_int
    /**
       Each of the vXXX pointers points to memory held in the
       similarly-named memXXX member.
    */
    cwal_value * vDblM1 /* TODO: eliminate these, as was done for integers. */;
    cwal_value * vDbl0;
    cwal_value * vDbl1;
    cwal_value * vEmptyString;

    /**
       Points to the cwal_string part of this->memEmptyString.
    */
    cwal_string * sEmptyString;
    cwal_value * vTuple0;
    cwal_value vTrue;
    cwal_value vFalse;
    cwal_value vNull;
    cwal_value vUndef;

    /**
       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
            /* Reminder to self: we don't currently have entries here for
               the built-in length-1 strings (memAsciiPrintable). Nor the
               length-0 Tuple, it seems. Oh, well. We removed the integer
               weak refs on 20171202 when that numeric range was made
               built-time configurable. */;
        cwal_weak_ref wDblM1;
        cwal_weak_ref wDbl0;
        cwal_weak_ref wDbl1;
    } wref;

} CWAL_BUILTIN_VALS = {
0/*inited*/,
{/*memInt*/ {0}},
{/*memDblM1*/ 0},
{/*memDbl0*/ 0},
{/*memDbl1*/ 0},
{/*memEmptyString*/ 0},
{/*memAsciiPrintable*/{0}},
{/*memTuple0*/0},
NULL/*vDblM1*/,
NULL/*vDbl0*/,
NULL/*vDbl1*/,
NULL/*vEmptyString*/,
NULL/*sEmptyString*/,
NULL/*vTuple0*/,
{/*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 },
#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/* wDblM1 */,
    cwal_weak_ref_empty_m/* wDbl0 */,
    cwal_weak_ref_empty_m/* wDbl1 */
}
};

static void cwal_init_builtin_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==CWAL_STRLEN(h->sEmptyString));
        assert(0==*cwal_string_cstr(h->sEmptyString));
    }

#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS
    {/* set up length-1 ASCII strings */
        int i = 0;
        cwal_string * s;
        char * cp;
        memset(h->memAsciiPrintable, 0, sizeof(h->memAsciiPrintable));
        for( ; i <= 127; ++i ){
            v = (cwal_value *)(h->memAsciiPrintable[i]);
            *v = cwal_value_string_empty;
            s = CWAL_STR(v);
            *s = cwal_string_empty;
            s->length = CWAL_STR_ASCII_MASK | 1;
            assert(1 == CWAL_STRLEN(s));
            cp = cwal_string_str_rw(s);
            cp[0] = (char)i;
            cp[1] = 0;
            assert(1 == CWAL_STRLEN(s));
            assert(cp == cwal_string_cstr(s));
        }
        v = 0;
    }
#endif

    {
        /* Set up integers [CWAL_BUILTIN_INT_FIRST .. CWAL_BUILTIN_INT_LAST].
           Maintenance reminder: the memcpy() is needed, rather than direct
           copy, to avoid a "type punning" error in certain compilation
           environments. */
        cwal_int_t v = CWAL_BUILTIN_INT_FIRST, ndx = 0;
        cwal_int_t const last = CWAL_BUILTIN_INT_LAST;
        assert(CWAL_BUILTIN_INT_FIRST <= 0);
        assert(CWAL_BUILTIN_INT_LAST >= 0);
        assert(CWAL_BUILTIN_INT_COUNT > 0);
        assert(sizeof(h->memInt)/sizeof(h->memInt[0]) ==
               (CWAL_BUILTIN_INT_COUNT ? CWAL_BUILTIN_INT_COUNT : 1));
        for( ; v <= last; ++v, ++ndx ){
            cwal_value * cv = CWAL_BUILTIN_INT_VAL(ndx);
            *cv = cwal_value_integer_empty;
            memcpy(CWAL_INT_NONULL(cv), &v, sizeof(cwal_int_t));
            assert( v == cwal_value_get_integer(cv) );
        }
    }

    /* 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) { \
        const cwal_double_t dv = V; \
        h->vDbl##N = v = (cwal_value*)h->memDbl##N; \
        *v = cwal_value_double_empty;               \
        memcpy(CWAL_DBL_NONULL(v), &dv, sizeof(cwal_double_t));   \
    } (void)0
    NUM(M1,dbl_m1);
    NUM(0,dbl_0);
    NUM(1,dbl_1);
#undef NUM
#undef dbl_m1
#undef dbl_0
#undef dbl_1

    {
        /** Sets up shared weak refs */
        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(wDblM1,h->vDblM1);
        REF(wDbl0,h->vDbl0);
        REF(wDbl1,h->vDbl1);
    }

    { /* Empty Tuple... */
        memset(h->memTuple0, 0, sizeof(h->memTuple0));
        v = h->vTuple0 = (cwal_value*)h->memTuple0;
        *v = cwal_tuple_value_empty;
        assert(CWAL_TUPLE(v));
        assert(0==CWAL_TUPLE(h->vTuple0)->n);
    }

    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, during the initialization of
           the first cwal_engine instance(s), and only if they are
           initialized in concurrent threads.
        */;

}

/**
   CWAL_MEM_IS_BUILTIN(V) determines if the (void const *) 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 CWAL_MEM_IS_BUILTIN(V) \
    ((((void const *)(V) >= (void const *)&CWAL_BUILTIN_VALS)         \
      && ( (void const *)(V) < (void const *)(&CWAL_BUILTIN_VALS+1))  \
      ) ? 1 : 0)


/**
   Intended for use in assertions to ensure that (cwal_value const *)
   V is either a builtin value or has an owning scope.
 */
#define V_SEEMS_OK(V) ((V)->scope || CWAL_MEM_IS_BUILTIN(V))

/**
   Type used for counting memory chunk sizes.
*/
typedef uint32_t cwal_memsize_t;

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

/**
   Static state for the recycling mechanism. Here we only
   store some calculated values which apply to all engines.

   Gets populated by cwal_setup_recycler_indexes().
*/
static struct {
    /**
       A map of cwal_type_id to integer array indexes
       for cwal_engine::recycler[]. It gets populated
       once during engine initialization and holds values
       valid for all engine instances.
    */
    int indexes[CWAL_TYPE_end];
    /**
       The total number of recycling bins calculated by
       cwal_setup_recycler_indexes(). cwal_engine::recycler
       must have at least this many elements.
    */
    cwal_size_t recyclerCount;
} cwalRecyclerInfo = {
{-1,-1,-1,-1, -1,-1,-1,-1,
 -1,-1,-1,-1, -1,-1,-1,-1,
 -1,-1,-1,-1, -1 },
0
};

/**
   Calculates an optimized value recycling bin layout for
   cwal_engine::recycler and stores the result in cwalRecyclerInfo.indexes.
   All non-recyclable types get a -1 put in
   cwalRecyclerInfo.indexes[thatTypeId].  All others get a value
   representing an index into cwal_engine::recycler.  All types with
   the same base Value size are grouped together in the same index.
*/
static void cwal_setup_recycler_indexes(){
    if(cwalRecyclerInfo.recyclerCount) return;
    else{
        static const cwal_type_id tlist[] = {
        /*
          Reminders to self: only set up VALUE TYPES this way because
          we cannot mix Value and non-Value types in one recycler
          because usage of their linked list members
          breaks. CWAL_TYPE_STRING must not be in this list, but
          CWAL_TYPE_XSTRING and ZSTRING must be (both have the same
          size and will be grouped together).
        */
        CWAL_TYPE_INTEGER,
        CWAL_TYPE_DOUBLE,
        CWAL_TYPE_ARRAY,
        CWAL_TYPE_OBJECT,
        CWAL_TYPE_NATIVE,
        CWAL_TYPE_BUFFER,
        CWAL_TYPE_FUNCTION,
        CWAL_TYPE_EXCEPTION,
        CWAL_TYPE_HASH,
        CWAL_TYPE_XSTRING,
        CWAL_TYPE_ZSTRING,
        CWAL_TYPE_UNIQUE,
        CWAL_TYPE_TUPLE,
        CWAL_TYPE_UNDEF /* list sentinel - gets excluded below */
        };
#define ASIZE(A) (sizeof(A)/sizeof(A[0]))
        cwal_size_t xlist[ASIZE(tlist)];
        cwal_size_t zlist[ASIZE(tlist)];
        cwal_size_t i, x, zCount = 0;
        memset(zlist, 0, sizeof(zlist));
#ifdef DEBUG
        /* Make sure the inlined intialization is as expected... */
        for(i = 0; i < CWAL_TYPE_end; ++i){
            assert(-1==cwalRecyclerInfo.indexes[i]);
            /* cwalRecyclerInfo.indexes[i] = -1; */
            /* cwalRecyclerSizes[i] = cwal_type_id_sizeof(i); */
        }
#endif
        /* Collect the sizes of each type... */
        for(i = 0; CWAL_TYPE_UNDEF != tlist[i] ; ++i){
            xlist[i] = cwal_type_id_sizeof(tlist[i]);
            assert(xlist[i]);
            /*MARKER(("xlist[%s] = %d\n",
              cwal_type_id_name(tlist[i]), (int)xlist[i]));*/
        };
        xlist[i] = 0;
        /* Bubblesort the sizes... */
        for(i = 0; i < ASIZE(xlist)-1; ++i){
            for( x = 1; x < ASIZE(xlist)-1; ++x){
                if(xlist[x] < xlist[x-1]){
                    cwal_size_t const tmp = xlist[x-1];
                    xlist[x-1] = xlist[x];
                    xlist[x] = tmp;
                }
            }
        }
#if 0
        for(i = 0; xlist[i] && i < CWAL_TYPE_end; ++i){
            MARKER(("Sorted index %d %d\n",
                    (int)i, (int)xlist[i]));
        }
#endif
        /* Remove dupes... */
        for( x = 0; x < ASIZE(xlist)-1; ++x ){
            int gotIt = 0;
            for(i = 0; zlist[i] && i < ASIZE(zlist)-1; ++i){
                if( zlist[i] == xlist[x] ){
                    gotIt = 1;
                    break;
                }
            }
            if(!gotIt) zlist[zCount++] = xlist[x];
        }
#if 0
        for(i = 0; i < CWAL_TYPE_end && zlist[i]; ++i){
            MARKER(("Index #%d sizeof=%d\n",
                    (int)i, (int)zlist[i]));
        }
#endif
        /* Match up sizes to types, to group recycle bins... */
        for(i = 0; zlist[i]; ++i){
            /* MARKER(("#%d = %d\n", (int)i, (int)zlist[i])); */
            for(x = 0; CWAL_TYPE_UNDEF != tlist[x]; ++x){
                cwal_size_t const sz = cwal_type_id_sizeof(tlist[x]);
                if(sz == zlist[i]){
                    /*MARKER(("Size match (%d): %s\n", (int)sz,
                      cwal_type_id_name(tlist[x])));*/
                    cwalRecyclerInfo.indexes[tlist[x]] = (int)i;
                }
            }
        }
        assert(cwalRecyclerInfo.indexes[CWAL_TYPE_XSTRING]
               ==cwalRecyclerInfo.indexes[CWAL_TYPE_ZSTRING]);
        /* assert(cwalRecyclerSizes[CWAL_TYPE_OBJECT]); */

        cwalRecyclerInfo.indexes[CWAL_TYPE_KVP] = zCount++;
        cwalRecyclerInfo.indexes[CWAL_TYPE_WEAK_REF] = zCount++;
        cwalRecyclerInfo.indexes[CWAL_TYPE_SCOPE] = zCount++;

        cwalRecyclerInfo.recyclerCount = zCount;
#ifdef DEBUG
        {
            /* Make sure cwal_engine::recycler is sized big enough. */
            cwal_engine * e = 0;
            assert(zCount <= sizeof(e->recycler)/sizeof(e->recycler[0]));
        }
#endif
#if 0
        for(i = 0; i < CWAL_TYPE_end; ++i){
            MARKER(("Recycler: %d %s index=%d\n",
                    (int)i,
                    cwal_type_id_name((cwal_type_id)i),
                    (int)cwalRecyclerInfo.indexes[i]
                    ));
        }
#endif
    }
#undef ASIZE
}

/**
   Returns the index in cwal_engine::recycler[] for the given
   type ID, or -1 if there is no recycler for that type.

   Special case: for CWAL_TYPE_STRING it returns the x-/z-string
   index. Cases which refer to the real string recycler need to be
   careful NOT to use this routine, but cwal_recycler_get()
   instead.
*/
static int cwal_recycler_index( cwal_type_id typeID ){
    static int once = 0;
    if(!once){
        cwal_setup_recycler_indexes();
        once=1;
        /*
          Pedantic side note: if two threads init two cwal_engine
          instances at the same time, they will both init the recycler
          indexes, but will set the same memory to the same values, so
          it makes no difference. We set once=1 last to allow two
          threads to do that, rather than to have the second thread
          continue before the first is filling up cwalRecyclerInfo.
        */
    }
    switch( typeID ){
      case CWAL_TYPE_STRING:
          return cwalRecyclerInfo.indexes[CWAL_TYPE_XSTRING]
              /* Special case because x/z-strings get tagged
                 with this type after construction. */
              ;
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_XSTRING:
      case CWAL_TYPE_ZSTRING:
          /* we only handle x/z-strings via e->recycler,
             and STRING via e->reString.  */
      case CWAL_TYPE_ARRAY:
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_HASH:
      case CWAL_TYPE_NATIVE:
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_EXCEPTION:
      case CWAL_TYPE_UNIQUE:
      case CWAL_TYPE_TUPLE:

      case CWAL_TYPE_KVP:
      case CWAL_TYPE_SCOPE:
      case CWAL_TYPE_WEAK_REF:

          assert(cwalRecyclerInfo.indexes[typeID]>=0);
          return cwalRecyclerInfo.indexes[typeID];
      default:
          return -1;
    }
}

/**
   Overwrites all state in buf with defaults (zeroes) but
   retains the buf->self member.
*/
static void cwal_buffer_wipe_keep_self( cwal_buffer * buf ){
    void * self = buf->self;
    *buf = cwal_buffer_empty;
    buf->self = self;
}

int cwal_is_dead(cwal_engine const * e){
    return e ? e->fatalCode : CWAL_RC_MISUSE;
}

/** @internal

   Checks whether child refers to a newer 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 since the
   transition from arrays to linked lists for child values, has no
   failure cases other than memory corruption or similar internal
   mismanagement of cwal's bits). 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) to stdout or stderr. 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) refCount=%u",
            v->vtab->typeName,
            (void*)v,
            v->scope ? (unsigned)v->scope->level : 0U,
            (unsigned)CWAL_REFCOUNT(v));
    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");
    }else if(cwal_value_is_tuple(v)){
        cwal_tuple const * p = CWAL_TUPLE(v);
        assert(p);
        fprintf( out, " n=%d", (int)p->n );
    }
    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))

/**
   Returns the recycler for the given type, distinguishing between
   CWAL_TYPE_STRING (==>e->reString) and CWAL_TYPE_XSTRING/ZSTRING
   (==>e->recycler[something]). BE CAREFUL: do not put x/z-strings
   in the other string bin, or vice versa!
*/
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);
        /* assert(ndx>=0); */
        return (ndx >= 0) ? &e->recycler[ndx] : 0;
    }
}


/**
   Either adds mem to e->reChunk or (if recycling is disabled or capacity/quota
   is reached) frees mem using cwal_free().
*/    
static void cwal_memchunk_add(cwal_engine * e, void * mem, cwal_size_t size ){
    cwal_memchunk_recycler * re = &e->reChunk;
    assert(mem);
    assert(size>0);
    assert((cwal_size_t)-1 != size);
    size = CWAL_MEMSZ_PAD(size);
    if(CWAL_F_TRACK_MEM_SIZE & e->flags){
        /* If we have the size info in the memory block then use that.
           This allows us to make up for list and string allocations truncating
           sizes, effectively "losing" bytes (making them unusable, but still
           part of a tracked block) each time they do that. */
        cwal_memsize_t * sz = MEMSZ_PTR_FROM_MEM(mem);
        /* static int counter1 = 0; ++counter1; */
        if(*sz != size+sizeof(void*)){
            /* static int counter2 = 0; */
            assert(*sz);
            assert(e->memcap.currentMem >= *sz);
            assert(size < *sz-sizeof(void*));
            /*MARKER(("#%d of %d Replacing size %d with %d from memory stamp.\n",
              ++counter2, counter1, (int)size, (int)(*sz - sizeof(void*))));*/
            e->metrics.recoveredSlackBytes += *sz - size - sizeof(void*);
            ++e->metrics.recoveredSlackCount;
            size = *sz - sizeof(void*)
                /* -sizeof is needed to avoid a semantic conflict in
                   cwal_memchunk_overlay::size
                   vs. *MEMSZ_PTR_FROM_MEM(mem). If we don't account
                   for it here, we end up corrupting malloc()-internal
                   state!
                */;
        }
    }
    if(!re->config.maxChunkCount
       || !e->current /* i.e. during finalization */
       || size < sizeof(cwal_memchunk_overlay)
       || (re->config.maxTotalSize < (size + re->currentTotal))
       || (re->config.maxChunkSize < size)
       || (re->headCount >= re->config.maxChunkCount)
       ){
        /* MARKER(("Freeing chunk @%p of size %d\n", mem, (int)size)); */
        cwal_free(e,mem);
    }else{
        cwal_memchunk_overlay * ovl;
        /* MARKER(("Adding mem chunk @%p of size %d\n", mem, (int)size)); */
        assert(re->headCount < re->config.maxChunkCount);
        /* memset(mem, 0, size); */
        ovl = (cwal_memchunk_overlay *)mem;
        *ovl = cwal_memchunk_overlay_empty;
        ovl->size = size;
        if(!re->metrics.smallestChunkSize
           || (size < re->metrics.smallestChunkSize)){
            re->metrics.smallestChunkSize = size;
        }
        if(size > re->metrics.largestChunkSize){
            re->metrics.largestChunkSize = size;
        }
        if( (re->currentTotal += size) > 
            re->metrics.peakTotalSize ){
            re->metrics.peakTotalSize = re->currentTotal;
        }
        if( ++re->headCount > re->metrics.peakChunkCount ){
            re->metrics.peakChunkCount = re->headCount;
        }
        re->metrics.runningAverageSize = (re->metrics.runningAverageSize + size)/2;
        /* Insert ovl into the linked list, sorted by size... */
        if(!re->head){
            assert(1==re->headCount) /* 1 b/c we increment it above */;
            re->head = ovl;
        }else{
            cwal_memchunk_overlay * h = re->head;
            cwal_memchunk_overlay * prev = 0;
            char gotIt = 0;
            for( ; h; prev = h, h = h->next ){
                if(h->size>=ovl->size){
                    gotIt = 1;
                    if(prev){
                        prev->next = ovl;
                    }else{
                        assert(h==re->head);
                        re->head = ovl;
                    }
                    ovl->next = h;
                    break;
                }
            }
            if(!gotIt){
                /* End of list: ovl is biggest */
                assert(prev);
                assert(!prev->next);
                assert(prev->size < ovl->size);
                prev->next = ovl;
            }
        }
        /* MARKER(("Added mem chunk @%p #%d of size %d\n", mem, (int)re->headCount, (int)size)); */
    }
    return;
}

/**
   Requests a chunk of memory from the memchunk cache. *size must be
   the size of the block we would "like" to have. A value of zero
   means to give back the first chunk (an O(1) op).

   deltaPolicy specifies a size tolerance. Legal values are:

   0: no tolerance - only an exact size match qualifies.

   N>100: a percent larger the buffer may be. If a larger buffer is
   found within that that size, it is returned. It is ignored if
   !*size. If deltaPolicy>999 then any chunk at least as large as *size
   will be accepted.

   N<100: at least *size, but no bigger than *size+N.

   N<0: the first buffer in the list will match, the same as if
   *size==0.

   If non-NULL is returned, *size is updated to the size of the chunk.

   Potential TODO: change sig to include a param which specifies how
   to interpret deltaPolicy (e.g. absolute vs percent vs
   smaller-or-larger).

   Potential TODO: if !*size, interpret deltaPolicy as a preferred value
*/
static void * cwal_memchunk_request(cwal_engine * e, cwal_size_t * size,
                                    int deltaPolicy,
                                    char const * debugDescription){
    cwal_memchunk_recycler * re = &e->reChunk;
    cwal_memchunk_overlay * ovl = 0;
    cwal_memchunk_overlay * left = 0;
    void * rc = 0;
    ++re->metrics.requests;
    for( ovl = re->head; ovl; left = ovl, ovl = ovl->next ){
        ++re->metrics.searchComparisons;
        if(deltaPolicy<0
           || !*size
           || *size == ovl->size) break;
        else if( deltaPolicy>100 ){
            /* Chunk size within (*size, *size+(deltaPolicy/100)) */
            cwal_size_t const max = (*size * (cwal_size_t)(deltaPolicy/100.0));
            assert(ovl->size!=*size && "Is caught above.");
            if( ovl->size>*size
                && (deltaPolicy>999 || ovl->size <= max)
                ){
                break;
            }else if(ovl->size > max){
                /* We won't find a match above this point */
                ovl = 0;
                break;
            }
        }else if(deltaPolicy>0){
            /* Interpret as absolute byte count */
            if(ovl->size>*size
               && ovl->size <= *size + deltaPolicy ){
                break;
            }else if(ovl->size > *size * deltaPolicy){
                /* We won't find a match above this point */
                ovl = 0;
                break;
            }
        }
    }
    if(!ovl) ++re->metrics.searchMisses;
    else{
        /*MARKER(("Donating chunk of size %d for request of size %d. Context=%s\n",
          (int)ch->size, (int)*size, debugDescription));*/
        if(debugDescription){/*avoid unused param warning*/}
        assert(re->headCount>0);
        if(left){
            assert(re->headCount>1);
            assert(left->next == ovl);
            left->next = ovl->next;
        }
        if(ovl->next){
            assert(re->headCount > (left ? 2 : 1));
        }
        if(re->head == ovl){
            re->head = ovl->next;
        }
        *size = ovl->size;
        memset(ovl, 0, *size);
        rc = ovl;
        assert(re->currentTotal >= *size);
        re->currentTotal -= *size;
        --re->headCount;
        if(!re->headCount){
            assert(!re->head);
        }
        ++re->metrics.totalChunksServed;
        re->metrics.totalBytesServed += *size;
        re->metrics.runningAverageResponseSize =
            (re->metrics.runningAverageResponseSize + *size)/2;
    }
    return rc;
}


static void cwal_memchunk_freeit( cwal_engine * e, cwal_memchunk_overlay * ovl ){
    cwal_memchunk_recycler * re = &e->reChunk;
    assert(!ovl->next);
    assert(re->headCount>0);
    assert(re->currentTotal >= ovl->size);
    --re->headCount;
    re->currentTotal -= ovl->size;
    *ovl = cwal_memchunk_overlay_empty;
    cwal_free(e, ovl);
}

/**
   Frees up all entries in e->reChunk.
*/
static void cwal_memchunks_free( cwal_engine * e ){
    cwal_memchunk_recycler * re = &e->reChunk;
    cwal_memchunk_overlay * ovl;
    for( ; (ovl = re->head);  ){
        re->head = ovl->next;
        ovl->next = 0;
        cwal_memchunk_freeit(e, ovl);
    }
    assert(0==re->headCount);
    assert(0==re->currentTotal);
    re->head = 0;
}


int cwal_engine_memchunk_config( cwal_engine * e,
                                 cwal_memchunk_config const * conf){
    if(!e || !conf) return CWAL_RC_MISUSE;
    else{
        /* Adjust size... */
        cwal_memchunk_recycler * re = &e->reChunk;
        cwal_memchunk_overlay * ovl;
        if(!conf->maxChunkCount
           || !conf->maxTotalSize
           || !conf->maxChunkSize){
            /* The simplest case: disable the recycler. */
            cwal_memchunks_free( e );
            re->config = *conf;
            return 0;
        }

        if(re->config.maxChunkSize > conf->maxChunkSize){
            /* Trim now-too-large chunks. */
            cwal_memchunk_overlay * prev = 0;
            cwal_memchunk_overlay * next = 0;
            ovl = re->head;
            assert(ovl);
            for( ; ovl; ovl = next){
                next = ovl->next;
                if(ovl->size > conf->maxChunkSize){
                    if(prev) prev->next = next;
                    ovl->next = 0;
                    cwal_memchunk_freeit(e, ovl);
                }else{
                    prev = ovl;
                }
            }
        }

        if(re->currentTotal > conf->maxTotalSize){
            /* Trim chunks until we're under the limit. */
            cwal_memchunk_overlay * prev = 0;
            cwal_memchunk_overlay * next = 0;
            ovl = re->head;
            assert(ovl);
            for( ; ovl && (re->currentTotal > conf->maxTotalSize);
                 ovl = next ){
                next = ovl->next;
                if(ovl->size > conf->maxChunkSize){
                    if(prev) prev->next = next;
                    ovl->next = 0;
                    cwal_memchunk_freeit(e, ovl);
                }else{
                    prev = ovl;
                }
            }
        }
        while(conf->maxChunkCount
              ? (re->headCount > conf->maxChunkCount)
              : !!re->headCount){
            /* Too many chunks. Lop off the smallest ones. */
            assert(re->head);
            ovl = re->head;
            re->head = ovl->next;
            ovl->next = 0;
            cwal_memchunk_freeit(e, ovl);
        }
        re->config = *conf;
        return 0;
    }
}

cwal_kvp * cwal_kvp_alloc(cwal_engine *e){
    cwal_kvp * kvp;
    cwal_recycler * re;
    ++e->metrics.requested[CWAL_TYPE_KVP];
    re = cwal_recycler_get(e, CWAL_TYPE_KVP);
    assert(re);
    if(re->list){
        ++re->hits;
        ++e->metrics.valuesRecycled;
        kvp = (cwal_kvp*)re->list;
        re->list = kvp->right;
        kvp->right = 0;
        --re->count;
    }
    else{
        ++re->misses;
        ++e->metrics.valuesRecycleMisses;
        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;
}

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) cwal_value_unref2(e, key);
        if(value) 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){
            cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_KVP);
            assert(re);
            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_value_set( cwal_kvp * kvp, cwal_value * v ){
    if(!kvp || !v) return CWAL_RC_MISUSE;
    else{
        assert(kvp->key);
        assert(kvp->value);
        cwal_value_ref(v);
        cwal_value_unref(kvp->value);
        kvp->value = v;
        return 0;
    }
}

int cwal_kvp_value_set2( cwal_kvp * kvp, cwal_value * v ){
    if(!kvp || !v) return CWAL_RC_MISUSE;
    else{
        if(CWAL_VAR_F_CONST & kvp->flags){
            return CWAL_RC_CONST_VIOLATION;
        }
        else return cwal_kvp_value_set(kvp, v);
    }
}

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

cwal_flags16_t cwal_kvp_flags_set( cwal_kvp * kvp, cwal_flags16_t flags ){
    cwal_flags16_t const rc = (cwal_flags16_t)(CWAL_VAR_F_PRESERVE & kvp->flags);
    if(CWAL_VAR_F_PRESERVE != flags){
        kvp->flags = flags;
    }
    return rc;
}

/** @internal

   Searchs for the given key in the given kvp list.

   Returns the found item if a match is found, NULL if not. If prev is
   not NULL then *prev is set to the returned value's left-hand-side
   kvp from the linked list (or the end of the list, if no match is
   found). A *prev value of NULL and return value of non-NULL
   indicates that the result kvp was found at the head of the list.
*/
static cwal_kvp * cwal_kvp_search( cwal_kvp * kvp, char const * key,
                                   cwal_size_t keyLen, cwal_kvp ** prev){
    if(!kvp || !key ) return NULL;
    else{
        cwal_kvp * left = 0;
        char const * cKey;
        cwal_size_t cLen;
        int cmp;
        for( left = 0; kvp; left = kvp, kvp = kvp->right ){
            assert( kvp->key );
            assert(kvp->right != kvp);
            cKey = cwal_value_get_cstr(kvp->key, &cLen);
            if(prev) *prev=left;
            if(!cKey) continue /* we don't know where non-strings sort in this world! */;
            else if(0 == (cmp = cwal_compare_cstr( key, keyLen, cKey, cLen ))){
                return kvp;
            }
#if CWAL_KVP_TRY_SORTING
            if(cmp<0) break;
#endif
        }
        if(prev) *prev = CWAL_KVP_TRY_SORTING ? left : 0;
        return 0;
    }
}

/**
   Variant of cwal_kvp_search() which takes a cwal_value key and uses
   key->vtab->compare() to check for equivalence.
*/
static cwal_kvp * cwal_kvp_search_v( cwal_kvp * kvp,
                                     cwal_value const * key,
                                     cwal_kvp ** prev){
    if(!kvp || !key ) return NULL;
    else{
        cwal_kvp * left;
        int cmp;
        const int keyIsBool = CWAL_TYPE_BOOL==key->vtab->typeID;
        for( left = 0; kvp; left = kvp, kvp = kvp->right ){
            const int kvpIsBool = CWAL_TYPE_BOOL==kvp->key->vtab->typeID;
            assert(kvp->key);
            assert(kvp->right != kvp);
            if(key==kvp->key){
                cmp = 0;
            }else if(keyIsBool || kvpIsBool){
                /*
                  20190706: it was *finally* discovered (by accident)
                  that:

                  var o = {};
                  o[true];

                  Matches the first truthy property key! In our case,
                  it was the s2 object prototype's constructor method.

                  Likewise:

                  o[true] = 3; // bool-typed property key
                  o['x']; // ==> 3

                  Because this lookup explicitly does type-loose
                  comparisons and most things are equivalent to true
                  in that context. So... now we special-case a
                  type-strict lookup if either of key or kvp->key is-a
                  boolean.
                */
                if(keyIsBool && kvpIsBool){
                    cmp = key->vtab->compare(key, kvp->key);
                    /* And fall through. */
                }else{
                    /* Do not allow a match if either key is a bool
                       but the other one is not. */
                    continue;
                }
            }else{
                cmp = key->vtab->compare(key, kvp->key);
            }
            if(prev) *prev=left;
            if(0==cmp) return kvp;
#if CWAL_KVP_TRY_SORTING
            else if(cmp<0) break;
#endif
        }
        if(prev) *prev = CWAL_KVP_TRY_SORTING ? left : 0;
        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 ) {
    /* assert(fromScope); */
    assert(e);
    assert(key);
    if( !e || !key || !list ) return CWAL_RC_MISUSE;
    else if(!*list) return CWAL_RC_NOT_FOUND;
    else {
        cwal_kvp * left = 0;
        cwal_kvp * kvp;
        kvp = cwal_kvp_search( *list, key, keyLen, &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. list must point to
   the head of a cwal_kvp list, and if the head is the unset kvp then
   *list is updated to point to kvp->right.

   Returns 0 on successs, CWAL_RC_NOT_FOUND if
   no match is found.
*/
static int cwal_kvp_unset_v( cwal_engine * e, cwal_kvp ** list,
                             cwal_value * key ) {
    assert(e);
    assert(key);
    if( !e || !key || !list ) return CWAL_RC_MISUSE;
    else if(!*list) return CWAL_RC_NOT_FOUND;
    else {
        cwal_kvp * left = 0;
        cwal_kvp * kvp;
        kvp = cwal_kvp_search_v( *list, key, &left );
        if( ! kvp ) return CWAL_RC_NOT_FOUND;
        if(left) left->right = kvp->right;
        else {
            assert(*list==kvp);
            *list = kvp->right;
        }
        kvp->right = NULL;
        cwal_kvp_free( e, 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 cwal_ptr_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.

   n MUST currently be the same as the owning table's hash size, 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 ){
    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.

   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 ){
    cwal_ptr_page * p = cwal_ptr_page_create( e, t->hashSize );
    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 {
        cwal_size_t reqSize = sizeof(cwal_ptr_table);
        t = (cwal_ptr_table*)cwal_memchunk_request(e, &reqSize, 0,
                                                   "cwal_ptr_table_create()");
        if(!t){
            t = (cwal_ptr_table*)cwal_malloc(e, reqSize);
        }else{
            assert(reqSize==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 );
    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_memchunk_add(e, t, sizeof(cwal_ptr_table));
        /* 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{
        cwal_size_t const psz = sizeof(cwal_ptr_page) +
          (sizeof(void*) * t->hashSize);
        void const * stamp = t->allocStamp;
        cwal_ptr_page * p = t->pg.head;
        assert(t->hashSize || !p);
        t->pg.head = t->pg.tail = NULL;
        while( p ){
            cwal_ptr_page * n = p->next;
            /* cwal_free(e, p); */
            cwal_memchunk_add(e, p, psz);
            p = n;
        }
        *t = cwal_ptr_table_empty;
        if( CwalConsts.AllocStamp == stamp ){
            cwal_free( e, t );
            /* cwal_memchunk_add(e, t, sizeof(cwal_ptr_table)); */
        }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;
    void * 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 );
          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_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_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->recycler (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 ){
    cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_WEAK_REF);
    assert(e && r && !r->next);
    assert(!CWAL_MEM_IS_BUILTIN(r));
    if(re->maxLength>0
       && re->count < re->maxLength){
       *r = cwal_weak_ref_empty;
        r->next = (cwal_weak_ref*)re->list;
        re->list = r;
        ++re->count;
    }
    else {
        *r = cwal_weak_ref_empty;
        cwal_free2( e, r, sizeof(cwal_weak_ref) );
        /* cwal_memchunk_add(e, r, sizeof(cwal_weak_ref)); */
    }
}

void cwal_weak_ref_free( cwal_engine * e, cwal_weak_ref * r ){
    assert(e && r);
    if(!e || !r || CWAL_MEM_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:
      case CWAL_TYPE_UNIQUE:
      case CWAL_TYPE_TUPLE:
          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
   the recycler 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){
        cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_WEAK_REF);
        r = (cwal_weak_ref *)re->list;
        if(r){
            assert(re->count);
            ++re->hits;
            re->list = r->next;
            --re->count;
        }else{
            ++re->misses;
            r = cwal_malloc2(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 && !CWAL_MEM_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;
              else{/* It's one of the length-1 strings */}
              break;
          case CWAL_TYPE_INTEGER:{
              /*
                Shared weak-ref-to-int removed 20171202 because (A)
                the built-in int range is now variable, (B) adding
                static weak refs to all of them would be a huge waste
                (weak refs to ints are NEVER used), and (C) ... i
                forgot (C) while reformatting this comment :/.
              */
              break;
          }
          case CWAL_TYPE_DOUBLE:
              if(CWAL_MEM_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;
                  else{assert(!"Impossible!");}
              }
              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 || CWAL_MEM_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 2.5kb/page (64-bit), 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 );
}



cwal_hash_t cwal_hash_bytes( void const * _zKey, cwal_size_t nKey ){
    unsigned char const * zKey = (unsigned char const *)_zKey;
#if 0
    /*
      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 const prime = 16777619U;
    cwal_hash_t const offset = 2166136261U;
#  else
    cwal_hash_t const prime = 1099511628211U;
    cwal_hash_t const 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 */
#elif 0
    /*
      CRC32a: http://www.hackersdelight.org/hdcodetxt/crc.c.txt
    */
#  define reverse(x) \
   x = ((x & 0x55555555) <<  1) | ((x >>  1) & 0x55555555); \
   x = ((x & 0x33333333) <<  2) | ((x >>  2) & 0x33333333); \
   x = ((x & 0x0F0F0F0F) <<  4) | ((x >>  4) & 0x0F0F0F0F); \
   x = (x << 24) | ((x & 0xFF00) << 8) | \
       ((x >> 8) & 0xFF00) | (x >> 24)
    int i, j;
    cwal_hash_t byte, crc;
    cwal_size_t n;
    i = 0;
    crc = 0xFFFFFFFF;
    for( n = 0; n < nKey; ++n ){
        byte = (unsigned char)zKey[i];
        reverse(byte);
        for (j = 0; j <= 7; j++) {
            if ((int)(crc ^ byte) < 0)
                crc = (crc << 1) ^ 0x04C11DB7;
            else crc = crc << 1;
            byte = byte << 1;
        }
        i = i + 1;
    }
    crc = ~crc;
    reverse(crc);
    return crc;
#  undef reverse
#elif 0
    /*
      CRC32b: http://www.hackersdelight.org/hdcodetxt/crc.c.txt
    */
   int j;
   unsigned char byte;
   cwal_hash_t crc, mask;
   cwal_size_t n = 0;
   crc = 0xFFFFFFFF;
   for ( ; n < nKey; ++n) {
       byte = (unsigned char)zKey[n];
       crc = crc ^ byte;
       for (j = 7; j >= 0; j--) {
           mask = -(crc & 1);
           crc = (crc >> 1) ^ (0xEDB88320 & mask);
       }
   }
   return ~crc;
#elif 0
   /*
     CRC32e: http://www.hackersdelight.org/hdcodetxt/crc.c.txt

     HOLY COW: this collides about every 2nd string!

     Worst. Hash. Ever.

     Seriously, in the s2 unit tests: interning tables with space for
     379 entries each, holdling a total of 264 strings: 32 tables are
     needed with this hash!
   */
   int j;
   cwal_hash_t byte, c;
   cwal_int_t crc;
   cwal_size_t i;
   const unsigned int g0 = 0xEDB88320, g1 = g0 >> 1,
                      g2 = g0 >> 2,    g3 = g0 >> 3;
#  if CWAL_SIZE_T_BITS < 64
   crc = 0xFFFFFFFF;
#  else
   crc = 0xFFFFFFFFFFFFFFFF;
#  endif
   for(i = 0; i < nKey; ++i ){
       byte = (unsigned char)zKey[i];
       crc = crc ^ byte;
       for (j = 1; j >= 0; j--) {
           c = ((crc<<31>>31) & g3) ^ ((crc<<30>>31) & g2) ^
               ((crc<<29>>31) & g1) ^ ((crc<<28>>31) & g0);
           crc = ((unsigned)crc >> 4) ^ c;
       }
       i = i + 1;
   }
   return (cwal_hash_t)~crc;
#elif 1
   /* another experiment... */
    /* 20181122: this one seems to perform the best for the s2 unit
       test suite, measured in terms of how many string interning
       tables get allocated and how tightly packed they get. */
    cwal_hash_t h = 0;
    unsigned char const * p = (unsigned char const *)zKey;
    cwal_size_t i = 0;
    /* This one performs a tick better than FNV-1a in my brief tests */
    for( ; i<nKey; ++i, ++p)
        h = 31 * h + (*p * 307); /* stackoverflow says 31 or 37 */
    return h;
#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 const prime = 16777619U;
        cwal_hash_t const offset = 2166136261U;
#    else
        cwal_hash_t const prime = 1099511628211U;
        cwal_hash_t const 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
        pick_a_hash_algo;
        /*assert(!"Pick a hash algo!");*/
#  endif    
    }
#endif
/* endless hash [algo] experimentation. */
}


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_bytes( 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 );
        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;
    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 * const v ){
    return CWAL_VOBASE(v);
}


void cwal_value_cleanup_noop( cwal_engine * e, void * v ){
    if(e || v){/*avoid unused param warning*/}
}

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
    if(e){/*avoid unused param warning*/}
}

void cwal_value_cleanup_double( cwal_engine * e, void * self ){
    static const cwal_double_t zero = 0.0;
    cwal_value * v = (cwal_value *)self;
    memcpy(CWAL_DBL_NONULL(v), &zero, sizeof(cwal_double_t));
    if(e){/*avoid unused param warning*/}
}


void cwal_value_cleanup_unique( cwal_engine * e, void * self ){
    cwal_value * v = (cwal_value *)self;
    cwal_value ** wrapped = CWAL_UNIQUE_VALPP(v);
    if(e){/*avoid unused param warning*/}
    assert(wrapped);
    if(*wrapped){
        cwal_value * w = *wrapped;
        *wrapped = 0;
        if(!CWAL_V_IS_IN_CLEANUP(w)){
            /*
              Without this is-in-cleanup check, code like the
              following (s2) crashes here during scope cleanup:

              var e = enum {a:{x:1},b:{x:2}};
              e.a.value.b = e.b;
              e.b.value.a = e.a;

              20180105: a string key and its Unique-type wrapper were
              being cleaned up during scope cleanup and the string got
              recycled (as one does) during that process before the
              Unique wrapper did. That ended up triggering the
              assert() below. As of 20180105, the cleanup process
              gc-queues all values, not just containers, which
              resolves this case. It's just amazing that it didn't
              trigger sooner.

              Addenda: the root cause, it turns out, is that Uniques
              and Tuples live in the cwal_scope::mine::headPod list,
              not headObj, and can therefore be destroyed after their
              values are.
            */
            assert( CWAL_REFCOUNT(w) || CWAL_MEM_IS_BUILTIN(w) );
            cwal_value_unref(w);
        }
    }
}

void cwal_value_cleanup_tuple( cwal_engine * e, void * self ){
    cwal_value * v = (cwal_value *)self;
    cwal_tuple * p = CWAL_TUPLE(v);
    cwal_size_t i;
    assert(p && p->n);
    for( i = 0; i < p->n; ++i ){
        /* hashtag CWAL_V_GOES_IN_HEADOBJ */
        if(!CWAL_V_IS_IN_CLEANUP(p->list[i])){
            /* ^^^ cleanup check is hypothetically needed for certain
               constellations because Tuples are managed via the
               cwal_scope::headPod list, not headObj. That said, it's
               never been seen to trigger a problem before (probably
               because tuples are seldom used?).*/
            cwal_value_unref(p->list[i]);
        }
        p->list[i] = 0;
    }
    cwal_free2(e, p->list, sizeof(cwal_value*) * p->n);
    *p = cwal_tuple_empty;
}
        
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(rc && 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_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_UNIQUE: t = &cwal_value_vtab_unique; break;
      case CWAL_TYPE_TUPLE: t = &cwal_value_vtab_tuple; 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";
      case CWAL_TYPE_LISTMEM: return "cwal_list";
      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;
}

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

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)
ISA(hash,HASH)
ISA(unique,UNIQUE)
ISA(tuple,TUPLE)
#undef ISA


char cwal_value_is_buffer( cwal_value const * v ){
    cwal_buffer_obj const * bo = CWAL_BUFOBJ(v);
    assert(bo ? (bo==bo->buf.self) : 1);
    return (bo && bo == bo->buf.self) ? 1 : 0;
}

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(e){/*avoid unused param warning*/}
    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{
        if(state){/*avoid unused param warning*/}
        return realloc( m, n );
    }
}

int cwal_output_f_FILE( void * state, void const * src, cwal_size_t n ){
    if( !state || !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;
    assert(ob->e == e);
    cwal_buffer_reserve(e, ob->b, 0);
    ob->e = NULL;
    *ob = cwal_output_buffer_state_empty;
}

void * cwal_malloc( cwal_engine * e, cwal_size_t n ){
    unsigned char * rc;
    cwal_size_t const origN = n;
    if(!e || !n || !e->vtab) return 0;
    n = CWAL_MEMSZ_PAD(n);
    if(CWAL_F_TRACK_MEM_SIZE & e->flags){
         n += sizeof(void*)
             /* To store the size in. Must be generically aligned, thus
                we don't use sizeof(uint32_t). */;
#if 64==CWAL_SIZE_T_BITS
         if(n > 0xFFFFFFFF){
             /* b/c our size marker is explicitly uint32_t. */
             return NULL;
         }
#endif
    }
    CWAL_TR_MEM(e,rc,n);
    if(n < origN) return NULL /* overflow after adjustment */;
    else if(/* Check cap constraints... */
       /* too-large single alloc */
       (e->vtab->memcap.maxSingleAllocSize
        && (n > e->vtab->memcap.maxSingleAllocSize)) 
       || /* too many concurrent allocs */
       (e->vtab->memcap.maxConcurrentAllocCount
        && (e->memcap.currentAllocCount
            >= e->vtab->memcap.maxConcurrentAllocCount))
       || /* too much concurrent memory */
       (e->vtab->memcap.maxConcurrentMem
        && (e->memcap.currentMem + n
            > e->vtab->memcap.maxConcurrentMem))
       || /* too many total allocs */
       (e->vtab->memcap.maxTotalAllocCount
        && (e->memcap.totalAllocCount
            >= e->vtab->memcap.maxTotalAllocCount))
       || /* Too much total memory... */
       (e->vtab->memcap.maxTotalMem
        && (e->memcap.currentMem + n
            > e->vtab->memcap.maxTotalMem))
       ){
        return 0;
    }
    rc = (unsigned char *)
        e->vtab->allocator.realloc( e->vtab->allocator.state.data, NULL, n );
    if(rc){
        if( ++e->memcap.currentAllocCount > e->memcap.peakAllocCount ){
            e->memcap.peakAllocCount =
                e->memcap.currentAllocCount;
        }
        e->memcap.totalMem += n;
        ++e->memcap.totalAllocCount;
        if(CWAL_F_TRACK_MEM_SIZE & e->flags){
            /* Stamp the size and adjust rc. */
            cwal_memsize_t * sz = (cwal_memsize_t*)rc;
            *sz = (cwal_memsize_t)n;
            rc += sizeof(void*);
            e->memcap.currentMem += n
                /* We can only decrement this if overallocating, so
                   only increment if we overallocate. */;
            if(e->memcap.peakMem < e->memcap.currentMem){
                e->memcap.peakMem = e->memcap.currentMem;
            }
        }
    }
    CWAL_TR2(e,CWAL_TRACE_MEM_MALLOC);
    return rc;
}

void * cwal_malloc2( cwal_engine * e, cwal_size_t n ){
    cwal_size_t newN = n;
    void * rc = cwal_memchunk_request(e, &newN,
                                      (CWAL_F_TRACK_MEM_SIZE & e->flags)
                                      ? 150 : 125
                                      /* If over-allocation/tracking
                                         is on, we can recover all
                                         slack bytes when the mem is
                                         passed to cwal_free2(), so
                                         allow more leeway in the size
                                         of the recycled chunk. */,
                                      "cwal_malloc2()");
    if(!rc){
        rc = cwal_malloc(e, n);
    }
    return rc;
}

        
void cwal_free( cwal_engine * e, void * m ){
    if(e && m){
        assert(e != m /* check for this corner case: e is allocated
                         before cwal_malloc() and friends are
                         configured, so e was allocated on the stack or
                         before CWAL_F_TRACK_MEM_SIZE could take
                         effect. */);
        if(CWAL_F_TRACK_MEM_SIZE & e->flags){
            cwal_memsize_t * sz = MEMSZ_PTR_FROM_MEM(m);
            assert(e->memcap.currentMem >= *sz);
            e->memcap.currentMem -= *sz;
            m = sz;
        }
        CWAL_TR_MEM(e,m,0);
        CWAL_TR2(e,CWAL_TRACE_MEM_FREE);
        e->vtab->allocator.realloc( e->vtab->allocator.state.data, m, 0 );
        --e->memcap.currentAllocCount;
    }
}

void cwal_free2(cwal_engine * e, void * mem, cwal_size_t size ){
    assert(e);
    if(mem){
        assert(e != mem /* check for this corner case: e is allocated
                           before cwal_malloc() and friends are
                           configured, so e was allocated on the stack or
                           before CWAL_F_TRACK_MEM_SIZE could take
                           effect. */);
        if(size) cwal_memchunk_add(e, mem, size);
        else cwal_free(e, mem);
    }
}

void * cwal_realloc( cwal_engine * e, void * m, cwal_size_t n ){
    if(!e || !e->vtab) return 0;
    else if( 0 == n ){
        cwal_free( e, m );
        return NULL;
    }else if( !m ){
        return cwal_malloc( e, n );
    }else{
        unsigned char * rc;
        uint32_t oldSize = 0;
        cwal_size_t const origN = n;
        n = CWAL_MEMSZ_PAD(n);
        if(CWAL_F_TRACK_MEM_SIZE & e->flags){
            cwal_memsize_t * sz = MEMSZ_PTR_FROM_MEM(m);
            oldSize = *sz;
            assert(oldSize>0);
            assert(e->memcap.currentMem >= oldSize);
            m = sz;
            n += sizeof(void*);
#if 64==CWAL_SIZE_T_BITS
            if(n > 0xFFFFFFFF){
                /* b/c our size marker is explicitly uint32_t. */
                return NULL;
            }
#endif
        }
        CWAL_TR_MEM(e,m,n);
        CWAL_TR2(e,CWAL_TRACE_MEM_REALLOC);
        if(n < origN) return NULL /* overflow after adjustment */;
        else if(/* Allocation is too big... */
                (e->vtab->memcap.maxSingleAllocSize
                  && (n > e->vtab->memcap.maxSingleAllocSize))
                || /* Too much concurrent memory... */
                (e->vtab->memcap.maxConcurrentMem
                 && (e->memcap.currentMem - oldSize + n
                     > e->vtab->memcap.maxConcurrentMem))
                || /* Too much total memory... */
                (e->vtab->memcap.maxTotalMem
                 && (e->memcap.currentMem + n
                     > e->vtab->memcap.maxTotalMem))
                 ){
            rc = NULL;
        }else{
            rc = (unsigned char*)
                e->vtab->allocator.realloc( e->vtab->allocator.state.data,
                                            m, n );
            if(rc && (CWAL_F_TRACK_MEM_SIZE & e->flags)){
                /* update e->memcap, re-stamp memory size */
                cwal_memsize_t * sz = (cwal_memsize_t*)rc;
                *sz = n;
                rc += sizeof(void*);
                e->memcap.currentMem -= oldSize;
                e->memcap.currentMem += n;
            }
        }
        return rc;
    }
}

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);
    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;
        }
        /* The "special" propagating values always get a reference,
           and therefore cannot be in the r0 list. */
        assert(e->values.exception != v);
        assert(e->values.propagating != v);
        cwal_value_unref2(e,v);
        ++rc;
    }
    return rc;
}

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

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

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

/**
   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.

   Reminder to self: this is also used to move v around within
   s->mine's various lists, so v->scope may be s.

   It only returns non-0 if it sets s->e->fatalCode.
*/
static int cwal_scope_insert( cwal_scope * s, cwal_value * 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 CWAL_MEM_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(!CWAL_REFCOUNT(v));
    assert(!CWAL_MEM_IS_BUILTIN(v));
    assert(s);
    assert(s->e);
    /* dump_val(v,"Re-probating value"); */
    cwal_scope_insert(s, v);
    /* dump_val(v,"Re-probated value"); */
}

/**
   "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. Also sets
   v->scope to 0.

   Returns the right-hand neighbor of v, or 0 if it has no neighbor.
*/
static cwal_value * cwal_value_snip( 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.

   CWAL_REFCOUNT(v) must be 1 (not 0) when this is called, and must have
   just gone from 0 to 1, as opposed to going from 2 to 1.
*/
static int cwal_scope_from_r0( cwal_value * v ){
    cwal_scope * s = v->scope;
    assert(1==CWAL_REFCOUNT(v));
    if(!s->mine.r0) return CWAL_RC_NOT_FOUND;
    else if(1!=CWAL_REFCOUNT(v)) 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_scope_insert( s, v ) /* does the list shuffling */;
    if(E_IS_DEAD(s->e)) return s->e->fatalCode;
    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.
   If any of the specially-propagating values live in s, they are
   re-scoped/moved to s's parent unless s is the top-most scope. in
   which case they get cleaned up.
*/
int cwal_scope_clean( cwal_engine * e, cwal_scope * s ){
    int rc = 0;
    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->values.exception and e->values.propagating. */
    {
        cwal_value * vPropagate;
        int phase = 1;
        propagate_next:
        vPropagate = (1==phase ? e->values.exception : e->values.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->values.exception : 0==e->values.propagating);
            }
            else {
                rc = cwal_value_xscope( e, parent, vPropagate, NULL );
#if 0
                /* we really should soldier on and clean up, but
                   we'd be doing so on possibly corrupt memory!

                   20200111: that said, this operation has never
                   failed in practice. */
                if(rc){
                    assert(rc == e->fatalCode);
                    return e->fatalCode;
                }
#endif
                assert(!rc && "Cannot fail anymore?");
                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(s->props){
        cwal_value * pv = s->props;
        cwal_obase * obase = CWAL_VOBASE(pv);
        assert(pv);
        assert(obase);
        assert(pv->scope && "It's not possible to have no scope at this point.");
        assert((pv->scope==s || (pv->scope->level<s->level))
               && "Scoping/lifetime precondition violation.");
        assert((CWAL_REFCOUNT(pv)>0) && "Who stole my properties ref?");
        /*
          If CWAL_REFCOUNT(pv)>1 then we know that "someone" still
          holds a reference.  During/after scope cleanup, using such a
          reference is illegal, so we don't really care about
          that. What we do care about is if...

          If we set the vacuum-safe flag on pv, we also need to turn
          off the flag, but...  If a client gets a handle to pv and
          explicitely sets it to vacuum-safe then we can no longer
          know (without a new flag) that we are the ones who set the
          vacuum-proofing flag, so unsetting it here "could be bad."
          One solution would be to use another flag bit in cwal_obase
          to mark property storage objects as being so, and unmark
          them if they are ever removed from their initial scope. It
          is possible/legal that an older scope holds a reference to
          pv, and in that case pv->scope!=s. So we can use that info
          to determine whether this scope really owns pv or not, which
          may help us do...  something or other useful...

          So CWAL_F_IS_PROP_STORAGE is born...
        */
        assert(obase->flags & CWAL_F_IS_PROP_STORAGE);
        obase->flags &= ~CWAL_F_IS_PROP_STORAGE;
        s->props = 0 /* if pv->scope==s, pv is in s->mine,
                        otherwise pv is in pv->scope->mine. Either way,
                        it's where it needs to be right now. */;
        cwal_value_unref(pv);
    }

    if(e->values.prototypes && (s == CWAL_VALPART(e->values.prototypes)->scope)){
        /* cwal_value_unhand(CWAL_VALPART(e->values.prototypes)); */
        e->values.prototypes = 0 /* it's in s->mine somewhere,
                             and will be cleaned up
                             momentarily. */;
    }

    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. (20180105: we gc-queue PODs now.)

       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 higher (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(!CWAL_MEM_IS_BUILTIN(v));
        assert(v->scope);
        assert(v->scope==s);
        assert(v->right ? v->right->scope==s : 1);
        while(v){
            cwal_value * n = v->right;
            assert(n != v);
            assert(!n || CWAL_REFCOUNT(n)>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(CWAL_REFCOUNT(v)>0);
                v = n;
                assert((!n || s == n->scope) && "unexpected. Quite.");
                continue;
                /* break; */
            }
            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;
            }
            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.headObj);
    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 rc;
}

static void cwal_scope_free( cwal_engine * e, cwal_scope * s, char allowRecycle ){
    void const * stamp;
    assert( e && s );
    stamp =  s->allocStamp;
    s->flags |= CWAL_F_IS_DESTRUCTING;
    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;
    }
}

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==CWAL_REFCOUNT(v));
        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;
        e->flags |= CWAL_F_IS_DESTRUCTING;
        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).
        */

        cwal_recycler_get(e, CWAL_TYPE_WEAK_REF)->maxLength = 0;

        e->values.exception = 0 /* its scope will clean it up */;
        e->values.propagating = 0 /* its scope will clean it up */;
        e->values.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 = cwal_recycler_get(e, T); assert(re && #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_OBJECT);
            UNCYCLE(CWAL_TYPE_HASH);
            UNCYCLE(CWAL_TYPE_ARRAY);
            UNCYCLE(CWAL_TYPE_NATIVE);
            UNCYCLE(CWAL_TYPE_BUFFER);
            UNCYCLE(CWAL_TYPE_FUNCTION);
            UNCYCLE(CWAL_TYPE_EXCEPTION);
            UNCYCLE(CWAL_TYPE_XSTRING /* actually x/z-strings! */);
            UNCYCLE(CWAL_TYPE_UNIQUE);
            UNCYCLE(CWAL_TYPE_TUPLE);
            /* WEAK_REF, 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);

            { /* Clean up weak ref recycler... */
                cwal_weak_ref * wr;
                cwal_weak_ref * wnext;
                RE(CWAL_TYPE_WEAK_REF);
                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);
            }
#undef RE
            
            /* 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];
                if(re->list){
                    MARKER(("Recycler index #%d has a list? count=%d\n", (int)i, (int)re->count));
                }
#if 0
                assert(0==re->list && "Steve seems to have forgotten "
                       "to account for cwal_engine::recycler-related changes.");
                assert(0==re->count);
#endif
            }
        }        

        { /* 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_gc_flush( e );

        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_memchunks_free(e);

        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->values.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->values.prototypes && (e->values.prototypes = cwal_new_array(e)) ){
        if(cwal_array_reserve(e->values.prototypes, (cwal_size_t)CWAL_TYPE_end-1)){
            cwal_value_unref(CWAL_VALPART(e->values.prototypes));
            e->values.prototypes = 0;
        }else{
            cwal_value_ref2(e, CWAL_VALPART(e->values.prototypes))
                /* So that it cannot get sweep()'d up. */
                ;
            cwal_value_make_vacuum_proof(CWAL_VALPART(e->values.prototypes),1);
        }
    }
    return e->values.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;
    static int once = 0;
    if(!once){
        /* Just making sure some assumptions are right... */
#if CWAL_VOID_PTR_IS_BIG
        assert(sizeof(void*)>4);
#else
        assert(sizeof(void*)<8);
#endif
        assert(sizeof(cwal_memsize_t) <= sizeof(void*))
            /* or we'll overwrite important stuff */;
        once = 1;
    }

    if(!E || !vtab){
        return CWAL_RC_MISUSE;
    }
    if(!CWAL_BUILTIN_VALS.inited) cwal_init_builtin_values();
    assert(CWAL_BUILTIN_VALS.inited);
    e = *E;
    if(!e){
        e = (cwal_engine*)vtab->allocator
            .realloc( vtab->allocator.state.data, NULL, sz )
            /* reminder: this does not take into account memory
               over-allocation. */;
        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;
    }

    e->vtab = vtab;
    if(CwalConsts.AutoInternStrings){
        e->flags |= CWAL_FEATURE_INTERN_STRINGS;
    }
    if(e->vtab->memcap.forceAllocSizeTracking
       || e->vtab->memcap.maxTotalMem
       || e->vtab->memcap.maxConcurrentMem){
        e->flags |= CWAL_F_TRACK_MEM_SIZE;
    }

    /* Tag e->recycler[*].id, just for sanity checking and to simplify
       metrics reporting. We store the virtual size of the Value type(s!)
       stored in that bin.
    */
    {
        int i = CWAL_TYPE_UNDEF;
        for( ; i < CWAL_TYPE_end; ++i ){
            cwal_recycler * re = cwal_recycler_get(e, (cwal_type_id)i);
            if(re){
                re->id = (int)cwal_type_id_sizeof((cwal_type_id)i);
            }
        }
        e->reString.id = (int)cwal_type_id_sizeof(CWAL_TYPE_STRING);
    }

#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 ){
    cwal_size_t sz;
    int rc;
    if(!b || !data) return CWAL_RC_MISUSE;
    sz = b->used + len + 1/*NUL*/;
    rc = cwal_buffer_reserve( e, b, sz );
    if(rc) return rc;
    memcpy( b->mem+b->used, data, len );
    b->used += len;
    b->mem[b->used] = 0;
    return 0;
}

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;
        cwal_size_t const oldUsed = b->used;
        ba.b = b;
        ba.e = e;
        ba.rc = 0;
        cwal_printfv( cwal_printfv_appender_buffer, &ba, fmt, args );
        if(ba.rc){
            b->used = oldUsed;
            if(b->capacity>oldUsed){
                b->mem[oldUsed] = 0;
            }
        }
        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;
}

/**
   Pops the e->current scope from the scope stack, cleaning it up and
   possibly cwal_free()ing it. If callHook is true then the
   cwal_engine_vtab::hook::scope_pop hook, if not NULL, will be called
   before the scope state is changed. The only error conditions are
   invalid arguments:

   !e = CWAL_RC_MISUSE

   !e->current = CWAL_RC_RANGE
 */
static int cwal_scope_pop_impl( cwal_engine * e, char callHook ){
    if(!e) return CWAL_RC_MISUSE;
    else if( 0 == e->current ) return CWAL_RC_RANGE;
    else{
        cwal_scope * p;
        cwal_scope * s = e->current;
        assert(s->e == e);
        if(callHook && e->vtab->hook.scope_pop){
            e->vtab->hook.scope_pop( s, e->vtab->hook.scope_state );
        }
        p = s->parent;
        assert(p || (e->top==s));
        e->current = p;
        if(e->top==s) e->top = 0;
        cwal_scope_free( e, s, 1 );
        return 0;
    }
}

int cwal_scope_pop( cwal_engine * e ){
    return cwal_scope_pop_impl(e, 1);
}

int cwal_scope_push( cwal_engine * e, cwal_scope ** S ){
    cwal_scope * s = S ? *S : NULL;
    int rc;
    if(!e) return CWAL_RC_MISUSE;
    rc = cwal_scope_alloc(e, &s);
    if(rc){
        assert(NULL == s);
        return rc;
    }
    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(e->vtab->hook.scope_push){
        rc = e->vtab->hook.scope_push( s, e->vtab->hook.scope_state );
        if(rc) cwal_scope_pop_impl(e, 0);
    }
    if(!rc && S) *S = s;
    return rc;
}

int cwal_scope_push2( cwal_engine * e, cwal_scope * s ){
    return (!e || !s || memcmp(s, &cwal_scope_empty, sizeof(cwal_scope)))
        ? CWAL_RC_MISUSE
        : cwal_scope_push( e, &s );
}

int cwal_scope_pop2( cwal_engine * e, cwal_value * resultVal ){
    if(!e) return CWAL_RC_MISUSE;
    else if(!e->current
            || (resultVal && !e->current->parent)) return CWAL_RC_RANGE;
    else{
        int rc = 0;
        if(resultVal){
            cwal_value_ref(resultVal);
            cwal_value_rescope(e->current->parent, resultVal);
        }
        rc = cwal_scope_pop_impl(e, 1);
        if(resultVal){
            cwal_value_unhand(resultVal);
        }
        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){
                if(l == l->left){
                    cwal_dump_value( "cwal.c", __LINE__, l, "Internal cwal_value list misuse.");
                    assert(l->left != l && "Internal cwal_value list misuse.");
                    E_IS_DEAD(p->e) = CWAL_RC_ASSERT;
                    return NULL;
                    /* abort(); */
                }
                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.");
        ++e->metrics.valuesRecycled;
        ++e->reString.hits;
        return li;
    }
    ++e->reString.misses;
    ++e->metrics.valuesRecycleMisses;
#if 0
    /* this is causing a valgrind warning via memset() via cwal_value_new() */
    if(e->reChunk.head
       && !e->reString.list /* see comments below */){
        /* Look in the chunk recycler as a last resort. Testing with
           the s2 amalgamation shows that this very rarely hits if
           recycling is on (only once in the whole test suite from
           20141201). If recycling is off, it hits surprisingly often
           (1224 times in the test suite). So we only do this O(N)
           lookup when string recycling is off or its recycling bin is
           empty.

           If both value recyling and string interning are off, this
           block hits 3798 times in the above-mentioned test suite.
        */
        cwal_size_t reqSize = sizeof(cwal_value)
            + sizeof(cwal_string)
            + len + 1 /*NUL*/;
        cwal_value * v = (cwal_value *)
            cwal_memchunk_request(e, &reqSize,
                                  len<CwalConsts.MaxRecycledStringLen
                                  /* TODO: tweak these values */
                                  ? 150 /* ==> up to 1.5x */
                                  : 125 /* ==> up to 1.25x. Wasting
                                           memory? They should be glad
                                           we're recycling it at
                                           all! */,
                                  "cwal_string_from_recycler()");
        if(v){
            /* MARKER(("Got string fallback!\n")); */
            *v = cwal_value_string_empty;
            s = CWAL_STR(v);
            assert(!(~CWAL_STRLEN_MASK & len));
            s->length = (cwal_size_t)(CWAL_STRLEN_MASK & len);
            return v;
        }
    }
#endif
    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 = 0;
    cwal_value * li;
    assert(s);
    if( slen > CwalConsts.MaxRecycledStringLen ){
        freeMsg = "String too long to recycle - freeing.";
        goto freeit;
    }
    else if(CWAL_STR_ISXZ(s)){
        assert(!"Cannot happe anymore - x/z-string recycled elsewhere/elsehow.");
        freeMsg = "Cannot recycle x/z-strings.";
        goto freeit;
    }
    else if( 0 == e->reString.maxLength ){
        freeMsg = "String recycling disabled - freeing.";
        goto freeit;
    }else if(freeMsg){
        /* avoiding an unused var in non-debug build */
        assert(!"impossible");
    }
    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?
        */
        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);
    }

    /* MARKER("String to recyler...\n"); */
    assert(!v->right);
    assert(!v->scope);
    assert(!CWAL_REFCOUNT(v));
    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);
#if 1
    {
        /**
           Pushing this memory to the chunk recycler can lower both
           allocs and peak marginally, but can also increase peak
           while lowering allocs, depending on the usage. Compare s2's
           UNIT.s2 vs UNIT-import.s2.
        */
        cwal_size_t sz = sizeof(cwal_value)+sizeof(cwal_string) + slen + 1 /*NUL byte*/;
        /* Account for string size padding */
        if(CwalConsts.StringPadSize){
            if(slen<CwalConsts.StringPadSize) sz = sz - slen + CwalConsts.StringPadSize;
            else if(slen>CwalConsts.StringPadSize){
                cwal_size_t const mod = (slen % CwalConsts.StringPadSize);
                if(mod) sz = sz + (CwalConsts.StringPadSize - mod);
            }
        }
        cwal_memchunk_add(e, v, sz);
    }
#else
    cwal_free( e, v );
#endif
    return 0;
    
}

int cwal_value_recycle( cwal_engine * e, cwal_value * v ){
    int ndx;
    cwal_recycler * re;    
    cwal_size_t max;
#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==CWAL_REFCOUNT(v));
    if(e->gcInitiator
            /*&& (CWAL_V_IS_OBASE(v)
             || (CWAL_TYPE_UNIQUE==v->vtab->typeID)
             || (CWAL_TYPE_TUPLE==v->vtab->typeID)
             //^^^^ We also need to put "not-quite-containers" here,
             // namely CWAL_TYPE_UNIQUE and any potentially similar
             // ones (CWAL_TYPE_TUPLE).
             )*/){
        /**
           20180105: historically we only put certain types in the GC
           queue, but we came across a situation where a Unique value
           wrapping a string choked at destruction because its wrapped
           string had already been sent to the string recycler
           (strings, at the time, did not get gc-queued)
         */
        /**
           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 hypothetically 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. Practice has (finally, 20180105) shown that
           certain non-cyclic relationships require that their values
           (in particular code constellations) must also be gc-queued.
        */
        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;
    }
    else if(CWAL_TYPE_STRING == v->vtab->typeID
            && !CWAL_STR_ISXZ(CWAL_STR(v))){
            return cwal_string_recycle( e, v );
    }

    assert( 0 == CWAL_REFCOUNT(v) );
    assert( 0 == v->right );
    assert( 0 == v->left );
    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(!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;
    }

    assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED));
    CWAL_RCFLAG_ON(v, CWAL_RCF_IS_RECYCLED);
    assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_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 ){
    assert( e->gcInitiator );
    assert(0 == v->scope);
    assert(0 == v->right);
    if(!e->gcInitiator) return CWAL_RC_MISUSE;
    if(e->values.gcList){
        cwal_value_insert_before( e->values.gcList, v );
    }
    assert(!CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_GC_QUEUED));
    CWAL_RCFLAG_ON(v,CWAL_RCF_IS_GC_QUEUED);
    assert(CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_GC_QUEUED));
    e->values.gcList = v;
    assert(0==e->values.gcList->left);
    return CWAL_RC_OK;
}

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

int 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 = CWAL_VOBASE(v);
    assert(s && v);
    assert(!CWAL_MEM_IS_BUILTIN(v));
    /*
      Reminder to self: v->scope == s when we're moving items
      around from s->mine.{r0,headSafe}.
    */
#if 0
    /* 20191211: The intent: if we are moving a scope's properties
       object into an older new scope, remove the is-prop-storage
       flag, as it's now hypothetically moved beyond that role. It's
       not yet certain that that's always correct, though, nor exactly
       what the ramifications of doing this would be.
    */
    if(b && (CWAL_F_IS_PROP_STORAGE & b->flags) && v->scope != s){
        b->flags &= ~CWAL_F_IS_PROP_STORAGE;
    }
#endif
    cwal_value_snip( v );
    assert(0==v->right);
    assert(0==v->left);
    assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED));
    if(0==CWAL_REFCOUNT(v)){
        listpp = &s->mine.r0;
    }else if(CWAL_V_IS_VACUUM_SAFE(v)
             || (b && (b->flags & CWAL_F_IS_PROP_STORAGE))){
        listpp = &s->mine.headSafe;
    }else if(CWAL_V_GOES_IN_HEADOBJ(v)){
        /* 20180105 reminder: this doesn't include Uniques and Tuples.
           TODO?: add those types to headObj once we're certain that
           doing so won't cause other Grief. We could undo the
           special-case destruction checks in
           cwal_value_cleanup_unique() and cwal_value_cleanup_tuple()
           if we do that, and revert the GC queue to only holding
           containers (plus Unique and Tuple). Initial tests in that
           regard didn't alleviate them (or Unique, at least) needing
           their extra cleanup checks :/.
        */
        listpp = &s->mine.headObj; 
    }else {
        listpp = &s->mine.headPod;
    }
    list = *listpp;
    if(list == v){
        cwal_dump_value( __FILE__, __LINE__, v,
                         "FATAL: inserting item we already have! "
                         "String interning backfire?");
        assert(list != v && "Insertion failed: this item is "
               "the head of one of the lists! String interning backfire?");
        E_IS_DEAD(s->e) = CWAL_RC_ASSERT;
#if 1
        return s->e->fatalCode;
#else
        abort(/* this is the only solution, as this error is indicative
                 or memory corruption within cwal and we cannot report
                 it to the user from this level. */);
#endif
    }
    else 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 scope.");
    return 0;
}


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( CWAL_MEM_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( CWAL_MEM_IS_BUILTIN( v ) ) return CWAL_RC_OK;
    else if(!v->scope){
        if(CWAL_V_IS_IN_CLEANUP(v)) return CWAL_RC_DESTRUCTION_RUNNING;
        assert(!"Cannot unref a Value with no scope: serious misuse or Value corruption.");
        return CWAL_RC_MISUSE;
    }
    else return cwal_value_unref2(v->scope->e, v);
}

int cwal_value_unref2(cwal_engine * e, cwal_value *v ){
    assert( e && v );
    if(NULL == e || NULL == v) return CWAL_RC_MISUSE;
    CWAL_TR_SV(e,v->scope,v);
    if(CWAL_MEM_IS_BUILTIN(v)) return 0;
    else {
        cwal_obase * b;
        b = CWAL_VOBASE(v);
        CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"Unref'ing");
        if(!CWAL_REFCOUNT(v) || !CWAL_RCDECR(v)){
            cwal_scope * vScope = v->scope;
            CWAL_TR_V(e,v);
            if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_GC_QUEUED)){
                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;
            }
            else if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED)){
                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;
            }
            else if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING)) {
                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;
            }
            CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_START,
                     "Starting finalization...");

            if(e->values.exception == v){
                e->values.exception = 0;
            }
            if(e->values.propagating == v){
                e->values.propagating = 0;
            }

            if(!v->scope){
                dump_val(v,"NULL scope???");
                assert(v->scope && "this is always true now. That was not always the case.");
            }
            cwal_value_take(e, v)
                /*ignoring rc! take() cannot fail any more under these
                  conditions.*/;
            if(b && (CWAL_F_IS_PROP_STORAGE & b->flags)){
                /* reminder to self: it's potentially possible for
                   clients to move this value into a place where
                   it's used for purposes other than property
                   storage, via cwal_scope_properties().
                */
                cwal_scope * sc = e->current;
                assert(CWAL_TYPE_OBJECT==v->vtab->typeID
                       || CWAL_TYPE_HASH==v->vtab->typeID);
                /*
                  Special case: if v is the cwal_scope::props handle
                  of a scope, we need to clear that to make sure it
                  doesn't get stale. This can hypothetically happen if
                  client code exposes cwal_scope_properties() to
                  script-space, causes it to get upscoped (for
                  ownership purposes) but still being pointed to by
                  cwal_scope::props, and then unrefs it. If that
                  happens, though...  hmmm... the scope storage is
                  always treated as vacuum-safe, which means that they
                  could become impossible to vacuum up if clients
                  introduced cycles and then abandoned all
                  script-visible references. Oh, well. They shouldn't
                  be exposing these to script code, probably.
                */
                for( ; sc ; sc = sc->parent ){
                    if(sc->props == v){
                        sc->props = 0;
                        break;
                    }
                }
            }
            /* The left/right assertions help ensure we're not now traversing
               through the recycle list.
            */
            if(v->left || v->right){
                /*
                  This is case checked above in a different manner.
                  If this check fails, then memory corruption is
                  what's going on and we need to stop that... the only
                  way we reasonably can. Alternately, we may at some
                  point add a flag to the engine telling it it's in an
                  unrecoverable/unusable state. That would require
                  adding that check to a great many routines, though.
                */
                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(?)!");
                return e->fatalCode = CWAL_RC_ASSERT;
                /* abort(); */
            }

            assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING));
            CWAL_RCFLAG_ON(v, CWAL_RCF_IS_DESTRUCTING);
            assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_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 ancient behaviour
                   which may or may not still be relevant.
                */;
            v->vtab->cleanup(e, v);
            v->scope = 0 /* END KLUDGE! */;
            assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING));
            CWAL_RCFLAG_OFF(v, CWAL_RCF_IS_DESTRUCTING);
            assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_DESTRUCTING));
            CWAL_TR_V(e,v);
            CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_END,
                     "Cleanup complete. Sending to recycler...");
            assert(0 == CWAL_REFCOUNT(v));
            cwal_value_recycle(e, v);
            return CWAL_RC_FINALIZED;
        }
        else{
            CWAL_TR_V(e,v);
            CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,
                     "It continues to live");
            return CWAL_RC_HAS_REFERENCES;
        }
    }
}

cwal_value * cwal_value_unhand( cwal_value * v ){
    if(!v || CWAL_MEM_IS_BUILTIN(v)) return v;
    else if(!v->scope){
        /* should we do this check, like we do in cwal_value_unref()?
           if(CWAL_V_IS_IN_CLEANUP(v)) return NULL;
        */
        assert(!"Cannot unhand a Value with no scope: serious misuse or Value corruption.");
        return NULL;
    }
    else if(CWAL_REFCOUNT(v) && 0==CWAL_RCDECR(v)){
        cwal_value_reprobate(v->scope, v);
    }
    return v;
}

/**
   Increments cv's reference count by 1. Asserts that !CWAL_MEM_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 int cwal_refcount_incr( cwal_engine *e, cwal_value * cv )
{
    assert( NULL != cv );
    /* assert(!CWAL_MEM_IS_BUILTIN(cv)); */
    assert(cv->scope) /* also catches builtins */;
    if( CWAL_RCFLAG_MAXRC <= CWAL_REFCOUNT(cv) ){
        /* Overflow! */
        cwal_dump_value( __FILE__, __LINE__, cv,
                         "FATAL: refcount overflow! How?!?");
        assert(!"Refcount overflow! Undefined behaviour!");
        return e->fatalCode = CWAL_RC_RANGE;
    }
    else if(1 == CWAL_RCINCR(cv)){
        if(cwal_scope_from_r0(cv)){
            assert(!"If this is failing then internal "
                   "preconditions/assumptions have not been met.");
#ifdef DEBUG
            abort();
#endif
            return e->fatalCode = CWAL_RC_ASSERT;
        }
    }
    if(CWAL_REFCOUNT(cv) > e->metrics.highestRefcount){
        e->metrics.highestRefcount = CWAL_REFCOUNT(cv);
        e->metrics.highestRefcountType = cv->vtab->typeID;
    }
    CWAL_TR_V(e,cv);
    CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"++Refcount");
    return 0;
}

int cwal_value_ref2( cwal_engine *e, cwal_value * cv ){
    assert(e && cv);
    if( !e || !cv ) return CWAL_RC_MISUSE;
    else if( CWAL_MEM_IS_BUILTIN(cv) ) return CWAL_RC_OK;
    else if( !cv->scope ){
        assert(!"Apparent attempt to ref() an "
               "invalid (cleaned up?) value.");
        return CWAL_RC_MISUSE;
    }
    else return CWAL_RCFLAG_MAXRC <= CWAL_REFCOUNT(cv)
        ? CWAL_RC_RANGE
        : cwal_refcount_incr( e, cv );
}
    
int cwal_value_ref( cwal_value * cv ){
    if( NULL == cv ) return CWAL_RC_MISUSE;
    else if( CWAL_MEM_IS_BUILTIN(cv) ) return CWAL_RC_OK;
    else if( !cv->scope ){
        assert(!"Apparent attempt to ref() an "
               "invalid (cleaned up?) value.");
        return CWAL_RC_MISUSE;
    }
    else {
        assert( cv->scope->e );
        return cwal_refcount_incr( cv->scope->e, cv );
    }
}

#if 0
int cwal_ref(cwal_value *v){ return cwal_value_ref(v); }
int cwal_unref(cwal_value *v){ return cwal_value_unref(v); }
cwal_value * cwal_unhand( cwal_value * v ){
    return cwal_value_unhand(v);
}
#endif

char cwal_refunref( cwal_value * v ){
    char rc = 0;
    if(v && v->scope && !CWAL_REFCOUNT(v)){
        /* This is a temp. Or, it turns out, it's an interned string
           which is used in 2+ places and our nuking it has disastrous
           side-effects. So... there is no obvious internal workaround
           because string interning does not count how many instances
           are interned (which we could use to "fudge" the refcount
           check). So we're going to call that a usage error and leave
           it at that. i'm suddenly very glad i resisted, at the time,
           the urge to intern integers/doubles as well.

           One potential workaround, but not a good one:

           if v is-a string AND (v->scope->level <
           v->scope->e->current->level) then don't nuke it.

           That would only hide the problem some (most?) of the time,
           though.
        */
        cwal_value_unref(v);
        rc = 1;
    }
    return rc;
}

cwal_refcount_t cwal_value_refcount( cwal_value const * v ){
    return v ? CWAL_REFCOUNT(v) : 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;
}



cwal_size_t cwal_type_id_sizeof( cwal_type_id id ){
    switch(id){
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_UNDEF:
      case CWAL_TYPE_NULL:
          return 0;
      case CWAL_TYPE_STRING: return sizeof(cwal_value)+sizeof(cwal_string);
      case CWAL_TYPE_UNIQUE: return sizeof(cwal_value)+sizeof(cwal_value*);
      case CWAL_TYPE_TUPLE: return sizeof(cwal_value)+sizeof(cwal_tuple);
      case CWAL_TYPE_INTEGER: return sizeof(cwal_value)+sizeof(cwal_int_t);
      case CWAL_TYPE_DOUBLE: return sizeof(cwal_value)+sizeof(cwal_double_t);
      case CWAL_TYPE_ARRAY: return sizeof(cwal_value)+sizeof(cwal_array);
      case CWAL_TYPE_OBJECT: return sizeof(cwal_value)+sizeof(cwal_object);
      case CWAL_TYPE_NATIVE: return sizeof(cwal_value)+sizeof(cwal_native);
      case CWAL_TYPE_BUFFER: return sizeof(cwal_value)+sizeof(cwal_buffer_obj);
      case CWAL_TYPE_FUNCTION: return sizeof(cwal_value)+sizeof(cwal_function);
      case CWAL_TYPE_EXCEPTION: return sizeof(cwal_value)+sizeof(cwal_exception);
      case CWAL_TYPE_HASH: return sizeof(cwal_value)+sizeof(cwal_hash);
      case CWAL_TYPE_SCOPE: return sizeof(cwal_scope);
      case CWAL_TYPE_KVP: return sizeof(cwal_kvp);
      case CWAL_TYPE_WEAK_REF: return sizeof(cwal_weak_ref);
      case CWAL_TYPE_XSTRING:
      case CWAL_TYPE_ZSTRING:
          return sizeof(cwal_value)
              +sizeof(cwal_string)
              + sizeof(char **);
      case CWAL_TYPE_LISTMEM: return 0;
      default:
          return 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_new_unique()
   @see cwal_new_tuple()
   @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 /* prototype to stamp over the
                                         newly-allocated value */;
    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
             32-bit 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 48
             bits of integer 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_obj);
          break;
      case CWAL_TYPE_HASH:
          assert( 0 == extra );
          def = cwal_value_hash_empty;
          tx = sizeof(cwal_hash);
          break;
      case CWAL_TYPE_UNIQUE:
          assert( 0 == extra );
          def = cwal_value_unique_empty;
          tx = sizeof(cwal_value*);
          break;
      case CWAL_TYPE_TUPLE:
          assert( 0 == extra );
          def = cwal_tuple_value_empty;
          tx = sizeof(cwal_tuple);
          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{
        cwal_recycler * re =
            cwal_recycler_get( e, t /*def.vtab->typeID (wrong for x/z-strings) */ );
        /* MARKER(("BIN #%d(%s)\n", recycleListIndex, def.vtab->typeName)); */
        if(re){
            if(!re->count){
                ++e->metrics.valuesRecycleMisses;
                ++re->misses;
            }else{
                /* Recycle (take) the first entry from the list. */
                ++re->hits;
                ++e->metrics.valuesRecycled;
                /* MARKER(("BIN #%d(%s) LENGTH=%u\n", recycleListIndex, cwal_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.");
                assert(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED));
                CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_RECYCLED);
                assert(!CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_RECYCLED));
                *v = def /* needed for types which share a recycling pool */;
            }
        }
    }
    ++e->metrics.requested[t];
    if(!v){
        /* Check the memchunk for an exact-fit match.  This saves a
           small handful of allocs and total memory, but increases the
           search/miss ratio notably, going from ~5% misses to ~20% misses
           in the s2 amalgamated unit tests.
         */
        cwal_size_t reqSize = sz+tx;
        v = (e->reChunk.config.useForValues
             && e->reChunk.head
             && e->reChunk.head->size<=reqSize)
            ? (cwal_value*)cwal_memchunk_request(e, &reqSize, 0,
                                                 "cwal_value_new()")
            : 0;
        if(v){
            assert(reqSize==sz+tx);
        }else{
            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 == CWAL_REFCOUNT(v));

        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 except
                   on inputs which are corrupt in "just the right way"
                   such that they appear valid by this point. */
                ;
            assert(0 == CWAL_REFCOUNT(v));
            assert(s == v->scope);
            assert(-1==check);
            assert(0==rc);
        }
        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->values.prototypes
                /* Will only be false once, while creating
                   e->values.prototypes! This also means e->values.prototypes will
                   not get the built-in prototype for arrays unless we
                   special-case that, but clients do not have access
                   to e->values.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_type_id_name(t), (void const*)proto));*/
                    cwal_value_prototype_set(v, proto);
                    assert(proto == cwal_value_prototype_get(e, v));
                }
            }
        }
#ifdef DEBUG
        if(v){ /* 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( int 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;
    if(v>=CWAL_BUILTIN_INT_FIRST &&
       v<= CWAL_BUILTIN_INT_LAST){
        METRICS_REQ_INCR(e,CWAL_TYPE_INTEGER);
        return (cwal_value*) &CWAL_BUILTIN_VALS.memInt[v + -CWAL_BUILTIN_INT_FIRST];
    }
    else{
        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 ){
            memcpy(CWAL_DBL_NONULL(c), &v, sizeof(cwal_double_t));
        }
        return c;
    }
}

cwal_value * cwal_new_unique( cwal_engine * e, cwal_value * wrapped ){
    cwal_value * v = e ? cwal_value_new(e, e->current, CWAL_TYPE_UNIQUE, 0) : 0;
    if(v){
        *CWAL_UNIQUE_VALPP(v) = wrapped;
        if(wrapped){
            cwal_value_ref(wrapped);
            cwal_value_rescope(v->scope, wrapped)
                /* that's just me being overly pedantic. It's not
                   possible that wrapped->scope is newer if v->scope
                   resp. e->current is running. */;
        }
    }
    return v;
}

cwal_value * cwal_unique_wrapped_get( cwal_value const * v ){
    cwal_value ** rc = CWAL_UNIQUE_VALPP(v);
    return rc ? *rc : 0;
}

int cwal_unique_wrapped_set( cwal_value * v, cwal_value * w ){
    cwal_value ** ch = CWAL_UNIQUE_VALPP(v);
    if(!ch) return CWAL_RC_TYPE;
    else if(v == w) return CWAL_RC_CYCLES_DETECTED;
    else{
        cwal_value * prev = *ch;
        if(prev == w) return 0;
        else{
            *ch = w;
            if(w){
                cwal_value_ref(w);
                cwal_value_rescope(v->scope, w);
            }
            if(prev){
                assert( CWAL_REFCOUNT(prev) || CWAL_MEM_IS_BUILTIN(prev) );
                cwal_value_unref(prev);
            }
            return 0;
        }
    }
}

uint16_t cwal_tuple_length(cwal_tuple const * p){
    return p ? p->n : 0;
}

int cwal_tuple_set(cwal_tuple * p, uint16_t n, cwal_value * v){
    cwal_value * self = CWAL_VALPART(p);
    if(!self) return CWAL_RC_MISUSE;
    else if(n>=p->n) return CWAL_RC_RANGE;
    /* else if(self == v) return CWAL_RC_CYCLES_DETECTED;
       Cycles are not a problem, are they? */
    else{
        cwal_value * ch = p->list[n];
        if(v) cwal_value_ref(v);
        if(ch) cwal_value_unref(ch);
        p->list[n] = v;
        if(v) cwal_value_rescope(self->scope, v);
        return 0;
    }
}

cwal_value * cwal_tuple_get(cwal_tuple const * p, uint16_t n){
    return (p && n<p->n) ? p->list[n] : 0;
}

cwal_tuple * cwal_new_tuple( cwal_engine * e, uint16_t n ){
    if(!n){
        assert(CWAL_MEM_IS_BUILTIN(CWAL_BUILTIN_VALS.vTuple0));
        return CWAL_TUPLE(CWAL_BUILTIN_VALS.vTuple0);
    }else{
        cwal_tuple * p = 0;
        cwal_value * v = e
            ? cwal_value_new(e, e->current, CWAL_TYPE_TUPLE, 0)
            : 0;
        if(v){
            cwal_size_t const reqSize = sizeof(cwal_value*) * n;
            p = CWAL_TUPLE(v);
            assert(p);
            p->list = (cwal_value**)cwal_malloc2(e, reqSize);
            if(!p->list){
                cwal_value_unref(v);
                p = 0;
            }else{
                memset(p->list, 0, reqSize);
                p->n = n;
            }
        }
        return p;
    }
}

cwal_value * cwal_new_tuple_value( cwal_engine * e, uint16_t n ){
    cwal_tuple * p = cwal_new_tuple(e, n);
    return CWAL_VALPART(p);
}

cwal_value * cwal_tuple_value( cwal_tuple const * p ){
    return CWAL_VALPART(p);
}

cwal_tuple * cwal_value_get_tuple( cwal_value * v ){
    return CWAL_TUPLE(v);
}

#if 0
/* Not needed because tuples cannot be prototypes. Maybe
   someday. */
cwal_tuple * cwal_tuple_part( cwal_engine * e,
                              cwal_value * v ){
    cwal_tuple * p;
    do{
        if( (p = CWAL_TUPLE(v)) ) return p;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}
#endif

int cwal_tuple_visit( cwal_tuple * o, cwal_value_visitor_f f, void * state ){
    cwal_value * const tv = CWAL_VALPART(o);
    if(!tv || !f) return CWAL_RC_MISUSE;
    /*else if(CWAL_OB_IS_LOCKED(o)) return CWAL_RC_LOCKED;*/
    else if(!o->n) return 0;
    else {
        uint16_t i;
        cwal_value * v;
        int rc = CWAL_RC_OK;
        int opaque;
        cwal_visit_list_begin(tv, &opaque);
        cwal_value_ref(tv);
        for( i = 0; i < o->n && !rc; ++i ){
            v = o->list[i];
            if(v) cwal_value_ref(v);
            rc = f( v, state );
            if(v) cwal_value_unref(v);
        }
        cwal_value_unhand(tv);
        cwal_visit_list_end(tv, opaque);
        return rc;
    }
}

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, if
   unrefProto is true, clears the
   CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET flag from b->flags and
   unrefs/NULLs b->prototype.
*/
static void cwal_cleanup_obase( cwal_engine * e, cwal_obase * b, char unrefProto ){
    while(b->kvp){
        cwal_kvp * kvp = b->kvp;
        cwal_kvp * next = 0;
        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 );
        }
        /* b->flags &= ~CWAL_F_IS_VISITING; */
    }
    if( unrefProto ){
        b->flags &= ~CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET;
        if( b->prototype ){
            cwal_value_prototype_set( CWAL_VALPART(b), NULL );
        }
    }
}

/**
   If li->list is not 0 and e has space in its list memory recycler,
   the contents of li are moved into the recycler and *li is reset to a
   clean state. If li->list but the recycler has no slots, the memory
   is freed instead.
*/
static void cwal_list_to_recycler( cwal_engine * e, cwal_list * li ){
    if(li->list){
        cwal_memchunk_add(e, li->list, li->alloced * sizeof(void*));
        *li = cwal_list_empty;
    }else{
        assert(!li->count);
        assert(!li->alloced);
    }
}    

/**
   Cleans up various parts of an array:

   self must be a (ceal_value*).

   Cleans up all list entries Then...

   freeList: if true, frees all list memory

   freeProps: if true, clears all properties.

   unrefProto: if true, clears the
   CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET flag from the cwal_obase part
   and unsets/unrefs the prototype.
 */
static void cwal_value_cleanup_array_impl( cwal_engine * e, void * self,
                                           char freeList, char freeProps,
                                           char unrefProto ){
    cwal_value * const vSelf = (cwal_value *)self;
    cwal_array * const ar = cwal_value_get_array(vSelf);
    int opaque;
    assert(NULL!=ar);
    cwal_visit_list_begin(vSelf, &opaque);
    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;
                if(!CWAL_V_IS_IN_CLEANUP(v)){
                    cwal_value_unref(v);
                }
            }
        }
        ar->list.count = 0;
    }
    if(freeList && ar->list.list){
        cwal_list_to_recycler(e, &ar->list);
    }
    cwal_visit_list_end(vSelf, opaque);
    if(freeProps) {
        cwal_cleanup_obase( e, &ar->base, 0 );
    }
    if(unrefProto){
        ar->base.flags &= ~CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET;
        if(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_value_prototype_set( vSelf, NULL );
        }
    }
}


/**
   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_TYPE_FUNCTION != val->vtab->typeID ) 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;
    int rc;
    cwal_value * vargs = argc ? CWAL_VALPART(args) : 0;
    char const aWasVacSafe = vargs
        ? CWAL_V_IS_VACUUM_SAFE(vargs)
        : 1;
    if(argc && !aWasVacSafe){
        cwal_value_make_vacuum_proof(vargs,1);
    }
    cwal_value_ref(vargs);
    rc = s
        ? cwal_function_call_in_scope( s, f, self, rv, argc, argv )
        : cwal_function_call( f, self, rv, argc, argv );
    cwal_value_unhand(vargs);
    if(!aWasVacSafe){
        cwal_value_make_vacuum_proof(vargs,0);
    }
    return rc;
}

int cwal_function_call_in_scope2( cwal_scope * s,
                                  cwal_function * f,
                                  cwal_value * propertyHolder,
                                  cwal_value * self,
                                  cwal_value ** _rv,
                                  uint16_t argc,
                                  cwal_value * const * argv){
    uint16_t cflags = 0;
    if(!s ||!s->e || !f) return CWAL_RC_MISUSE;
    else if((cflags = cwal_container_flags_get(CWAL_VALPART(f)))
            && (CWAL_CONTAINER_INTERCEPTOR_RUNNING & cflags)){
        return CWAL_RC_CYCLES_DETECTED;
    }
    else {
        cwal_engine * const 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 const hook = e->cbHook /* why (again) do we take/need a copy? */;
        char const fWasVacuumProof = CWAL_V_IS_VACUUM_SAFE(fv);
        cwal_obase * selfBase = CWAL_VOBASE(self);
        char const selfWasVacSafe = selfBase
            ? CWAL_V_IS_VACUUM_SAFE(self)
            : 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;
        args.propertyHolder = propertyHolder;
        /* s->flags |= CWAL_F_IS_CALL_SCOPE; */

        /* 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. */
        rc = cwal_refcount_incr(e, fv);
        if(rc) return rc /* game over, man */;

        /*
          We set the vacuum-proofing flag and an artificial
          reference on f to proactively 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! In s2, the eval engine disables
          sweeping/vacuuming during any given single expression, so
          it's not a problem there unless/until we add recursive
          sweeping/vacuuming.
        */
        if(!fWasVacuumProof){
            cwal_value_make_vacuum_proof(fv, 1);
            /* why does this trigger in th1ish? */
            assert(fv == (CWAL_REFCOUNT(fv) ? fv->scope->mine.headSafe : fv->scope->mine.r0));
            /*
              Reminder: in th1ish we have refcount-0 funcs being
              called. That's potentially unsafe (we've learned in the
              meantime).
            */
        }
        if(self){
            cwal_value_ref(self);
            if(selfBase && !selfWasVacSafe){
                cwal_value_make_vacuum_proof(self,1);
            }
        }
        cwal_value_ref(propertyHolder);
        if(hook.pre){
            rc = hook.pre(&args, hook.state);
        }
        if(!rc){
            uint16_t i;
            for(i = 0; i < argc; ++i ) cwal_value_ref(argv[i]);
            if(CWAL_CONTAINER_INTERCEPTOR & cflags){
                cwal_container_flags_set(fv,cflags | CWAL_CONTAINER_INTERCEPTOR_RUNNING);
                assert(CWAL_CONTAINER_INTERCEPTOR & cwal_container_flags_get(fv));
            }
            rc = f->callback( &args, &rv );
            if(CWAL_CONTAINER_INTERCEPTOR & cflags){
                cwal_container_flags_set(fv, cflags);
            }
            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;
            }
            for(i = 0; i < argc; ++i ) cwal_value_unhand(argv[i]);
        }
        /* assert(CWAL_REFCOUNT(fv)>0 && "Someone took my ref!"); */
        cwal_value_unhand(propertyHolder);
        cwal_value_unhand(fv);
        if(!fWasVacuumProof){
            cwal_value_make_vacuum_proof(fv, 0);
            /*20181122: removed because this is triggering and i'm not 100% sure
              whether this assertion is truly valid (i've been away from the
              intricaces of this level too long :/)

              assert(fv == (CWAL_REFCOUNT(fv)
                ? fv->scope->mine.headSafe : fv->scope->mine.r0));

                For the time being we'll replace it with something less
                intrusive...
            */
            assert(fv->scope);
            assert(!CWAL_RCFLAG_HAS(fv,CWAL_RCF_IS_GC_QUEUED));
        }
        if(self){
            cwal_value_unhand(self);
            if(selfBase && !selfWasVacSafe){
                cwal_value_make_vacuum_proof(self,0);
            }
        }
        /* 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) cwal_refunref(rv);
        }else{
#if 0
            /* Historical: disabled 20141124. The docs do not imply
               that we do this, and no C code (aside from a stray
               assertion) assumes it, either. And we need to leave
               rv==0 in order to distinguish between "no return" and
               "return undefined" (if we ever really want/need to).
               FWIW, my callbacks almost always explicitly set it to
               undefined, but that's more of a style thing than a
               requirement. */
            if(!rv) rv = cwal_value_undefined();
#endif
            if(_rv) *_rv = rv;
            else if(rv) cwal_refunref(rv);
        }
        return rc;
    }
}

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){
    return cwal_function_call_in_scope2( s, f, 0, self, rv, argc, argv );
}

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

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

}

int cwal_function_set_rescoper( cwal_function * f,
                                cwal_value_rescoper_f rescoper){
    if(!f) return CWAL_RC_MISUSE;
    else {
        f->rescoper = rescoper;
        return 0;
    }
}

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) );
        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_call2( cwal_function * f,
                         cwal_value * propertyHolder,
                         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 = 0;
        cwal_scope _sub = cwal_scope_empty;
        cwal_scope * s = &_sub;
        rc = cwal_scope_push(e, &s);
        if(!rc){
            rc = cwal_function_call_in_scope2( s, f, propertyHolder,
                                               self, rv, argc, argv );
            rc2 = cwal_scope_pop2(e, rc ? 0 : (rv ? *rv : 0));
        }
        return rc2 ? rc2 : rc;
    }
}

int cwal_function_call( cwal_function * f,
                        cwal_value * self,
                        cwal_value ** rv,
                        uint16_t argc,
                        cwal_value * const * argv ){
    return cwal_function_call2( f, 0, self, rv, argc, argv );
}

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_obj * bo = CWAL_BUFOBJ(v);
        cwal_buffer * b;
        assert(NULL != bo);
        b = &bo->buf;
        b->self = bo;
        cwal_buffer_wipe_keep_self(b);
        assert(bo == b->self);
        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){
    cwal_buffer_obj * bo;
    if( ! val ) return CWAL_RC_MISUSE;
    else if( !(bo = CWAL_BUFOBJ(val)) ) return CWAL_RC_TYPE;
    else{
        if(x) *x = &bo->buf;
        return 0;
    }
}
    
cwal_buffer * cwal_value_get_buffer( cwal_value const * v ) {
    cwal_buffer * b = NULL;
    cwal_value_fetch_buffer( v, &b );
    return b;
}

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 || !s->self) return 0;
    else{
        cwal_buffer_obj const * bo = (cwal_buffer_obj const *)s->self;
        cwal_value * v = CWAL_VALPART(bo);
        if(s->self != CWAL_BUFOBJ(v)){
            assert(!"It seems that that the 'self' member of a buffer got screwed up.");
            return 0;
        }
        if(v && v->vtab && (CWAL_TYPE_BUFFER==v->vtab->typeID)){
            return v;
        }else{
            assert(!"It seems that we were passed a non-Value cwal_buffer.");
            return NULL;
        }
    }
}

cwal_string * cwal_buffer_to_zstring(cwal_engine * e, cwal_buffer * b){
    if(!e || !e->current || !b) return 0;
    else if((b->used+1) & ~((cwal_size_t)CWAL_STRLEN_MASK)) return 0 /* too big */;
    else{
        cwal_string * s = cwal_new_zstring(e, (char *)b->mem, b->used)
            /* reminder: that might cwal_free(e, b->mem) */;
        if(!s) return NULL;
        else if(s && !CWAL_MEM_IS_BUILTIN(s)){
            /* Re-tweak the metrics which the z-string ctor just
               counted. Those bytes were already counted by wherever
               buf->mem came from (it might have initially been from,
               e.g. cwal_list::list).

               Because b->mem's source is unclear, we cannot subtract
               the metric from its origin entry in e->metrics. The
               best we can do here is _subtract_ the b->used+1 which
               the z-string ctor just added to its metrics, to avoid
               double-counting.

               Not perfect, but there it is.
            */
            assert(e->metrics.bytes[CWAL_TYPE_ZSTRING] >= b->used+1);
            e->metrics.bytes[CWAL_TYPE_ZSTRING] -= b->used+1;
        }
        cwal_buffer_wipe_keep_self(b);
        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_obj * bo = CWAL_BUFOBJ(v);
    cwal_buffer_reserve(e, &bo->buf, 0);
    cwal_cleanup_obase(e, &bo->base, 1);
    *bo = cwal_buffer_obj_empty;
}

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 cwal_size_t codeKeyLen = 0;
        static cwal_size_t msgKeyLen = 0;
        int rc;
        if(!codeKeyLen){
            msgKeyLen = cwal_strlen(CwalConsts.ExceptionMessageKey);
            codeKeyLen = cwal_strlen(CwalConsts.ExceptionCodeKey);
        }
        /*
          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 );
        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 );
}
    
    
char * cwal_string_str_rw(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
           );
}

/**
   Intended to be called immediately after initialization of s and
   assignment of its string content, and it assert()'s that the
   is-ascii flag is no set on s. If the byte length of s equals its
   UTF8 length, the is-ascii flag is encoded in s->length, else this
   has no side effects.
 */
static void cwal_string_check_for_ascii( cwal_string * s ){
    unsigned char const * c = (unsigned char const *) cwal_string_cstr(s);
    unsigned char const * end = c + CWAL_STRLEN(s);
    assert(!CWAL_STR_ISASCII(s));
    assert(c);
    assert(c < end);
    for( ; c < end; ++c ){
        if(*c & 0x80) return;
    }
    s->length |= CWAL_STR_ASCII_MASK;
}

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 https://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
}


char const * cwal_string_cstr2(cwal_string const *v, cwal_size_t * len){
    if(v && len) *len = CWAL_STRLEN(v);
    return cwal_string_cstr(v);
}

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_MEM_IS_BUILTIN(v)) return;
    else if(CWAL_STR_ISZ(s)){
        unsigned char ** pos = (unsigned char **)(s+1);
        char * cs = cwal_string_str_rw(s);
        cwal_size_t const slen = CWAL_STRLEN(s);
        assert(cs == (char *)*pos);
        *pos = NULL;
        if(e->flags & CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP){
            memset(cs, 0, slen+1/*NUL*/);
        }
        cwal_memchunk_add(e, cs, slen+1/*NUL*/);
        /* cwal_free(e, cs); */
    }else if(CWAL_STR_ISX(s)){
        unsigned char const ** pos = (unsigned char const **)(s+1);
#ifdef DEBUG
        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_rw(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
        ? CWAL_STRLEN(str)
        : 0U;
}


cwal_size_t cwal_string_length_utf8( cwal_string const * str ){
    return str
        ? (CWAL_STR_ISASCII(str)
           ? CWAL_STRLEN(str)
           : cwal_strlen_utf8( cwal_string_cstr(str),
                               CWAL_STRLEN(str) )
           )
        : 0U;
}

char cwal_string_is_ascii( cwal_string const * str ){
    return str ? CWAL_STR_ISASCII(str) : 0;
}

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 ){
    if(state || str){/*avoid unused param warning*/}
    return !CwalConsts.MaxInternedStringSize
        || (len <= CwalConsts.MaxInternedStringSize);
}

#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS
/**
   Expects (asserts) char to be in the range [0,127]. Gets the shared
   length-1 string for that character and returns it. If it fails, ndx
   is of an unexpected value.

   Intended ONLY to be called from cwal_new_string/xstring/zstring()
   and ONLY after they have verified that ndx is in range.
*/
static cwal_string * cwal_len1_ascii_string(int ndx){
    cwal_value * v;
    assert(ndx>=0 && ndx<=127);
    v = (cwal_value *)CWAL_BUILTIN_VALS.memAsciiPrintable[ndx];
    assert(CWAL_STR(v));
    return CWAL_STR(v);
}
#endif

cwal_string * cwal_new_string(cwal_engine * e, char const * str, cwal_size_t len){
    if(!e || CWAL_STRLEN_TOO_LONG(len)){
        return NULL ;
    }
    else if( !str || !len ){
        METRICS_REQ_INCR(e,CWAL_TYPE_STRING);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }
#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS
    else if( 1==len
             && ((unsigned char)*str)<=127 ){
        METRICS_REQ_INCR(e,CWAL_TYPE_STRING);
        ++e->metrics.len1StringsSaved[0];
        return cwal_len1_ascii_string((signed char)*str);
    }
#endif
    else{
        cwal_value * c = 0;
        cwal_string * s = 0;
        assert(len);
        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");
            assert(c->scope->level <= e->current->level);
        }
        else{ /* Create new string... */
            c = cwal_value_new(e, e->current,
                               CWAL_TYPE_STRING,
                               len);
            if( c ){
                char * dest = NULL;
                s = CWAL_STR(c);
                assert( s );
                *s = cwal_string_empty;
                s->length = ((cwal_size_t)CWAL_STRLEN_MASK) & len;
                assert(CWAL_STRLEN(s) == len);
                dest = cwal_string_str_rw(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!");
                {
                    unsigned char isAscii = 0;
                    unsigned char const * usrc = (unsigned char const *)str;
                    unsigned char * udest = (unsigned char *)dest;
                    unsigned char const * end = udest + len;
                    for( ; udest < end; ++udest, ++usrc ){
                        isAscii |= (*udest = *usrc);
                    }
                    *udest = 0;
                    if(!(isAscii & 0x80)){
                        s->length |= CWAL_STR_ASCII_MASK;
                    }    
                }
                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(!e || (len & ~((cwal_size_t)CWAL_STRLEN_MASK) /* too big */)){
        return NULL;
    }else if( !len ){
        METRICS_REQ_INCR(e,CWAL_TYPE_XSTRING);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }
#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS
    else if( 1==len
             && ((unsigned char)*str)<=127 ){
        METRICS_REQ_INCR(e,CWAL_TYPE_XSTRING);
        ++e->metrics.len1StringsSaved[1];
        return cwal_len1_ascii_string((signed char)*str);
    }
#endif
    else{
        cwal_value * c = NULL;
        cwal_string * s = NULL;
        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;
            cwal_string_check_for_ascii( s );
        }
        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 || (len & ~((cwal_size_t)CWAL_STRLEN_MASK) /* too big */)){
        return NULL;
    }else if(!str){
        METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }
    else if(!len){
        /* Special case: free source memory immediately. */
        METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING);
        cwal_free(e, str);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }
#if CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS
    else if( 1==len
             && ((unsigned char)*str)<=127 ){
        /* Special case: free source memory immediately. */
        cwal_string * rc = cwal_len1_ascii_string((signed char)*str);
        assert(rc && 1==CWAL_STRLEN(rc));
        METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING);
        cwal_free(e, str);
        ++e->metrics.len1StringsSaved[2];
        return rc;
    }
#endif
    else{
        cwal_value * c = NULL;
        cwal_string * s = NULL;
        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;
            cwal_string_check_for_ascii( s );
        }else{
            /* See the API docs for why we do this. */
            cwal_free2( e, str, len/*+1 would be safe, until it wasn't.*/ );
        }
        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*/ );
      }
      /* reminder to self: e->metrics.bytes[CWAL_TYPE_BUFFER] might be
         0 here because buf->mem might have come from a recycler. That
         means we're not byte-counting buffer resize(), which is a bit
         disturbing. We could measure it if over-allocation is on, but
         we don't know which pool (if any) to modify the count in. e.g.
         buf->mem might have come from the recycler after having been
         allocated as a cwal_list::list. So... hmmm.
      */
      /* assert(e->metrics.bytes[CWAL_TYPE_BUFFER]) >= buf->capacity; */
      /* e->metrics.bytes[CWAL_TYPE_BUFFER] -= buf->capacity; */
      buf->capacity = n + 1 /*NUL*/;
      /* e->metrics.bytes[CWAL_TYPE_BUFFER] += buf->capacity; */
      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_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, (char const*)(e->buffer.mem+oldUsed), slen);
            ;
    }
}

cwal_string * cwal_new_stringf(cwal_engine * e, char const * fmt, ...){
    if(!e || !fmt) return 0;
    else if(!*fmt) return cwal_new_string(e,NULL,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*/;
        if( CWAL_STRLEN_TOO_LONG(newLen) ) return NULL;
        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;
          }
          case CWAL_TYPE_UNIQUE:
              b = 1;
              break;
          case CWAL_TYPE_TUPLE:
              b = CWAL_TUPLE(val)->n ? 1 : 0;
              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_UNIQUE:
          case CWAL_TYPE_TUPLE:
          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_UNIQUE:
          case CWAL_TYPE_TUPLE:
          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:
              memcpy(&d, CWAL_DBL_NONULL(val), sizeof(cwal_double_t));
              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(CWAL_MEM_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_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST;
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    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 const rc = cwal_array_reserve( ar, newSize );
        if(!rc){
            ar->list.count = newSize;
        }
        return rc;
    }
}


/**
   Internal helper for recycling array list memory. li must be a new,
   clean list with no memory (that might get assert()ed).  If the
   recyling list has an entry then that entry's memory is transfered
   into li. If no entry is capable of holding it, li is left
   unmolested. There are no error conditions except for precondition
   violations (assertions). If minCount is not 0 then only a recycled
   chunk with enough space for at least that many entries will serve
   the request.
*/
static void cwal_list_from_recycler( cwal_engine * e, cwal_list * li,
                                     cwal_size_t minCount );

/**
   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;
    }
#if 0
    else if(!ar->list.list){
        cwal_list_from_recycler(e, &ar->list, size);
        if(ar->list.list){
            assert(ar->list.alloced>=size);
            assert(NULL == ar->list.list[0]);
            assert(size ? (NULL == ar->list.list[size-1]) : 1);
            return 0;
        }
    }
#endif
    else{
        CWAL_UNUSED_VAR cwal_size_t const oldLen = ar->list.alloced;
        cwal_size_t rrc;
        rrc = cwal_list_reserve( e, &ar->list, size );
        if(rrc < size) return CWAL_RC_OOM;
        else{
            assert(rrc > oldLen);
            return 0;
        }
    }
}


/** @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_RCFLAG_HAS(v,CWAL_RCF_IS_RESCOPING));
    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->rescoper){
        rc = n->rescoper( v->scope, v );
    }
    return rc;
}

int cwal_rescope_children_function( cwal_value * v ){
    int rc;
    cwal_function * f = cwal_value_get_function(v);
    assert(v->scope);
    assert(f);
    assert(CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_RESCOPING));
    rc = cwal_rescope_children_obase(v);
    if(!rc && f->rescoper){
        rc = f->rescoper( v->scope, v );
    }
    return rc;
}

int cwal_rescope_children_unique( cwal_value * v ){
    cwal_value * ch = *CWAL_UNIQUE_VALPP(v);
    int rc = 0;
    assert(v->scope);
    if(ch){
        *CWAL_UNIQUE_VALPP(v) = 0
            /* a poor man's recursion-prevention scheme. */;
        rc = cwal_value_rescope(v->scope, ch);

        *CWAL_UNIQUE_VALPP(v) = ch;
    }
    return rc;
}

int cwal_rescope_children_tuple( cwal_value * v ){
    cwal_tuple * p = CWAL_TUPLE(v);
    cwal_size_t i;
    cwal_value * ch;
    int rc = 0;
    assert(!CWAL_MEM_IS_BUILTIN(v));
    assert(p->n || !p->list /* this gets called once from cwal_value_new() */);
    assert(v->scope);
    for( i = 0; !rc && i < p->n; ++i ){
        if( (ch = p->list[i]) ){
            rc = cwal_value_xscope(v->scope->e, v->scope, ch, 0);
        }
    }
    return rc;
}

int cwal_rescope_children_array( cwal_value * v ){
    int rc;
    cwal_array * ar = cwal_value_get_array(v);
    assert(ar);
    assert(CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_RESCOPING));
    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)
        ? CWAL_RC_MISUSE
        : ((v && CWAL_MEM_IS_BUILTIN(v))
           ? 0
           : cwal_value_xscope( s->e, s, v, NULL ) );
}

/**
   Transfers child to the given scope if child is in a lower-level (newer)
   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 :/.

   It may set e->fatalCode, in which case it returns that.
*/
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 );
    start:
    if( CWAL_MEM_IS_BUILTIN(child) ) {
        *res = 1;
        return CWAL_RC_OK;
    }
    else if(child->scope == par) {
        *res = 0;
        return CWAL_RC_OK;
    }
    chb = CWAL_VOBASE(child);
    assert(chb ? !CWAL_RCFLAG_HAS(child,CWAL_RCF_IS_DESTRUCTING) : 1);
    if(CWAL_RCFLAG_HAS(child,CWAL_RCF_IS_RESCOPING)){
        *res = 0;
        assert(child->scope->level <= par->level);
        /* MARKER(("Skipping re-rescoping.\n")); */
        /* haven't yet seen this happen! */
        return 0;
    }
    else
#if 0
    /*
      20160206: what was this for, way back when?  This block IS being
      triggered via s2 unit tests, but i'm curious what this is
      supposed to accomplish. Seems to work fine without it, but
      probably only because child->scope->level is always < par->level
      in this case. And yet i'm not certain why!

      Was it for breaking cycles? The children-rescopers don't
      set the CWAL_F_IS_VISITING flag. Maybe they used to?

      i think i see now... we've got a complex call chain which is
      trying to upscope a child, but that child is currently being
      visited. Failing to upscope would be an error, but continuing
      from here would lead to an assertion later (and we'd be unable
      to catch cycles). We need a flag for "is rescoping."
    */
    if( chb && ( CWAL_F_IS_VISITING & chb->flags ) ){
        /* dump_val(child,"is visiting?"); */
        assert(child->scope->level <= par->level)
            /* This assertion is triggered in at least 1 s2 unit test
               script. Troubling.
            */;
        *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
#endif
    {
        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 e->fatalCode = rc;
                }
            }
        }
        assert(!child->scope);
        *res = -1;
        if( cwal_scope_insert( par, child ) ){
            assert(e->fatalCode);
            return e->fatalCode;
        }
        if(child->vtab->rescope_children){
            /* For containers we now, for the sake of cross-scope
               cycles, we need to ensure that any sub-(sub...)children
               are up-scoped.
            */
            if(!chb){
                assert(CWAL_TYPE_UNIQUE==child->vtab->typeID
                       || CWAL_TYPE_TUPLE==child->vtab->typeID);
                /*
                  Doh... we have a potentially problem: we can
                  potentially endlessly cycle on cwal_uniques. They
                  have no flags which will let us stop recursion!
                  Does that matter, since the value being wrapped has
                  them (if a container, else it's moot)? In the
                  non-container case, the 2nd rescope would not
                  recurse because the scope level will have already
                  been adjusted. Whew.
                 */
                /* dump_val(child,"Has rescope_children but no obase!?!?!?"); */
            }
            assert(!CWAL_RCFLAG_HAS(child,CWAL_RCF_IS_RESCOPING));
            CWAL_RCFLAG_ON(child,CWAL_RCF_IS_RESCOPING);
            assert(CWAL_RCFLAG_HAS(child,CWAL_RCF_IS_RESCOPING));
            rc = child->vtab->rescope_children(child);
            assert(CWAL_RCFLAG_HAS(child,CWAL_RCF_IS_RESCOPING));
            CWAL_RCFLAG_OFF(child,CWAL_RCF_IS_RESCOPING);
            assert(!CWAL_RCFLAG_HAS(child,CWAL_RCF_IS_RESCOPING));
            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 e->fatalCode = rc;
            }
        }
        assert(par == child->scope);
        if(chb && chb->prototype && chb->prototype->scope
           && chb->prototype->scope->level > par->level){
            /*
              Added 20141205 when it suddenly occurred to me that we
              do not otherwise make prototypes vacuum-safe (not that i
              could find, anyway). Seems to have never been necessary
              before.
            */
            /* MARKER(("Rescoping my prototype! Why does this never need to happen?\n")); */
            child = chb->prototype;
            res = &RC /* keep original res result in place */;
            goto start;
        }
        return rc;
    }
}

int cwal_value_upscope( cwal_value * v ){
    cwal_engine * e = CWAL_VENGINE(v);
    if(!e || !e->current || !v->scope) return CWAL_MEM_IS_BUILTIN(v) ? 0 : CWAL_RC_MISUSE;
    else if(!e->current->parent){
        assert(1==e->current->level);
        assert(e->current == v->scope);
        return 0;
    }
    else if(e->current->parent == v->scope) return 0;
    else {
        int rc, dir = 0;
        rc = cwal_value_xscope( e, e->current->parent, v, &dir);
        assert(0==rc);
        return rc;
    }    
}


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

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

cwal_value * cwal_propagating_take( cwal_engine * e ){
  cwal_value * rv = e->values.propagating;
  if(rv){
      cwal_value_ref(rv);
      cwal_propagating_set(e, 0);
      cwal_value_unhand(rv);
  }
  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->values.exception)) /* This is ok. */ return CWAL_RC_EXCEPTION;
    else if(!v) {
#if 0
        if(e->values.exception
           && e->values.exception->scope==e->current
           && 1==CWAL_REFCOUNT(e->values.exception)){
            /* 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->values.exception);
        }
#endif
        if(e->values.exception) cwal_value_unref(e->values.exception);
        e->values.exception = 0 /* its scope owns it */;
        return 0;
    }
    else{
        cwal_value_ref(v);
        if(e->values.exception) cwal_value_unref(e->values.exception);
        e->values.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;
        switch(code){
          case CWAL_RC_OOM: rc = code;
              break;
          default: {
              cwal_exception * x;
              x = (fmt && *fmt)
                  ? cwal_new_exceptionfv(e, code, fmt, args)
                  : cwal_new_exception(e, code, NULL);
              if(!x) rc = CWAL_RC_OOM;
              else{
                  cwal_value * xv = cwal_exception_value(x);
                  cwal_value_ref(xv);
                  rc = cwal_exception_set( e, xv );
                  cwal_value_unref(xv);
                  assert(0!=rc);
              }
              break;
          }
        }
        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->values.exception : 0;
}

#if 0
/* keeping around for a look at its heurists later on. */
/**
   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
}
#endif

void cwal_list_from_recycler( cwal_engine * e, cwal_list * list,
                            cwal_size_t minCount ){
#if 0
    /*Enable this section to effectively disable array->list recycling
      for memory cost/savings comparisons. */
    return;
#else
    if(list->list) return;
    else{
        void * mem;
        cwal_size_t reqSize = minCount * sizeof(void*);
        assert(!list->alloced);
        if( (mem = cwal_memchunk_request(e, &reqSize, 1000,
                                         minCount
                                         ? "cwal_list_from_recycler(min)"
                                         : "cwal_list_from_recycler(0)"
                                         ))){
            assert(reqSize>= minCount * sizeof(void*));
            list->list = (void **)mem;
            list->alloced = reqSize/sizeof(void*)
                /* Yes, we might be losing a few bytes here. The alternatives
                   include:

                   (A) Put it back and try for another (aligned) one
                   (or give up).
                   
                   (B) Add a flag to cwal_memchunk_request() which specifies
                   we need it aligned.

                   (C) Make cwal_re/alloc() always align up and rely on that
                   in the recycler? The culprit is really cwal_buffer_reserve(),
                   so that would be the one to patch.

                   (D) Something different?
                */
                ;
            assert(list->alloced >= minCount);
            /*MARKER(("Reused array list memory: %u entries from %u bytes\n",
              (unsigned)list->alloced, (unsigned)reqSize));*/
        }        
    }
#endif
}
    
int cwal_array_set( cwal_array * const ar, cwal_size_t ndx, cwal_value * const v )
{
    SETUP_ARRAY_ARGS;
    if( !ar ) return CWAL_RC_MISUSE;
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    else if( (ndx+1) > CwalConsts.MaxSizeTCounter) /* overflow */return CWAL_RC_RANGE;
    else{
        cwal_size_t len;
        len = ar->list.alloced;
        if(len <= ndx){
            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(old) cwal_value_unref2(e, old);
            ar->list.list[ndx] = v;
            if( ndx >= ar->list.count ){
                ar->list.count = ndx+1;
            }
            return 0;
        }
    }
}

int cwal_array_append( cwal_array * const ar, cwal_value * const v ){
    SETUP_ARRAY_ARGS;
    if( !ar ) return CWAL_RC_MISUSE;
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    else if( (ar->list.count+1) < ar->list.count ) return CWAL_RC_RANGE;
    else{
        if(!ar->list.list) cwal_list_from_recycler(e, &ar->list, 0);
        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 * const ar, cwal_value * const v ){
    SETUP_ARRAY_ARGS;
    if( !ar || !v ) return CWAL_RC_MISUSE;
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    else{
        int rc;
        cwal_value ** vlist;
        if(!ar->list.list) cwal_list_from_recycler(e, &ar->list, 1);
        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(rv) cwal_value_unhand(v);
            else cwal_value_unref(v);
        }
        return 0;
    }
}

int cwal_array_index_of( cwal_array const * ar, cwal_value const * v,
                         cwal_size_t * index, char strictComparison ){
    if(!ar) return CWAL_RC_MISUSE;
    else if(!ar->list.count) return CWAL_RC_NOT_FOUND;
    else{
        cwal_size_t i;
        cwal_value * const arv = CWAL_VALPART(ar);
        int opaque;
        cwal_visit_list_begin(arv, &opaque);
        for( i = 0; i < ar->list.count; ++i ){
            cwal_value const * rhs = (cwal_value const *)ar->list.list[i];
            if(v==rhs) break /* also match on NULL */;
            else if((v && !rhs) || (!v && rhs)) continue;
            else if(strictComparison){
                if(v->vtab->typeID == rhs->vtab->typeID
                   && 0 == cwal_value_compare(v, rhs)) break;
            }else if(0 == cwal_value_compare(v, rhs)) break;
        }
        cwal_visit_list_end(arv, opaque);
        if(i < ar->list.count){
            if(index) *index = i;
            return 0;
        }else{
            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 if(CWAL_OB_IS_LOCKED(ar) || (*dest && CWAL_OB_IS_LOCKED(*dest))) return CWAL_RC_LOCKED;
    else{
        int rc = 0;
        cwal_size_t i;
        cwal_array * tgt = *dest ? *dest : cwal_new_array(e);
        cwal_value ** vlist;
        cwal_value * const arv = CWAL_VALPART(ar);
        int opaque;
        cwal_visit_list_begin(arv, &opaque);
        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 );
            }
        }
        cwal_visit_list_end(arv, opaque);
        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;
}

/**
   The C-string equivalent of cwal_obase_search_v().
*/
static cwal_value * cwal_obase_search( cwal_obase const * base,
                                       char searchProto,
                                       char const * const key,
                                       cwal_size_t keyLen,
                                       cwal_kvp ** prev){
    if(!base || !key) return NULL;
    else {
        cwal_kvp * kvp;
        cwal_value * rc = NULL;
        while(base){
            if(CWAL_F_LOCKED & base->flags) break;
            kvp = cwal_kvp_search( base->kvp, key, keyLen, prev );
            if(kvp) {
                rc = kvp->value;
                break;
            }
            else base = searchProto ? CWAL_VOBASE(base->prototype) : 0;
        }
        return rc;
    }
}

#if 0
static int cwal_prop_getter_check( cwal_obase const * originalThis,
                                      /* cwal_obase const * foundIn, */
                                      cwal_value const * key,
                                      cwal_value * value,
                                      cwal_value ** rv ){
    cwal_function * f = cwal_value_get_function(value);
    if(!f || !(CWAL_CONTAINER_PROP_GETTER & f->base.flags)){
        *rv = value;
        return 0;
    }else{
        cwal_value * origin = CWAL_VALPART(originalThis);
        cwal_value * argv[1] = { 0 };
        int rc;
        argv[0] = (cwal_value*)key /* casting away the constness here is bad.
                                      Fixing it requires non-const search keys.
                                      Not a big deal in this context.*/;
        rc = cwal_function_call(f, origin, rv, 1, argv);
        /* How to propagate errors? We need a getter API which returns
           an integer :/. */
        if(rc){
            *rv = 0;
        }
        return rc;
    }
}
#endif                                      

/**
   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 a locked object (base or one of its
   prototypes) while searching, NULL is returned. It does not simply
   skip over the locked member because the search results could then
   arguably be inconsistent, depending on whether the locked member
   actually overrides the property from a prototype further up the
   chain.
*/
static cwal_value * cwal_obase_search_v( cwal_obase const * base,
                                         char searchProto,
                                         cwal_value const * key,
                                         cwal_kvp ** prev){
    if(!base || !key) return NULL;
    else {
        cwal_kvp * kvp;
        cwal_value * rc = NULL;
        while(base){
            if(CWAL_F_LOCKED & base->flags) break;
            kvp = cwal_kvp_search_v( base->kvp, key, prev );
            if(kvp) {
                rc = kvp->value;
                /*
                  Here is where we would check if kvp->value is
                  a function with the CWAL_CONTAINER_PROP_GETTER
                  container flag. If so, we would return the call
                  to that function, passing it (key).
                */
                break;
            }
            else {
                base = searchProto ? CWAL_VOBASE(base->prototype) : 0;
            }
        }
        return rc;
    }
}


char cwal_prop_has( cwal_value const * v,
                    char const * key, cwal_size_t keyLen,
                    char searchPrototype ) {
    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 )
                : (void const *)cwal_kvp_search(base->kvp, key, keyLen, NULL)
            ) ? 1 : 0;
    }
}

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


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


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;
        while(b){
            rc = cwal_kvp_search(b->kvp, key, keyLen, NULL);
            if(rc){
                if(foundIn) *foundIn = c;
                break;
            }else if(searchProtos
                     && (c = b->prototype)
                     && (b = CWAL_VOBASE(c))){
                continue;
            }
            b = 0;
        }while(0);
        return rc;
    }
}

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

static cwal_function * cwal_value_is_interceptor( cwal_value const * v ){
    return (v
            && (cwal_container_flags_get(v) & CWAL_CONTAINER_INTERCEPTOR))
        ? cwal_value_get_function(v)
        : NULL;
}

/**
   Proxy for handling get/set interceptor calls.

   If cwal_value_is_interceptor(maybeFunc) then it is treated like an
   interceptor, calling the function on self and passing it the
   setterArg (if not NULL) or no arguments (assumed to be the getter
   call form). If rv and setterArg are not NULL, the result goes in
   *rv: the result of setters is ignored by the framework (the setter
   APIs simply have no way to communicate overridden results all the
   way back up the stack).

   propFoundIn must be the property in which maybeFunc was found,
   presumably self or a prototype of self.
*/
static int cwal_prop_check_intercept( cwal_value * propFoundIn,
                                      cwal_value * self,
                                      cwal_kvp * kvp,
                                      cwal_value * setterArg,
                                      cwal_value ** rv ){
    int rc = 0;
    cwal_value * maybeFunc = kvp->value;
    cwal_function* f = cwal_value_is_interceptor(maybeFunc);
    assert( kvp );
    assert( propFoundIn );
    assert( self );
    assert( maybeFunc );
    if(f){
        cwal_value * xrv = 0;
        /**
           Getter/setter semantics:

           GET:  value f.call(self, key)

           SET:  void f.call(self, key, setterArg)

           We can't realistically propagate result values of setter
           calls, because doing so would inherently replace the
           interceptor with its call() result.

           It is up to e->cbHook.pre() to convey any needed local
           script-side variables, e.g. "this" and the arguments array.
        */
        rc = cwal_function_call2( f,
                                  propFoundIn,
                                  self,
                                  (setterArg || !rv) ? NULL : &xrv,
                                  setterArg ? 1 : 0,
                                  setterArg ? &setterArg : NULL );
        if(!rc && rv && !setterArg){
            *rv = xrv ? xrv : cwal_value_undefined();
        }
    }else if(setterArg && (CWAL_VAR_F_CONST & kvp->flags)){
        rc = CWAL_RC_CONST_VIOLATION;
    }else if(rv){
        *rv = maybeFunc;
    }
    return rc;
}

int cwal_prop_getX( cwal_value * c,
                    char processInterceptors,
                    char const * key,
                    cwal_size_t keyLen,
                    cwal_value ** rv ){
    cwal_obase const * base = CWAL_VOBASE(c);
    if(!base || !key) return CWAL_RC_MISUSE;
    else{
        cwal_value * foundIn = 0;
        cwal_kvp * kvp = cwal_prop_get_kvp( c, key, keyLen, 1, &foundIn );
        int rc;
        if(!kvp){
            rc = CWAL_RC_NOT_FOUND;
        }else if(processInterceptors){
            assert(foundIn);
            rc = cwal_prop_check_intercept(foundIn, c, kvp, 0, rv);
        }else{
            *rv = kvp->value;
            rc = 0;
        }
        return rc;
    }
}

int cwal_prop_getX_v( cwal_value * c,
                      char processInterceptors,
                      cwal_value const * key,
                      cwal_value ** rv ){
    cwal_obase const * base = CWAL_VOBASE(c);
    if(!base || !key) return CWAL_RC_MISUSE;
    else{
        cwal_value * foundIn = 0;
        cwal_kvp * kvp = cwal_prop_get_kvp_v( c, key, 1, &foundIn );
        int rc;
        if(!kvp){
            rc = CWAL_RC_NOT_FOUND;
        }else if(processInterceptors){
            assert(foundIn);
            rc = cwal_prop_check_intercept(foundIn, c, kvp, 0, rv);
        }else{
            *rv = kvp->value;
            rc = 0;
        }
        return rc;
    }
}


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

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;
    if(!e) return CWAL_RC_MISUSE;
    else if(CWAL_V_IS_VISITING(c)) return CWAL_RC_IS_VISITING;
    return cwal_kvp_unset_v( e, &b->kvp, key );
}


/**
   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. key->vtab->compare() is used to test for equivalence.

   On success listOwner->kvp may be modified.

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

   Currently returns CWAL_RC_NOT_FOUND if the key is not found, but
   that's highly arguable to do from this level. We do it here because
   it would otherwise require a search-then-set (second lookup) in
   some oft-called client code.

   Returns CWAL_RC_CONST_VIOLATION if key refers to an existing value
   which is flagged as CWAL_VAR_F_CONST _unless_ v==theExistingValue
   (in which case it is a silent no-op which reports success). The
   (v==existing) workaround is admittedly to support operator
   overloading in th1ish (and subsequently s2), where (x+=1) is
   logically assigning to x (which may be const) but (because of the
   overload) is actually doing something quite different. TODO: see if
   we can remove this workaround - i don't think s2 needs it, but
   th1ish might rely on it.

   Returns CWAL_RC_DISALLOW_PROP_SET if listOwner has the
   CWAL_CONTAINER_DISALLOW_PROP_SET flag.

   Returns CWAL_RC_DISALLOW_NEW_PROPERTIES if listOwner is flagged
   with the CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES flag _AND_
   key refers to a new property.

   If flags!=CWAL_VAR_F_PRESERVE then the flags of the kvp are set to
   the given value (but the CONST check trumps this).

*/
static int cwal_kvp_set_v( cwal_engine * e, cwal_value * listOwner,
                           cwal_value * key, cwal_value * v, uint32_t flags ){
    if( !e || !key || !listOwner || !v ) return CWAL_RC_MISUSE;
    else {
        int rc;
        cwal_kvp * kvp = 0;
        cwal_kvp * left = 0;
        char i = 0;
        char iAlloccedKvp = 0;
        cwal_obase * ob = CWAL_VOBASE(listOwner);
        cwal_kvp ** list = ob ? &ob->kvp : 0;
        assert(ob);
        assert(ob->kvp == *list
               && "just checking - we can factor out the list param.");
        if(CWAL_CONTAINER_DISALLOW_PROP_SET & ob->flags){
            return CWAL_RC_DISALLOW_PROP_SET;
        }
        kvp =
            cwal_kvp_search_v( *list, key, &left );
        if( !kvp ){
            if(CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES & ob->flags){
                return CWAL_RC_DISALLOW_NEW_PROPERTIES;
            }
            else if(!v){
                return CWAL_RC_NOT_FOUND;
            }                 
            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;
        }
        /*
          Here is where we would check if kvp->value is
          a function with the CWAL_CONTAINER_PROP_SETTER
          container flag. If so, we would return the call
          to that function, passing it (key, value).
        */
        else if(CWAL_VAR_F_CONST & kvp->flags){
            return CWAL_RC_CONST_VIOLATION;
        }
        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 );
                }
                assert(!"This cannot fail since the transition from arrays to linked lists.");
                return rc;
            }
            /* The ref/unref order is important in case key==kvp->key or...
               in case v==kvp->value, key, or listOwner.
            */
            cwal_value_ref2( e, vv );
            if(0==i) { /* KEY part... */
                if(kvp->key){
                    cwal_value_unref2( e, kvp->key );
                }
                kvp->key = vv;
            }else{ /* VALUE part... */
                if(kvp->value){
                    cwal_value_unref2( e, kvp->value );
                }
                kvp->value = vv;
                break;
            }
        }
        assert(1 == i);
        assert(kvp->key != 0);
        assert(kvp->value != 0);
        if(!iAlloccedKvp){
            /* kvp is already in *list */
            assert(left || *list==kvp);
        }
        else if(left){
            /* kvp compares > left, so insert it here. */
            cwal_kvp * r = left->right;
            assert(!kvp->right);
            left->right = kvp;
            kvp->right = r;
        }
        else {
            assert(!left);
            /* Make kvp the head of the list */
            assert(*list != kvp);
            assert(!kvp->right);
            kvp->right = *list;
            *list = kvp;
        }
        return CWAL_RC_OK;
    }
}

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

static int cwal_prop_setX_impl_v( cwal_value * c,
                                  char processInterceptors,
                                  cwal_value * key, cwal_value * v,
                                  uint16_t flags ) {
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = CWAL_VENGINE(c);
    assert(v ? (!!e || CWAL_MEM_IS_BUILTIN(v)) : 1);
    if( !e || !key ) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else if(CWAL_RCFLAG_HAS(c,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING;
    else if(CWAL_RCFLAG_HAS(c, CWAL_RCF_IS_VISITING)) return CWAL_RC_IS_VISITING;
    else if(CWAL_CONTAINER_DISALLOW_PROP_SET & b->flags){
        return CWAL_RC_DISALLOW_PROP_SET;
    }
    else if( NULL == v ) {
        return cwal_prop_unset_v( c, key );
    }
    else if(!processInterceptors){
        return cwal_kvp_set_v( e, c, key, v, flags );
    }
    else {
        /* Check for setter interceptor... */
        /* This is utterly failing in s2 and i'm not yet sure why. */
        cwal_value * foundIn = 0;
        cwal_kvp * kvp = cwal_prop_get_kvp_v(c, key, 1, &foundIn);
        cwal_function * fint = 0;
        if(!kvp){
            /*
              TODO: this requires a duplicate lookup in c :(. The fix
              for that would be to reimplement much of the kvp_set()
              logic here, or move interceptors into that code, which
              proved impractical without further refactoring of that
              interface to support the differentiation of "this" vs
              "propertyHolder".
            */
            return cwal_kvp_set_v( e, c, key, v, flags );
        }else if((fint = cwal_value_is_interceptor(kvp->value))){
            assert(!"untested");
            return cwal_prop_check_intercept( foundIn, c, kvp, v, 0 );
            /* Never reset kvp->flags for these */
        }else if(CWAL_VAR_F_CONST & kvp->flags){
            return CWAL_RC_CONST_VIOLATION;
        }else if(foundIn != c){
            assert(!"why is this never hit?");
            /* Set new property in c. See notes above about duplicate
               lookup. */
            return cwal_kvp_set_v( e, c, key, v, flags );
        }else{
            assert(!"why is this never hit?");
            /* If found directly in c, as opposed to a prototype,
               reimplement some of what the lower-level set op does. */
            assert(foundIn);
            assert(foundIn->scope);
            cwal_value_ref(key);
            cwal_value_ref(v);
            cwal_value_unref(kvp->key);
            cwal_value_unref(kvp->value);
            kvp->key = key;
            kvp->value = v;
            cwal_kvp_flags_set(kvp, flags);
            cwal_value_rescope(foundIn->scope, key);
            cwal_value_rescope(foundIn->scope, v);
            return 0;
        }
    }
}

int cwal_prop_setX_with_flags_v( cwal_value * c,
                                 char processInterceptors,
                                 cwal_value * key, cwal_value * v,
                                 uint16_t flags ) {
    return cwal_prop_setX_impl_v( c, processInterceptors, key, v, flags );
}

int cwal_prop_set_with_flags_v( cwal_value * c, cwal_value * key, cwal_value * v,
                                uint16_t flags ) {
    return cwal_prop_setX_impl_v( c, 0, key, v, flags );
}


int cwal_prop_set_v( cwal_value * c, cwal_value * key, cwal_value * v ) {
    return cwal_prop_set_with_flags_v( c, key, v, CWAL_VAR_F_PRESERVE );
}

static int cwal_prop_setX_impl( cwal_value * c,
                                char processInterceptors,
                                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 || !key ) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else if(CWAL_V_IS_VISITING(c)) return CWAL_RC_IS_VISITING;
    else if(CWAL_CONTAINER_DISALLOW_PROP_SET & b->flags){
        return CWAL_RC_DISALLOW_PROP_SET;
    }else if(CWAL_RCFLAG_HAS(c,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING;
    else if( NULL == v ) return cwal_prop_unset( c, key, keyLen );
    else {
        cwal_value * vkey;
        vkey = cwal_new_string_value(e, key, keyLen);
        if(!vkey) return CWAL_RC_OOM;
        else {
            int rc;
            cwal_value_ref(vkey);
            rc = cwal_prop_setX_impl_v( c, processInterceptors, vkey, v, flags);
            cwal_value_unref2(e, vkey);
            return rc;
        }
    }
}

int cwal_prop_setX_with_flags( cwal_value * c,
                               char processInterceptors,
                               char const * key, cwal_size_t keyLen, cwal_value * v,
                               uint16_t flags ) {
    return cwal_prop_setX_impl( c, processInterceptors, key,
                                keyLen, v, flags );
}

int cwal_prop_set_with_flags( cwal_value * c,
                              char const * key, cwal_size_t keyLen, cwal_value * v,
                              uint16_t flags ) {
    return cwal_prop_setX_impl( c, 0, key, keyLen, v, flags );
}


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 if(CWAL_V_IS_VISITING(c)) 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,
                                          cwal_strlen(key), &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, kvp, 1 );
        if(!CWAL_MEM_IS_BUILTIN(rc)){
            assert( (CWAL_REFCOUNT(rc) > 0) && "Should still have kvp's reference!" );
            cwal_value_unhand(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 if(CWAL_V_IS_VISITING(c)) return NULL;
    else {
        cwal_kvp * left = 0;
        cwal_value * rc = 0;
        cwal_kvp * kvp = cwal_kvp_search_v( b->kvp, key, &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;
        assert( rc );
        if(takeKeyAsWell){
            *takeKeyAsWell = kvp->key;
        }else{
            cwal_value_unref2(e, kvp->key);
        }
        kvp->key = 0;
        kvp->value = 0;
        cwal_kvp_free( e, kvp, 1 );
        if(takeKeyAsWell && !CWAL_MEM_IS_BUILTIN(*takeKeyAsWell)){
            assert( (CWAL_REFCOUNT(*takeKeyAsWell) > 0) && "Should still have our reference!" );
#if 1
            cwal_value_unhand(*takeKeyAsWell);
#else
            if(0==CWAL_RCDECR(*takeKeyAsWell)){
                cwal_value_reprobate(1 ? (*takeKeyAsWell)->scope
                                     : (*takeKeyAsWell)->scope->e->current /*?*/, vk);
            }
#endif
        }
        if(!CWAL_MEM_IS_BUILTIN(rc)){
            assert( (CWAL_REFCOUNT(rc) > 0) && "Should still have our reference!" );
            cwal_value_unhand(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;
    int rc;
    switch( mode ){
      case 0:
          v = kvp->key;
          break;
      case 1:
          v = kvp->value;
          assert(0 != v && "i am pretty sure we don't currently allow this.");
          if(!v) return 0;
          break;
      default:
          assert(!"Invalid visit mode.");
          return CWAL_RC_CANNOT_HAPPEN;
    }
    assert(0 != v);
    cwal_value_ref(v);
    rc = func( v, fState );
    cwal_value_unhand(v);
    return rc;
}

char cwal_props_can( cwal_value const * c ){
    return 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;
    else if(CWAL_CONTAINER_DISALLOW_PROP_SET
            &b->flags){
        return CWAL_RC_DISALLOW_PROP_SET;
    }
    else {
        /**
           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.
        */
        cwal_cleanup_obase( e, b, 0 );
        return CWAL_RC_OK;
    }
}


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

int cwal_props_copy( cwal_value * src, cwal_value * dest ){
    cwal_obase const * const bSrc = CWAL_VOBASE(src);
    cwal_obase const * const bDest = CWAL_VOBASE(dest);
    if(!src || !dest) return CWAL_RC_MISUSE;
    else if(!bSrc || !bDest) return CWAL_RC_TYPE;
    else if(CWAL_V_IS_VISITING(dest)) return CWAL_RC_IS_VISITING;
    /*else if(CWAL_OB_IS_LOCKED(bSrc) || CWAL_OB_IS_LOCKED(bDest)) return CWAL_RC_LOCKED;*/
    else return cwal_props_visit_kvp( src, cwal_kvp_visitor_props_copy, dest );
}

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 {
        int rc = 0;
        cwal_kvp * kvp = b->kvp;
        int opaque;
        cwal_value_ref(c);
        cwal_visit_props_begin(c, &opaque);
        for( ; kvp && (0==rc); kvp = kvp->right ){
            rc = (CWAL_VAR_F_HIDDEN & kvp->flags)
                ? 0
                : cwal_obase_visit_impl( kvp, 1, f, state );
        }
        cwal_visit_props_end(c, opaque);
        cwal_value_unhand(c);
        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;
    else{
        int rc = 0;
        cwal_kvp * kvp = b->kvp;
        int opaque;
        cwal_visit_props_begin(c, &opaque);
        for( ; kvp && (0==rc); kvp = kvp->right ){
            rc = (CWAL_VAR_F_HIDDEN & kvp->flags)
                ? 0
                : cwal_obase_visit_impl( kvp, 0, f, state );
        }
        cwal_visit_props_end(c, opaque);
        return rc;
    }
}

char cwal_value_may_iterate( cwal_value const * const c ){
    if(!c) return 0;
    else if(CWAL_MEM_IS_BUILTIN(c)){
        return 0;
    }
    else{
        cwal_obase const * const ob = CWAL_VOBASE(c);
#if 0
        /* No... this will disable iteration even when array indexes
           (but not properties) are locked... */
        return ob ? ((CWAL_F_LOCKED & ob->flags) ? 0 : 1) : 0;
#else
        return ob ? 1 : 0;
#endif
    }
}

char cwal_value_is_iterating_list( cwal_value const * const c ){
    return c ? CWAL_V_IS_VISITING_LIST(c) : 0;
}
char cwal_value_is_iterating_props( cwal_value const * const c ){
    return c ? CWAL_V_IS_VISITING(c) : 0;
}

char cwal_value_may_iterate_list( cwal_value const * const c ){
    if(!c) return 0;
    switch(c->vtab->typeID){
      case CWAL_TYPE_TUPLE:
          return 1 /* nothing can currently lock a tuple */;
      case CWAL_TYPE_ARRAY:
      case CWAL_TYPE_HASH:
          return (CWAL_F_LOCKED & CWAL_VOBASE(c)->flags) ? 0 : 1;
      default:
          return 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 {
        cwal_kvp * kvp;
        int rc = CWAL_RC_OK;
        int opaque;
        cwal_visit_props_begin(c, &opaque);
        assert( CWAL_RCFLAG_HAS(c, CWAL_RCF_IS_VISITING) );
        cwal_value_ref(c);
        for( kvp = b->kvp;
             kvp && (0==rc);
             kvp = kvp->right ){
            if(!(CWAL_VAR_F_HIDDEN & kvp->flags)){
                /* to support modification during traversal, we'll need
                   a couple new refs... */
                cwal_value * const lhs = kvp->key;
                cwal_value * const rhs = kvp->value;
                cwal_value_ref(lhs);
                cwal_value_ref(rhs);
                rc = f( kvp, state );
                cwal_value_unref(lhs);
                cwal_value_unref(rhs);
            }
        }
        cwal_visit_props_end(c, opaque);
        cwal_value_unhand(c);
        return rc;
    }
}

int cwal_array_visit( cwal_array * const o, cwal_value_visitor_f const f, void * const state ){
    if(!o || !f) return CWAL_RC_MISUSE;
    else if(CWAL_OB_IS_LOCKED(o)) return CWAL_RC_LOCKED;
    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;
        int opaque;
        cwal_visit_list_begin(vSelf, &opaque);
        cwal_value_ref(vSelf);
        for( i = 0;
             (CWAL_RC_OK==rc)
             && (i < li->count);
             ++i ){
            v = (cwal_value*)li->list[i];
            /* if(v){ */
                /* to support modification during traversal, we need
                   another ref... */
            if(v) cwal_value_ref(v);
            rc = f( v, state );
            if(v) cwal_value_unref(v);
            /* } */
        }
        cwal_value_unhand(vSelf);
        cwal_visit_list_end(vSelf, opaque);
        return rc;
    }
}

int cwal_array_visit2( cwal_array * const o, cwal_array_visitor_f const f, void * const state ){
    if(!o || !f) return CWAL_RC_MISUSE;
    else if(CWAL_OB_IS_LOCKED(o)) return CWAL_RC_LOCKED;
    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;
        int opaque;
        cwal_visit_list_begin(vSelf, &opaque);
        cwal_value_ref(vSelf);
        for( i = 0;
             (CWAL_RC_OK==rc)
             && (i < li->count);
             ++i ){
            v = (cwal_value*)li->list[i];
            /* if(v){ */
                /* to support modification during traversal, we need
                   another ref... */
            if(v) cwal_value_ref(v);
            rc = f( o, v, i, state );
            if(v) cwal_value_unref(v);
            /* } */
        }
        cwal_value_unhand(vSelf);
        cwal_visit_list_end(vSelf, opaque);
        return rc;
    }
}

/**
   qsort implementation adapted from:

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

   Based on the "iterative quicksort" C implementation.
   
   li is the cwal_list to sort. cmp is the comparison function to use
   and the state parameter is passed as-is to cmp().

   Returns 0 on success or non-0 if any call to cmp() returns, via
   cmp()'s final parameter, a non-0 code (in which case that code is
   returned).
*/
static int cwal_value_list_sort_impl( cwal_engine * const e,
                                      cwal_list * const li,
                                      cwal_value_stateful_compare_f const cmp,
                                      void * const state ){
    enum { MAX = 64 /* stack size for max 2^(64/2) array elements  */};
    cwal_size_t left = 0, right, pos = 0,
        seed = /*(cwal_size_t)rand()*/
        li->count/2
        /* reminder to self (20180620): using a random pivot requires
           that we seed the RNG (call srand()), and it seems a bit
           rude for the cwal core to do so, as the best we can do for
           a seed is to call time(). Checkin f40e1afaede9a6fd (which
           removed an srand() call in s2) uncovered an error
           propagation problem when we use an unseeded random
           pivot. Funnily enough, the checkin comment said:

           <<<Added (integer prototype).srand() and rand() no longer
           automatically calls srand() the first time rand() is
           called. It seems that rand() was never documented, so this
           should not break anything.>>>

           Haha. Running the s2 unit tests before checkin would have
           revealed this problem then, rather than a month later
           :|. */,
        stack[MAX];
    cwal_size_t len = li->count;
    int errc = 0, cmprc;
    void * pivot = 0;
    void * tmp;
    assert(len > 1 && "this is never called for an empty- or length-1 array");
#define CV (cwal_value*)
    for( ;; ){
      for (; left+1 < len; ++len) {              /* sort left to len-1 */
          if (pos == MAX) len = stack[pos = 0];  /* stack overflow, reset */
          pivot = li->list[left+seed%(len-left)];/* pick [possibly random] pivot */
          seed = seed*69069+1;                   /* next pseudorandom number */
          stack[pos++] = len;                    /* sort right part later */
          for(right = left-1; ; ) {              /* inner loop: partitioning */
              while(1){
                  /* look for greater element */
                  cmprc = cmp( CV li->list[++right], CV pivot, state, &errc );
                  if(errc) return errc;
                  else if(cmprc>=0) break;
              }
              while(1){
                  /* look for smaller element */
                  cmprc = cmp( CV pivot, CV li->list[--len], state, &errc );
                  if(errc) return errc;
                  else if(cmprc>=0) break;
              }
              if(right >= len) break;           /* partition point found? */
              tmp = li->list[right];
              li->list[right] = li->list[len]; /* the only swap */
              li->list[len] = tmp;
          } /* partitioned, continue left part */
      }
      if (pos == 0) break;     /* stack empty? */
      left = len;              /* left to right is sorted */
      len = stack[--pos];      /* get next range to sort */
    }
#undef CV
    assert(!errc);
    if(e){/*avoid unused param warning*/}
    return 0;
}

int cwal_array_sort_stateful( cwal_array * const ar,
                              cwal_value_stateful_compare_f cmp,
                              void * state ){
    cwal_value * const arv = CWAL_VALPART(ar);
    if(!ar || !cmp || !arv) return CWAL_RC_MISUSE;
    else if(CWAL_V_IS_VISITING_LIST(arv)) return CWAL_RC_IS_VISITING_LIST;
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    else if(ar->list.count<2) return 0;
    else {
        cwal_engine * e;
        int rc = 0;
        int opaque;
        cwal_visit_list_begin(arv, &opaque);
        assert(arv->scope);
        e = arv->scope->e;
        CWAL_OB_LOCK(ar);
        assert(CWAL_OB_IS_LOCKED(ar));
        rc = cwal_value_list_sort_impl( e, &ar->list, cmp, state );
        CWAL_OB_UNLOCK(ar);
        assert(!CWAL_OB_IS_LOCKED(ar));
        cwal_visit_list_end(arv, opaque);
        return rc ? rc : (e->values.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,
                                    int * errCode ){
    cwal_value * argv[2];
    ArrayFuncState * st = (ArrayFuncState *)state;
    cwal_value * rv = 0;
    int rc = 0;
    argv[0] = lhs ? lhs : cwal_value_undefined();
    argv[1] = rhs ? rhs : cwal_value_undefined();
    *errCode = cwal_function_call(st->f, st->self, &rv, 2, argv);
    cwal_value_ref(rv);
    if(!*errCode && rv){
        /* We have to resolve this using doubles, not integers. See
           https://github.com/ccxvii/mujs/issues/122 for why. */
        cwal_double_t const d = cwal_value_get_double(rv);
        rc = (0.0==d) ? 0 : ((d<0) ? -1 : 1);
        /* An alternate, more clever, formulation (via the above
           ticket link), but i find it less readable:
           rc = (d > 0) - (d < 0) */
        /* There's a corner-case bug here, though: if rv is an
           integer value larger than cwal_double_t can represent, the
           results will very possibly be wrong. */           
    }
    cwal_value_unref(rv);
    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_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST;
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    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_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST;
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    else if(ar->list.count<2) return 0;
    else {
        cwal_value * arv = CWAL_VALPART(ar);
        int opaque;
        cwal_visit_list_begin(arv, &opaque);
        /* We don't know what cmp may do, thus we need to flag the visiting-related bits. */
        CWAL_OB_LOCK(ar);
        qsort( ar->list.list, ar->list.count, sizeof(void*), cmp );
        CWAL_OB_UNLOCK(ar);
        cwal_visit_list_end(arv, opaque);
        return 0;
    }
}

int cwal_array_reverse( cwal_array * ar ){
    if(!ar) return CWAL_RC_MISUSE;
#if 0
    /* reversal is just a special case of get/set, so there seems to
       be little harm in disallowing it during list
       traversal. (Sorting is kinda also just a special case of
       get/set, but it's a lot more involved.) */
    else if(CWAL_V_IS_VISITING_LIST(CWAL_VALPART(ar))) return CWAL_RC_IS_VISITING_LIST;
#endif
    else if(CWAL_OB_IS_LOCKED(ar)) return CWAL_RC_LOCKED;
    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;
}

int cwal_int_to_cstr( cwal_int_t iVal, char * dest, cwal_size_t * nDest ){
  enum { BufLen = CWAL_SIZE_T_BITS * 2 };
  /* 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   
}

#if 0 /* cwal_double_to_cstr() ... */
/* This conversion is easy to implement but fails some comparison
   tests in cwal's test.c because 42.242 converts to 42.2419999... */
int cwal_double_to_cstr( cwal_double_t fVal, char * dest, cwal_size_t * nDest ){
  enum { BufLen = 256 };
  char zBuf[BufLen];   /* Output buffer */
  int n;
  n = sprintf(zBuf, "%.17f", fVal);
  if(n<1) return CWAL_RC_ERROR;
  if(1==n){/*kludge!*/
      zBuf[1] = '.';
      zBuf[2] = '0';
      n += 2;
  }
  zBuf[n] = 0;
  MARKER(("double zBuf=%s\n", zBuf));
  if(*nDest <= (cwal_size_t)n) return CWAL_RC_RANGE;
  *nDest = (cwal_size_t)n;
  memcpy(dest, zBuf, (size_t)n);
  return 0;
}
#else
/**
   Internal helper to proxy double-to-string conversions through
   cwal_printf() (because it has far better precision handling
   than i know how to implement properly).
*/
typedef struct FixedBufferAppender {
    char const * begin;
    char const * end;
    char * pos;
    char const * dotPos;
    int rc;
} FixedBufferAppender;
/** Internal cwal_printfv_appender() impl to append data to
    a FixedBufferAppender.
*/
static long cwal_printfv_appender_double( void * arg, char const * data, long n )
{
    long i;
    FixedBufferAppender * ba = (FixedBufferAppender*)arg;
    for( i = 0; i < n; ++i ){
        if(ba->pos==ba->end){
            ba->rc = CWAL_RC_RANGE;
            return -1;
        }
        if('.' == (*ba->pos = data[i])) ba->dotPos = ba->pos;
        else if('e' == data[i] || 'E' == data[i]){
            assert(!"We're expecting no exponent notation!");
            ba->rc = CWAL_RC_CANNOT_HAPPEN;
            return -1;
        }
        ++ba->pos;
        assert(ba->pos<ba->end);
    }
    return i;
}

int cwal_double_to_cstr( cwal_double_t fVal, char * dest, cwal_size_t * nDest ){
  enum { BufLen = 120 };
  char zBuf[BufLen] = {0,0,0,0,0,0};
  FixedBufferAppender dba;
  cwal_size_t dLen;
  dba.begin = dba.pos = zBuf;
  dba.end = zBuf + BufLen;
  dba.dotPos = 0;
  dba.rc = 0;
  cwal_printf( cwal_printfv_appender_double, &dba, "%lf", fVal );
  if(!dba.rc){
      if('N'==*zBuf || 'I'==*zBuf || 'I'==zBuf[1]){
          /* Assume NaN/[+-]Inf. */
      }else{
          assert(dba.dotPos
                 && "We're expecting the output to _always_ contain a decimal point!");
          if(!dba.dotPos) return CWAL_RC_CANNOT_HAPPEN;
      }
      *dba.pos = 0;
      dLen = (cwal_size_t)(dba.pos - dba.begin);
      --dba.pos;
      /* The obligatory kludge: trim zeroes... */
      for( ; dba.dotPos && dba.pos > dba.dotPos+1
               && '0'==*dba.pos
               /*&& '.' != *(dba.pos-1)*/; --dba.pos){
          *dba.pos = 0;
          --dLen;
      }
      if(*nDest<=dLen+1/*NUL*/) return CWAL_RC_RANGE;
      *nDest = dLen;
      memcpy(dest, zBuf, dLen+1/*NUL*/);
  }
  return dba.rc;
}
#endif /* cwal_double_to_cstr() */

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;
    int digitCount = 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;
        }
        ++digitCount;
        v += mult
            ? (mult*(*p-'0'))
            : (*p-'0');
        if(v < oflow) return CWAL_RC_RANGE;
        mult = mult ? (mult*10) : 10;
    }
    if(!digitCount) return CWAL_RC_TYPE;
    else 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 = (cwal_double_t)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;
        /* cwal_value * hv = CWAL_VALPART(h); */
        if(freeProps){
            cwal_cleanup_obase( e, &h->base, 0 );
        }
        /* MARKER("Freeing hash @%p\n", (void const *)h); */
        for( i = 0; h->table.count && (i < h->hashSize); ++i ){
            kvp = (cwal_kvp*)h->table.list[i];
            if(kvp){
                h->table.list[i] = next = NULL;
                for( ; kvp; kvp = next ){
                    assert(h->table.count>0);
                    next = kvp->right;
                    kvp->right = NULL;
                    cwal_kvp_clean(e/* , hv->scope */, kvp)
                        /* ensure values in higher scopes are unhanded
                           instead of unrefed, if needed */;
                    cwal_kvp_free(e/* , sc */, kvp, 1);
                    --h->table.count;
                }
            }
        }
        assert(0==h->table.count);
    }
}


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 );
    cwal_list_to_recycler(e, &h->table);
    assert(0==h->table.count);
    assert(0==h->table.alloced);
    assert(0==h->table.list);
    *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;
    assert(sc && h && v);
    assert(CWAL_RCFLAG_HAS(v,CWAL_RCF_IS_RESCOPING));
    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 = (cwal_kvp*)h->table.list[i];
        if(kvp){
            cwal_kvp * next = NULL;
            for( ; !rc && kvp; kvp = next){
                next = kvp->right;
                assert(kvp->key);
                assert(kvp->value);
                rc = cwal_value_xscope(sc->e, sc, kvp->key, 0);
                if(!rc && kvp->key != kvp->value){
                    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_value * v = cwal_value_new(e, e->current,
                                        CWAL_TYPE_HASH, 0);
        if(v){
            cwal_hash * h = CWAL_HASH(v);
            cwal_value * proto = h->base.prototype;
            assert(v->scope);
            *h = cwal_hash_empty;
            h->base.prototype = proto;
            if( 0 != cwal_hash_resize(h, hashSize) ){
                cwal_value_unref(v);
                v = 0;
            }else{
                assert(h->table.alloced >= hashSize);
                assert(!h->table.count);
                assert(hashSize == h->hashSize);
            }
        }
        return 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 in the hash table (required for re-linking
    collisions).

    This function is intollerant of NULLs for (h,key).
*/
static cwal_kvp * cwal_hash_search_impl_v( cwal_hash * h,
                                         cwal_value const * 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 = (cwal_kvp*)h->table.list[ndx];
    cwal_type_id const kType = key->vtab->typeID /*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 != kvp->key->vtab->typeID
                /*cwal_value_type_id(kvp->key)*/){
            continue;
        }
        else if(0==key->vtab->compare(key, kvp->key)){
            return kvp;
        }
    }
    return NULL;
}

static cwal_kvp * cwal_hash_search_impl_cstr( cwal_hash const * h,
                                              char const * key,
                                              cwal_size_t keyLen,
                                              cwal_hash_t * tableIndex,
                                              cwal_kvp ** left ){
    cwal_hash_t const ndx =
        cwal_hash_bytes( key, keyLen ) % 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->table.list[ndx];
    cwal_string const * sk;
    if(tableIndex) *tableIndex = ndx;
    for( ; kvp; (left?(*left=kvp):NULL), 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;
        }
    }
    return NULL;
}

cwal_kvp * cwal_hash_search_kvp( cwal_hash * h, char const * key,
                                 cwal_size_t keyLen ){
    cwal_hash_t ndx;
    cwal_kvp * kvp;
    cwal_string const * sk;
    if(!h || !key) return NULL;
    ndx = cwal_hash_bytes( key, keyLen ) % h->hashSize;
    kvp = (cwal_kvp*)h->table.list[ndx];
    for( ; kvp; kvp = kvp->right ){
        assert(kvp->key);
        sk = CWAL_STR(kvp->key);
        if(!sk || keyLen != CWAL_STRLEN(sk)) continue;
        else if(0==cwal_compare_str_cstr(sk, key, keyLen)){
            return kvp;
        }
    }
    return NULL;
}

cwal_value * cwal_hash_search( cwal_hash * h, char const * key,
                               cwal_size_t keyLen ){
    cwal_kvp const * kvp = cwal_hash_search_kvp(h, key, keyLen);
    return kvp ? kvp->value : 0;
}
    
cwal_value * cwal_hash_search_v( cwal_hash * h, cwal_value const * key ){
    cwal_kvp const * kvp = (h && key)
        ? cwal_hash_search_impl_v(h, key, NULL, NULL)
        : NULL;
    return kvp ? kvp->value : NULL;
}

cwal_kvp * cwal_hash_search_kvp_v( cwal_hash * h, cwal_value const * key ){
    return (h && key)
        ? cwal_hash_search_impl_v(h, key, NULL, NULL)
        : NULL;
}


int cwal_hash_insert_with_flags_v( cwal_hash * h, cwal_value * key, cwal_value * v,
                                   char allowOverwrite, uint16_t kvpFlags ){
    cwal_hash_t ndx = 0;
    cwal_kvp * left = 0;
    cwal_kvp * kvp;
    cwal_value * const hv = CWAL_VALPART(h);
    cwal_scope * sc;
    if(!hv || !key || !v) return CWAL_RC_MISUSE;
    else if(CWAL_V_IS_IN_CLEANUP(hv)) return CWAL_RC_DESTRUCTION_RUNNING;
    else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST;
    else if(CWAL_CONTAINER_DISALLOW_PROP_SET & h->base.flags){
        /* FIXME: this is a wrong mix of semantics. It's currently
           required/relied upon by s2, but we need a different flag
           for a hash-disallow-insert flag. */
        return CWAL_RC_DISALLOW_PROP_SET;
    }
    sc = hv->scope;
    kvp = cwal_hash_search_impl_v( h, key, &ndx, &left );
    if(kvp){
#if 0
        MARKER(("found hash entry kvp: flags=0x%08x, new flags=0x%08x\n", (int)kvp->flags, kvpFlags));
        dump_val(kvp->key,"key");
        dump_val(kvp->value,"val");
#endif
        if(!allowOverwrite) return CWAL_RC_ALREADY_EXISTS;
        else if(CWAL_VAR_F_CONST & kvp->flags){
            return CWAL_RC_CONST_VIOLATION;
        }
        else if(kvp->key != key){
            cwal_value * const old = kvp->key;
            cwal_value_xscope(sc->e, sc, key, 0);
            if(E_IS_DEAD(sc->e)) return sc->e->fatalCode;
            kvp->key = key;
            cwal_value_ref(key);
            cwal_value_unref(old);
        }
        if(kvp->value != v){
            cwal_value * const old = kvp->value;
            cwal_value_xscope(sc->e, sc, v, 0);
            if(E_IS_DEAD(sc->e)) return sc->e->fatalCode;
            kvp->value = v;
            cwal_value_ref(v);
            cwal_value_unref(old);
        }
        if(CWAL_VAR_F_PRESERVE!=kvpFlags) kvp->flags = kvpFlags;
    }else{
        if(CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES & h->base.flags){
            return CWAL_RC_DISALLOW_NEW_PROPERTIES;
        }
        if(sc->e->values.hashXfer){
            kvp = sc->e->values.hashXfer;
            sc->e->values.hashXfer = 0;
        }else{
            kvp = cwal_kvp_alloc(sc->e);
            if(!kvp) return CWAL_RC_OOM;
        }
        assert(!kvp->key);
        assert(!kvp->value);
        assert(!kvp->right);
        if(left){
            kvp->right = left->right;
            left->right = kvp;
        }else{
            h->table.list[ndx] = kvp;
        }
        kvp->key = key;
        kvp->value = v;
        if(CWAL_VAR_F_PRESERVE!=kvpFlags) kvp->flags = kvpFlags;
        cwal_value_xscope(sc->e, sc, key, 0);
        cwal_value_xscope(sc->e, sc, v, 0);
        cwal_value_ref(key);
        cwal_value_ref(v);
#if 0
        dump_val(kvp->key,"inserted hash key");
        dump_val(kvp->value,"inserted hash value");
        dump_val(CWAL_VALPART(h),"inserted into hash");
        MARKER(("kvp->flags=0x%04x\n", kvp->flags));
#endif
        ++h->table.count;
    }
    return 0;
}

int cwal_hash_insert_v( cwal_hash * h, cwal_value * key, cwal_value * v,
                        char allowOverwrite ){
    return cwal_hash_insert_with_flags_v(h, key, v, allowOverwrite, CWAL_VAR_F_PRESERVE);
}

int cwal_hash_insert_with_flags( cwal_hash * h, char const * key, cwal_size_t keyLen,
                                 cwal_value * v, char allowOverwrite, uint16_t kvpFlags ){
    cwal_kvp * kvp;
    cwal_value * const hv = CWAL_VALPART(h);
    if(!h || !key || !v) return CWAL_RC_MISUSE;
    else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST;
    else if(CWAL_CONTAINER_DISALLOW_PROP_SET & h->base.flags){
        return CWAL_RC_DISALLOW_PROP_SET;
    }
    kvp = cwal_hash_search_kvp(h, key, keyLen);
    if(kvp && !allowOverwrite) return CWAL_RC_ALREADY_EXISTS;
    else if(kvp && (CWAL_VAR_F_CONST & kvp->flags)){
        return CWAL_RC_CONST_VIOLATION;
    }
    else {
        int rc;
        cwal_value * check = cwal_new_string_value(CWAL_VENGINE(hv),
                                                    key, keyLen);
        if(!check) rc = CWAL_RC_OOM;
        else{
            cwal_value_ref(check);
            rc = kvp
                ? cwal_kvp_value_set2(kvp, check)
                : cwal_hash_insert_with_flags_v( h, check, v, allowOverwrite, kvpFlags );
            if(kvp && !rc){
                cwal_value_rescope(hv->scope, v);
            }
            cwal_value_unref(check);
        }
        return rc;
    }
}

int cwal_hash_insert( cwal_hash * h, char const * key, cwal_size_t keyLen,
                      cwal_value * v, char allowOverwrite ){
    return cwal_hash_insert_with_flags( h, key, keyLen, v, allowOverwrite, CWAL_VAR_F_PRESERVE );
}

int cwal_hash_remove_v( cwal_hash * h, cwal_value * key ){
    cwal_value * const vSelf = CWAL_VALPART(h);
    if(!h || !vSelf || !key) return CWAL_RC_MISUSE;
    else if(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING;
    else if(CWAL_CONTAINER_DISALLOW_PROP_SET & h->base.flags) return CWAL_RC_DISALLOW_PROP_SET;
    else if(CWAL_V_IS_VISITING_LIST(vSelf)) return CWAL_RC_IS_VISITING_LIST;
    else{
        cwal_hash_t ndx = 0;
        cwal_kvp * left = 0;
        cwal_kvp * kvp = cwal_hash_search_impl_v( h, key, &ndx, &left );
        cwal_engine * e = vSelf->scope->e;
        /* cwal_scope * sc = vSelf->scope; */
        if(!kvp) return CWAL_RC_NOT_FOUND;
        else{
            assert(h->table.count>0);
            if(left){
                left->right = kvp->right;
            }else{
                h->table.list[ndx] = kvp->right;
            }
            kvp->right = NULL;
            --h->table.count;
            cwal_kvp_free(e/* , sc */, kvp, 1);
            return 0;
        }
    }
}

int cwal_hash_remove( cwal_hash * h, char const * key, cwal_size_t keyLen ){
    cwal_value * const vSelf = CWAL_VALPART(h);
    if(!h || !vSelf || !key) return CWAL_RC_MISUSE;
    else if(CWAL_RCFLAG_HAS(vSelf,CWAL_RCF_IS_DESTRUCTING)) return CWAL_RC_DESTRUCTION_RUNNING;
    else if(CWAL_CONTAINER_DISALLOW_PROP_SET & h->base.flags) return CWAL_RC_DISALLOW_PROP_SET;
    else if(CWAL_V_IS_VISITING_LIST(vSelf)) return CWAL_RC_IS_VISITING_LIST;
    else{
        cwal_hash_t ndx = 0;
        cwal_kvp * left = 0;
        cwal_kvp * kvp;
        cwal_engine * e = vSelf->scope->e;
        kvp = cwal_hash_search_impl_cstr( h, key, keyLen, &ndx, &left );
        if(!kvp) return CWAL_RC_NOT_FOUND;
        else{
            assert(h->table.count>0);
            if(left){
                left->right = kvp->right;
            }else{
                h->table.list[ndx] = kvp->right;
            }
            kvp->right = NULL;
            --h->table.count;
            cwal_kvp_free(e/* , sc */, kvp, 1);
            return 0;
        }
    }
}

cwal_size_t cwal_hash_entry_count(cwal_hash const *h){
    return h ? h->table.count : 0;
}

cwal_size_t cwal_hash_size( cwal_hash const * h ){
    return h ? h->hashSize : 0;
}

int cwal_hash_visit_kvp( cwal_hash * h, cwal_kvp_visitor_f f, void * state ){
    cwal_value * const hv = (h && f) ? CWAL_VALPART(h) : NULL;
    cwal_obase * const b = CWAL_VOBASE(hv);
    if(!hv || !f) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else {
        cwal_kvp * kvp;
        cwal_size_t i;
        int rc = CWAL_RC_OK;
        int opaque;
        cwal_visit_list_begin(hv, &opaque);
        for( i = 0; !rc && (i < h->hashSize); ++i ){
            kvp = (cwal_kvp*)h->table.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_visit_list_end(hv, opaque);
        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 {
        cwal_kvp * kvp;
        cwal_size_t i;
        int rc = CWAL_RC_OK;
        int opaque;
        cwal_visit_list_begin(hv, &opaque);
        for( i = 0; !rc && (i < h->hashSize); ++i ){
            kvp = (cwal_kvp*)h->table.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 );
                }
            }
        }
        cwal_visit_list_end(hv, opaque);
        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);
}

int cwal_hash_resize( cwal_hash * h, cwal_size_t newSize ){
    cwal_value * const hv = CWAL_VALPART(h);
    if(!h || !hv) return CWAL_RC_MISUSE;
    else if(!newSize) return CWAL_RC_RANGE;
    else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST;
    /* highly arguable: newSize = cwal_trim_hash_size( newSize ); */
    if(h->hashSize == newSize) return 0;
#if 0
   else if(!newSize){
        cwal_hash_clear( h, 1 );
        cwal_list_to_recycler(e, &h->table);
        assert(0==h->table.count);
        assert(0==h->table.alloced);
        assert(0==h->table.list);
        /* h->hashSize = 0; */
        return 0;
    }
#endif
    else{
        cwal_list li = cwal_list_empty;
        int rc = 0;
        cwal_engine * e = hv->scope->e;
        cwal_size_t i;
        cwal_size_t const oldHashSize = h->hashSize;
#ifdef DEBUG
        cwal_size_t const oldCount = h->table.count;
#endif
        if(newSize > cwal_list_reserve(e, &li, newSize)){
            assert(!li.list);
            return CWAL_RC_OOM;
        }
        {
            /* Swap the table memory */
            cwal_list const tmp = h->table;
            h->table = li;
            li = tmp;
        }
        h->hashSize = newSize;
        assert(newSize <= h->table.alloced);
        assert(!h->table.count);
        assert(li.alloced ? !!li.list : !li.list);
        for( i = 0; li.count && i < oldHashSize; ++i ){
            cwal_kvp * kvp = (cwal_kvp*)li.list[i];
            cwal_kvp * next = 0;
            for( ; !rc && kvp; kvp = next ){
                cwal_value * k = kvp->key;
                cwal_value * v = kvp->value;
                assert(CWAL_REFCOUNT(k) || CWAL_MEM_IS_BUILTIN(k));
                assert(CWAL_REFCOUNT(v) || CWAL_MEM_IS_BUILTIN(v));
                next = kvp->right;
                /* kvp holds refs to k/v, but we're going to steal them... */
                kvp->right = 0;
                kvp->key = 0;
                kvp->value = 0;
                /* *kvp = cwal_kvp_empty; must retain flags */
                e->values.hashXfer = kvp;
                rc = cwal_hash_insert_with_flags_v( h, k, v, 0, kvp->flags );
                assert(!rc) /* cannot fail under these conditions - no allocation */;
                assert(0==e->values.hashXfer);
                assert(kvp->key == k);
                assert(kvp->value == v);
                /* We got an extra ref on k/v up there, so... */
                assert(CWAL_REFCOUNT(k)>1 || CWAL_MEM_IS_BUILTIN(k));
                assert(CWAL_REFCOUNT(v)>1 || CWAL_MEM_IS_BUILTIN(v));
                cwal_value_unref(k);
                cwal_value_unref(v);
                --li.count;
                li.list[i] = 0;
            }
        }
        assert(0==li.count);
#ifdef DEBUG
        assert(h->table.count == oldCount);
#endif
        cwal_list_reserve(e, &li, 0);
        return rc;
    }
}



/**
   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.
*/
cwal_hash_t cwal_next_prime( cwal_hash_t n ){
#if 1
    int const * p = cwal_first_1000_primes();
    int const last = 999;
    int i = 0;
    if((int)n >= p[last]) return (cwal_hash_t)p[last];
    for( ; i < last; ++i){
        if(p[i] > (int)n) return (cwal_hash_t)p[i];
    }
    return p[i];
#else
    /**
     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.
  */
  static const cwal_hash_t list[] = {
  5, 7, 11, 17, 19, 27, 41, 59, 71, 97, 109, 137,
  167, 199, 239, 373, 457, 613, 983, 1319, 2617,
  5801, 7919, 9973, 14563, 20011,
  30241, 40637,
#if CWAL_INT_T_BITS <= 16
/* max for 16-bit */
  49999,
#else
  60913, 80897,
  104729, 160969,
#endif
  0/*sentinel*/};
  cwal_hash_t const * i;
  for( i = list; *i && *i<n; ++i );
#if CWAL_INT_T_BITS == 16
  return *i ? *i : 49999;
#else
  return *i ? *i : 160969;
#endif
#endif
}

int cwal_hash_grow_if_loaded( cwal_hash * h, double load ){
  double const hashSize = (double)cwal_hash_size(h);
  double const entryCount = (double)cwal_hash_entry_count(h);
  cwal_value const * hv = CWAL_VALPART(h);
  int rc = 0;
  if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST;
  else if(!entryCount) return 0;
#if 0
  /* arguable. If someone wants loads of 5.0, let him. */
  else if(load >= 0.95) load = 0.95;
#endif
  else if(load < 0.50) load = 0.5;
  assert(hashSize);
  if((entryCount / hashSize) > load){
    cwal_size_t const newSize =
      cwal_next_prime((cwal_size_t)(entryCount + (entryCount * (1.0-load))));
    assert(newSize > entryCount);
    /*MARKER(("Resizing hash: old size=%f, count=%f, Going for load %f, size=%d\n",
      hashSize, entryCount, load, (int)newSize));*/
    rc = cwal_hash_resize(h, newSize);
  }
  return rc;
}


int cwal_hash_take_props( cwal_hash * h, cwal_value * src,
                          int overwritePolicy ){
    cwal_obase * srcBase;
    cwal_value * const hv = CWAL_VALPART(h);
    if(!src || !h || !hv) return CWAL_RC_MISUSE;
    else if(CWAL_V_IS_VISITING_LIST(hv)) return CWAL_RC_IS_VISITING_LIST;
    else if(CWAL_V_IS_VISITING(src)) return CWAL_RC_IS_VISITING;
    else if(!(srcBase = CWAL_VOBASE(src))) return CWAL_RC_TYPE;
    else{
        cwal_kvp * kvp;
        cwal_kvp * keepThese = 0;
        cwal_kvp * toKeepTail = 0;
        int rc = 0;
        cwal_engine * const e = hv->scope->e;
        /*nope - will break following set ops: cwal_value_set_visiting_list(hv, 1);*/
        while( (kvp = srcBase->kvp) ){
            cwal_value * k = kvp->key;
            cwal_value * v = kvp->value;
            assert(CWAL_REFCOUNT(k) || CWAL_MEM_IS_BUILTIN(k));
            assert(CWAL_REFCOUNT(v) || CWAL_MEM_IS_BUILTIN(v));
            srcBase->kvp = kvp->right;
            *kvp = cwal_kvp_empty;
            e->values.hashXfer = kvp;
            rc = cwal_hash_insert_v( h, k, v, overwritePolicy>0 ? 1 : 0 );
            switch(rc){
              case 0:
                  if(e->values.hashXfer){
                      assert(0!=overwritePolicy);
                      cwal_kvp_free(e/* , src->scope */, e->values.hashXfer, 1);
                      e->values.hashXfer = 0;
                  }else{
                      /* e->values.hashXfer was taken by insert() */
                      assert(CWAL_REFCOUNT(k)>1 || CWAL_MEM_IS_BUILTIN(k));
                      assert(CWAL_REFCOUNT(v)>1 || CWAL_MEM_IS_BUILTIN(v));
                      assert(kvp->key == k);
                      assert(kvp->value == v);
                  }
                  /* account for src container refs */
                  cwal_value_unref(k);
                  cwal_value_unref(v);
                  continue;
              case CWAL_RC_ALREADY_EXISTS:
                  assert(kvp == e->values.hashXfer);
                  e->values.hashXfer = 0;
                  assert(overwritePolicy<=0);
                  if(overwritePolicy<0){
                      rc = 0;
                      assert(!kvp->right);
                      assert(!kvp->key);
                      assert(!kvp->value);
                      kvp->right = keepThese;
                      kvp->key = k;
                      kvp->value = v;
                      keepThese = kvp;
                      if(!toKeepTail) toKeepTail = keepThese;
                      continue;
                  }else{
                      goto fixkvp;
                  }
              default:
                  assert(kvp == e->values.hashXfer);
                  e->values.hashXfer = 0;
                  goto fixkvp;
            }
            assert(!"not reached");
            fixkvp:
            assert(CWAL_REFCOUNT(k) || CWAL_MEM_IS_BUILTIN(k));
            assert(CWAL_REFCOUNT(v) || CWAL_MEM_IS_BUILTIN(v));
            assert(0!=rc);
            /* Put kvp back in srcBase */
            assert(!kvp->right);
            kvp->right = srcBase->kvp;
            srcBase->kvp = kvp;
            kvp->key = k;
            kvp->value = v;
            break;
        }
        if(toKeepTail){
            assert(keepThese);
            assert(toKeepTail->key);
            assert(toKeepTail->value);
            assert(CWAL_REFCOUNT(toKeepTail->key) || CWAL_MEM_IS_BUILTIN(toKeepTail->key));
            assert(CWAL_REFCOUNT(toKeepTail->value) || CWAL_MEM_IS_BUILTIN(toKeepTail->value));
            toKeepTail->right = srcBase->kvp;
            srcBase->kvp = keepThese;
        }
        /* nope cwal_value_set_visiting_list(hv, 0); */
        return rc;
    }
}

    
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_value_rescoper_f rescoper){
    if(!nv) return CWAL_RC_MISUSE;
    else {
        nv->rescoper = 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->rescoper = 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;
      default:{
          cwal_recycler const * re = cwal_recycler_get( e, type );
          return re ? re->maxLength : 0;
      }
    }
}

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(HASH);
        DO(SCOPE);
        DO(STRING);
        DO(XSTRING/* also Z-strings*/);
        DO(UNIQUE);
        DO(TUPLE);
        DO(WEAK_REF);
#undef DO
        return rc;
    }
    else {
        cwal_recycler * li;
        void * mem;
        li = cwal_recycler_get( e, typeID );
        if( !li ) return CWAL_RC_TYPE;
        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 for a given scope call it "cannot fail" on subsequent
   calls unless the scope somehow loses its properties, in which case
   this routine would create a new s->props object/hash.
*/
static int cwal_scope_init_props(cwal_scope *s){
    if(s->props) return 0;
    assert( s->e );
    s->props = (CWAL_FEATURE_SCOPE_STORAGE_HASH & s->e->flags)
        ? cwal_new_hash_value(s->e, CwalConsts.ScopePropsHashSize)
        : cwal_new_object_value(s->e)
        /* reminder: its owning scope might be a different one!  We'll
           fix that below. */;
    if(s->props){
        cwal_value * const pv = s->props;
        cwal_obase * const obase = CWAL_VOBASE(s->props);
        assert(obase);
        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. */
            ;
        obase->flags |= CWAL_F_IS_PROP_STORAGE;
        if(pv->scope->level > s->level){
            /* This can happen if a scope creates no vars but one is
               added after a subscope is active. Our newly-created pv
               is owned by the newer scope initially, and we need to
               back-door it into the scope on whose behalf it stores
               properties (at least initially - it can move out later
               on).
            */
            cwal_value_xscope(s->e, s, pv, NULL);
        }else{
            assert(s == pv->scope);
            assert( obase->flags & CWAL_F_IS_PROP_STORAGE );
            if(cwal_scope_insert(pv->scope, pv)){
                assert(s->e->fatalCode);
                return s->e->fatalCode;
            }
            assert( obase->flags & CWAL_F_IS_PROP_STORAGE );
        }
        assert(pv->scope->mine.headSafe == pv);
#if 1
        if(obase->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
               }

               20181128: i still can't shake the feeling that there
               might be interesting uses for that in s2. Could we
               possibly implement a JS-like "with" feature using that,
               by setting the prototype to the "with'd" value?

               Nevermind: cwal_scope_search_v() and friends, via
               cwal_prop_get_kvp_v(), explicitly do not search
               prototypes for values. Maybe they should? Enabling that
               "shouldn't" break anything because scope properties
               have explicitely (via this upcoming call) had no
               prototypes since our earliest ancestors wrote code.
               Adding that feature would require adding support for it
               in cwal_hash_search_impl_v() and
               cwal_hash_search_impl_cstr(), as those don't currently
               support prototype traversal. Enabling prototype lookups
               might also have negative side effects if we "with'd"
               one scope properties with another and both scopes used
               different storage types (hash vs object). All routines
               involved would have to know to potentially switch
               types/modes, and that quickly turns into a rat's nest
               of special cases.
            */
            cwal_value_prototype_set(pv, NULL);
        }
#endif
    }
    return s->props
        ? 0
        : CWAL_RC_OOM;
}

static int cwal_scope_adjust_prop_size(cwal_scope * s){
    cwal_hash * h = CWAL_HASH(s->props);
    return h ? cwal_hash_grow_if_loaded(h, 0.85) : 0;
}

/**
   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; */
        cwal_scope * origin = s;
        cwal_hash * h;
        cwal_kvp * kvp = 0;
        assert(!(s->flags & CWAL_F_IS_DESTRUCTING));
        if(!v) searchParents = 0 /* do not remove props from parens this way! */;
        while(s && !foundIn){
            kvp = cwal_scope_search_kvp_v( s, 0, key, &foundIn );
            if(kvp) break;
            s = searchParents ? s->parent : 0;
        }
#if 0
        dump_val(key,"setting this key");
        MARKER(("var set v impl new kvp flags=0x%04x\n", flags));
        if(kvp){
            MARKER(("var set v impl kvp flags=0x%04x, new flags=0x%04x\n", (int)kvp->flags, flags));
            dump_val(kvp->key,"key");
            dump_val(kvp->value,"val");
        }
#endif
        if(kvp){
            /*MARKER(("kvp found. flags=0x%04d\n", (int)kvp->flags));*/
            /* dump_val(kvp->key,"key"); */
            assert(s == foundIn);
            assert(foundIn->props);
            if(CWAL_VAR_F_CONST & kvp->flags){
                return CWAL_RC_CONST_VIOLATION;
            }
            else if(v){
                rc = cwal_kvp_value_set2(kvp, v);
                if(!rc) cwal_value_rescope(foundIn, v);
                return rc;
            }
        }

        if(!foundIn){
            if(!v) return CWAL_RC_NOT_FOUND;
            foundIn = origin;
        }
        if(!foundIn->props){
            rc = cwal_scope_init_props(foundIn);
        }else if(CWAL_V_IS_VISITING(foundIn->props)){
            rc = CWAL_RC_IS_VISITING;
        }else if(CWAL_HASH(foundIn->props) &&
                 CWAL_V_IS_VISITING_LIST(foundIn->props)){
            rc = CWAL_RC_IS_VISITING_LIST;
        }
        if(rc) return rc;                 
        assert(foundIn && foundIn->props);
#if 0
        MARKER(("Setting [opaque key type] in scope#%d via scope#%d.\n",
                (int)foundIn->level, (int)origin->level));
#endif
        if((h = CWAL_HASH(foundIn->props))){
#if 0
            dump_val(key,"setting this key in hash");
            dump_val(foundIn->props,"in this container");
            MARKER(("var set v impl new kvp flags=0x%04x\n", flags));
            if(kvp){
                MARKER(("var set v impl kvp flags=0x%04x, new flags=0x%04x\n", (int)kvp->flags, flags));
                dump_val(kvp->key,"key");
                dump_val(kvp->value,"val");
            }
#endif

            rc = v
                ? cwal_hash_insert_with_flags_v( h, key, v, 1, flags )
                : cwal_hash_remove_v(h, key);
            if(v && !rc) rc = cwal_scope_adjust_prop_size(foundIn);
            return rc;
        }else{
            cwal_obase * b = CWAL_VOBASE(foundIn->props);
            return v
                ? cwal_kvp_set_v( e, foundIn->props,
                                  key, v, flags )
                : cwal_kvp_unset_v( e, &b->kvp, key )
                ;
        }
    }
}
    
static int cwal_kvp_visitor_scope_import_props( cwal_kvp const * kvp,
                                                void * state ){
    cwal_scope * dest = (cwal_scope*)state;
    return cwal_var_set_v_impl( dest->e, dest, kvp->key, kvp->value, 0, kvp->flags );
}

int cwal_scope_import_properties( cwal_scope * dest, cwal_value * src ){
    if(!src || !dest) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_DESTRUCTING & dest->flags) return CWAL_RC_DESTRUCTION_RUNNING;
    else if(!cwal_props_can(src)) return CWAL_RC_TYPE;
    else{
        int const rc = cwal_scope_init_props(dest);
        cwal_hash * const h = rc ? 0 : CWAL_HASH(src);
        if(rc) return rc;
        assert(dest->props);
        if(CWAL_V_IS_VISITING(dest->props)) return CWAL_RC_IS_VISITING;
        else if(h && CWAL_V_IS_VISITING_LIST(dest->props)) return CWAL_RC_IS_VISITING_LIST;
        if(dest->props && CWAL_V_IS_IN_CLEANUP(dest->props)){
            assert(!"This really shouldn't be possible unless this "
                   "scope is destructing, which it's not.");
            return CWAL_RC_DESTRUCTION_RUNNING;
        }
        return h
            ? cwal_hash_visit_kvp( h, cwal_kvp_visitor_scope_import_props, dest )
            : cwal_props_visit_kvp( src, cwal_kvp_visitor_scope_import_props, dest );
    }
}


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

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 ){
            cwal_hash * h = CWAL_HASH(os->props);
            if(h) kvp = cwal_hash_search_impl_v(h, key, NULL, NULL);
            else if(os->props){
                kvp = cwal_prop_get_kvp_v( os->props, key, 0, NULL );
            }
            if(kvp){
                if(foundIn) *foundIn = os;
                break;
            }
            else os = os->parent;
        }
#if 0
        if(kvp){
            dump_val(os ? os->props : 0,"cwal_scope_search_kvp_v() storage");
            MARKER(("kvp->flags=0x%04x\n", kvp->flags));
        }
#endif
        return kvp;
    }
}

cwal_value * cwal_scope_search_v( cwal_scope * s,
                                  int upToDepth,
                                  cwal_value const * key,
                                  cwal_scope ** foundIn ){
    cwal_kvp * kvp = cwal_scope_search_kvp_v(s, upToDepth, key, foundIn);
    return kvp ? kvp->value : NULL;
}

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( upToDepth<0 ){
            assert(s->level);
            upToDepth = (int)(s->level-1);
        }
        for( os = s;
             os && (upToDepth>=0);
             --upToDepth ){
            cwal_hash * h = CWAL_HASH(os->props);
            if(h) kvp = cwal_hash_search_impl_cstr(h, key, keyLen, NULL, NULL);
            else if(os->props){
                kvp = cwal_prop_get_kvp( os->props, key, keyLen, 0, 0 );
            }
            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 ){
    cwal_kvp * kvp = cwal_scope_search_kvp(s, upToDepth,
                                            key, keyLen, foundIn);
    return kvp ? kvp->value : NULL;
}

int cwal_scope_chain_set_with_flags_v( cwal_scope * s, int upToDepth,
                                       cwal_value * k, cwal_value * v,
                                       uint16_t flags ){
    if(!s || !k) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_DESTRUCTING & s->flags) return CWAL_RC_DESTRUCTION_RUNNING;
    else {
        int rc;
        cwal_scope * os = NULL;
        cwal_hash * h;
        cwal_kvp * kvp = cwal_scope_search_kvp_v( s, upToDepth, k, &os );
        cwal_obase * ob;
        assert(!(s->flags & CWAL_F_IS_DESTRUCTING));
        if(!os){
            os = s;
        }
        if(os->props && CWAL_RCFLAG_HAS(os->props,CWAL_RCF_IS_DESTRUCTING)){
            return CWAL_RC_DESTRUCTION_RUNNING;
        }else if(os->props && CWAL_V_IS_VISITING(os->props)){
            return CWAL_RC_IS_VISITING;
        }else if(CWAL_HASH(os->props) && CWAL_V_IS_VISITING_LIST(os->props)){
            return CWAL_RC_IS_VISITING_LIST;
        }
        ob = CWAL_VOBASE(os->props);
        if(ob){
            if(CWAL_CONTAINER_DISALLOW_PROP_SET & ob->flags){
                return CWAL_RC_DISALLOW_PROP_SET;
            }else if(!kvp && (CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES & ob->flags)){
                return CWAL_RC_DISALLOW_NEW_PROPERTIES;
            }
        }
        if(kvp && (CWAL_VAR_F_CONST & kvp->flags)){
            return CWAL_RC_CONST_VIOLATION;
        }
#if 0
        if(kvp){
            MARKER(("kvp flags=0x%08x, new flags=0x%08x\n", (int)kvp->flags, flags));
            dump_val(kvp->key,"key");
            dump_val(kvp->value,"val");
        }
#endif
        if(v && kvp){
            /* avoid a second lookup for the property... */
            assert(os->props);
            rc = cwal_kvp_value_set2( kvp, v );
            if(!rc) cwal_value_rescope(os->props->scope, v);
            return rc;
        }else if(!os->props){
            rc = cwal_scope_init_props( os );
            if(rc) return rc;
            assert(os->props);
        }
        if( (h = CWAL_HASH(os->props)) ){
            rc = v
                ? cwal_hash_insert_with_flags_v( h, k, v, 1, flags )
                : cwal_hash_remove_v(h, k);
            if(!rc) rc = cwal_scope_adjust_prop_size(os);
        }else{
            rc = cwal_prop_set_with_flags_v( os->props, k, v, flags );
        }
        return rc;
    }
}

int cwal_scope_chain_set_v( cwal_scope * s, int upToDepth,
                            cwal_value * k, cwal_value * v ){
    return cwal_scope_chain_set_with_flags_v(s, upToDepth, k, v, CWAL_VAR_F_PRESERVE);
}

int cwal_scope_chain_set_with_flags( cwal_scope * s, int upToDepth,
                                      char const * k, cwal_size_t keyLen,
                                      cwal_value * v, uint16_t flags ){
    if(!s || !k) return CWAL_RC_MISUSE;
    else {
        int rc;
        cwal_value * kv = cwal_new_string_value(s->e, k, keyLen);
        if(!v) return CWAL_RC_OOM;
        cwal_value_ref(kv);
        rc = cwal_scope_chain_set_with_flags_v( s, upToDepth, kv, v, flags );
        cwal_value_unref(kv);
        return rc;
    }
}

int cwal_scope_chain_set( cwal_scope * s, int upToDepth,
                          char const * k, cwal_size_t keyLen,
                          cwal_value * v ){
    return cwal_scope_chain_set_with_flags( s, upToDepth, k, keyLen,
                                             v, CWAL_VAR_F_PRESERVE );
}

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);
        if(rc) return rc;
        assert(s->props);
        if(CWAL_V_IS_VISITING(s->props)) return CWAL_RC_IS_VISITING;
        else if(CWAL_HASH(s->props) && CWAL_V_IS_VISITING_LIST(s->props)) return CWAL_RC_IS_VISITING_LIST;
        return (cwal_scope_search_v(s, 0, key, NULL)
                ? CWAL_RC_ALREADY_EXISTS
                : cwal_var_set_v_impl( e, s, key,
                                       v ? v : cwal_value_undefined(),
                                       0, 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;
        cwal_value * k = cwal_new_string_value(e, key, keyLen);
        if(!k) return CWAL_RC_OOM;
        cwal_value_ref(k);
        rc = cwal_var_decl_v(e, s, k, v, flags);
        cwal_value_unref(k);
        return rc;
    }
}

#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 ){
#if CWAL_PLATFORM_ARM
    return (cwal_hash_t) cwal_value_get_double(v)
        /* See comments below. This formulation does not
           bus fault on my ARM, but is slower. */
        ;
#else
    return (cwal_hash_t) *CWAL_DBL(v)
        /* On my ARM box this is causing a bus fault for the builtin
           double 1.0, but not for a dynamically-allocated double! */
        ;
#endif
}

cwal_hash_t cwal_value_hash_string( cwal_value const * v ){
    cwal_string const * s = CWAL_STR(v);
    assert(s);
    return cwal_hash_bytes( 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 / (ptr_int_t)sizeof(void*))
                         + ((ptr_int_t)v % (ptr_int_t)sizeof(void*)));
}

cwal_hash_t cwal_value_hash_tuple( cwal_value const * v ){
#if 1
    return cwal_value_hash_ptr(v);
#else
    /* needed? If so, do we want to start with the ptr hash or 0?
       No - then we cannot use tuples as hash keys (hash value must
       be constant).*/
    cwal_hash_t h = cwal_value_hash_ptr(v);
    cwal_tuple const * p = CWAL_TUPLE(v);
    uint16_t i = 0;
    for( ; i < p->n; ++i ) h += cwal_value_hash(p->list[i]);
    return h;
#endif
}

cwal_hash_t cwal_value_hash( cwal_value const * 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_tuple( 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 {
        /*
          Compare the cwal_tuple::list parts using normal value
          compare semantics... Only compare equivalent if all
          entries compare equivalent.
        */
        cwal_tuple const * l  = CWAL_TUPLE(lhs);
        cwal_tuple const * r  = CWAL_TUPLE(rhs);
        assert(l);
        assert(r);
        assert(l != r);
        if(l->n != r->n) return (int)l->n - (int)r->n;
        else{
            cwal_size_t i;
            int cmp = 0;
            assert(l->n && r->n && "We can't have made it this far with the 0-tuple.");
            for( i = 0; i < l->n && !cmp; ++i ){
                cmp = cwal_value_compare(l->list[i], r->list[i]);
            }
            return cmp;
        }
    }
}

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_UNUSED_VAR cwal_value const * lhs, cwal_value const * rhs ){
#ifdef DEBUG
    char const isNull = (CWAL_TYPE_NULL==lhs->vtab->typeID) ? 1 : 0;
    assert(isNull || CWAL_TYPE_UNDEF==lhs->vtab->typeID);
#endif
    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);
#if 1
      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.

             20181127: tried that (see #if'd-out block below)
             and it introduce this Damned Weird behaviour:

             var o = {2:'two'};
             assert o.hasOwnProperty(2); // okay
             assert o.hasOwnProperty('2'); // FAILS

             And yet:

             var o = {a:1, 2: 'two'}
             assert o.hasOwnProperty(2); // okay
             assert o.hasOwnProperty('2'); // okay

             That's a bonafide bug but i'm far too tired to chase
             it down, so we'll go back to this comparison for
             the time being.
          */
          enum { BufSize = 100 };
          char buf[BufSize] = {0,};
          cwal_size_t sz = BufSize;
          switch(rhs->vtab->typeID){
            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;
            case CWAL_TYPE_BOOL:
                /* FIXME? Use "true" and "false" instead of 0 and 1? */
            default:
                assert(!"CANNOT HAPPEN");
                break;
          }
          return cwal_compare_cstr( cwal_string_cstr(s), CWAL_STRLEN(s),
                                    buf, sz );
      }
#else
      /* This breaks unit tests */
      case CWAL_TYPE_INTEGER:{
          cwal_int_t i = 0, iR = cwal_value_get_integer(rhs);
          cwal_string_to_int( s, &i );
          return i==iR ? 0 : i<iR ? -1 : 1;
      }
      case CWAL_TYPE_DOUBLE:{
          cwal_double_t i = 0, iR = cwal_value_get_double(rhs);
          cwal_string_to_double( s, &i );
          return i==iR ? 0 : i<iR ? -1 : 1;
      }
#endif
      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);
    };
}

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_value_get_double(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_value_get_double(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_stream( cwal_input_f inF, void * inState,
                 cwal_output_f outF, void * outState ){
  if(!inF || !outF) return CWAL_RC_MISUSE;
  else{
    int rc = 0;
    enum { BufSize = 1024 * 4 };
    unsigned char buf[BufSize];
    cwal_size_t rn = BufSize;
    for( ; !rc &&
           (rn==BufSize)
           && (0==(rc=inF(inState, buf, &rn)));
         rn = BufSize){
      if(rn) rc = outF(outState, buf, rn);
      else break;
    }
    return rc;
  }
}

int cwal_output_f_cwal_engine( void * state, void const * src, cwal_size_t n ){
  return cwal_output( (cwal_engine *)state, src, n );
}

int cwal_output_f_cwal_outputer( void * state, void const * src, cwal_size_t n ){
  cwal_outputer * out = (cwal_outputer *)state;
  if(!src || !n) return 0;
  else return out->output ? out->output( out->state.data, src, n ) : 0;
}

cwal_outputer const * cwal_engine_outputer_get( cwal_engine const * e ){
  return e ? &e->vtab->outputer : 0;
}

void cwal_engine_outputer_set( cwal_engine * e,
                               cwal_outputer const * replacement,
                               cwal_outputer * tgt ){
  assert(e && replacement);
  if(tgt) *tgt = e->vtab->outputer;
  e->vtab->outputer = *replacement;
}


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_f_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;
                }
            }
        }
        if(rc) errno = 0;
        return cwal_buffer_fill_from( e, dest, cwal_input_f_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_f_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{
        char nameBuf[BufSize] = {0};
        memcpy( nameBuf, filename, (size_t)nameLen );
        nameBuf[nameLen] = 0;
        return cwal_buffer_fill_from_filename(e, dest, filename);
    }
}



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){
            assert(buf->capacity);
            assert((cwal_size_t)-1 != buf->capacity);
            cwal_memchunk_add(e, buf->mem, buf->capacity);
            cwal_buffer_wipe_keep_self(buf);
            assert(0==buf->mem);
            assert(0==buf->capacity);
            assert(0==buf->used);
        }
        return 0;
    }
    else if( buf->capacity >= n ){
            return 0;
    }
    
    if(!buf->mem){
        cwal_size_t reqSize = n;
        void * mem = cwal_memchunk_request(e, &reqSize, 1000,
                                           "cwal_buffer_reserve()");
        if(mem){
            assert(reqSize>=n);
            buf->mem = (unsigned char *)mem;
            buf->capacity = reqSize;
            buf->mem[0] = 0;
            return 0;
        }
        /* else fall through */
    }

    {
        unsigned char * x;
#if 0
        /* A theoretical micro-optimization for the memchunk
           recycler. In s2's unit tests (20141206) it gains no
           recycling and costs ~80 bytes. */
        if(n<sizeof(cwal_memchunk_overlay)) n = sizeof(cwal_memchunk_overlay);
#endif
        x = (unsigned char *)cwal_realloc( e, buf->mem, n );
        if( ! x ) return CWAL_RC_OOM;
        e->metrics.bytes[CWAL_TYPE_BUFFER] += (n - buf->capacity);
        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;
    }
}

int cwal_buffer_replace_str( cwal_engine * e, cwal_buffer * self,
                             unsigned char const * needle, cwal_size_t needleLen,
                             unsigned char const * repl, cwal_size_t replLen,
                             cwal_size_t limit,
                             cwal_size_t * changeCount){
  int rc = 0;
  if(!e || !self || !needle || (!repl && replLen>0)) rc = CWAL_RC_MISUSE;
  else if(!needleLen) rc = CWAL_RC_RANGE;
  if(rc || !self->used || needleLen>self->used){
     /* nothing to do */
    if(changeCount) *changeCount = 0;
    return rc;
  }else{
    cwal_size_t matchCount = 0;
    cwal_size_t slen = self->used;
    unsigned char const * pos = self->mem;
    unsigned char const * eof = pos + slen;
    unsigned char const * start = pos;
    cwal_buffer * buf;
    cwal_buffer bufLocal = cwal_buffer_empty;
    buf = &bufLocal;
    while( 1 ){
      if( (pos>=eof)
          || (0==memcmp(needle, pos, needleLen))
          ){
        cwal_size_t sz;
        char last = (pos>=eof) ? 1 : 0;
        if(!last && matchCount++==limit && limit>0){
          pos = eof;
          last = 1;
        }
        else if(pos>eof) pos=eof;
        sz = pos-start;
        if(sz){
          /* Append pending unmatched part... */
          rc = cwal_buffer_append(e, buf, start, sz);
        }
        if(!rc && pos<eof && replLen){
          /* Append replacement... */
          /* assert(pos <= (eof-replLen)); */
          rc = cwal_buffer_append(e, buf, repl, replLen);
        }
        if(rc || last) break;
        pos += needleLen;
        start = pos;
      }else{
        cwal_utf8_read_char( pos, eof, &pos );
      }
    }
    if(!rc){
      if(matchCount){
        cwal_buffer btmp = *self;
        void * const vself = self->self;
        btmp.self = 0;
        *self = *buf;
        self->self = vself;
        *buf = btmp;        
      }/*else self->buf is still okay*/
    }
    if(changeCount) *changeCount = matchCount;
    cwal_buffer_clear(e, buf);
    assert(self->capacity > self->used);
    self->mem[self->used] = 0;
    return rc;
  }
}

int cwal_buffer_replace_byte( cwal_engine * e, cwal_buffer * buf,
                              unsigned char needle, unsigned char repl,
                              cwal_size_t limit,
                              cwal_size_t * changeCount){
  cwal_size_t matchCount = 0;
  int rc = 0;
  if(!e || !buf){
    rc = CWAL_RC_MISUSE;
  }else if(!buf->used || needle==repl){
    /* nothing to do! */
    rc = 0;
  }else{
    cwal_size_t i = 0;
    for( ; i < buf->used; ++i ){
      if(buf->mem[i] == needle){
        buf->mem[i] = repl;
        if(++matchCount && limit && matchCount==limit) break;
      }
    }
  }
  if(changeCount) *changeCount = matchCount;
  return rc;
}



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 = 0;
    cwal_size_t totalRequests = 0;
    cwal_size_t totalAlloced = 0;
    cwal_size_t totalSizeof = 0;
    cwal_size_t grandTotal = 0;
    struct {
        char const * note;
        int size;
    } sizes[] = {
    {/*CWAL_TYPE_UNDEF*/ 0, 0},
    {/*CWAL_TYPE_NULL*/ 0, 0},
    {/*CWAL_TYPE_BOOL*/ 0, 0},
    {/*CWAL_TYPE_INTEGER*/ 0, sizeof(cwal_value)+sizeof(cwal_int_t)},
    {/*CWAL_TYPE_DOUBLE*/0, sizeof(cwal_value)+sizeof(cwal_double_t)},
    {/*CWAL_TYPE_STRING*/ "Incl. string bytes plus NULs", sizeof(cwal_value)+sizeof(cwal_string)},
    {/*CWAL_TYPE_ARRAY*/ "Not incl. cwal_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*/ "[2] Incl. total allocated buffer memory and non-Value buffers", sizeof(cwal_value)+sizeof(cwal_buffer_obj)},
    {/*CWAL_TYPE_HASH*/ "Not incl. cwal_list table memory.", sizeof(cwal_value)+sizeof(cwal_hash)},
    {/*CWAL_TYPE_SCOPE*/ "[3]", 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*/ "Not incl. external string bytes", sizeof(cwal_value)+sizeof(cwal_string)+sizeof(char **)},
    {/*CWAL_TYPE_ZSTRING*/ "Incl. string bytes", sizeof(cwal_value)+sizeof(cwal_string)+sizeof(char **)},
    {/*CWAL_TYPE_UNIQUE*/ 0, sizeof(cwal_value)+sizeof(cwal_value*)},
    {/*CWAL_TYPE_TUPLE*/ "[4]", sizeof(cwal_value)+sizeof(cwal_tuple)},
    {/*CWAL_TYPE_LISTMEM*/ "[5] Total alloced cwal_list::list memory used by arrays, hashtables, ...", 0},
    {0,0}
    };
    assert(e);

        
    cwal_outputf(e, "%-18s   %-16s"
                 "Actually allocated count * Value-type sizeof() "
                 "==> total bytes from allocator\n\n",
                 "TypeId/Bin[1]/Name",
                 "AllocRequests"
                 );

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

        reIndex = cwalRecyclerInfo.indexes[i]
            /* because the index cwal_recycler_index()
               would give for strings is misleading. */
            ;
        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%2d %-15s"
                     "%-16"CWAL_SIZE_T_PFMT
                     "%-7"CWAL_SIZE_T_PFMT
                     "(%06.2f%%) " /* reminder to self: the 06 applies to the _whole_ number! */
                     " * %2d "
                     "==> %-8"CWAL_SIZE_T_PFMT" %s"
                     "\n",
                     i,
                     reIndex,
                     cwal_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:              "
                     "%-16"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,"\nNotes:\n");
        cwal_outputf(e,"\nThe recycler moves some bits of memory around, "
                     "counting them only once, so do not search for an Absolute "
                     "Truth in these metrics!\n");
        cwal_outputf(e,"\n  [1] = Types with the same 'bin' value share a "
                     "recycling bin. A value of -1 indicates either no "
                     "recycling or a separate mechanism.\n");
        cwal_outputf(e,"\n  [2] = "
                     "%% 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  [3] = Scopes are always stack allocated "
                     "and skew the statistics, so only allocated scopes "
                     "are counted for totals purposes.\n");
        cwal_outputf(e, "\n  [4] = Tuples of all lengths>0, excluding list "
                     "memory (managed via the chunk recycler).\n");
        cwal_outputf(e, "\n  [5] = cwal_list memory is internally a special case: "
                     "1) 'sizeof()' is meaningless and 2) the allocation count "
                     "includes re-allocations to larger sizes, but the total includes "
                     "only the maximum (re)alloc'd size of each list. List memory "
                     "recycled from other places (e.g. the chunk recycler) is not "
                     "counted against this type's memory total, but do count as alloc "
                     "requests.\n");
    }

    if(CWAL_TYPE_UNDEF != e->metrics.highestRefcountType){
        cwal_outputf(e, "\nHighest individual refcount=%d on a value of type '%s'.\n"
                     "(FYI, that's essentially guaranteed to be one of the "
                     "base-most prototypes.)\n",
                     (unsigned)e->metrics.highestRefcount,
                     cwal_type_id_name( e->metrics.highestRefcountType ));
    }
    
    { /* Recycling bin sizes... */
        int i, head = 0;
        unsigned rtotal = 0, binCount = 0, itemCount = 0;
        cwal_recycler * re;
        cwal_outputf(e, "\nValue/KVP Recycling: %u recycled, "
                     "%u recycler misses.\n",
                     (unsigned)e->metrics.valuesRecycled,
                     (unsigned)e->metrics.valuesRecycleMisses);
        for( i = 0; i < (int)cwalRecyclerInfo.recyclerCount; ++i){
            char const * specialCaseLabel = 0;
            re = e->recycler + i;
            if(!re->id) continue;
            if(re->hits || re->misses){
                if(!head){
                    ++head;
                    cwal_outputf(e, "Bin#   SlotSize  "
                                 "Hits     Misses  Current#  Capacity "
                                 "(recyclable for type(s))\n");
                }
                if(re->count){
                    rtotal += re->id * re->count;
                    itemCount += re->count;
                    ++binCount;
                }
                cwal_outputf(e, "%3d    %-9d %-8d %-7d %-9d %d ",
                             i, re->id,
                             (int)re->hits, (int)re->misses,
                             re->count, re->maxLength);
                if(i==cwalRecyclerInfo.indexes[CWAL_TYPE_KVP]){
                    specialCaseLabel = cwal_type_id_name(CWAL_TYPE_KVP);
                }else if(i==cwalRecyclerInfo.indexes[CWAL_TYPE_WEAK_REF]){
                    specialCaseLabel = cwal_type_id_name(CWAL_TYPE_WEAK_REF);
                }else if(i==cwalRecyclerInfo.indexes[CWAL_TYPE_SCOPE]){
                    specialCaseLabel = cwal_type_id_name(CWAL_TYPE_SCOPE);
                }
                if(specialCaseLabel){
                    cwal_outputf(e, "\t(%s)\n", specialCaseLabel);
                }else{
                    int x, x2;
                    for( x2=0, x = CWAL_TYPE_UNDEF; x < CWAL_TYPE_end; ++x ){
                        switch((cwal_type_id)x){
                          case CWAL_TYPE_KVP:
                          case CWAL_TYPE_WEAK_REF:
                          case CWAL_TYPE_SCOPE:
                          case CWAL_TYPE_STRING:
                              continue;
                          default:
                              if(re->id==(int)cwal_type_id_sizeof((cwal_type_id)x)){
                                  cwal_outputf(e, "%s%s", (x2 ? ", " : "\t("),
                                               cwal_type_id_name((cwal_type_id)x));
                                  ++x2;
                              }
                        }
                    }
                    cwal_outputf(e, "%s\n", x2 ? ")" : "");
                }
            }
        }
        re = &e->reString;
        if(re->count){
            ++binCount;
            cwal_outputf(e, "strings:         %-9d%-8d%-9d %d\n",
                         (int)re->hits, (int)re->misses, re->count, re->maxLength);
            rtotal += cwal_type_id_sizeof(CWAL_TYPE_STRING) * re->count;
        }
        if(rtotal){
            cwal_outputf(e, "Currently holding %u bytes%s in "
                         "%u slot(s) in %u bin(s).\n",
                         rtotal,
                         re->count ? " (sans raw string bytes)" : "",
                         itemCount, binCount);
        }
    }

    { /* 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;
                unsigned entryCount = 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 ){
                    entryCount += h->entryCount;
                }
                grandTotal += i * pgSize;
                cwal_outputf(e, "\n%-25s %u page(s) of size %u (%u bytes) ==> %u bytes "
                             "holding %u entry(ies).\n",
                             label,
                             i, (unsigned)ptbl->hashSize, pgSize,
                             i * pgSize,
                             entryCount);
            }
            if(0==x && ptbl->pg.head){
                unsigned totalRefCount = 0;
                cwal_ptr_page const * pPage;
                cwal_value const * sv;
                cwal_string const * str;
                uint64_t size1 = 0, size2 = 0;
                for( pPage = ptbl->pg.head; pPage; pPage = pPage->next ){
                    for(i = 0; i < ptbl->hashSize; ++i ){
                        sv = (cwal_value const *)pPage->list[i];
                        str = CWAL_STR(sv);
                        if(str){
                            cwal_size_t const len = CWAL_STRLEN(str) + 1/*NUL*/;
                            size1 += len;
                            size2 += len * CWAL_REFCOUNT(sv);
                            totalRefCount += CWAL_REFCOUNT(sv);
                        }
                    }
                }
                cwal_outputf(e, "Total size of all interned strings: %"PRIu64" bytes",
                             size1);
                cwal_outputf(e, ", adjusted for %u refcounts = %"PRIu64" bytes\n",
                             totalRefCount, size2);

            }
        }
    }

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

    cwal_outputf(e, "\nAll built-in Values (sizeof(CWAL_BUILTIN_VALS)): "
                 "%u static bytes\n",
                 sizeof(CWAL_BUILTIN_VALS));
   
    if(e->buffer.capacity){
        cwal_outputf(e,"\nGeneral-purpose buffer capacity: %u bytes\n",
                     (unsigned)e->buffer.capacity);
        grandTotal += e->buffer.capacity;
    }
    if(!e->reChunk.metrics.peakChunkCount){
        cwal_outputf(e,"\nChunk recycler went unused.\n");
    }else{
        cwal_memchunk_recycler * re = &e->reChunk;
        cwal_outputf(e,"\nChunk recycler capacity=%u chunks, %u bytes.\n",
                     (unsigned)re->config.maxChunkCount,
                     (unsigned)re->config.maxTotalSize
                     );
        cwal_outputf(e,"Chunk requests: %u, Comparisons: %u, Misses: %u, "
                     "Smallest chunk: %u bytes, Largest: %u bytes\n",
                     (unsigned)re->metrics.requests,
                     (unsigned)re->metrics.searchComparisons,
                     (unsigned)re->metrics.searchMisses,
                     (unsigned)re->metrics.smallestChunkSize,
                     (unsigned)re->metrics.largestChunkSize
                     );
        cwal_outputf(e,"Reused %u chunk(s) totaling %u byte(s), "
                     "with peaks of %u chunk(s) and %u byte(s).\n",
                     (unsigned)re->metrics.totalChunksServed,
                     (unsigned)re->metrics.totalBytesServed,
                     (unsigned)re->metrics.peakChunkCount,
                     (unsigned)re->metrics.peakTotalSize
                     );
        cwal_outputf(e,"Running averages: chunk size: %u, "
                     "response size: %u\n",
                     (unsigned)re->metrics.runningAverageSize,
                     (unsigned)re->metrics.runningAverageResponseSize
                     );
        if(e->metrics.recoveredSlackCount){
            cwal_outputf(e, "Recovered %u byte(s) of \"slack\" memory "
                         "from %u block(s) via memory-capping metadata.\n",
                         (unsigned)e->metrics.recoveredSlackBytes,
                         (unsigned)e->metrics.recoveredSlackCount);
        }
        if(re->headCount){
            /* Output list of chunks... */
            cwal_memchunk_overlay * c;
            cwal_memchunk_overlay * prev = 0;
            int colCount = -1, sameSizeCount = 0, entriesPerLine = 8;
            cwal_outputf(e,"Currently contains %u chunk(s) "
                         "totaling %u byte(s).\n",
                         (unsigned)re->headCount, (unsigned)re->currentTotal
                         );
            cwal_outputf(e,"Current chunks, by size:\n");
            for( c = re->head; c; prev = c, c = c->next) {
                if(prev){
                    assert(prev->size<=c->size);
                }
                if(prev && prev->size==c->size){
                    ++sameSizeCount;
                }else{
                    if(sameSizeCount){
                        cwal_outputf( e, " (x%d)", sameSizeCount+1);
                        sameSizeCount = 0;
                    }
                    if(++colCount == entriesPerLine){
                        colCount = 0;
                        cwal_output( e, ",\n\t", 3);
                        prev = 0;
                    }else if(!prev){
                        cwal_output( e, "\t", 1);
                    }
                    cwal_outputf( e, "%s%u", prev ? ", " : "",
                                  (unsigned)c->size);
                }
            }
            if(sameSizeCount){
                cwal_outputf( e, " (x%d)", sameSizeCount+1);
            }
            cwal_outputf(e,"\n");
        }
    }
    if(CWAL_ENABLE_BUILTIN_LEN1_ASCII_STRINGS)
    { /* e->metrics.len1StringsSaved[...] */
        int i, showTotals = 0;
        cwal_size_t len1TotalCount = 0;
        cwal_size_t len1TotalMem = 0;
        cwal_size_t const sizeOfStringBase
            = sizeof(cwal_value) + sizeof(cwal_string)
            /**
               The real alloced size for normal stings depends on
               CwalConsts.StringPadSize.
            */;
        cwal_outputf(e, "\nLength-1 ASCII string optimization "
                     "savings...");
        for( i = 0; i < 3; ++i ){
            cwal_size_t len1This;
            cwal_size_t sz;
            len1This = e->metrics.len1StringsSaved[i];
            if(len1This) ++showTotals;
            len1TotalCount += len1This;
            sz = (unsigned)len1This
                * (sizeOfStringBase
                   + (i
                      ? (unsigned)sizeof(unsigned char **)/*x-/z-strings*/
                      : (unsigned)CwalConsts.StringPadSize/*normal strings*/));
            len1TotalMem += sz;
            cwal_outputf(e,"\n    %13s:\t%u alloc(s), %u bytes",
                         (0==i ? "plain strings"
                          : (1==i ? "x-strings" : "z-strings")),
                         (unsigned)len1This,
                         (unsigned)sz);
        }
        if(showTotals>1){
            if(len1TotalMem){
                cwal_outputf(e,"\nTotal savings: %u alloc(s), %u bytes\n",
                             (unsigned)len1TotalCount,
                             (unsigned)len1TotalMem);
            }else{
                cwal_outputf(e,"\n\tnone :`(\n");
            }
        }else{
            cwal_outputf(e,"\n");
        }
    }
    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,"was not allocated by cwal_engine_init(). "
                     "sizeof(cwal_engine)=%u\n",
                     (unsigned)sizeof(cwal_engine));
    }


    if(CWAL_F_TRACK_MEM_SIZE & e->flags){
        cwal_outputf(e,"\nMemory size tracking/capping enabled:\n");
#define OUT(OPT) cwal_outputf(e,"\tvtab->memcap.%s = %u\n", #OPT, (unsigned)e->vtab->memcap.OPT)
        OUT(maxTotalAllocCount);
        OUT(maxTotalMem);
        OUT(maxConcurrentAllocCount);
        OUT(maxConcurrentMem);
        OUT(maxSingleAllocSize);
#undef OUT
#define OUT(OPT) cwal_outputf(e,"\tengine->memcap.%s = %u\n", #OPT, (unsigned)e->memcap.OPT)
        OUT(currentMem);
        OUT(currentAllocCount);
        OUT(peakMem);
        OUT(peakAllocCount);
        OUT(totalAllocCount);
        OUT(totalMem);
#undef OUT
        cwal_outputf(e,
                     "\tOver-allocation overhead =\n"
                     "\t\tengine->memcap.totalAllocCount (%u) "
                     "* sizeof(void*) (%u) =\n"
                     "\t\t%u bytes\n",
                     (unsigned)e->memcap.totalAllocCount,
                     (unsigned)sizeof(void*),
                     (unsigned)(e->memcap.totalAllocCount * sizeof(void*)));
        grandTotal += e->memcap.totalAllocCount * sizeof(void*);
    }


    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;
    unsigned totalRefCount = 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 #%d: entry count=%"PRIu16"\n",
                      (int)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)CWAL_REFCOUNT(v));
            f( outState, buf, RC );
            totalRefCount += CWAL_REFCOUNT(v);
            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 #%d (%"PRIu16" entry(ies))\n",
                          (int)i+1, p->entryCount );
            f( outState, buf, RC );
        }
    }
    rc = sprintf(buf, "  Total entry count=%"PRIu32", with %u refcount point(s).\n",
                 total, totalRefCount);
    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, CWAL_REFCOUNT(v));
        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);
#else
    if(ev || filePtr){/*avoid unused param warning*/}
#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
    if(e || mask){/*avoid unused param warning*/}
    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);
#else
    if(e){/*avoid unused param warning*/}
#endif
}


char const * cwal_rc_cstr(int rc)
{
    char const * s = cwal_rc_cstr2(rc);
    return s ? s : "Unknown result code";
}

char const * cwal_rc_cstr2(int rc)
{
    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_IS_VISITING);
        C(CWAL_RC_IS_VISITING_LIST);
        C(CWAL_RC_DISALLOW_NEW_PROPERTIES);
        C(CWAL_RC_DISALLOW_PROP_SET);
        C(CWAL_RC_DISALLOW_PROTOTYPE_SET);
        C(CWAL_RC_CONST_VIOLATION);
        C(CWAL_RC_LOCKED);
        
        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);

        C(CWAL_RC_CLIENT_BEGIN);
      default: return 0;
    }
#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->values.prototypes, (cwal_size_t)t, proto );
    }
}

cwal_value * cwal_prototype_base_get( cwal_engine * e, cwal_type_id t ){
    if(!e || !e->values.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->values.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 0
    cwal_engine * e;
#endif
    if((prototype && !chPro) || CWAL_MEM_IS_BUILTIN(v)){
        return CWAL_RC_TYPE;
    }else if(!child || (child == chPro)){
        return CWAL_RC_MISUSE;
    }
    if(CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET & child->containerFlags){
        return CWAL_RC_DISALLOW_PROTOTYPE_SET;
    }
    if( prototype == child->prototype ){
        return 0;
    }
#if 0
    e = v->scope ? v->scope->e : (prototype->scope ? prototype->scope->e : 0);
    assert(e);
#endif
    if(prototype){
        /* Ensure that v is nowhere in prototype's chain. */
        /*
          TODO: Strictly speaking, there's little reason to disallow
          non-containers as prototypes.

          Maybe refactor to use cwal_prototype_base_get() for
          non-containers and allow non-container prototypes.
        */
        for( pCheck = prototype; pCheck ; ){
            if(pCheck == v) return CWAL_RC_CYCLES_DETECTED;
            pBase = CWAL_VOBASE(pCheck);
            pCheck = (pBase?pBase->prototype:NULL);
        }
        cwal_value_ref2( v->scope->e, prototype );
        cwal_value_rescope( v->scope, prototype );
    }
    if(child->prototype){
        cwal_value_unref/* _from */(/* child->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_object * cwal_value_object_part( cwal_engine * e,
                                      cwal_value * v ){
    cwal_object * obj;
    do{
        if( (obj = CWAL_OBJ(v)) ) return obj;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_buffer * cwal_value_buffer_part( cwal_engine * e,
                                      cwal_value * v ){
    cwal_buffer_obj * f;
    do{
        if( (f = CWAL_BUFOBJ(v)) ) return &f->buf;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_native * cwal_value_native_part( cwal_engine * e,
                                      cwal_value * v,
                                      void const * typeID){
    cwal_native * n;
    do{
        if( (n = cwal_value_get_native(v))
            && (!typeID || typeID==n->typeID) ){
            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 ){
    cwal_function * f;
    do{
        if( (f = cwal_value_get_function(v)) ) 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 ){
    cwal_array * a;
    do{
        if( (a = CWAL_ARRAY(v)) ) return a;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

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

cwal_hash * cwal_value_hash_part( cwal_engine * e,
                                  cwal_value * v ){
    cwal_hash * f;
    do{
        if( (f = CWAL_HASH(v)) ) 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 ){
    cwal_string * f;
    do{
        if( (f = CWAL_STR(v)) ) return f;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_value * cwal_value_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 )
{
    assert(e);
    assert(self);
    if( !e || !self ) return 0;
    else if(0 == n)
    {
        if(0 == self->alloced) return 0;
        cwal_memchunk_add(e, self->list, self->alloced * sizeof(void*));
        self->list = NULL;
        self->alloced = self->count = 0;
        return 0;
    }
    if( self->alloced >= n ){
        return self->alloced;
    }
    METRICS_REQ_INCR(e,CWAL_TYPE_LISTMEM);
    if(!self->list){
        /* Check the e->reChunk recycler... */
        cwal_size_t reqSize = n * sizeof(void*);
        void** m = (void**)cwal_memchunk_request( e, &reqSize, 1000,
                                                  "cwal_list_reserve(n)");
        if(m){
            self->alloced = reqSize/sizeof(void*);
            self->list = m;
            assert(self->alloced >= n);
            /*MARKER(("list recycler got: n=%d, reqSize=%d, self->alloced=%d\n",
              (int)n, (int)reqSize, (int)self->alloced));*/
            return self->alloced;
        }
        /* else fall through... */
    }
    {
        size_t const sz = sizeof(void*) * n;
        cwal_size_t i;
        void ** m = (void**)cwal_realloc( e, self->list, sz );
        if( ! m ) return self->alloced;
#if 0
        memset( m + self->alloced, 0, (sizeof(void*)*(n-self->alloced)));
#else
        for( i = self->count; i < n; ++i ) m[i] = NULL;
#endif
        ++e->metrics.allocated[CWAL_TYPE_LISTMEM]
            /* This isn't _quite_ pedantically true, as it counts
               reallocs as allocs, and we don't _really_ know if the
               system actually had to realloc the memory, but it'll
               do. We "could" only increment this is self->alloced==0,
               but that would also not be quite right. Better to err
               on the side of reporting more allocs (via realloc)
               here. */;
        e->metrics.bytes[CWAL_TYPE_LISTMEM] += (n - self->alloced) * sizeof(void*);
        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, int 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, int 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){
    cwal_size_t sLen;
    if(!e) return NULL;
    else if(!arg) return cwal_value_null();
    sLen = cwal_strlen(arg);
    if((('0'>*arg) || ('9'<*arg)) && ('+'!=*arg) && ('-'!=*arg)){
        goto do_string;
    }
    else{ /* try numbers... */
        cwal_int_t nI = 0;
        cwal_double_t nD = 0.0;
        if(0==cwal_cstr_to_int(arg, sLen, &nI)){
            return cwal_new_integer(e, (cwal_int_t)nI);
        }
#if 1
        /* cwal_cstr_to_double() might have some overflow-related
           problems (not certain - we've never made heavy use of
           doubles in client code!). If so, we can fall back
           to strtod(). */
        else if(0==cwal_cstr_to_double(arg, sLen, &nD)){
            return cwal_new_double(e, nD);
        }
#else
        else {
            char const * end = 0;
            double const val = strtod(arg, &end);
            if(!*end){
                return cwal_new_double(e, val);
            }
        }
#endif
        /* fall through and treat it like a string... */
    }
    do_string:
    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();
    else if(9==sLen && 'u'==*arg && 0==strcmp("undefined",arg)) return cwal_value_undefined();
    else if(sLen>1 && (('\''==*arg && '\''==arg[sLen-1])
                       ||('"'==*arg && '"'==arg[sLen-1]))){
      /* trim quote marks */
      ++arg;
      sLen -= 2;
    }
    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;
    cwal_array * tgtArray = 0;
    char const allocateTgt = (tgt && *tgt) ? 0 : 1;
    int rc = 0;
    int i = 0;
    if(!e || !tgt) return CWAL_RC_MISUSE;
    else if(*tgt && !cwal_props_can(*tgt)) return CWAL_RC_MISUSE;
    else if(argc<0) return CWAL_RC_RANGE;

    flags = cwal_new_object_value(e);
    if(!flags) return CWAL_RC_OOM;
    cwal_value_ref(flags);
    cwal_value_prototype_set(flags, 0);
    nonFlags = cwal_new_array(e);
    if(!nonFlags) {
        cwal_value_unref(flags);
        return CWAL_RC_OOM;
    }
    cwal_value_ref(cwal_array_value(nonFlags));
    o = allocateTgt ? cwal_new_object_value(e) : *tgt;
    if(!o){
        cwal_value_unref(flags);
        cwal_value_unref(cwal_array_value(nonFlags));
        return CWAL_RC_OOM;
    }
    if(allocateTgt) cwal_value_ref(o);
    else{
        tgtArray = cwal_value_get_array(o);
        if(tgtArray){
            cwal_array_reserve( tgtArray,
                                cwal_array_length_get(tgtArray)
                                + (unsigned)argc );
        }
    }
    rc = cwal_prop_set(o, "flags", 5, flags);
    if(rc){
        goto end;
    }
    rc = cwal_prop_set(o, "nonFlags", 8, cwal_array_value(nonFlags));
    if(rc){
        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;
        char invertFlag = 0;
        if(!arg) continue;
        else if(tgtArray){
            v = cwal_new_string_value(e, arg, cwal_strlen(arg));
            if(v){
                cwal_value_ref(v);
                rc = cwal_array_append(tgtArray, v);
                cwal_value_unref(v);
            }else{
                rc = CWAL_RC_OOM;
            }
            if(rc) break;
        }        
        if('+' == *arg){
            invertFlag = 1;
            ++key;
        }
        else if('-' != *arg){
            v = cwal_new_string_value(e, arg, cwal_strlen(arg));
            if(!v){
                rc = CWAL_RC_OOM;
                break;
            }
            cwal_value_ref(v);
            rc = cwal_array_append(nonFlags, v);
            cwal_value_unref(v);
            if(rc) 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;
        }
        cwal_value_ref(k);
        if(!*pos){ /** --key or +key */
            v = invertFlag
                ? cwal_value_false()
                : cwal_value_true();
        }else{ /** --key=...*/
            assert('=' == *pos);
            ++pos /*skip '='*/;
            v = cwal_value_from_arg(e, pos);
            if(!v){
                cwal_value_unref(k);
                rc = CWAL_RC_OOM;
                break;
            }
        }
        cwal_value_ref(v);
        rc=cwal_prop_set_v(flags, k, v);
        cwal_value_unref(k);
        cwal_value_unref(v);
        if(rc) break;
    }
    end:
    /* On success, o holds refs to flags/nonFlags via
       its properties. */
    cwal_value_unref(cwal_array_value(nonFlags));
    cwal_value_unref(flags);
    if(rc){
        if(allocateTgt) cwal_value_unref(o);
        else assert(o == *tgt);
    }else{
        if(allocateTgt){
            *tgt = o;
            cwal_value_unhand(o);
        }else{
            assert(o == *tgt);
        }
    }
    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;
        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 ){
            CWAL_UNUSED_VAR char const 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 ){
    return v->scope
        ? CWAL_V_IS_VACUUM_SAFE(v)
        : CWAL_MEM_IS_BUILTIN(v);
}

int cwal_value_make_vacuum_proof( cwal_value * v, char yes ){
    if(!v) return CWAL_RC_MISUSE;
    else if(CWAL_MEM_IS_BUILTIN(v)) return 0;
    else if(yes && CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_VACUUM_PROOF)){
        return 0;
    }else if(!yes && !CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_VACUUM_PROOF)){
        return 0;
    }else{
        cwal_scope * s = v->scope;
        assert(s);
        if(yes) CWAL_RCFLAG_ON(v, CWAL_RCF_IS_VACUUM_PROOF);
        else CWAL_RCFLAG_OFF(v, CWAL_RCF_IS_VACUUM_PROOF);
        return cwal_scope_insert(s, v);
    }
}

int cwal_scope_vacuum( cwal_scope * s, int * sweepCount ){
    cwal_engine * e = s->e;
    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 = s->props;
#ifdef DEBUG
        cwal_obase * obase = CWAL_VOBASE(pv);
        assert(obase->flags & CWAL_F_IS_PROP_STORAGE);
#endif
        cwal_value_rescope( &s2, pv );
        assert(pv->scope == &s2);
        s2.props = s->props;
        s->props = 0 /* transfer ref point */;
    }
    while(s->mine.headSafe){
#ifdef DEBUG
        cwal_value const * vcheck = s->mine.headSafe;
#endif
        cwal_value_rescope( &s2, s->mine.headSafe );
#ifdef DEBUG
        assert(s->mine.headSafe != vcheck);
#endif
    }
    /* 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_rescope( s, s2.props );
        assert(s2.props->scope == s);
        s->props = s2.props;
        s2.props = 0 /* transfer ref point */;
    }
    while(s2.mine.headSafe){
#ifdef DEBUG
        cwal_value const * vcheck = s2.mine.headSafe;
#endif
        cwal_value_rescope( s, s2.mine.headSafe );
#ifdef DEBUG
        assert(s2.mine.headSafe != vcheck);
#endif
    }

#if 0
    if(e->values.propagating && e->values.propagating->scope==&s2){
        cwal_value_rescope(s, e->values.propagating);
    }
    if(e->values.exception && e->values.exception->scope==&s2){
        cwal_value_rescope(s, e->values.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;
}

int cwal_engine_vacuum( cwal_engine * e, int * sweepCount ){
    cwal_scope * s = e ? e->current : 0;
    if(!e || !s) return CWAL_RC_MISUSE;
    return cwal_scope_vacuum( s, sweepCount );
}

cwal_flags16_t cwal_container_flags_set( cwal_value * v, cwal_flags16_t flags ){
    cwal_obase * b = CWAL_VOBASE(v);
    if(!b) return 0;
    else{
        cwal_flags16_t const rc = b->containerFlags;
        b->containerFlags = flags;
        return rc;
    }
}

cwal_flags16_t cwal_container_flags_get( cwal_value const * v ){
    cwal_obase const * b = CWAL_VOBASE(v);
    return b ? b->containerFlags : 0;
}

cwal_flags16_t cwal_container_client_flags_set( cwal_value * v, cwal_flags16_t flags ){
    cwal_obase * b = CWAL_VOBASE(v);
    if(!b) return 0;
    else{
        cwal_flags16_t const rc = b->clientFlags;
        b->clientFlags = flags;
        return rc;
    }
}

cwal_flags16_t cwal_container_client_flags_get( cwal_value const * v ){
    cwal_obase const * b = CWAL_VOBASE(v);
    return b ? b->clientFlags : 0;
}



/**
   Internal implementation details for cwal_printfv_appender_stringbuf.
*/
typedef struct cwal_printfv_stringbuf
{
    /** dynamically allocated buffer */
    char * buffer;
    /** bytes allocated to buffer */
    cwal_size_t alloced;
    /** Current position within buffer. */
    cwal_size_t pos;
    cwal_engine * e;
    /* TODO: replace the above bits with cwal_buffer */
} cwal_printfv_stringbuf;
static const cwal_printfv_stringbuf cwal_printfv_stringbuf_init = { 0, 0, 0, 0 /* TODO:, cwal_buffer_empty_m*/ };

/**
   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 *)cwal_realloc(sb->e, 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( cwal_engine * e, 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 )
        {
            cwal_free2( e, sb.buffer, (cwal_size_t)sb.alloced );
            sb.buffer = 0;
        }
        return sb.buffer;
    }
}

char * cwal_printf_cstr( cwal_engine * e, char const * fmt, ... )
{
    va_list vargs;
    char * ret;
    va_start( vargs, fmt );
    ret = cwal_printfv_cstr( e, fmt, vargs );
    va_end( vargs );
    return ret;
}


int const * cwal_first_1000_primes(){
    static const int first1000Primes[1001] = {
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
    53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
    127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197,
    199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
    283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379,
    383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
    467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571,
    577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
    661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761,
    769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
    877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977,
    983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
    1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187,
    1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
    1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427,
    1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
    1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613,
    1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733,
    1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867,
    1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
    1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087,
    2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213,
    2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333,
    2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
    2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557,
    2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687,
    2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789,
    2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
    2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037,
    3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181,
    3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307,
    3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
    3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539,
    3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643,
    3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769,
    3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907,
    3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019,
    4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139,
    4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261,
    4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409,
    4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523,
    4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657,
    4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793,
    4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937,
    4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039,
    5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179,
    5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323,
    5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443,
    5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569,
    5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693,
    5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827,
    5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939,
    5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091,
    6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221,
    6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337,
    6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473,
    6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619,
    6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761,
    6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871,
    6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997,
    7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151,
    7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297,
    7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459,
    7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561,
    7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687,
    7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829,
    7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919,
    0
#if 0
    , 0 /* causes an "excess elements in initializer" error if
           we've counted properly above. */
#endif
    };
    return first1000Primes;
}

cwal_build_info_t const * cwal_build_info(){
    static const cwal_build_info_t info = {

    /* cwal_size_t's */
    CWAL_SIZE_T_BITS,
    CWAL_INT_T_BITS,
    CWAL_STRLEN_MASK /* maxStringLength */,

    /* strings... */
    CWAL_VERSION_STRING,
    CWAL_CPPFLAGS,
    CWAL_CFLAGS,
    CWAL_CXXFLAGS,

    /* booleans... */

    CWAL_ENABLE_JSON_PARSER/* isJsonParserEnabled */,
#if defined(DEBUG)
    /* isDebug */
    1,
#else
    0,
#endif
    {/* sizeofs... */
        sizeof(CWAL_BUILTIN_VALS),
        sizeof(cwal_value),
        sizeof(void*)
    }
    };
    return &info;
}

cwal_value * cwal_build_info_object(cwal_engine * e){
    cwal_value * obj;
    cwal_value * setter;
    cwal_value * v;
    int rc;
    cwal_build_info_t const * const bi = cwal_build_info();
    char const * key;
    if(!e || !e->current) return 0;
    assert(e);
    obj = cwal_new_object_value(e);
    if(!obj) goto fail;
    cwal_value_ref(obj);
    setter = obj;
    
#define SET1                                                \
    if(!v) goto fail;                                       \
    cwal_value_ref(v);                                      \
    rc = cwal_prop_set(setter, key, cwal_strlen(key), v);   \
    cwal_value_unref(v);                                    \
    v = 0;                                                  \
    if(rc) goto fail

    /*************************************************************
      Numeric members...
    */
#define SET_INT(NAME,MEMBER)                                    \
    v = cwal_new_integer(e, (cwal_int_t) bi->MEMBER);           \
    key = (char const *)NAME ? (char const *)NAME : #MEMBER;   \
    SET1

    SET_INT(0,size_t_bits);
    SET_INT(0,int_t_bits);
    SET_INT(0,maxStringLength);

    /*************************************************************
       Boolean members...
    */
#define SET_BOOL(NAME,MEMBER)                                   \
    v = bi->MEMBER ? cwal_value_true() : cwal_value_false();    \
    key = NAME ? NAME : #MEMBER;                                \
    SET1

    SET_BOOL(0,isJsonParserEnabled);
    SET_BOOL(0,isDebug);

    /*************************************************************
      String members...
    */
#define SET_STR(NAME,MEMBER)                                            \
    v = cwal_new_string_value(e, bi->MEMBER, cwal_strlen(bi->MEMBER));  \
    key = NAME ? NAME : #MEMBER;                                        \
    SET1

    SET_STR(0,versionString);
    SET_STR(0,cppFlags);
    SET_STR(0,cFlags);
    SET_STR(0,cxxFlags);

    /*************************************************************
      sizeofs...
    */
    {
        cwal_value * so = cwal_new_object_value(e);
        if(!so) goto fail;
        cwal_value_ref(so);
        rc = cwal_prop_set(obj, "sizeofs", 7, so);
        cwal_value_unref(so);
        if(rc) goto fail;
        setter = so;
#define SO(X) SET_INT(#X, sizeofs.X)
        SO(voidPointer);
        SO(builtinValues);
        SO(cwalValue);
#undef SO
    }
    
#undef SET1
#undef SET_INT
#undef SET_BOOL
#undef SET_STR
    cwal_value_unhand(obj);
    return obj;
    fail:
    cwal_value_unref(obj);
    return 0;
}

int cwal_callback_f_build_info(cwal_callback_args const * args, cwal_value ** rv){
    return (*rv = cwal_build_info_object(args->engine))
        ? 0
        : CWAL_RC_OOM;
}

enum {
/**
   Indicates that one of the cwal_visit_XXX_begin() routines
   set a flag on its operand value.
*/
visitOpaqueMarkerYes = 0x01234567,
/**
   Indicates that one of the cwal_visit_XXX_begin() routines
   did not set a flag on its operand value.
*/
visitOpaqueMarkerNo =  0x76543210
};

void cwal_visit_props_begin( cwal_value * const v, int * const opaque ){
#ifdef DEBUG
    cwal_obase const * const ob = CWAL_VOBASE(v);
    assert(ob && "Invalid use of cwal_visit_props_begin()");
#endif
    assert(v);
    assert(opaque);
    if(CWAL_V_IS_VISITING(v)){
        *opaque = visitOpaqueMarkerNo;
    }else{
        *opaque = visitOpaqueMarkerYes;
        CWAL_RCFLAG_ON(v,CWAL_RCF_IS_VISITING);
    }
}

void cwal_visit_props_end( cwal_value * const v, int opaque ){
#ifdef DEBUG
    cwal_obase const * const ob = CWAL_VOBASE(v);
    assert(ob && "Invalid use of cwal_visit_props_end()");
#endif
    assert(v);
    assert(visitOpaqueMarkerYes==opaque || visitOpaqueMarkerNo==opaque);
    if(visitOpaqueMarkerYes==opaque){
        CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_VISITING);
    }
}

void cwal_visit_list_begin( cwal_value * const v, int * const opaque ){
    assert(v);
    assert(CWAL_TYPE_TUPLE==v->vtab->typeID
           || CWAL_TYPE_ARRAY==v->vtab->typeID
           || CWAL_TYPE_HASH==v->vtab->typeID);
    assert(opaque);
    if(CWAL_V_IS_VISITING_LIST(v)){
        *opaque = visitOpaqueMarkerNo;
    }else{
        *opaque = visitOpaqueMarkerYes;
        CWAL_RCFLAG_ON(v,CWAL_RCF_IS_VISITING_LIST);
    }
}

void cwal_visit_list_end( cwal_value * const v, int opaque ){
    assert(v);
    assert(CWAL_TYPE_TUPLE==v->vtab->typeID
           || CWAL_TYPE_ARRAY==v->vtab->typeID
           || CWAL_TYPE_HASH==v->vtab->typeID);
    assert(visitOpaqueMarkerYes==opaque || visitOpaqueMarkerNo==opaque);
    if(visitOpaqueMarkerYes==opaque){
        CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_VISITING_LIST);
    }
}

int cwal_visit_acyclic_begin( cwal_value * v, int * opaque ){
    assert(v);
    assert(opaque);
    switch(v->vtab->typeID){
      case CWAL_TYPE_ARRAY:
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_EXCEPTION:
      case CWAL_TYPE_TUPLE:
          if(CWAL_RCFLAG_HAS(v, CWAL_RCF_IS_VISITING_ACYCLIC)){
              *opaque = visitOpaqueMarkerNo;
              return CWAL_RC_CYCLES_DETECTED;
          }else{
              CWAL_RCFLAG_ON(v, CWAL_RCF_IS_VISITING_ACYCLIC);
              *opaque = visitOpaqueMarkerYes;
              return 0;
          }
          break /* not reached */;
      default:
          *opaque = visitOpaqueMarkerNo;
          return 0;
    }
}

void cwal_visit_acyclic_end( cwal_value * v, int opaque ){
    assert(v);
    assert(visitOpaqueMarkerYes==opaque || visitOpaqueMarkerNo==opaque);
    if(visitOpaqueMarkerYes==opaque){
        CWAL_RCFLAG_OFF(v,CWAL_RCF_IS_VISITING_ACYCLIC);
    }
}

#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 CWAL_MEM_IS_BUILTIN
#undef V_SEEMS_OK
#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_STR_ASCII_MASK
#undef CWAL_STR_ISASCII
#undef CWAL_KVP_TRY_SORTING
#undef TRY_CLONE_SHARING
#undef COMPARE_TYPE_IDS
#undef CWAL_V_IS_VISITING
#undef CWAL_V_IS_VISITING_LIST
#undef METRICS_REQ_INCR
#undef CWAL_V_IS_VACUUM_SAFE
#undef CWAL_ALLOC_DO_PAD
#undef CWAL_MEMSZ_PAD
#undef CWAL_UNIQUE_VAL
#undef MEMSZ_PTR_FROM_MEM
#undef E_IS_DEAD

#undef CWAL_RCFLAG_MAXRC
#undef CWAL_RCFLAGS_BITS
#undef CWAL_RCFLAGS_BITS_MASK
#undef CWAL_REFCOUNT
#undef CWAL_REFCOUNT_SHIFTED
#undef CWAL_RCFLAGS
#undef CWAL_RCADJ
#undef CWAL_RCDECR
#undef CWAL_RCINCR
#undef CWAL_RCFLAG_ON
#undef CWAL_RCFLAG_ON
#undef CWAL_RCFLAG_OFF
#undef CWAL_RCFLAG_HAS
#undef CWAL_V_IS_IN_CLEANUP
#undef CWAL_VVPCAST_NONULL
#undef CWAL_DBL_NONULL
#undef CWAL_INT_NONULL
#undef CWAL_STRLEN_TOO_LONG
#undef CWAL_BUILTIN_INT_VAL
#undef CWAL_BUILTIN_INT_FIRST
#undef CWAL_BUILTIN_INT_LAST
#undef CWAL_BUILTIN_INT_COUNT
#undef CWAL_V_GOES_IN_HEADOBJ
#undef CWAL_OB_LOCK
#undef CWAL_OB_IS_LOCKED
#undef CWAL_OB_UNLOCK


#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_URLENCODE,
CWAL_FMT_URLDECODE,
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;
};

#define cwalFormatNoPrecision ((int)0x7FFFFFFF)

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 int cwal_format_part_urlenc( cwal_engine * e, cwal_buffer * tgt,
                                    cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_urldec( 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),
FMT(URLENCODE,urlenc),
FMT(URLDECODE,urldec),
#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*/,
cwalFormatNoPrecision/*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 = CWAL_INT_T_BITS * 2 };
    char fmtBuf[BufSize+1] = {'%',0};
    char * b = fmtBuf + 1;
    int rc;
    cwal_int_t const i = cwal_value_get_integer(v);
    char const * intFmt = 0;
    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(fi->precision && cwalFormatNoPrecision!=fi->precision){
        b += sprintf(b, ".%d", fi->precision);
    }
    switch(fi->type){
      case CWAL_FMT_INT_DEC:
          intFmt = CWAL_INT_T_PFMT;
          break;
      case CWAL_FMT_INT_HEXLC:
          intFmt = CWAL_INT_T_PFMTx;
          break;
      case CWAL_FMT_INT_HEXUC:
          intFmt = CWAL_INT_T_PFMTX;
          break;
      case CWAL_FMT_INT_OCTAL:
          intFmt = CWAL_INT_T_PFMTo;
          break;
      default:
          assert(!"CANNOT HAPPEN");
          return CWAL_RC_RANGE;
    }
    assert(intFmt);
    {
        cwal_size_t const fmtLen = cwal_strlen(intFmt);
        assert(b + fmtLen <= (fmtBuf + BufSize));
        memcpy(b, intFmt, (size_t)fmtLen);
        b += fmtLen;
        *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 && cwalFormatNoPrecision != 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 || cwalFormatNoPrecision == 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_strlen_utf8( cstr, slen );
    }
    if((cwalFormatNoPrecision != fi->precision)
       && (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((cwalFormatNoPrecision != fi->precision)
            && (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 = fi->width;
    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]));
        assert(slen && slen<5);
        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 = (cwalFormatNoPrecision == fi->precision)
            ? 1
            : ((fi->precision>=0) ? fi->precision : 0)
            ;
        /* 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;
    }
}

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

int cwal_format_part_urlenc( cwal_engine * e,
                             cwal_buffer * tgt,
                             cwal_format_info * fi,
                             cwal_value const * v ){
    cwal_size_t slen = 0;
    char const * str = cwal_value_get_cstr(v, &slen);
    char ch;
    int rc = 0;
    cwal_size_t j = 0;
    char * bufpt = NULL;
    cwal_size_t const oldUsed = e->buffer.used;
    static char const * hex = "0123456789ABCDEF";
    if(!str){
        fi->errMsg = "URL encoding requires a String value.";
        return CWAL_RC_TYPE;
    }
    rc = cwal_buffer_reserve(e, &e->buffer, oldUsed + slen * 3 );
    if(rc) return rc;
    bufpt = (char *)(e->buffer.mem + oldUsed);
    ch = *str;
    for( ; !rc && ch; ch = *(++str) ){
        if(!cwal_httpurl_needs_escape(ch) ){
            bufpt[j++] = ch;
        }else{
            bufpt[j++] = '%';
            bufpt[j++] = hex[((ch>>4)&0xf)];
            bufpt[j++] = hex[(ch&0xf)];
        }
    }
    assert( (void *)bufpt == (void*)e->buffer.mem );
    rc = cwal_buffer_append(e, tgt, bufpt, j);
    e->buffer.used = oldUsed;
    e->buffer.mem[e->buffer.used] = 0;
    return 0;
}

/* 
   cwal_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 cwal_hexchar_to_int( 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 cwal_is_xdigit( int ch ){
  return ('a'<=ch && 'f'>=ch)
    ? 1
    : (('A'<=ch && 'F'>=ch)
       ? 1
       : ('0'<=ch && '9'>=ch ? 1 : 0));
}

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

    cwal_size_t slen = 0;
    char const * str = cwal_value_get_cstr(v, &slen);
    int rc = 0;
    char ch = 0, ch2 = 0;
    char xbuf[4];
    int decoded;
    if(!str){
        fi->errMsg = "URL decoding requires a String value.";
        return CWAL_RC_TYPE;
    }
    else if(!slen) return 0;
    else if(slen<3){
        /* can't be urlencoded */
        return cwal_buffer_append(e, tgt, str, slen);
    }
    ch = *str;
    while( !rc && ch ){
        if( ch == '%' ){
            ch = *(++str);
            ch2 = *(++str);
            if( cwal_is_xdigit((int)ch) &&
                cwal_is_xdigit((int)ch2) ){
                decoded = (cwal_hexchar_to_int( ch ) * 16)
                    + cwal_hexchar_to_int( ch2 );
                xbuf[0] = (char)decoded;
                xbuf[1] = 0;
                rc = cwal_buffer_append(e, tgt, xbuf, 1);
                ch = *(++str);
                continue;
            }
            else{
                xbuf[0] = '%';
                xbuf[1] = ch;
                xbuf[2] = ch2;
                xbuf[3] = 0;
                rc = cwal_buffer_append(e, tgt, xbuf, 3);
                ch = *(++str);
                continue;
            }
        }
        else if( ch == '+' ){
            xbuf[0] = ' ';
            xbuf[1] = 0;
            rc = cwal_buffer_append(e, tgt, xbuf, 1);
            ch = *(++str);
            continue;
        }
        xbuf[0] = ch;
        xbuf[1] = 0;
        rc = cwal_buffer_append(e, tgt, xbuf, 1);
        ch = *(++str);
    }
    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;
      case 'r': fi->type = CWAL_FMT_URLENCODE;
          ++p;
          break;
      case 'R': fi->type = CWAL_FMT_URLDECODE;
          ++p;
          break;
      default:
          fi->errMsg = "Unknown format type specifier.";
          return CWAL_RC_RANGE;
    }
    switch(fi->type){
      case CWAL_FMT_URLENCODE:
      case CWAL_FMT_URLDECODE:
          if(fi->width){
              fi->errMsg = "Conversion does not support width.";
              return CWAL_RC_MISUSE;
          }
          CWAL_SWITCH_FALL_THROUGH;
      case CWAL_FMT_INT_DEC:
      case CWAL_FMT_INT_HEXLC:
      case CWAL_FMT_INT_HEXUC:
          if(fi->precision && cwalFormatNoPrecision != fi->precision){
              fi->errMsg = "Conversion does not support precision.";
              return CWAL_RC_MISUSE;
          }
          break;
      case CWAL_FMT_CHAR:
          if(fi->precision<0){
              fi->errMsg = "Character precision (repetition) may not be negative.";
              return CWAL_RC_MISUSE;
          }
          break;
      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
#undef cwalFormatNoPrecision
/* 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 */
#ifdef _WIN32
typedef __int64 JSON_int_t;
#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%I64d"
#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%I64d"
#elif (__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 JSON_value* 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 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


#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 = 13 * 2
        /* gcc 7.3 is misdiagnosing ubuf (below) as being "between 3
           and 5" bytes in one sprintf() call, so we have to inflate
           UBLen beyond what we really need. */
        };
        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);
                if(ch <= 0xFFFF){
                    rc = sprintf(ubuf, "\\u%04x",ch);
                    if( rc != 6 )
                    {
                        rc = CWAL_RC_RANGE;
                        break;
                    }
                    rc = f( state, ubuf, 6 );
                }else{ /* encode as a UTF16 surrugate pair */
                    /* http://unicodebook.readthedocs.org/en/latest/unicode_encodings.html#surrogates */
                    ch -= 0x10000;
                    rc = sprintf(ubuf, "\\u%04x\\u%04x",
                                 (0xd800 | (ch>>10)),
                                 (0xdc00 | (ch & 0x3ff)));
                    if( rc != 12 )
                    {
                        rc = CWAL_RC_RANGE;
                        break;
                    }
                    rc = f( state, ubuf, 12 );
                }
                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,
                                    cwal_json_output_opt const * opt,
                                    unsigned int depth )
{
    if(!opt->indent && (!opt->indentString.str || !opt->indentString.len)){
        return 0;
    }
    else if(opt->indentString.str){
        int i;
        int rc = f(state, "\n", 1 );
        assert(opt->indentString.len);
        for( i = 0; (i < (int)depth) && (0 == rc); ++i ){
            rc = f(state, opt->indentString.str,
                   opt->indentString.len);
        }
        return rc;
    }else{
        int blanks = opt->indent;
        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 );
static int cwal_json_output_tuple( 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 = 128};
    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>60U ? 60U : 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 = 0;
    cwal_type_id const tid = cwal_value_type_id( src );
    switch( tid ){
      case CWAL_TYPE_UNDEF:
#if 0
          rc = f( state, "undefined", 9);
          break;
          /* We should arguably elide this values - that's what JS's
             standard impl does. */
#endif
          CWAL_SWITCH_FALL_THROUGH /* transform it 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:
      case CWAL_TYPE_TUPLE:
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_EXCEPTION:
      case CWAL_TYPE_OBJECT:{
          int opaque;
          rc = cwal_visit_acyclic_begin(src, &opaque);
          if(rc){
              if(fmt->cyclesAsStrings){
                  rc = cwal_json_cycle_string(src, f, state, fmt);
              }
              break;
          }
          switch(tid){
            case CWAL_TYPE_ARRAY:
                rc = cwal_json_output_array( src, f, state, fmt, level );
                break;
            case CWAL_TYPE_TUPLE:
                rc = cwal_json_output_tuple( src, f, state, fmt, level );
                break;
            case CWAL_TYPE_FUNCTION:
                if(!fmt->functionsAsObjects){
                    rc = CWAL_RC_TYPE;
                    break;
                }
                CWAL_SWITCH_FALL_THROUGH;
            case CWAL_TYPE_EXCEPTION:
            case CWAL_TYPE_OBJECT:
                rc = cwal_json_output_obase( src, f, state, fmt, level );
                break;
            default:
                assert(!"impossible!");
                break;
          }
          cwal_visit_acyclic_end(src, opaque);
          break;
      }
      default:
          rc = CWAL_RC_TYPE;
          break;
    }
    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 || fmt->indentString.len) ? 1 : 0;
        cwal_list const * li;
        cwal_array const * ar = cwal_value_get_array(src);
        int opaque = 0;
        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;
        cwal_visit_list_begin( src, &opaque );
        rc = f(state, "[", 1);
        ++level;
        if( doIndent ){
            rc = cwal_json_output_indent( f, state, fmt, 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, level )
                            : (fmt->addSpaceAfterComma
                               ? f( state, " ", 1 )
                               : 0);
                    }
                }
            }
        }
        if(!rc){
            if( doIndent ){
                rc = cwal_json_output_indent( f, state, fmt, --level );
            }
            if(!rc) rc = f(state, "]", 1);
        }
        cwal_visit_list_end(src, opaque);
        end:
        return rc;
    }
}

int cwal_json_output_tuple( 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_tuple(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 || fmt->indentString.len) ? 1 : 0;
        cwal_tuple const * tp = cwal_value_get_tuple(src);
        cwal_size_t const n = cwal_tuple_length(tp);
        int opaque = 0;
        if( 0 == n ){
            rc = f(state, "[]", 2 );
            goto end;
        }
        else if( (1 == n) && !fmt->indentSingleMemberValues ) doIndent = 0;
        cwal_visit_list_begin( src, &opaque );
        rc = f(state, "[", 1);
        ++level;
        if( doIndent ){
            rc = cwal_json_output_indent( f, state, fmt, level );
        }
        for( i = 0; (i < n) && (0 == rc); ++i ){
            if(i>0){
                rc = f(state, ",", 1);
                if( 0 == rc ){
                    rc = doIndent
                        ? cwal_json_output_indent( f, state, fmt, level )
                        : (fmt->addSpaceAfterComma
                           ? f( state, " ", 1 )
                           : 0);
                }
                if(rc) break;
            }
            v = (cwal_value *) cwal_tuple_get(tp, i);
            rc = (v && cwal_json_can_output(v))
                ? cwal_json_output_impl( v, f, state, fmt, level )
                : cwal_json_output_null( f, state );
        }
        if(!rc){
            if( doIndent ){
                rc = cwal_json_output_indent( f, state, fmt, --level );
            }
            if(!rc) rc = f(state, "]", 1);
        }
        cwal_visit_list_end( src, opaque );
        end:
        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:
      case CWAL_TYPE_TUPLE:
          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 || fmt->indentString.len) ? 1 : 0;
        cwal_obase * base = cwal_value_obase(self);
        int outputCount = 0 /* keep track of where we need a comma */;
        int opaque;
        if(!base) return CWAL_RC_MISUSE;
        assert( (NULL != fmt));
        kvp = base->kvp;
        if( 0 == kvp ) return f(state, "{}", 2 );
        cwal_visit_props_begin(self, &opaque);
        if( !kvp->right && !fmt->indentSingleMemberValues ) doIndent = 0;
        rc = f(state, "{", 1);
        ++level;
        if( !rc && doIndent ){
            rc = cwal_json_output_indent( f, state, fmt, level );
        }
        for( i = 0; (0==rc) && kvp; kvp = kvp->right, ++i ){
            char const * cKey;
            cwal_value * key;
            if(CWAL_VAR_F_HIDDEN & kvp->flags) continue;
            key = cwal_kvp_key( kvp );
            sKey = cwal_value_get_string(key);
            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;
            else 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, 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, level );
        }
        cwal_visit_props_end(self, opaque);
        return rc ? rc : f(state, "}", 1);
    }
}


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 * ckeyV = cwal_string_value(p->ckey);
        assert(cwal_value_is_builtin(ckeyV) || cwal_value_refcount(ckeyV));
        cwal_value_unref(ckeyV);
    }
    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.
   
    This function always takes and removes a ref to val, regardless of
    success or failure. Thus it will, on error, clean up val if there
    is no other reference to it. (This simplifies error handling in
    the core parser.)
*/
static int cwal_json_parser_set_key( cwal_json_parser * p, cwal_value * val )
{
    int rc;
    assert( p && val );
    cwal_value_ref(val);
    if( p->ckey && cwal_value_is_object(p->node) ){
        cwal_value * ckeyV = cwal_string_value(p->ckey);
        assert(cwal_value_is_builtin(ckeyV) || cwal_value_refcount(ckeyV));
        rc = cwal_prop_set_v( p->node, ckeyV, val );
        cwal_value_unref(ckeyV);
        p->ckey = NULL /* required to avoid mis-cleanup */;
    }
    else{
        rc = p->errNo = CWAL_RC_ERROR;
    }
    cwal_value_unref(val);
    return rc;
}
#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.

    This function always takes and removes a ref to val, regardless of
    success or failure. Thus it will, on error, clean up val if there
    is no other reference to it. (This simplifies error handling in
    the core parser.)

    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 ){
    int rc;
    cwal_value_ref(val);
    if( p->ckey ){
        /* we're in Object mode */
        assert( cwal_value_is_object( p->node ) );
        rc = cwal_json_parser_set_key( p, val );
    }
    else if( cwal_value_is_array( p->node ) ){
        /* we're in Array mode */
        cwal_array * ar = cwal_value_get_array( p->node );
        rc = cwal_array_append( ar, val );
    }
    else
    { /* WTF? */
        assert( !"Internal error in cwal_parser code" );
        rc = p->errNo = CWAL_RC_ERROR;
    }
    cwal_value_unref(val);
    return rc;
}
#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 ){
              cwal_value_ref(p->root);
              p->root = p->node = obja;
              rc = cwal_list_append( p->e, &p->stack, obja );
              if( rc ){
                  /* work around a (potential) corner case in the cleanup code. */
                  p->root = p->node = NULL;
                  cwal_value_unref(obja);
              }
          }
          else{
              cwal_value_ref(obja);
              rc = cwal_json_parser_push_value( p, obja );
              if(!rc){
                  rc = cwal_list_append( p->e, &p->stack, obja );
                  if( !rc ){
                      p->node = obja;
                  }
              }
              cwal_value_unref(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.value,
                                      (cwal_size_t)value->vu.str.length );
          if( ! p->ckey ){
              rc = CWAL_RC_OOM;
              break;
          }
          cwal_value_ref(cwal_string_value(p->ckey));
          break;
      }
      case JSON_T_STRING: {
          v = cwal_new_string_value( p->e,
                                      value->vu.str.value,
                                      (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;
    if( !e || !tgt || !src ) return CWAL_RC_MISUSE;
    memset( &jopt, 0, sizeof(JSON_config) );
    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_f_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 = (*src && !src[1] && ('-'==*src))
            ? stdin
            : fopen(src, "rb");
        if( !f ) return CWAL_RC_IO;
        else{
            int const rc = cwal_json_parse_FILE( e, f, tgt, pInfo );
            if(stdin != f) 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
#undef MARKER
/* 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 !defined(CWAL_SWITCH_FALL_THROUGH)
#if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7)
/*
  gcc v7+ treats implicit 'switch' fallthrough as a warning
  (i.e. error because we always build with -Wall -Werror -Wextra



  -pedantic). Because now it's apparently considered modern to warn
  for using perfectly valid features of the language. Holy cow, guys,
  what the hell were you thinking!?!?!?

  Similarly braindead, clang #defines __GNUC__.
*/
#  define CWAL_SWITCH_FALL_THROUGH __attribute__ ((fallthrough))
#else
#  define CWAL_SWITCH_FALL_THROUGH
#endif
#endif


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

/**
   If WHPRINTF_OMIT_DYNSTRING is defined to a true value then the
   %z (dynamically-allocated string) specifier is disabled.
*/
#ifndef WHPRINTF_OMIT_DYNSTRING
#  define WHPRINTF_OMIT_DYNSTRING 1
#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 */
#if !WHPRINTF_OMIT_DYNSTRING
  etDYNSTRING = 7, /* Dynamically allocated strings. %z */
#endif
  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 },
#if !WHPRINTF_OMIT_DYNSTRING
  {  'z',  0, FLAG_STRING, etDYNSTRING,  0,  0 },
#else
  {  'z',  0, 0, 0,  0,  0 },
#endif
  {  '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,    16,  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 }
  /* reminder to self: i'd like to port in fossil's path-sanitization
     here (%/), which replaces \\ with /, but it requires allocating
     and cwal_printf() has no mechanism to pass in a cwal_engine
     instance, which means that it can neither use the correct
     allocator nor propagate allocation failure properly back to cwal.

     Or... it _could_ be reimplemented to stream output in multiple
     calls, so allocation wouldn't strictly need to allocate. Hmmm.
  */,
  {'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, 16, 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 },
#if !WHPRINTF_OMIT_DYNSTRING
  {'z'/*122*/, 0, FLAG_STRING, etDYNSTRING,  0,  0},
#else
  {'z'/*122*/, 0, 0, 0,  0,  0},
#endif
  {'{'/*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 );


#if !WHPRINTF_OMIT_DYNSTRING
/**
  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 )
{
	char const * ch = (char const *) varg;
    long const ret = ch ? pf( pfArg, ch, pfLen ) : 0;
    free( (char *) varg );
    return ret;
}
#endif

#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
    if(pfLen){/*avoid unused param warning*/}
    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);
    }
    if(pfLen){/*avoid unused param warning*/}
    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 if( pfrc<0 ) { WHPRINTF_RETURN; } else outCount += pfrc
#define WHPRINTF_SPACES(N) \
if(1){				       \
    WHPRINTF_CHARARRAY(zSpaces,N);		      \
    memset( zSpaces,' ',N);			      \
    pfrc = pfAppend(pfAppendArg, zSpaces, N);	      \
    WHPRINTF_CHARARRAY_FREE(zSpaces);		      \
    WHPRINTF_CHECKERR;			      \
}(void)0
#else
  /* But this one is subject to potential wrong output
     on "unusual" inputs.

     FIXME: turn this into a loop on N, 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 if( pfrc<0 ) { 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;                           \
}(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;
      if( c==0 ) break;
    }
    if( (c=(*++fmt))==0 ){
      pfrc = pfAppend( pfAppendArg, "%", 1);
      WHPRINTF_CHECKERR;
      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 >= -2147483647 ? -width : 0;
      }
      c = *++fmt;
    }else{
      unsigned wx = 0;
      while( c>='0' && c<='9' ){
        wx = wx * 10 + c - '0';
        width = width*10 + c - '0';
        c = *++fmt;
      }
      width = wx & 0x7fffffff;
    }
    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;
        if( precision<0 ){
          precision = precision >= -2147483647 ? -precision : -1;
        }

      }else{
        unsigned px = 0;
        while( c>='0' && c<='9' ){
          px = px*10 + c - '0';
          c = *++fmt;
        }
        precision = px & 0x7fffffff;
      }
    }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);
        CWAL_SWITCH_FALL_THROUGH;
      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 & 0xfff, rounder=0.4999; idx>0; idx--, rounder*=0.1);
#else
        /* It makes more sense to use 0.5 */
        for(idx=precision & 0xfff, 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;
      }
#if !WHPRINTF_OMIT_DYNSTRING
      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;
          length = 0;
          break;
      }
#endif
#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;
          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;
          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;
          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;
          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;
    }
    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;
}
/* end of file cwal_printf.c */
/* start of file cwal_utf.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 <string.h> /* for a single sprintf() need :/ */

#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

/**
   Parts of the UTF code was originally taken from sqlite3's
   public-domain source code (https://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.
**
** Taken from sqlite3:
** https://www.sqlite.org/src/artifact?ln=48-61&name=810fbfebe12359f1
*/
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
};

unsigned 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 */
){
  /*
    Adapted from sqlite3:
    https://www.sqlite.org/src/artifact?ln=155-165&name=810fbfebe12359f1
  */
  unsigned c;
  if(zIn>=zTerm){
    *pzNext = zTerm;
    c = 0;
  }else{
    c = (unsigned int)*(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;
}

unsigned int cwal_utf8_read_char1(const unsigned char **pz){
  /* taken from sqlite3's utf.c:sqlite3Utf8Read():

     https://www.sqlite.org/src/artifact?ln=166-185&name=810fbfebe12359f1
  */
  unsigned int c;
  c = *((*pz)++);
  if( c>=0xc0 ){
    c = cwal_utfTrans1[c-0xc0];
    while( (*(*pz) & 0xc0)==0x80 ){
      c = (c<<6) + (0x3f & *((*pz)++));
    }
    if( c<0x80
        || (c&0xFFFFF800)==0xD800
        || (c&0xFFFFFFFE)==0xFFFE ){  c = 0xFFFD; }
  }
  return c;

}

cwal_size_t cwal_strlen_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 ){
          switch(0xF0 & *x) {
            case 0xF0: /* length 4 */
              x += 3;
              break;
            case 0xE0: /* length 3 */
              x+= 2;
              break;
            case 0xC0: /* length 2 */
              x += 1;
              break;
            default:
              break;
          }
        }
#else
        for( ; (pos < end) && cwal_utf8_read_char(pos, end, &pos);
             ++rc )
        {}
#endif
        return rc;
    }
}

#if 0
/**
   Given THE FIRST BYTE of a UTF-8 character, this function returns
   that character's length in bytes.  It "should" return -1 for invalid
   characters, but currently doesn't. Callers should treat it as if it
   does/could, though, just in case it is fixed to do so at some point.
*/
static int cwal_utf8_char_length( unsigned char c ){
  switch(0xF0 & c) {
    case 0xF0: return 4;
    case 0xE0: return 3;
    case 0xC0: return 2;
    default: return 1;
   /* See also: https://stackoverflow.com/questions/4884656/utf-8-encoding-size */
}
#endif

#if 0
/* fundamentally broken interface */
int cwal_utf8_char_next( char const * pos, char const * end,
                         unsigned int * unicode){
  int const len = (pos && pos<end)
    ? cwal_utf8_char_length_from_first_byte(*(unsigned char*)pos)
    : 0;
    if(len<=0 || (pos+len>end)){
        /* arguable: if(unicode) *unicode = 0xFFFD; */
        return 0;
    }
    assert(len>=1 && len<=4);
    if(unicode){
        unsigned int u = 0;
        unsigned char const * pos_ = (unsigned char const *)pos;
        unsigned char const * p = pos_ + len - 1;
        unsigned int shift = 0;
        for( ; p >= pos_; --p ){
            u += ((0xFF & (int)*p) << shift);
            shift += 8;
        }
        *unicode = u;
    }
    return len;
}
#endif

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 (int)size;
    else if(size > length) return -1;
    else switch(size) {
      case 0:
          assert(!"can't happen anymore");
          output[0] = 0;
          return 0;
      case 4:
          output[3] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0x10000;
          CWAL_SWITCH_FALL_THROUGH;
      case 3:
          output[2] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0x800;
          CWAL_SWITCH_FALL_THROUGH;
      case 2:
          output[1] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0xc0; 
          CWAL_SWITCH_FALL_THROUGH;
      case 1:
        output[0] = (unsigned char)c;
        CWAL_SWITCH_FALL_THROUGH;
      default:
        return (int)size;
    }
}

int cwal_utf8_char_at( unsigned char const * begin,
                       unsigned char const * end,
                       cwal_size_t index,
                       unsigned int * unicode ){
  unsigned char const * pos = (unsigned char const *)begin;
  unsigned char const * next = (unsigned char const *)begin;
  cwal_size_t n = 0;
  unsigned int codepoint = 0;
  if(begin>=end || index>=(cwal_size_t)(end - begin)){
    return CWAL_RC_RANGE;
  }
  for( ; pos<end; ++n, pos = next ){
    codepoint = cwal_utf8_read_char( pos, end, &next );
    if(n==index) break;
  }
  if(pos>=end){
    return CWAL_RC_RANGE;
  }else{
    if(unicode) *unicode = codepoint;
    return 0;
  }
}


int cwal_string_case_fold( cwal_engine * e,
                           cwal_string const * str,
                           cwal_value **rv,
                           char doUpper ){
    cwal_size_t len = 0;
    char const * cs = cwal_string_cstr2(str, &len);
    if(!e) return CWAL_RC_MISUSE;
    else if(!len){
        *rv = cwal_string_value(str);
        return 0;
    }else{
#if 0
        /* TODO?... */
        if(cwal_string_is_ascii(str)){
          /* todo?: maybe add slightly faster impl for ASCII
             strings... */
        }else{
          return cwal_utf8_case_fold(e, cs, len, rv, doUpper );
        }
#else
        return cwal_utf8_case_fold(e, cs, len, rv, doUpper );
#endif
    }
}

#if 0
static int cwal_utf8_char_len( int ch ){
  if(ch<=0x7F) return 1;
  else if(ch<=0x7FF) return 2;
  else if(ch<=0xffff) return 3;
  else return 4;
}
#endif

int cwal_utf8_case_fold_to_buffer( cwal_engine * e, char const * cstr,
                                   cwal_size_t len,
                                   cwal_buffer *buf,
                                   char doUpper ){
    int rc = 0;
    unsigned char const * cs = (unsigned char const *)cstr;
    unsigned char const * csEnd = cs+len;
    int ch;
    int clen;
    unsigned char cbuf[5] = {0,0,0,0,0};
    if(!e || !cstr || !buf ||
       (cs>=buf->mem && cs<buf->mem+buf->capacity
        /* source/dest memory may not overlap */)
       ) return CWAL_RC_MISUSE;
    else if(!len){
        return 0;
    }
    rc = cwal_buffer_reserve( e, buf, buf->used + len + 1 /*NUL byte*/)
      /**
         Sidebar: there are actually UTF-8 characters in cwal's
         library for which the tolower and toupper values have
         different byte lengths. Thus this buffer reservation is a
         conservative guess, and not a guaranty that we won't need to
         allocate again.

         Just a day or so after writing ^^^^^ that, i randomly came
         across a tweet about that very topic:

         https://twitter.com/mikko/status/1059521508765298691

         See also:

         https://github.com/minimaxir/big-list-of-naughty-strings
      */;
    if(rc) return rc;
    for( ; cs < csEnd; ){
        unsigned char const * pos = cs;
        ch = cwal_utf8_read_char( cs, csEnd, &cs );
        if(!(cs-pos)){
          assert(!"can't happen with the current code.");
          rc = CWAL_RC_RANGE;
          break;
        }
        ch = doUpper
            ? cwal_utf8_char_toupper(ch)
            : cwal_utf8_char_tolower(ch);
        clen = cwal_utf8_char_to_cstr( (unsigned int)ch, cbuf, 4 );
        if(clen>0){
          rc = cwal_buffer_append( e, buf, cbuf, (cwal_size_t)clen );
          if(rc) break;
        }else{
          rc = CWAL_RC_RANGE;
          break;
        }
    }
    if(!rc){
      assert(0==buf->mem[buf->used] && "performed by/via cwal_buffer_append()");
    }
    return rc;
}

int cwal_utf8_case_fold( cwal_engine * e, char const * cstr,
                         cwal_size_t len,
                         cwal_value **rv,
                         char doUpper ){
    cwal_buffer buf = cwal_buffer_empty;
    int rc = 0;
    if(!e || !cstr || !rv) return CWAL_RC_MISUSE;
    else if(!len){
        *rv = cwal_new_string_value(e, "", 0) /* does not allocate */;
        return 0;
    }
    rc = cwal_utf8_case_fold_to_buffer(e, cstr, len, &buf, doUpper);
    if(!rc){
      assert(buf.mem);
      assert(0==buf.mem[buf.used]);
      *rv = cwal_string_value(cwal_buffer_to_zstring(e, &buf));
      if(!*rv) rc = CWAL_RC_OOM;
      else{
        assert(!buf.mem && "taken over by z-string");
      }
    }
    cwal_buffer_reserve(e, &buf, 0);
    return rc;
}

cwal_int_t cwal_utf8_indexof( char const * haystack, cwal_size_t hayLen,
                              cwal_int_t offset,
                              char const * _needle, cwal_size_t nLen,
                              char returnAsByteOffset ){
  if(!haystack || !hayLen || !_needle || !nLen) return -1;
  else if(nLen > hayLen) return -1;
  else if(!offset && nLen == hayLen){
    /* micro-optimization... */
    return memcmp(haystack, _needle, (size_t)nLen)
      ? -1 : 0;
  }
  else{
    unsigned char const * hay =
      (unsigned char const *)haystack;
    unsigned char const * eof = hay + hayLen;
    unsigned char const * origin = hay;
    unsigned char const * needle = (unsigned char const *)_needle;
    cwal_int_t i = 0;
    if(offset<0){
      if(hayLen < ((cwal_size_t)-offset)){
        offset = 0;
      }else{
        offset = hayLen + offset;
      }
      assert(offset >= 0);
    }
    while(1){
      unsigned char const * start = hay;
      if((start==eof) || ((cwal_int_t)nLen > (eof-start))) break;
      cwal_utf8_read_char( start, eof, &hay );
      if(i<offset){
        ++i;
        continue;
      }
      assert(hay <= eof && "Invalid input! Length is wrong or not UTF-8!");
      /*MARKER(("tick i=%d [%.*s]\n",
        (int)i, (int)(haystack-start),(char const*)start));*/
      if(hay > eof) return -2;
      else if(0==memcmp(start, needle, nLen)){
        return returnAsByteOffset
          ? ((cwal_int_t)(start - origin))
          : i;
      }
      else ++i;
    }
    return -1;
  }
}


int cwal_utf8_char_tolower( int ch ){
  /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
     dated April 1, 2019. */
  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 0x037F: return 0x03F3;
    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 0x0528: return 0x0529;
    case 0x052A: return 0x052B;
    case 0x052C: return 0x052D;
    case 0x052E: return 0x052F;
    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 0x13A0: return 0xAB70;
    case 0x13A1: return 0xAB71;
    case 0x13A2: return 0xAB72;
    case 0x13A3: return 0xAB73;
    case 0x13A4: return 0xAB74;
    case 0x13A5: return 0xAB75;
    case 0x13A6: return 0xAB76;
    case 0x13A7: return 0xAB77;
    case 0x13A8: return 0xAB78;
    case 0x13A9: return 0xAB79;
    case 0x13AA: return 0xAB7A;
    case 0x13AB: return 0xAB7B;
    case 0x13AC: return 0xAB7C;
    case 0x13AD: return 0xAB7D;
    case 0x13AE: return 0xAB7E;
    case 0x13AF: return 0xAB7F;
    case 0x13B0: return 0xAB80;
    case 0x13B1: return 0xAB81;
    case 0x13B2: return 0xAB82;
    case 0x13B3: return 0xAB83;
    case 0x13B4: return 0xAB84;
    case 0x13B5: return 0xAB85;
    case 0x13B6: return 0xAB86;
    case 0x13B7: return 0xAB87;
    case 0x13B8: return 0xAB88;
    case 0x13B9: return 0xAB89;
    case 0x13BA: return 0xAB8A;
    case 0x13BB: return 0xAB8B;
    case 0x13BC: return 0xAB8C;
    case 0x13BD: return 0xAB8D;
    case 0x13BE: return 0xAB8E;
    case 0x13BF: return 0xAB8F;
    case 0x13C0: return 0xAB90;
    case 0x13C1: return 0xAB91;
    case 0x13C2: return 0xAB92;
    case 0x13C3: return 0xAB93;
    case 0x13C4: return 0xAB94;
    case 0x13C5: return 0xAB95;
    case 0x13C6: return 0xAB96;
    case 0x13C7: return 0xAB97;
    case 0x13C8: return 0xAB98;
    case 0x13C9: return 0xAB99;
    case 0x13CA: return 0xAB9A;
    case 0x13CB: return 0xAB9B;
    case 0x13CC: return 0xAB9C;
    case 0x13CD: return 0xAB9D;
    case 0x13CE: return 0xAB9E;
    case 0x13CF: return 0xAB9F;
    case 0x13D0: return 0xABA0;
    case 0x13D1: return 0xABA1;
    case 0x13D2: return 0xABA2;
    case 0x13D3: return 0xABA3;
    case 0x13D4: return 0xABA4;
    case 0x13D5: return 0xABA5;
    case 0x13D6: return 0xABA6;
    case 0x13D7: return 0xABA7;
    case 0x13D8: return 0xABA8;
    case 0x13D9: return 0xABA9;
    case 0x13DA: return 0xABAA;
    case 0x13DB: return 0xABAB;
    case 0x13DC: return 0xABAC;
    case 0x13DD: return 0xABAD;
    case 0x13DE: return 0xABAE;
    case 0x13DF: return 0xABAF;
    case 0x13E0: return 0xABB0;
    case 0x13E1: return 0xABB1;
    case 0x13E2: return 0xABB2;
    case 0x13E3: return 0xABB3;
    case 0x13E4: return 0xABB4;
    case 0x13E5: return 0xABB5;
    case 0x13E6: return 0xABB6;
    case 0x13E7: return 0xABB7;
    case 0x13E8: return 0xABB8;
    case 0x13E9: return 0xABB9;
    case 0x13EA: return 0xABBA;
    case 0x13EB: return 0xABBB;
    case 0x13EC: return 0xABBC;
    case 0x13ED: return 0xABBD;
    case 0x13EE: return 0xABBE;
    case 0x13EF: return 0xABBF;
    case 0x13F0: return 0x13F8;
    case 0x13F1: return 0x13F9;
    case 0x13F2: return 0x13FA;
    case 0x13F3: return 0x13FB;
    case 0x13F4: return 0x13FC;
    case 0x13F5: return 0x13FD;
    case 0x1C90: return 0x10D0;
    case 0x1C91: return 0x10D1;
    case 0x1C92: return 0x10D2;
    case 0x1C93: return 0x10D3;
    case 0x1C94: return 0x10D4;
    case 0x1C95: return 0x10D5;
    case 0x1C96: return 0x10D6;
    case 0x1C97: return 0x10D7;
    case 0x1C98: return 0x10D8;
    case 0x1C99: return 0x10D9;
    case 0x1C9A: return 0x10DA;
    case 0x1C9B: return 0x10DB;
    case 0x1C9C: return 0x10DC;
    case 0x1C9D: return 0x10DD;
    case 0x1C9E: return 0x10DE;
    case 0x1C9F: return 0x10DF;
    case 0x1CA0: return 0x10E0;
    case 0x1CA1: return 0x10E1;
    case 0x1CA2: return 0x10E2;
    case 0x1CA3: return 0x10E3;
    case 0x1CA4: return 0x10E4;
    case 0x1CA5: return 0x10E5;
    case 0x1CA6: return 0x10E6;
    case 0x1CA7: return 0x10E7;
    case 0x1CA8: return 0x10E8;
    case 0x1CA9: return 0x10E9;
    case 0x1CAA: return 0x10EA;
    case 0x1CAB: return 0x10EB;
    case 0x1CAC: return 0x10EC;
    case 0x1CAD: return 0x10ED;
    case 0x1CAE: return 0x10EE;
    case 0x1CAF: return 0x10EF;
    case 0x1CB0: return 0x10F0;
    case 0x1CB1: return 0x10F1;
    case 0x1CB2: return 0x10F2;
    case 0x1CB3: return 0x10F3;
    case 0x1CB4: return 0x10F4;
    case 0x1CB5: return 0x10F5;
    case 0x1CB6: return 0x10F6;
    case 0x1CB7: return 0x10F7;
    case 0x1CB8: return 0x10F8;
    case 0x1CB9: return 0x10F9;
    case 0x1CBA: return 0x10FA;
    case 0x1CBD: return 0x10FD;
    case 0x1CBE: return 0x10FE;
    case 0x1CBF: return 0x10FF;
    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 0xA698: return 0xA699;
    case 0xA69A: return 0xA69B;
    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 0xA796: return 0xA797;
    case 0xA798: return 0xA799;
    case 0xA79A: return 0xA79B;
    case 0xA79C: return 0xA79D;
    case 0xA79E: return 0xA79F;
    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 0xA7AB: return 0x025C;
    case 0xA7AC: return 0x0261;
    case 0xA7AD: return 0x026C;
    case 0xA7AE: return 0x026A;
    case 0xA7B0: return 0x029E;
    case 0xA7B1: return 0x0287;
    case 0xA7B2: return 0x029D;
    case 0xA7B3: return 0xAB53;
    case 0xA7B4: return 0xA7B5;
    case 0xA7B6: return 0xA7B7;
    case 0xA7B8: return 0xA7B9;
    case 0xA7BA: return 0xA7BB;
    case 0xA7BC: return 0xA7BD;
    case 0xA7BE: return 0xA7BF;
    case 0xA7C2: return 0xA7C3;
    case 0xA7C4: return 0xA794;
    case 0xA7C5: return 0x0282;
    case 0xA7C6: return 0x1D8E;
    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;
    case 0x104B0: return 0x104D8;
    case 0x104B1: return 0x104D9;
    case 0x104B2: return 0x104DA;
    case 0x104B3: return 0x104DB;
    case 0x104B4: return 0x104DC;
    case 0x104B5: return 0x104DD;
    case 0x104B6: return 0x104DE;
    case 0x104B7: return 0x104DF;
    case 0x104B8: return 0x104E0;
    case 0x104B9: return 0x104E1;
    case 0x104BA: return 0x104E2;
    case 0x104BB: return 0x104E3;
    case 0x104BC: return 0x104E4;
    case 0x104BD: return 0x104E5;
    case 0x104BE: return 0x104E6;
    case 0x104BF: return 0x104E7;
    case 0x104C0: return 0x104E8;
    case 0x104C1: return 0x104E9;
    case 0x104C2: return 0x104EA;
    case 0x104C3: return 0x104EB;
    case 0x104C4: return 0x104EC;
    case 0x104C5: return 0x104ED;
    case 0x104C6: return 0x104EE;
    case 0x104C7: return 0x104EF;
    case 0x104C8: return 0x104F0;
    case 0x104C9: return 0x104F1;
    case 0x104CA: return 0x104F2;
    case 0x104CB: return 0x104F3;
    case 0x104CC: return 0x104F4;
    case 0x104CD: return 0x104F5;
    case 0x104CE: return 0x104F6;
    case 0x104CF: return 0x104F7;
    case 0x104D0: return 0x104F8;
    case 0x104D1: return 0x104F9;
    case 0x104D2: return 0x104FA;
    case 0x104D3: return 0x104FB;
    case 0x10C80: return 0x10CC0;
    case 0x10C81: return 0x10CC1;
    case 0x10C82: return 0x10CC2;
    case 0x10C83: return 0x10CC3;
    case 0x10C84: return 0x10CC4;
    case 0x10C85: return 0x10CC5;
    case 0x10C86: return 0x10CC6;
    case 0x10C87: return 0x10CC7;
    case 0x10C88: return 0x10CC8;
    case 0x10C89: return 0x10CC9;
    case 0x10C8A: return 0x10CCA;
    case 0x10C8B: return 0x10CCB;
    case 0x10C8C: return 0x10CCC;
    case 0x10C8D: return 0x10CCD;
    case 0x10C8E: return 0x10CCE;
    case 0x10C8F: return 0x10CCF;
    case 0x10C90: return 0x10CD0;
    case 0x10C91: return 0x10CD1;
    case 0x10C92: return 0x10CD2;
    case 0x10C93: return 0x10CD3;
    case 0x10C94: return 0x10CD4;
    case 0x10C95: return 0x10CD5;
    case 0x10C96: return 0x10CD6;
    case 0x10C97: return 0x10CD7;
    case 0x10C98: return 0x10CD8;
    case 0x10C99: return 0x10CD9;
    case 0x10C9A: return 0x10CDA;
    case 0x10C9B: return 0x10CDB;
    case 0x10C9C: return 0x10CDC;
    case 0x10C9D: return 0x10CDD;
    case 0x10C9E: return 0x10CDE;
    case 0x10C9F: return 0x10CDF;
    case 0x10CA0: return 0x10CE0;
    case 0x10CA1: return 0x10CE1;
    case 0x10CA2: return 0x10CE2;
    case 0x10CA3: return 0x10CE3;
    case 0x10CA4: return 0x10CE4;
    case 0x10CA5: return 0x10CE5;
    case 0x10CA6: return 0x10CE6;
    case 0x10CA7: return 0x10CE7;
    case 0x10CA8: return 0x10CE8;
    case 0x10CA9: return 0x10CE9;
    case 0x10CAA: return 0x10CEA;
    case 0x10CAB: return 0x10CEB;
    case 0x10CAC: return 0x10CEC;
    case 0x10CAD: return 0x10CED;
    case 0x10CAE: return 0x10CEE;
    case 0x10CAF: return 0x10CEF;
    case 0x10CB0: return 0x10CF0;
    case 0x10CB1: return 0x10CF1;
    case 0x10CB2: return 0x10CF2;
    case 0x118A0: return 0x118C0;
    case 0x118A1: return 0x118C1;
    case 0x118A2: return 0x118C2;
    case 0x118A3: return 0x118C3;
    case 0x118A4: return 0x118C4;
    case 0x118A5: return 0x118C5;
    case 0x118A6: return 0x118C6;
    case 0x118A7: return 0x118C7;
    case 0x118A8: return 0x118C8;
    case 0x118A9: return 0x118C9;
    case 0x118AA: return 0x118CA;
    case 0x118AB: return 0x118CB;
    case 0x118AC: return 0x118CC;
    case 0x118AD: return 0x118CD;
    case 0x118AE: return 0x118CE;
    case 0x118AF: return 0x118CF;
    case 0x118B0: return 0x118D0;
    case 0x118B1: return 0x118D1;
    case 0x118B2: return 0x118D2;
    case 0x118B3: return 0x118D3;
    case 0x118B4: return 0x118D4;
    case 0x118B5: return 0x118D5;
    case 0x118B6: return 0x118D6;
    case 0x118B7: return 0x118D7;
    case 0x118B8: return 0x118D8;
    case 0x118B9: return 0x118D9;
    case 0x118BA: return 0x118DA;
    case 0x118BB: return 0x118DB;
    case 0x118BC: return 0x118DC;
    case 0x118BD: return 0x118DD;
    case 0x118BE: return 0x118DE;
    case 0x118BF: return 0x118DF;
    case 0x16E40: return 0x16E60;
    case 0x16E41: return 0x16E61;
    case 0x16E42: return 0x16E62;
    case 0x16E43: return 0x16E63;
    case 0x16E44: return 0x16E64;
    case 0x16E45: return 0x16E65;
    case 0x16E46: return 0x16E66;
    case 0x16E47: return 0x16E67;
    case 0x16E48: return 0x16E68;
    case 0x16E49: return 0x16E69;
    case 0x16E4A: return 0x16E6A;
    case 0x16E4B: return 0x16E6B;
    case 0x16E4C: return 0x16E6C;
    case 0x16E4D: return 0x16E6D;
    case 0x16E4E: return 0x16E6E;
    case 0x16E4F: return 0x16E6F;
    case 0x16E50: return 0x16E70;
    case 0x16E51: return 0x16E71;
    case 0x16E52: return 0x16E72;
    case 0x16E53: return 0x16E73;
    case 0x16E54: return 0x16E74;
    case 0x16E55: return 0x16E75;
    case 0x16E56: return 0x16E76;
    case 0x16E57: return 0x16E77;
    case 0x16E58: return 0x16E78;
    case 0x16E59: return 0x16E79;
    case 0x16E5A: return 0x16E7A;
    case 0x16E5B: return 0x16E7B;
    case 0x16E5C: return 0x16E7C;
    case 0x16E5D: return 0x16E7D;
    case 0x16E5E: return 0x16E7E;
    case 0x16E5F: return 0x16E7F;
    case 0x1E900: return 0x1E922;
    case 0x1E901: return 0x1E923;
    case 0x1E902: return 0x1E924;
    case 0x1E903: return 0x1E925;
    case 0x1E904: return 0x1E926;
    case 0x1E905: return 0x1E927;
    case 0x1E906: return 0x1E928;
    case 0x1E907: return 0x1E929;
    case 0x1E908: return 0x1E92A;
    case 0x1E909: return 0x1E92B;
    case 0x1E90A: return 0x1E92C;
    case 0x1E90B: return 0x1E92D;
    case 0x1E90C: return 0x1E92E;
    case 0x1E90D: return 0x1E92F;
    case 0x1E90E: return 0x1E930;
    case 0x1E90F: return 0x1E931;
    case 0x1E910: return 0x1E932;
    case 0x1E911: return 0x1E933;
    case 0x1E912: return 0x1E934;
    case 0x1E913: return 0x1E935;
    case 0x1E914: return 0x1E936;
    case 0x1E915: return 0x1E937;
    case 0x1E916: return 0x1E938;
    case 0x1E917: return 0x1E939;
    case 0x1E918: return 0x1E93A;
    case 0x1E919: return 0x1E93B;
    case 0x1E91A: return 0x1E93C;
    case 0x1E91B: return 0x1E93D;
    case 0x1E91C: return 0x1E93E;
    case 0x1E91D: return 0x1E93F;
    case 0x1E91E: return 0x1E940;
    case 0x1E91F: return 0x1E941;
    case 0x1E920: return 0x1E942;
    case 0x1E921: return 0x1E943;
    default: return ch;
  }
}

int cwal_utf8_char_toupper( int ch ){
  /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
     dated April 1, 2019. */
  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 0x025C: return 0xA7AB;
    case 0x0260: return 0x0193;
    case 0x0261: return 0xA7AC;
    case 0x0263: return 0x0194;
    case 0x0265: return 0xA78D;
    case 0x0266: return 0xA7AA;
    case 0x0268: return 0x0197;
    case 0x0269: return 0x0196;
    case 0x026A: return 0xA7AE;
    case 0x026B: return 0x2C62;
    case 0x026C: return 0xA7AD;
    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 0x0282: return 0xA7C5;
    case 0x0283: return 0x01A9;
    case 0x0287: return 0xA7B1;
    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 0x029D: return 0xA7B2;
    case 0x029E: return 0xA7B0;
    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 0x03F3: return 0x037F;
    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 0x0529: return 0x0528;
    case 0x052B: return 0x052A;
    case 0x052D: return 0x052C;
    case 0x052F: return 0x052E;
    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 0x10D0: return 0x1C90;
    case 0x10D1: return 0x1C91;
    case 0x10D2: return 0x1C92;
    case 0x10D3: return 0x1C93;
    case 0x10D4: return 0x1C94;
    case 0x10D5: return 0x1C95;
    case 0x10D6: return 0x1C96;
    case 0x10D7: return 0x1C97;
    case 0x10D8: return 0x1C98;
    case 0x10D9: return 0x1C99;
    case 0x10DA: return 0x1C9A;
    case 0x10DB: return 0x1C9B;
    case 0x10DC: return 0x1C9C;
    case 0x10DD: return 0x1C9D;
    case 0x10DE: return 0x1C9E;
    case 0x10DF: return 0x1C9F;
    case 0x10E0: return 0x1CA0;
    case 0x10E1: return 0x1CA1;
    case 0x10E2: return 0x1CA2;
    case 0x10E3: return 0x1CA3;
    case 0x10E4: return 0x1CA4;
    case 0x10E5: return 0x1CA5;
    case 0x10E6: return 0x1CA6;
    case 0x10E7: return 0x1CA7;
    case 0x10E8: return 0x1CA8;
    case 0x10E9: return 0x1CA9;
    case 0x10EA: return 0x1CAA;
    case 0x10EB: return 0x1CAB;
    case 0x10EC: return 0x1CAC;
    case 0x10ED: return 0x1CAD;
    case 0x10EE: return 0x1CAE;
    case 0x10EF: return 0x1CAF;
    case 0x10F0: return 0x1CB0;
    case 0x10F1: return 0x1CB1;
    case 0x10F2: return 0x1CB2;
    case 0x10F3: return 0x1CB3;
    case 0x10F4: return 0x1CB4;
    case 0x10F5: return 0x1CB5;
    case 0x10F6: return 0x1CB6;
    case 0x10F7: return 0x1CB7;
    case 0x10F8: return 0x1CB8;
    case 0x10F9: return 0x1CB9;
    case 0x10FA: return 0x1CBA;
    case 0x10FD: return 0x1CBD;
    case 0x10FE: return 0x1CBE;
    case 0x10FF: return 0x1CBF;
    case 0x13F8: return 0x13F0;
    case 0x13F9: return 0x13F1;
    case 0x13FA: return 0x13F2;
    case 0x13FB: return 0x13F3;
    case 0x13FC: return 0x13F4;
    case 0x13FD: return 0x13F5;
    case 0x1C80: return 0x0412;
    case 0x1C81: return 0x0414;
    case 0x1C82: return 0x041E;
    case 0x1C83: return 0x0421;
    case 0x1C84: return 0x0422;
    case 0x1C85: return 0x0422;
    case 0x1C86: return 0x042A;
    case 0x1C87: return 0x0462;
    case 0x1C88: return 0xA64A;
    case 0x1D79: return 0xA77D;
    case 0x1D7D: return 0x2C63;
    case 0x1D8E: return 0xA7C6;
    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 0xA699: return 0xA698;
    case 0xA69B: return 0xA69A;
    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 0xA794: return 0xA7C4;
    case 0xA797: return 0xA796;
    case 0xA799: return 0xA798;
    case 0xA79B: return 0xA79A;
    case 0xA79D: return 0xA79C;
    case 0xA79F: return 0xA79E;
    case 0xA7A1: return 0xA7A0;
    case 0xA7A3: return 0xA7A2;
    case 0xA7A5: return 0xA7A4;
    case 0xA7A7: return 0xA7A6;
    case 0xA7A9: return 0xA7A8;
    case 0xA7B5: return 0xA7B4;
    case 0xA7B7: return 0xA7B6;
    case 0xA7B9: return 0xA7B8;
    case 0xA7BB: return 0xA7BA;
    case 0xA7BD: return 0xA7BC;
    case 0xA7BF: return 0xA7BE;
    case 0xA7C3: return 0xA7C2;
    case 0xAB53: return 0xA7B3;
    case 0xAB70: return 0x13A0;
    case 0xAB71: return 0x13A1;
    case 0xAB72: return 0x13A2;
    case 0xAB73: return 0x13A3;
    case 0xAB74: return 0x13A4;
    case 0xAB75: return 0x13A5;
    case 0xAB76: return 0x13A6;
    case 0xAB77: return 0x13A7;
    case 0xAB78: return 0x13A8;
    case 0xAB79: return 0x13A9;
    case 0xAB7A: return 0x13AA;
    case 0xAB7B: return 0x13AB;
    case 0xAB7C: return 0x13AC;
    case 0xAB7D: return 0x13AD;
    case 0xAB7E: return 0x13AE;
    case 0xAB7F: return 0x13AF;
    case 0xAB80: return 0x13B0;
    case 0xAB81: return 0x13B1;
    case 0xAB82: return 0x13B2;
    case 0xAB83: return 0x13B3;
    case 0xAB84: return 0x13B4;
    case 0xAB85: return 0x13B5;
    case 0xAB86: return 0x13B6;
    case 0xAB87: return 0x13B7;
    case 0xAB88: return 0x13B8;
    case 0xAB89: return 0x13B9;
    case 0xAB8A: return 0x13BA;
    case 0xAB8B: return 0x13BB;
    case 0xAB8C: return 0x13BC;
    case 0xAB8D: return 0x13BD;
    case 0xAB8E: return 0x13BE;
    case 0xAB8F: return 0x13BF;
    case 0xAB90: return 0x13C0;
    case 0xAB91: return 0x13C1;
    case 0xAB92: return 0x13C2;
    case 0xAB93: return 0x13C3;
    case 0xAB94: return 0x13C4;
    case 0xAB95: return 0x13C5;
    case 0xAB96: return 0x13C6;
    case 0xAB97: return 0x13C7;
    case 0xAB98: return 0x13C8;
    case 0xAB99: return 0x13C9;
    case 0xAB9A: return 0x13CA;
    case 0xAB9B: return 0x13CB;
    case 0xAB9C: return 0x13CC;
    case 0xAB9D: return 0x13CD;
    case 0xAB9E: return 0x13CE;
    case 0xAB9F: return 0x13CF;
    case 0xABA0: return 0x13D0;
    case 0xABA1: return 0x13D1;
    case 0xABA2: return 0x13D2;
    case 0xABA3: return 0x13D3;
    case 0xABA4: return 0x13D4;
    case 0xABA5: return 0x13D5;
    case 0xABA6: return 0x13D6;
    case 0xABA7: return 0x13D7;
    case 0xABA8: return 0x13D8;
    case 0xABA9: return 0x13D9;
    case 0xABAA: return 0x13DA;
    case 0xABAB: return 0x13DB;
    case 0xABAC: return 0x13DC;
    case 0xABAD: return 0x13DD;
    case 0xABAE: return 0x13DE;
    case 0xABAF: return 0x13DF;
    case 0xABB0: return 0x13E0;
    case 0xABB1: return 0x13E1;
    case 0xABB2: return 0x13E2;
    case 0xABB3: return 0x13E3;
    case 0xABB4: return 0x13E4;
    case 0xABB5: return 0x13E5;
    case 0xABB6: return 0x13E6;
    case 0xABB7: return 0x13E7;
    case 0xABB8: return 0x13E8;
    case 0xABB9: return 0x13E9;
    case 0xABBA: return 0x13EA;
    case 0xABBB: return 0x13EB;
    case 0xABBC: return 0x13EC;
    case 0xABBD: return 0x13ED;
    case 0xABBE: return 0x13EE;
    case 0xABBF: return 0x13EF;
    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;
    case 0x104D8: return 0x104B0;
    case 0x104D9: return 0x104B1;
    case 0x104DA: return 0x104B2;
    case 0x104DB: return 0x104B3;
    case 0x104DC: return 0x104B4;
    case 0x104DD: return 0x104B5;
    case 0x104DE: return 0x104B6;
    case 0x104DF: return 0x104B7;
    case 0x104E0: return 0x104B8;
    case 0x104E1: return 0x104B9;
    case 0x104E2: return 0x104BA;
    case 0x104E3: return 0x104BB;
    case 0x104E4: return 0x104BC;
    case 0x104E5: return 0x104BD;
    case 0x104E6: return 0x104BE;
    case 0x104E7: return 0x104BF;
    case 0x104E8: return 0x104C0;
    case 0x104E9: return 0x104C1;
    case 0x104EA: return 0x104C2;
    case 0x104EB: return 0x104C3;
    case 0x104EC: return 0x104C4;
    case 0x104ED: return 0x104C5;
    case 0x104EE: return 0x104C6;
    case 0x104EF: return 0x104C7;
    case 0x104F0: return 0x104C8;
    case 0x104F1: return 0x104C9;
    case 0x104F2: return 0x104CA;
    case 0x104F3: return 0x104CB;
    case 0x104F4: return 0x104CC;
    case 0x104F5: return 0x104CD;
    case 0x104F6: return 0x104CE;
    case 0x104F7: return 0x104CF;
    case 0x104F8: return 0x104D0;
    case 0x104F9: return 0x104D1;
    case 0x104FA: return 0x104D2;
    case 0x104FB: return 0x104D3;
    case 0x10CC0: return 0x10C80;
    case 0x10CC1: return 0x10C81;
    case 0x10CC2: return 0x10C82;
    case 0x10CC3: return 0x10C83;
    case 0x10CC4: return 0x10C84;
    case 0x10CC5: return 0x10C85;
    case 0x10CC6: return 0x10C86;
    case 0x10CC7: return 0x10C87;
    case 0x10CC8: return 0x10C88;
    case 0x10CC9: return 0x10C89;
    case 0x10CCA: return 0x10C8A;
    case 0x10CCB: return 0x10C8B;
    case 0x10CCC: return 0x10C8C;
    case 0x10CCD: return 0x10C8D;
    case 0x10CCE: return 0x10C8E;
    case 0x10CCF: return 0x10C8F;
    case 0x10CD0: return 0x10C90;
    case 0x10CD1: return 0x10C91;
    case 0x10CD2: return 0x10C92;
    case 0x10CD3: return 0x10C93;
    case 0x10CD4: return 0x10C94;
    case 0x10CD5: return 0x10C95;
    case 0x10CD6: return 0x10C96;
    case 0x10CD7: return 0x10C97;
    case 0x10CD8: return 0x10C98;
    case 0x10CD9: return 0x10C99;
    case 0x10CDA: return 0x10C9A;
    case 0x10CDB: return 0x10C9B;
    case 0x10CDC: return 0x10C9C;
    case 0x10CDD: return 0x10C9D;
    case 0x10CDE: return 0x10C9E;
    case 0x10CDF: return 0x10C9F;
    case 0x10CE0: return 0x10CA0;
    case 0x10CE1: return 0x10CA1;
    case 0x10CE2: return 0x10CA2;
    case 0x10CE3: return 0x10CA3;
    case 0x10CE4: return 0x10CA4;
    case 0x10CE5: return 0x10CA5;
    case 0x10CE6: return 0x10CA6;
    case 0x10CE7: return 0x10CA7;
    case 0x10CE8: return 0x10CA8;
    case 0x10CE9: return 0x10CA9;
    case 0x10CEA: return 0x10CAA;
    case 0x10CEB: return 0x10CAB;
    case 0x10CEC: return 0x10CAC;
    case 0x10CED: return 0x10CAD;
    case 0x10CEE: return 0x10CAE;
    case 0x10CEF: return 0x10CAF;
    case 0x10CF0: return 0x10CB0;
    case 0x10CF1: return 0x10CB1;
    case 0x10CF2: return 0x10CB2;
    case 0x118C0: return 0x118A0;
    case 0x118C1: return 0x118A1;
    case 0x118C2: return 0x118A2;
    case 0x118C3: return 0x118A3;
    case 0x118C4: return 0x118A4;
    case 0x118C5: return 0x118A5;
    case 0x118C6: return 0x118A6;
    case 0x118C7: return 0x118A7;
    case 0x118C8: return 0x118A8;
    case 0x118C9: return 0x118A9;
    case 0x118CA: return 0x118AA;
    case 0x118CB: return 0x118AB;
    case 0x118CC: return 0x118AC;
    case 0x118CD: return 0x118AD;
    case 0x118CE: return 0x118AE;
    case 0x118CF: return 0x118AF;
    case 0x118D0: return 0x118B0;
    case 0x118D1: return 0x118B1;
    case 0x118D2: return 0x118B2;
    case 0x118D3: return 0x118B3;
    case 0x118D4: return 0x118B4;
    case 0x118D5: return 0x118B5;
    case 0x118D6: return 0x118B6;
    case 0x118D7: return 0x118B7;
    case 0x118D8: return 0x118B8;
    case 0x118D9: return 0x118B9;
    case 0x118DA: return 0x118BA;
    case 0x118DB: return 0x118BB;
    case 0x118DC: return 0x118BC;
    case 0x118DD: return 0x118BD;
    case 0x118DE: return 0x118BE;
    case 0x118DF: return 0x118BF;
    case 0x16E60: return 0x16E40;
    case 0x16E61: return 0x16E41;
    case 0x16E62: return 0x16E42;
    case 0x16E63: return 0x16E43;
    case 0x16E64: return 0x16E44;
    case 0x16E65: return 0x16E45;
    case 0x16E66: return 0x16E46;
    case 0x16E67: return 0x16E47;
    case 0x16E68: return 0x16E48;
    case 0x16E69: return 0x16E49;
    case 0x16E6A: return 0x16E4A;
    case 0x16E6B: return 0x16E4B;
    case 0x16E6C: return 0x16E4C;
    case 0x16E6D: return 0x16E4D;
    case 0x16E6E: return 0x16E4E;
    case 0x16E6F: return 0x16E4F;
    case 0x16E70: return 0x16E50;
    case 0x16E71: return 0x16E51;
    case 0x16E72: return 0x16E52;
    case 0x16E73: return 0x16E53;
    case 0x16E74: return 0x16E54;
    case 0x16E75: return 0x16E55;
    case 0x16E76: return 0x16E56;
    case 0x16E77: return 0x16E57;
    case 0x16E78: return 0x16E58;
    case 0x16E79: return 0x16E59;
    case 0x16E7A: return 0x16E5A;
    case 0x16E7B: return 0x16E5B;
    case 0x16E7C: return 0x16E5C;
    case 0x16E7D: return 0x16E5D;
    case 0x16E7E: return 0x16E5E;
    case 0x16E7F: return 0x16E5F;
    case 0x1E922: return 0x1E900;
    case 0x1E923: return 0x1E901;
    case 0x1E924: return 0x1E902;
    case 0x1E925: return 0x1E903;
    case 0x1E926: return 0x1E904;
    case 0x1E927: return 0x1E905;
    case 0x1E928: return 0x1E906;
    case 0x1E929: return 0x1E907;
    case 0x1E92A: return 0x1E908;
    case 0x1E92B: return 0x1E909;
    case 0x1E92C: return 0x1E90A;
    case 0x1E92D: return 0x1E90B;
    case 0x1E92E: return 0x1E90C;
    case 0x1E92F: return 0x1E90D;
    case 0x1E930: return 0x1E90E;
    case 0x1E931: return 0x1E90F;
    case 0x1E932: return 0x1E910;
    case 0x1E933: return 0x1E911;
    case 0x1E934: return 0x1E912;
    case 0x1E935: return 0x1E913;
    case 0x1E936: return 0x1E914;
    case 0x1E937: return 0x1E915;
    case 0x1E938: return 0x1E916;
    case 0x1E939: return 0x1E917;
    case 0x1E93A: return 0x1E918;
    case 0x1E93B: return 0x1E919;
    case 0x1E93C: return 0x1E91A;
    case 0x1E93D: return 0x1E91B;
    case 0x1E93E: return 0x1E91C;
    case 0x1E93F: return 0x1E91D;
    case 0x1E940: return 0x1E91E;
    case 0x1E941: return 0x1E91F;
    case 0x1E942: return 0x1E920;
    case 0x1E943: return 0x1E921;
    default: return ch;
  }
}


#undef MARKER
#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) 2007-2013 Jean Gressmann (jean@0x42.de)

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.
*/

/*
    Changelog:
        2013-09-08
            Updated license to to be compatible with Debian license requirements.

        2012-06-06
            Fix for invalid UTF16 characters and some comment fixex (thomas.h.moog@intel.com).

        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 XX   -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.
*/
    XX,      XX,      XX,      XX,      XX,      XX,      XX,      XX,
    XX,      C_WHITE, C_WHITE, XX,      XX,      C_WHITE, XX,      XX,
    XX,      XX,      XX,      XX,      XX,      XX,      XX,      XX,
    XX,      XX,      XX,      XX,      XX,      XX,      XX,      XX,

    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   */
    ESC,  /* 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,XX,-5,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*ok     OK*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*object OB*/ {OB,OB,XX,-9,XX,XX,XX,XX,SB,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*key    KE*/ {KE,KE,XX,XX,XX,XX,XX,XX,SB,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*colon  CO*/ {CO,CO,XX,XX,XX,XX,-2,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*value  VA*/ {VA,VA,-6,XX,-5,XX,XX,XX,SB,XX,CB,XX,MX,XX,ZX,IX,XX,XX,XX,XX,XX,FA,XX,NU,XX,XX,TR,XX,XX,XX,XX,XX},
/*array  AR*/ {AR,AR,-6,XX,-5,-7,XX,XX,SB,XX,CB,XX,MX,XX,ZX,IX,XX,XX,XX,XX,XX,FA,XX,NU,XX,XX,TR,XX,XX,XX,XX,XX},
/*string ST*/ {ST,XX,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*/ {XX,XX,XX,XX,XX,XX,XX,XX,ST,ST,ST,XX,XX,XX,XX,XX,XX,ST,XX,XX,XX,ST,XX,ST,ST,XX,ST,U1,XX,XX,XX,XX},
/*u1     U1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U2,U2,U2,U2,U2,U2,U2,U2,XX,XX,XX,XX,XX,XX,U2,U2,XX,XX},
/*u2     U2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U3,U3,U3,U3,U3,U3,U3,U3,XX,XX,XX,XX,XX,XX,U3,U3,XX,XX},
/*u3     U3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U4,U4,U4,U4,U4,U4,U4,U4,XX,XX,XX,XX,XX,XX,U4,U4,XX,XX},
/*u4     U4*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,UC,UC,UC,UC,UC,UC,UC,UC,XX,XX,XX,XX,XX,XX,UC,UC,XX,XX},
/*minus  MI*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,ZE,IT,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*zero   ZE*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,DF,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*int    IT*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,DF,IT,IT,XX,XX,XX,XX,DE,XX,XX,XX,XX,XX,XX,XX,XX,DE,XX,XX},
/*frac   FR*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,XX,FR,FR,XX,XX,XX,XX,E1,XX,XX,XX,XX,XX,XX,XX,XX,E1,XX,XX},
/*e      E1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,E2,E2,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*ex     E2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*exp    E3*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,XX,XX,XX,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*tr     T1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,T2,XX,XX,XX,XX,XX,XX,XX},
/*tru    T2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,T3,XX,XX,XX,XX},
/*true   T3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*fa     F1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F2,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*fal    F2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F3,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*fals   F3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F4,XX,XX,XX,XX,XX,XX},
/*false  F4*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*nu     N1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,N2,XX,XX,XX,XX},
/*nul    N2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,N3,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*null   N3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*/      C1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,C2},
/*/star  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,XX,-8,XX,-7,XX,-3,XX,XX,XX,XX,XX,XX,FR,FR,XX,XX,XX,XX,E1,XX,XX,XX,XX,XX,XX,XX,XX,E1,XX,XX},
/*\      D1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,D2,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX},
/*\      D2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U1,XX,XX,XX,XX},
};


/*
    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)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;

    if (uc == 0xffff || uc == 0xfffe) {
        return false;
    }

    /* 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 <= XX) {
            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 = ESC;
            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;
    }
}

#undef XX
#undef COUNTOF
#undef parse_buffer_clear
#undef parse_buffer_pop_back_char
/* end of file JSON_parser/JSON_parser.c */
/* end of file /home/stephan/fossil/cwal/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() */
#include <errno.h>

#define S2_USE_SIGNALS S2_HAVE_SIGACTION

#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:\t",__FILE__,__LINE__); 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_scope s2_scope_empty = s2_scope_empty_m;
const s2_enum_builder s2_enum_builder_empty = {
0/*se*/,
0/*flags*/,
0/*entryCount*/,
0/*entries*/
};
const s2_kvp_each_state s2_kvp_each_state_empty = {
0/*e*/, 0/*self*/, 0/*callback*/, 0/*valueArgFirst*/
};

#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 */;

/**
   Frees up all data stored in se which refers to cwal_values. It
   must only be called immediately before the top-most stack is
   popped. Cleaned-up data includes any s2_ob()-related buffers.
*/
static void s2_engine_cleanup_values(s2_engine * se){
  assert(se->e);
  assert(!se->scopes.current);
  assert(cwal_scope_current_get(se->e) == &se->scopes.topScope);
  if(se->stash){
    cwal_value_unref( se->stash );
    se->stash = 0;
  }
  memset(&se->cache, 0, sizeof(se->cache))
    /* se->cache.keyXXX all point back into se->stash */;
  if(se->funcStash){
    cwal_value_unref( cwal_hash_value(se->funcStash) );
    se->funcStash = 0;
  }
  cwal_exception_set( se->e, 0 );
  s2_propagating_set( se, 0 );
  while(se->ob.count) s2_ob_pop(se);
  cwal_list_reserve(se->e, &se->ob, 0);
}

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->dotOp.lhs);
    *se = s2_engine_empty;
  }else{
    s2_ukwd_free(se);
    while( se->scopes.level ){
      /* reminder: se took over cwal's top-most scope during init. */
      cwal_scope_pop(se->e);
    }
    assert(!se->e->current);
    assert(!se->scopes.list && "Should have been cleaned up via pop hook.");
    assert(!se->scopes.current && "Should have been cleaned up via pop hook.");
    s2_modules_close(se)
      /* must come after all scopes are gone because the modules might
         have introduced memory which the scopes reference via native
         values or function bindings. */;

    /*
      Reminder: the following cleanup is only legal as long as we
      don't use/deref any Values...

      e.g if the token stack is changed to ref its values, we need to
      moved its cleanup higher up.
    */
    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
};

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

static void s2_engine_subexpr_save2(s2_engine * e, s2_subexpr_savestate * to,
                                    char resetIt){
  to->ternaryLevel = e->ternaryLevel;
  if(resetIt) e->ternaryLevel = 0;
}

void s2_engine_subexpr_save(s2_engine * e, s2_subexpr_savestate * to){
  s2_engine_subexpr_save2(e, to, 1);
}

#define s2__scope_for_level(SE,LVL)             \
  (((LVL) && (LVL)<=(SE)->scopes.level)         \
   ? ((SE)->scopes.list + (LVL) - 1)            \
   : (s2_scope*)NULL)

s2_scope * s2_scope_for_level( s2_engine const * se,
                               cwal_size_t level ){
  assert(se);
  return s2__scope_for_level(se, level);
}

#define s2__scope_current(SE)                            \
  (((SE) && (SE)->e && (SE)->e->current)                  \
   ? s2__scope_for_level((SE), (SE)->e->current->level)   \
   : 0)

s2_scope * s2_scope_current( s2_engine const * se ){
  return s2__scope_current(se);
}



/**
   Ensures that se->scopes.list has at least newDepth scopes in
   reserve. Returns 0 on success, CWAL_RC_OOM on allocation error. If
   newDepth is 0 then it frees se->scopes.list, but that may only
   be used during s2_engine cleanup (else an assert() will fail).
*/
static int s2_reserve_scopes( s2_engine * se, cwal_size_t newDepth ){
  if(0==newDepth){
    assert(!se->scopes.current && "Else internal mismanagement of s2_engine::scopes.");
    cwal_realloc(se->e, se->scopes.list, 0);
    se->scopes.list = 0;
    se->scopes.alloced = 0;
  }
  else if(se->scopes.alloced < newDepth){
    s2_scope * sc;
    cwal_size_t newCount = se->scopes.alloced
      ? (se->scopes.alloced * 8 / 5) : 16;
    assert(newCount > newDepth);
    sc = (s2_scope *)cwal_realloc( se->e, se->scopes.list,
                                   (cwal_size_t)(newCount * sizeof(s2_scope)) );
    if(!sc) return CWAL_RC_OOM;
    ++se->metrics.totalScopeAllocs;
    se->scopes.list = sc;
    se->scopes.alloced = newCount;
  }
  return 0;
}

/**
   cwal_engine_vtab::hook::scope_push() hook. state must be a
   (s2_engine*). This function syncronizes the cwal scope stack with
   our s2_scope stack. The only error condition is a potential
   CWAL_RC_OOM if reserving a block of s2_scope entries fails. It
   keeps all scopes in a single array, which has a maximum length
   directly related to (but not identical to) the highest-ever scope
   depth and is expanded as needed (but never shrinks until the engine
   is finalized).
*/
static int s2_scope_hook_push( cwal_scope * s, void * state ){
  s2_engine * se = (s2_engine*)state;
  s2_scope * s2sc;
  int rc;
  assert(0 < s->level);
  assert(se);
  assert(s2_engine_from_state(s->e) == se);
  rc = s2_reserve_scopes(se, s->level);
  if(rc) return rc;
  /*MARKER(("Pushing scope level %d (%d reserved)\n", (int)s->level,
    (int)se->scopes.alloced));*/
  se->scopes.level = s->level;
  s2sc = s2__scope_for_level(se, s->level);
  *s2sc = s2_scope_empty;
  s2sc->cwalScope = s;
  s2sc->flags = se->scopes.nextFlags;
  s2_engine_subexpr_save2(se, &s2sc->saved,
                         (S2_SCOPE_F_KEEP_TERNARY_LEVEL
                          & se->scopes.nextFlags)
                         ? 0 : 1);
  se->scopes.nextFlags = 0;
  se->scopes.current = s2sc;
  ++se->metrics.totalScopesPushed;
  if(s->level > se->metrics.maxScopeDepth){
    se->metrics.maxScopeDepth = (int)s->level;
  }
  return rc;
}

/**
   cwal_engine_vtab::hook::scope_pop() hook. state must be
   a (s2_engine*). 
*/
static void s2_scope_hook_pop( cwal_scope const * s, void * state ){
  s2_engine * se = (s2_engine*)state;
  s2_scope * s2sc;
  /*MARKER(("Popping scope level %d\n", (int)s->level));*/
  assert(se);
  assert(0 < s->level);
  assert(s2_engine_from_state(s->e) == se);
  assert(se->scopes.alloced >= s->level);
  s2sc = s2__scope_for_level(se, s->level);
  assert(s2sc);
  assert(s == s2sc->cwalScope);
  se->scopes.current = s2__scope_for_level(se, s->level-1);
  se->scopes.level = s->level-1;
  assert(s->level>1
         ? (s->level == 1 + se->scopes.current->cwalScope->level)
         : !se->scopes.current);
  s2_dotop_state(se, 0, 0, 0);
  if(s2sc->evalHolder){
    cwal_value * av = cwal_array_value(s2sc->evalHolder);
    s2sc->evalHolder = 0;
    assert(1 == cwal_value_refcount(av));
    assert(s2sc->cwalScope == cwal_value_scope(av));
    cwal_value_unref(av);
  }
  s2_engine_subexpr_restore(se, &s2sc->saved);
  *s2sc = s2_scope_empty;
  if(1==s->level){
    /* Final scope is popping. Let's clean se->scopes.list. */
    /*MARKER(("pop hook: the engine is shutting down. Freeing %d s2_scopes.\n",
      (int)se->scopes.alloced));*/
    assert(!se->scopes.current);
    s2_engine_cleanup_values(se);
    s2_reserve_scopes(se, 0);
  }
}


int s2_engine_init( s2_engine * se, cwal_engine * e ){
  void const * allocStamp;
  int rc = 0;
  if(!se || !e) return CWAL_RC_MISUSE;
  assert(S2_RC_CLIENT_BEGIN == CWAL_RC_CLIENT_BEGIN + 2000);
  assert(5000 == S2_RC_CLIENT_BEGIN);
  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);
    cwal_engine_type_name_proxy( e, s2_type_name_proxy );
  }

  /* 
     What follows assumes that the client has created no cwal_values
     [of interest] before this routine was called. cwal necessarily
     pushes a scope during its setup, but we need to pop that scope
     and start a new top scope so that the s2/cwal scope levels
     stay in sync.
  */
  assert(se->e->current);
  assert(se->e->current==&se->e->topScope);

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

  /* Clear all cwal-level scopes and start with a new scope
     stack, so that we can hook up our s2_scopes. */
  if( (rc = cwal_scope_pop(se->e)) ) return rc;
  assert(!se->e->current);
  assert(!e->vtab->hook.scope_push);
  assert(!e->vtab->hook.scope_pop);
  e->vtab->hook.scope_push = s2_scope_hook_push;
  e->vtab->hook.scope_pop = s2_scope_hook_pop;
  e->vtab->hook.scope_state = se;

  /**
     cwal always needs at least 1 scope active. Now that we have our
     scope hooks in place, pop a new top-level scope.
  */
  {
    cwal_scope * cs = &se->scopes.topScope;
    if( (rc = cwal_scope_push(se->e, &cs) ) ) return rc;
    assert(se->e->current);
    assert(1==se->e->current->level);
    assert(se->e->current==&se->scopes.topScope);
    assert(se->e==se->scopes.topScope.e);
    assert(se->scopes.alloced >= 10 && "But we start with at least 10 scopes?");
  }

  /* We need some strings as keys in a few places, so put a copy in
     the stash.

     Reminder: it/they're owned by the stash.
  */
#define STASHVAL(MEMBER,VAL)                    \
  if(!rc) do{                                           \
      if(!(se->cache.MEMBER = VAL)) rc = CWAL_RC_OOM;                   \
      else{                                                             \
        cwal_value_ref(se->cache.MEMBER);                               \
        rc = s2_stash_set_v( se, se->cache.MEMBER, se->cache.MEMBER );  \
        cwal_value_unref(se->cache.MEMBER);                             \
        if(rc){                                                         \
          se->cache.MEMBER = 0;                                         \
        }                                                               \
      }                                                                 \
    } while(0)
#define STASHKEY(MEMBER,STR)                                            \
  STASHVAL(MEMBER,cwal_new_string_value(e, (STR), (cwal_size_t)(sizeof(STR)-1)))

  STASHKEY(keyPrototype,"prototype");
  STASHKEY(keyThis,"this");
  STASHKEY(keyArgv,"argv");
  STASHKEY(keyValue,"value");
  STASHKEY(keyName,"name");
  STASHKEY(keyTypename,"__typename");
  STASHKEY(keyScript,"script");
  STASHKEY(keyLine,"line");
  STASHKEY(keyColumn,"column");
  STASHKEY(keyStackTrace,"stackTrace");
  STASHKEY(keyCtorNew,"__new");
  STASHKEY(keyImportFlag,"doPathSearch");
#if S2_TRY_INTERCEPTORS
  STASHKEY(keyInterceptee,"interceptee");
#endif
#undef STASHKEY
#undef STASHVAL

  return rc;
}

/**
   Enumeration of various scope-sweeping options.
 */
enum S2SweepModes {
SweepMode_VacuumRecursive = -2,
SweepMode_SweepRecursive = -1,
SweepMode_Default = 0,
SweepMode_Sweep = 1,
SweepMode_Vacuum = 2
};
/**
   Might or might not trigger a sweep or vacuum, depending partially
   on the initialBroomMode _hint_ and various internal state.
*/
static int s2_engine_sweep_impl( s2_engine * se, enum S2SweepModes initialBroomMode ){
  int rc = 0;
  s2_scope * sc = s2__scope_current(se);
  assert(se && se->e);
  assert(sc);
  /*MARKER(("SWEEP RUN #%d? sweepTick=%d, s-guard=%d, v-guard=%d\n",
          se->sweepTotal, sc->sguard.sweepTick,
          sc->sguard.sweep, sc->sguard.vacuum));*/
  if(!sc){
    assert(!"Someone called s2_engine_sweep() without cwal_scope_push().");
    return CWAL_RC_MISUSE;
  }
#if defined(DEBUG)
  switch(initialBroomMode){
    case SweepMode_Default:
    case SweepMode_Sweep:
    case SweepMode_Vacuum:
    case SweepMode_VacuumRecursive:
    case SweepMode_SweepRecursive:
      break;
    default:
      assert(!"Invalid sweep mode!");
      return CWAL_RC_MISUSE;
  }
#endif
  if(sc->sguard.sweep>0 || se->sweepInterval<=0) return 0;
  else if(SweepMode_Default!=initialBroomMode
          && (++sc->sguard.sweepTick != se->sweepInterval)) return 0;
  else{
    enum S2SweepModes sweepMode =
      (SweepMode_Default==initialBroomMode)
      ? SweepMode_Sweep : initialBroomMode;
    int valsSwept = 0;
    ++se->sweepTotal;
    sc->sguard.sweepTick = 0;
    /* See if we can/should use sweep2 or vacuum... */
    if(SweepMode_Default==initialBroomMode
       && se->vacuumInterval>0
       && !sc->sguard.vacuum
       && (0 == (se->sweepTotal % se->vacuumInterval))
       ){
      sweepMode = SweepMode_Vacuum;
    }
    /* MARKER(("SWEEP RUN #%d mode=%d\n", se->sweepTotal, sweepMode)); */
    switch(sweepMode){
      default:
        assert(!"Invalid sweep mode!");
        return CWAL_RC_MISUSE;
      case SweepMode_Default:
      case SweepMode_Sweep:
        valsSwept = (int)cwal_engine_sweep(se->e);
        break;
      case SweepMode_SweepRecursive:
#if 0
        assert(!"NO!"); /* this cannot work with current code */
#else
        /* 20181126: this actually works, at least in the s2 unit
           test scripts. It's not terribly interesting in terms of
           cleaning up, though - a recursive vacuum is really the
           Holy Grail of cleanup. */
#endif
        /*MARKER(("SWEEP RUN #%d mode=%d\n", se->sweepTotal, sweepMode));*/
        valsSwept = (int)cwal_engine_sweep2(se->e, 1);
        break;
      case SweepMode_Vacuum:
        assert(SweepMode_Vacuum==sweepMode);
        rc = cwal_engine_vacuum(se->e, se->flags.traceSweeps
                                ? &valsSwept : 0
                                /* b/c this reporting costs */);
        assert(!rc && "Vacuum \"cannot fail\".");
        break;
      case SweepMode_VacuumRecursive:{
        /**
           20181126: nope, recursive vacuum still doesn't quite
           work. It pulls being-eval'd values out from under various
           pending expressions. e.g. the 'using' part of a function
           definition can trigger it, as can loop constructs.

           [Later that day:] If we guard a scope against vacuuming
           based on both s->sguard.vacuum and s->sguard.sweep,
           recursive vacuum actually works (meaning it doesn't crash
           us), but (A) in the unit test suite it's not cleaning up
           any more than non-recursive and (B) it's slow, and would
           need to be called infrequently. A recursive vacuum would
           only hypothetically be useful for catching pathological
           use cases such as this contrived bit of code:

           // Create a pathologically cyclic structure:
           var a = [1,2,3], o = {a};
           o.a[] = o; o.a[] = a;
           o[o] = o; o[a] = a;
           scope {
             print(__FLC, 'nulling...');
             o = a = null;
             print(__FLC, 'nulled');
             for( var i = 0; i < 20; ++i ){print(__FLC, i)}
             print(__FLC,"scope closing");
           }
           print(__FLC,"scope closed");
           ; ; ; ; ; ;
           print(__FLC,"done");

           As long as we're in that inner scope (or lower), neither
           'a' nor 'o' can be cleaned up without a recursive vacuum
           because they're cyclic. Even if we try to recursively
           vacuum, we can only clean them if the sweep/vacuum guards
           do not prohibit it the vacuum. (The guards are there to
           protect being-eval'd stuff, and the main eval impl always
           (IIRC) sweep-guards the currently-evaluating expression.)
           i.e. chances are good(?) that we wouldn't be able to vacuum
           the scope even if we wanted to.

           A quick test of exactly that case shows that the scope
           which owns 'a' and 'o' is indeed protected (because the
           'scope' keyword is part of a pending expression, and
           therefore causes that scope to sweep-guard) until the
           'scope' completes.

           Recursive vacuum is arbitrarily expensive and is rarely
           useful, so there's really no reason to enable it by
           default, but it might be interesting to add a "mega-gc"
           function or keyword which forces a recursive vacuum, with
           the caveat that that the sguard can block it from
           happening. If we bypass the sguard, we eventually _will_
           clean up stuff we definitely don't want to clean up.
        */
        s2_scope * s = s2__scope_current(se);
        for( ; !rc && s ; s = s2__scope_for_level(se, s->cwalScope->level-1) ){
          if(!s->sguard.vacuum && !s->sguard.sweep){
            int count = 0;
            MARKER(("Attempting recursive vaccum on scope level %d...\n",
                    (int)s->cwalScope->level));
            rc = cwal_scope_vacuum(s->cwalScope, se->flags.traceSweeps
                                   ? &count : 0
                                   /* b/c this reporting costs */);
            valsSwept += count;
          }else{
            MARKER(("Skipping recursive vaccum on scope level %d due to sguard.\n",
                    (int)s->cwalScope->level));
          }
        }
        assert(!rc && "Vacuum \"cannot fail\".");
        break;
      }
    }
    if(se->flags.traceSweeps>2
       || (se->flags.traceSweeps && valsSwept)){
      char const * label = "???";
      s2_ptoker const * const pt = se->currentScript;
      s2_ptoken const * tok = pt ? &pt->token : 0;
      char const * tokEnd = tok ? s2_ptoken_end(tok) : 0;
      const char * ptEnd = pt ? s2_ptoker_end(pt) : 0;
      switch(sweepMode){
        case SweepMode_Vacuum: label = "vacuum"; break;
        case SweepMode_VacuumRecursive: label = "recursive vacuum"; break;
        case SweepMode_Sweep: label = "sweep"; break;
        case SweepMode_SweepRecursive: label = "recursive sweep"; break;
        default: break;
      }
      if(se->flags.traceSweeps>1 && valsSwept
         && tokEnd
         && tokEnd>=ptEnd
         && tokEnd-1<ptEnd){
        s2_linecol_t line = 0, col = 0;
        cwal_size_t nLen = 0;
        char const * scriptName = s2_ptoker_name_first( pt, &nLen );
        s2_ptoker_count_lines( pt, tokEnd-1
                               /* ^^^tokEnd might be at EOF, which isn't legal here */,
                               &line, &col );
        MARKER(("Swept up %d value(s) in %s mode somewhere around %.*s:%d:%d\n",
                valsSwept, label,
                (int)(nLen ? nLen : 5), nLen ? scriptName : "<" "???" ">" /* trigraph! */,
                line, col));
      }else{
        MARKER(("Swept up %d value(s) in %s mode\n", valsSwept, label));
      }
    }
    return rc;
  }
}

int s2_engine_sweep( s2_engine * se ){
  return s2_engine_sweep_impl(se, SweepMode_Default);
}

int s2_cb_internal_experiment( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);
  se->flags.traceSweeps += 3;
  rc = s2_engine_sweep_impl( se, SweepMode_VacuumRecursive );
  se->flags.traceSweeps -= 3;
  *rv = cwal_value_undefined();
  return rc;
}

void s2_engine_vacuum( s2_engine * se ){
  s2_scope * sc = s2__scope_current(se);
  assert(se && se->e);
  assert(sc);
  if(!sc->sguard.sweep && !sc->sguard.vacuum){
#ifdef DEBUG
    int vacCount = 0;
    cwal_engine_vacuum(se->e, &vacCount);
    assert(vacCount >= 0);
#else
    cwal_engine_vacuum(se->e, NULL);
#endif
  }
}

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.traceTokenStack);
  cwal_outputf(se->e, "s2_engine::traceTokenStack: 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.traceTokenStack) 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_valtok( 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_valtok( 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_valtok( 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 ){
  /*
    Reminder: t does not hold a reference to t->value, so we do not
    let a reference go here. Any stray stack machine values
    (e.g. those left over during error handling mid-expression) will
    be cleaned up by the scope which is, more likely than not, about
    to pop as a result of error propagation (or it's the global scope,
    in which case it's free to sweep them up).
  */
  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_free2( se->e, t, sizeof(s2_stoken) );
  }
}

static int s2_process_op_impl( s2_engine * se, s2_op const * op,
                               char popOpStack ){
  int rc = se->flags.interrupted;
  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(rc) return rc;
  else if(se->flags.traceTokenStack){
    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, 1);
  }
  if(!op->call){
    rc = s2_engine_err_set(se, CWAL_RC_UNSUPPORTED,
                           "Operator %s does not have an internal "
                           "call() impl.",
                           op->sym);
  }     
  else 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{
      rc = op->call(op, se, op->arity, &rv);
      popArgCount = op->arity;
    }
  }else{
#if 0
    assert(!"not possible... except when running the old test.c, it seems...");
#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_UNUSED_VAR 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 */;
    }
#endif
  }
  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);
    }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_valtok(se, tResult);
#if 1
        /*
          20171115: 'abc'[2][0][0][0][0][0]

          is assert()ing (sometimes) in cwal_value_ref() when string
          interning is on because 'c' is an interned value but the
          above op chain is not ref'ing it (as it should). We could
          patch that in s2_eval_expr_impl(), but this is a more
          general problem which also potentially affect any place this
          routine is used, e.g. function calls may theoretically
          trigger it.

          Reminder: the above might fail in s2sh interactive mode
          while not failing in a unit test script: triggering it is
          dependent on engine-level state. The eval-hold resolves it,
          in either case.
        */
        rc = s2_eval_hold(se, rv);
#endif
      }else{
        if(rv) cwal_refunref(rv);
      }
    }
  }else{
    assert(!rv);
  }
  if(topOp){
    if(rc && !se->opErrPos) se->opErrPos = topOp->srcPos;
    s2_stoken_free(se, topOp, 1);
  }
  return s2_check_interrupted(se, 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 = s2_check_interrupted(se, 0);
  if(rc) return rc;
  else if(!op){
    rc = s2_engine_err_set(se, CWAL_SCR_SYNTAX,
                           "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_SCR_SYNTAX,
                             "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;
  }
  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;
}

int s2_error_copy( s2_engine * se, s2_error const * src, s2_error * dest ){
  if(!se || !src || !dest || src==dest) return CWAL_RC_MISUSE;
  else{
    int rc = 0;
    s2_error_reset( dest );
    dest->line = src->line;
    dest->col = src->col;
    dest->code = src->code;
    if(src->msg.used){
      rc = cwal_buffer_append(se->e, &dest->msg, src->msg.mem,
                            src->msg.used);
    }
    if(!rc && src->script.used){
      rc = cwal_buffer_append(se->e, &dest->script,
                            src->script.mem, src->script.used);
    }
    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;
}

void s2_error_move( s2_error * lower, s2_error * higher ){
  s2_error const err = *lower;
  *lower = *higher;
  lower->line = lower->col = lower->code = 0;
  lower->msg.used = lower->script.used = 0U;
  *higher = err;
}



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;
  /**
     FIXME: clean up these setters to not strand these temporary
     values (on error cases) until the next sweep.
  */
  if(scriptName && *scriptName){
    cwal_value * snv = cwal_new_string_value(se->e,
                                              scriptName,
                                              cwal_strlen(scriptName));
    cwal_value_ref(snv);
    rc = snv
      ? cwal_prop_set_v(ex, se->cache.keyScript, snv)
      : CWAL_RC_OOM;
    cwal_value_unref(snv);
  }
  if(!rc && line>0){
    rc = cwal_prop_set_v(ex, se->cache.keyLine,
                         cwal_new_integer(se->e, line));
    if(!rc) rc = cwal_prop_set_v(ex, se->cache.keyColumn,
                                 cwal_new_integer(se->e, col));
  }
  /* MARKER(("strace count=%u\n", se->strace.count)); */
  if(!rc
     && se->flags.exceptionStackTrace
     && se->strace.count
     && !cwal_prop_has_v(ex, se->cache.keyStackTrace, 0)){
    cwal_value * stackTrace = 0;
    rc = s2_strace_generate(se, &stackTrace);
    if(!rc && stackTrace){
      /* s2_dump_val(stackTrace, "stackTrace"); */
      cwal_value_ref(stackTrace);
      rc = cwal_prop_set_v(ex, se->cache.keyStackTrace, stackTrace);
      cwal_value_unref(stackTrace);
    }
  }
  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_v(ex, se->cache.keyLine, 1)
     /* ^^^ very cursory check for "already has this state" */
     ) return 0;
  else{
    s2_linecol_t line = 0, col = 0;
    cwal_size_t scriptNameLen = 0;
    s2_ptoker const * top = 0;
    char const * scriptName = s2_ptoker_name_first(script, &scriptNameLen);
    char const * errPos = s2_ptoker_err_pos(script);
    assert(errPos);
    s2_ptoker_count_lines(top ? top : 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)));
  }
  if(msg) cwal_value_ref(msg);
  ex = msg
    ? cwal_new_exception_value(se->e, err->code, msg)
    : 0;
  if(msg){
    cwal_value_unref(msg);
    msg = 0;
  }
  if(!ex){
    rc = CWAL_RC_OOM;
  }else{
    cwal_value_ref(ex);
    assert(cwal_value_is_exception(ex));
    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;
    }else{
      cwal_value_unhand(ex);
    }
  }
  s2_error_reset(err);
  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 * exv;
  char wasException;
  assert(v);
  if(CWAL_RC_OOM==code) return code;
  else if(!pr) pr = se->currentScript;
  exv = cwal_exception_value(cwal_value_exception_part(se->e, v));
  wasException = exv ? 1 : 0;
  if(!exv){
    exv = cwal_new_exception_value(se->e, code ? code : CWAL_RC_EXCEPTION, v);
    if(!exv) rc2 = CWAL_RC_OOM;
  }
  if(exv){
    cwal_value * const propTarget = wasException ? v : exv;
    cwal_value_ref(exv);
    if(pr && !cwal_prop_has_v(propTarget, se->cache.keyLine, 1)
      /* ^^^ *seems* (at an admittedly quick glance) to not inherit
         location information, so we'll harvest it. */){
      /* 20191228: this was changed to only set the properties if the
         passed-in object seems to not have/inherit them to begin
         with. Previously we were, when v inherited an exception,
         setting this state in v even through v inherited it from exv,
         which led to, e.g., duplicate (but ever-so-slightly
         different) stackTrace values.
      */
      rc2 = s2_add_script_props(se, propTarget, pr);
    }
    if(!rc2){
      rc = cwal_exception_set(se->e, propTarget);
    }
    cwal_value_unref(exv)
      /* On success, cwal holds a reference. On error, if exv was
         derived from v then the caller had better hold a reference to
         it or else we're all doomed. If exv is a prototype of v,
         rather than being v itself, it has a reference through that
         association. */;
  }
  return rc2 ? rc2 : rc;
}


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

int s2_cb_throw( cwal_callback_args const * args, int code, char const * fmt, ... ){
  int rc;
  switch(code){
    case CWAL_RC_OOM: rc = code;
      break;
    default: {
      va_list vargs;
      va_start(vargs,fmt);
      rc = cwal_exception_setfv(args->engine, code, fmt, vargs);
      va_end(vargs);
      break;
    }
  }
  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_reset2( s2_engine * se ){
  s2_engine_err_reset(se);
  cwal_exception_set(se->e, 0);
  s2_propagating_set(se, 0);
}

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 ){
  int const check = se->flags.interrupted;
  if(check && check!=se->err.code){
    static const char * msgInterrupt = "Engine was interrupted.";
    static const cwal_size_t msgInterruptLen = (cwal_size_t)(sizeof(msgInterrupt)
                                                             -sizeof(msgInterrupt[0]/*NUL*/));
    if(msg) *msg = 0;
    if(msgLen) *msgLen = msgInterruptLen;
    return check;
  }else{
    return s2_error_get( &se->err, msg, msgLen );
  }
}



void s2_dump_value( cwal_value * v, char const * msg,
                    char const * file, 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 /* Reminder: we cannot use cwal_output() because
                         v might be a built-in const without a cwal_engine
                         instance. */;
  if(v){
    assert((sc || cwal_value_is_builtin(v))
           && "Seems like we've cleaned up too early.");
  }
  if(!once){
    jopt.cyclesAsStrings = 1;
    jopt.functionsAsObjects = 0;
    jopt.addNewline = 1;
    jopt.indentSingleMemberValues = 0;
    jopt.indent = 0;
    jopt.indentString.str = "  ";
    jopt.indentString.len = cwal_strlen(jopt.indentString.str);
    once = 1;
  }
  if(file && func && line>0){
    fprintf(out,"%s:%d:%s(): ", file, line, func);
  }
  fprintf(out,"%s%s%s@%p[scope=#%d ref#=%d] "
          "==> ",
          msg ? msg : "", msg?": ":"",
          cwal_value_type_name(v),
          (void const *)v,
          (int)(sc ? sc->level : 0),
          (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:
    case CWAL_TYPE_UNIQUE:
      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);
  }
  abort();
}

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

static int s2_stash_init(s2_engine * se){
  assert(!se->stash);
  se->stash = cwal_new_hash_value(se->e, 47)
    /* 20200118: the stash currently only contains
       ~26 values, not including anything which
       loadable modules might install. */
    ;
  if(!se->stash) return CWAL_RC_OOM;
  else{
    cwal_scope * topScope;
    topScope = &se->scopes.topScope;
    assert(topScope->level);
    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 0;
}

int s2_stash_set_v( s2_engine * se, cwal_value * key, cwal_value * v ){
  int rc = 0;
  cwal_hash * h;
  if(!key || !v) return CWAL_RC_MISUSE;
  else if(!se->stash){
    rc = s2_stash_init(se);
    if(rc) return rc;
  }
  h = cwal_value_get_hash(se->stash);
  rc = cwal_hash_insert_v(h, key, v, 1 );
  if(!rc){
    rc = cwal_hash_grow_if_loaded(h, 0.75);
  }
  return rc;
}

int s2_stash_set( s2_engine * se, char const * key, cwal_value * v ){
  int rc;
  cwal_value * kv = cwal_new_string_value(se->e, key, cwal_strlen(key));
  if(kv){
    cwal_value_ref(kv);
    rc = s2_stash_set_v(se, kv, v);
    cwal_value_unref(kv);
  }else{
    rc = CWAL_RC_OOM;
  }
  return rc;
}

cwal_value * s2_stash_get2( s2_engine * se, char const * key,
                            cwal_size_t keyLen){
    if(!se || !key || !*key || !se->stash) return NULL;
    else {
      return cwal_hash_search( cwal_value_get_hash(se->stash),
                                key, keyLen );
    }
}

cwal_kvp * s2_stash_get2_kvp( s2_engine * se, char const * key,
                              cwal_size_t keyLen){
    if(!se || !key || !*key || !se->stash) return NULL;
    else {
      if(0==keyLen && *key) keyLen = cwal_strlen(key);
      return cwal_hash_search_kvp( cwal_value_get_hash(se->stash),
                                    key, keyLen );
    }
}

cwal_value * s2_stash_get( s2_engine * se, char const * key ){
  return s2_stash_get2(se, key, cwal_strlen(key));
}

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


#define HAS_ENUM_FLAG(ClientFlags) \
  (S2_VAL_F_CLASS_ENUM & (ClientFlags))
#define HAS_DOTLIKE_FLAG(ClientFlags) \
  ((S2_VAL_F_DOT_LIKE_OBJECT & (ClientFlags))   \
   || HAS_ENUM_FLAG(ClientFlags))


#if S2_TRY_INTERCEPTORS

/* static */ cwal_function * s2_value_is_interceptor( cwal_value const * v ){
  return (cwal_container_flags_get(v) & CWAL_CONTAINER_INTERCEPTOR)
    ? cwal_value_get_function(v)
    : 0;
}

/**
   Proxy for handling get/set interceptor calls.

   If S2_TRY_INTERCEPTORS is false, this is a no-op which returns 0,
   else...

   If s2_value_is_interceptor(func) then it is treated like an
   interceptor, calling the function on self and passing it the
   setterArg (if not NULL) or no arguments (assumed to be the getter
   call form). If rv and setterArg are not NULL, the result goes in
   *rv: the result of setters is ignored by the framework (the setter
   APIs simply have no way to communicate overridden results all the
   way back up the stack).
 */
static int s2__check_intercept( s2_engine * se,
                                cwal_value * propFoundIn,
                                cwal_value * self,
                                cwal_value * func,
                                cwal_value * setterArg,
                                cwal_value **rv ){
  int rc = 0;
  cwal_function * f = s2_value_is_interceptor(func);
  if(f){
    cwal_value * frv = 0;
    /*if(propFoundIn != self){
      s2_dump_val(propFoundIn,"propFoundIn");
      s2_dump_val(self,       "self       ");
      }*/
    rc = cwal_function_call2( f, propFoundIn,
                              self,
                              setterArg ? NULL : &frv,
                              setterArg ? 1 : 0,
                              setterArg ? &setterArg : NULL );
    if(rc){
      /* Error! We hope an exception was thrown so that
         the caller of this func can respond to it. */
      assert(!frv);
    }else{
      if(rv) *rv = frv ? frv : cwal_value_undefined();
      else if(frv){
        cwal_refunref(frv);
        frv = 0;
      }
    }
  }
  return rc;
}

#endif/* end S2_TRY_INTERCEPTORS */

/**
   Internal helper for s2_get_v_proxy2(). Fetches the n'th UTF8
   character from the given str, returning cwal_value_undefined() if n
   is out of range or the input appears to not be UTF8, and NULL on
   allocation error. The value is returned as a new length-1 string.

   TODO: negative values to count from the end, but then we'll
   also need to go patch string.charAt() for that.
*/
cwal_value * s2_charat_by_index( s2_engine *se,
                                 cwal_string const * str,
                                 cwal_int_t n ){
  cwal_size_t slen = 0;
  unsigned char const * cstr =
    (unsigned char const * )cwal_string_cstr2(str, &slen);
  unsigned int cp = 0;
  assert(cstr);
  if(n < 0 || (cwal_size_t)n >= cwal_string_length_bytes(str)){
    return cwal_value_undefined();
  }else if(cwal_string_is_ascii(str)){
    cp = cstr[n];
  }else if(cwal_utf8_char_at( cstr, cstr + slen, (cwal_size_t)n,
                              &cp )){
    return cwal_value_undefined();
  }

  {
    unsigned char buf[6] = {0,0,0,0,0,0};
    int const clen =
      cwal_utf8_char_to_cstr(cp, buf,(cwal_size_t)sizeof(buf));
    assert(clen<(int)sizeof(buf));
    if(clen<1) return cwal_value_undefined();
    else return cwal_new_string_value(se->e, (char const *)buf,
                                      (cwal_size_t)clen);
  }
}

/**
   Internal s2_get_v() impl. Possibly recursively looks up self's
   protototype chain until it finds the given property key. Implements
   several special-case lookups as well:

   - a hash-type 'self' container self with the "dot uses hash
   entries" flag searches hash entries first and falls back to
   property lookup.

   - If key is the string "prototype" then self's prototype is
   returned.

   - If self is-a string and key is-a integer then it does a
   character-at operation, returning the value as a length-1 string or
   (if the index is out of range) the undefined value (or NULL on
   allocation error).

   Reminder to self: this is what fixed the problem that in some
   callbacks, this.xyx was always resolving to undefined.

   if foundIn is not NULL then (A) prototype lookups are enabled and
   (B) *foundIn gets set to the prototype in which the property is
   found (which can be different from self).
*/
static cwal_value * s2_get_v_proxy2( s2_engine * se,
                                     cwal_value * self,
                                     cwal_value * key,
                                     cwal_value ** foundIn){
  cwal_value * v = NULL;
  cwal_hash * h = 0;
  cwal_value * foundAt = 0;
#if S2_TRY_INTERCEPTORS
  cwal_value * origin = self;
#endif
  
  uint16_t vflags = cwal_container_client_flags_get(self);
  assert(se && self && key);
  s2_engine_err_reset(se);
  /* s2_dump_val(key,"get_v_proxy key"); */
  if(s2_value_is_prototype_string(se, key)){
    /* x.prototype */
    return cwal_value_prototype_get( se->e, self );
  }else if(cwal_value_is_unique(self)
           && s2_value_is_value_string(se, key)){
    /* enum.entry.value */
    return cwal_unique_wrapped_get( self );
  }else if(cwal_value_is_string(self)
           && cwal_value_is_integer(key)){
    /* string[index] */
    cwal_int_t const n = cwal_value_get_integer(key);
    return s2_charat_by_index( se, cwal_value_get_string(self), n );
  }

#if 0 && S2_TRY_INTERCEPTORS
  cwal_prop_getX_v( self, key, &v );
#else
  while(!v && self){
    cwal_kvp const * kvp = 0;
    if(HAS_DOTLIKE_FLAG(vflags)
       && (h = cwal_value_get_hash(self))){
      kvp = cwal_hash_search_kvp_v(h, key);
#if 1
      /* Fall back to its own properties, instead of
         immediately going up to the prototype. */
      if(!kvp){
        kvp = cwal_prop_get_kvp_v(self, key, 0, NULL);
      }
#endif
    }else{
      kvp = cwal_prop_get_kvp_v(self, key, 0, NULL);
    }
    if(kvp) v = cwal_kvp_value(kvp);
    if(v) {
      foundAt = self;
      break;
    }
    else if(!foundIn) break;
    self = cwal_value_prototype_get(se->e, self);
    if(self) vflags = cwal_container_client_flags_get(self);
  }

  if(v){
    if(foundIn) *foundIn = foundAt;
#if S2_TRY_INTERCEPTORS
    s2__check_intercept( se, foundAt, origin, v, 0, &v );
#endif
  }
#endif
  return v;
}

cwal_value * s2_var_get_v( s2_engine * se, int scopeDepth,
                           cwal_value const * 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 ){

  return s2_set_with_flags_v(se, self, key, v,
                             CWAL_VAR_F_PRESERVE);
}

/**
   Internal proxy for s2_set_with_flags_v(). It handles the setting of
   Object properties and Hash entries if
   HAS_DOTLIKE_FLAG(clientFlags). kvpFlags are the flags to set on the
   property (e.g. CWAL_VAR_F_CONST and CWAL_VAR_F_HIDDEN are fairly
   common). Pass CWAL_VAR_F_PRESERVE to keep any existing flags.
*/
static int s2_set_prop_proxy(s2_engine * se,
                             cwal_value * self,
                             cwal_value * key, cwal_value * v,
                             uint16_t kvpFlags ){
  int rc = 0;
  uint16_t const clientFlags = cwal_container_client_flags_get(self);
  cwal_hash * h = HAS_DOTLIKE_FLAG(clientFlags)
    ? cwal_value_get_hash(self)
    : 0;
  if(se){/*avoid unused param warning*/}
#if !S2_TRY_INTERCEPTORS
  if(h){
    rc = v
      ? cwal_hash_insert_with_flags_v(h, key, v, 1, kvpFlags )
      : cwal_hash_remove_v(h, key);
  }else{
    rc = cwal_prop_set_with_flags_v( self, key, v, kvpFlags );
  }
#else
  if(!v){
    rc = h
      ? cwal_hash_remove_v(h, key)
      : cwal_prop_unset_v(self, key);
  }
  else if(!cwal_value_may_iterate(self)){
    /* compatibility crutch. Some (not all) of the 'set' code works
       fine without this, but we have tests which check for this
       condition. Plus, it matches what the s2 manual says. */
    return CWAL_RC_IS_VISITING;
  }
  else{
    cwal_value * foundIn = 0;
    cwal_kvp * kvp = h
      ? cwal_hash_search_kvp_v(h, key)
      : cwal_prop_get_kvp_v(self, key, 1, &foundIn);
    cwal_value * const fv = kvp ? cwal_kvp_value(kvp) : 0;
    cwal_function * f = 0;
    assert( kvp ? !!fv : 1 );
    /* s2_dump_val(key,"set key"); */
    /* s2_dump_val(fv,"set val"); */
    /* s2_dump_val(cwal_prop_get_v(self, key),"prop-get()"); */
    /*if(cwal_value_is_array(self)){
      s2_dump_val(self,"set self");
      }*/
    /*if(fv && cwal_value_is_function(fv)){
      s2_dump_val(fv,"fv");
      MARKER(("func with flags: %02x\n",
        cwal_container_client_flags_get(fv)));
    }*/
    if(!kvp && (clientFlags & S2_VAL_F_DISALLOW_UNKNOWN_PROPS)){
      rc = CWAL_RC_DISALLOW_NEW_PROPERTIES;
    }
    else if(fv && (f = s2_value_is_interceptor(fv))){
      /* MARKER(("setter interceptor!\n")); */
      rc = s2__check_intercept( se,
                                foundIn ? foundIn : self,
                                self, fv, v, 0 );
      /* Never reset kvpFlags for these */
    }
    else{
      if(!kvp && h){
        rc = cwal_hash_insert_with_flags_v(h, key, v, 1, kvpFlags);
      }
      else if(kvp && (h || foundIn==self)){
        rc = cwal_kvp_value_set2(kvp, v);
        if(!rc){
          cwal_value_rescope( cwal_value_scope(self), v )
            /* Without this rescope, we end up using a stale
               value at some point in some specific code
               constellations. */;
          cwal_kvp_flags_set( kvp, kvpFlags );
        }
      }
      else{
        rc = cwal_prop_set_with_flags_v( self, key, v, kvpFlags )
          /* ^^^ that requires a second property lookup internally,
             but with no prototype search. It's required to keep the
             "property assignment never overwrites inherited
             properties" behaviour.
          */
        ;
      }
    }
    assert(CWAL_RC_MISUSE != rc);
  }
#endif/* end S2_TRY_INTERCEPTORS */
  return rc;
}

int s2_set_with_flags_v( s2_engine * se, cwal_value * self,
                         cwal_value * key, cwal_value * v,
                         uint16_t kvpFlags ){
  int rc = 0;
  char const *errMsg = 0;
  /* if(!kvpFlags) kvpFlags = CWAL_VAR_F_PRESERVE; */
  s2_engine_err_reset(se);
  if(self){
    /* Object/property access */
    char const isKeyAnInt = cwal_value_is_integer(key);
    cwal_tuple * tp = 0;
#if 0
    /* 20191210: we may want to consider extending the
       CWAL_CONTAINER_DISALLOW_PROP_SET flag semantics to include
       array and tuple indexes. Noting, however, that tuples don't
       have container flags.
    */
    if(cwal_container_flags_get(self)
       & CWAL_CONTAINER_DISALLOW_PROP_SET){
      rc = CWAL_RC_DISALLOW_PROP_SET;
      errMsg = "Setting properties is disallowed on this value.";
    }else
#endif
#if 0
    /* Reminder we probably(?) don't(?) want this to guard prototype
       changing */
    if((rc = s2_immutable_container_check(se,self, 0))){
      return rc;
    }else
#endif
    if(isKeyAnInt && (tp=cwal_value_get_tuple(self))){
      uint16_t const tlen = cwal_tuple_length(tp);
      cwal_int_t const n = cwal_value_get_integer(key);
      if(n<0 || n>=(cwal_int_t)tlen){
        return s2_engine_err_set(se, CWAL_RC_RANGE,
                                 "Index %d is out of range "
                                 "for a length-%d tuple.",
                                 (int)n, (int)tlen);
      }
      rc = cwal_tuple_set(tp, (uint16_t)n, v);
      assert(!rc && "the only error cases involve memory corruption or bad args.");
    }
    else if(cwal_props_can(self)){ /* A container */
      cwal_array * ar;
      if(isKeyAnInt && (ar=cwal_value_array_part(se->e,self))){
        /* ==> Array[Index] */
        /*
          Reminder: cwal_value_array_part() ends up setting entries in
          arrays used as prototypes, but that is arguably expected.
        */
        cwal_int_t i;
        if((rc = s2_immutable_container_check(se,self, 0))){
          return rc;
        }
        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.";
        }
      }/* end array[int] */
      else if(s2_value_is_prototype_string(se, key)){
        /* Special case: prototype pseudo-keyword/property */
        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.";
        }
        else 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 (as a
             prototype) a container type or NULL. Maybe that
             limitation (in cwal) is unnecessary.
          */
          v = 0;
        }
        if(!rc){
          rc = cwal_value_prototype_set( self, v );
          switch(rc){
            case 0: break;
            case CWAL_RC_DISALLOW_PROTOTYPE_SET:
              errMsg = "Setting the prototype is disallowed on this value.";
              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;
          }
        }
      }/*prototype pseudo-property*/
      else{
        /* Set container property... */
        uint16_t const clientFlags = cwal_container_client_flags_get(self);
        if((rc = s2_immutable_container_check(se,self, 0))){
          return rc;
        }
        else if(HAS_ENUM_FLAG(clientFlags)){
          rc = CWAL_RC_DISALLOW_PROP_SET;
        }else{
          rc = s2_set_prop_proxy( se, self, key, v, kvpFlags );
        }
        /*if(rc){
          MARKER(("rc=%s\n", s2_rc_cstr(rc)));
        }*/
        switch(rc){
          case CWAL_RC_OOM: break;
          case CWAL_RC_NOT_FOUND:{
            if(clientFlags & S2_VAL_F_DISALLOW_UNKNOWN_PROPS){
              cwal_size_t keyLen = 0;
              char const * keyStr = cwal_value_get_cstr(key,&keyLen);
              if(keyStr){
                return s2_engine_err_set(se, rc,
                                         "Unknown property '%.*s'.",
                                         (int)keyLen, keyStr);
              }else{
                return s2_engine_err_set(se, rc,
                                         "Unknown property with key type '%s'.",
                                         cwal_value_type_name(key));
              }
            }
            break;
          }
          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_DISALLOW_NEW_PROPERTIES:
            return s2_engine_err_set(se, rc,
                                     "Container does not allow "
                                     "new properties.");
          case CWAL_RC_DISALLOW_PROP_SET:
            return s2_engine_err_set(se, rc,
                                     "Container is marked as immutable.");
          case CWAL_RC_CONST_VIOLATION:{
            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));
            }
          }
          case CWAL_RC_LOCKED:{
            cwal_size_t len = 0;
            char const * tname = cwal_value_type_name2(v, &len);
            return s2_engine_err_set(se, rc,
                                     "'%.*s' value is currently locked against modification.",
                                     (int)len, tname);
          }
          case CWAL_RC_IS_VISITING_LIST:
            return s2_engine_err_set(se, rc,
                                     "Cannot perform this operation on a list/hash "
                                     "during traversal.");
          case CWAL_RC_IS_VISITING:
          case CWAL_RC_ACCESS/*historical*/:{
            return s2_engine_err_set(se, rc,
                                     "Cannot modify properties "
                                     "during traversal.");
          }
        }/*prop/hash set rc check*/
      }/*set property*/
    }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 */
    cwal_scope * s = cwal_scope_current_get(se->e);
#if 0
    s2_dump_val(key,"KEY Setting scope var");
    s2_dump_val(v,"VALUE Setting scope var");
    {
      cwal_kvp const * kvp = cwal_scope_search_kvp_v(s, -1, key, 0);
      if(kvp){
        s2_dump_val(cwal_kvp_key(kvp),"KEY Setting scope var");
        s2_dump_val(cwal_kvp_value(kvp),"VALUE Setting scope var");
        MARKER(("KVP flags=0x%04u\n", cwal_kvp_flags(kvp)));
      }
    }
#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. That's handled at the evaluation/operator level.
    */
    rc = cwal_scope_chain_set_with_flags_v( s,
                                           v ? -1 : 0 /* don't allow 'unset'
                                                         across scopes*/,
                                           key, v, kvpFlags );
    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_CONST_VIOLATION:{
        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;
      }
      case CWAL_RC_DISALLOW_NEW_PROPERTIES:
        rc = s2_engine_err_set(se, rc,
                               "Scope's storage does not allow "
                               "new properties.");
        break;
      case CWAL_RC_DISALLOW_PROP_SET:
        rc = s2_engine_err_set(se, rc,
                               "Scope's storage is marked as immutable.");
        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_with_flags( s2_engine * se, cwal_value * self,
                       char const * key, cwal_size_t keyLen,
                       cwal_value * v,
                       uint16_t flags ){
  int rc = 0;
  /* This impl removes lots of dupe code at the cost of a potentially
     temporary string. */
  cwal_value * kv;
  if(!se || !key) return CWAL_RC_MISUSE;
  kv = cwal_new_string_value(se->e, key, keyLen);
  rc = kv ? 0 : CWAL_RC_OOM;
  if(!rc) {
    cwal_value_ref(kv);
    rc = s2_set_with_flags_v( se, self, kv, v, flags );
    cwal_value_unref(kv);
  }
  return rc;
}


int s2_set( s2_engine * se, cwal_value * self,
            char const * key, cwal_size_t keyLen,
            cwal_value * v ){
  return s2_set_with_flags( se, self, key, keyLen, v, CWAL_VAR_F_PRESERVE );
}

static int s2__get_check_exception(s2_engine * se, int rc){
  if(!rc){
    /* rc = s2_check_interrupted(se, rc); */
    if(!rc && cwal_exception_get(se->e)) rc = CWAL_RC_EXCEPTION;
    else if( se->err.code ){
      rc = 1
        ? se->err.code
        : s2_throw_err_ptoker(se, 0);
    }
  }
  return rc;
}


static cwal_value * s2_get_v_proxy( s2_engine * se,
                                    cwal_value * self,
                                    cwal_value * key ){
  cwal_value * foundIn = 0;
  return s2_get_v_proxy2(se, self, key, &foundIn);
}


/**
   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 ){
#if 1
  /* quick/dirty hack to avoid having add S2_TRY_INTERCEPTORS
     support to this function just yet. This adds allocations,
     which is bad, of course. */
  cwal_value * ks = cwal_new_string_value(se->e, key, keyLen);
  cwal_value * rv = 0;
  s2_engine_err_reset(se);
  if(!ks){
    if(se->currentScript){
      s2_err_ptoker(se, se->currentScript, CWAL_RC_OOM, 0);
    }else{
      s2_error_set(se, 0, CWAL_RC_OOM, 0);
      assert(se->err.code);
    }
    return 0;
  }
  cwal_value_ref(ks);
  rv = s2_get_v_proxy(se, self, ks);
  cwal_value_unref(ks);
  return rv;
#else
  cwal_hash * h = 0;
  cwal_value * v = 0;
  uint16_t vflags = 0;
  assert(se && self && key);
  if(9==keyLen && 'p'==*key && (0==memcmp( key, "prototype", 9 ))){
    return cwal_value_prototype_get( se->e, self );
  }
  else if(5==keyLen && 'v'==*key && (0==memcmp( key, "value", 5 ))
          && cwal_value_is_unique(self)){
    return cwal_unique_wrapped_get( self );
  }

  /* s2_dump_val(self,key); */
  while(!v && self){
    vflags = cwal_container_client_flags_get(self);
    if(HAS_DOTLIKE_FLAG(vflags)
       && (h = cwal_value_get_hash(self))){
      v = cwal_hash_search(h, key, keyLen);
#if 1
      /* Fall back to its own properties, instead of
         immediately going up to the prototype. */
      if(!v) v = cwal_prop_get(self, key, keyLen);
#endif
    }else{
      v = cwal_prop_get(self, key, keyLen);
    }
    self = v ? 0 : cwal_value_prototype_get(se->e, self);
    /* s2_dump_val(self,key); */
  }
  /* s2_dump_val(v,key); */
  return v;
#endif
}

int s2_get_v( s2_engine * se, cwal_value * self,
              cwal_value * key, cwal_value ** rv ){
  int rc = 0;
  cwal_value * xrv = 0;
  if(!self){
    /* do a scope lookup */
    xrv = s2_var_get_v( se, -1, key );
    rc = 0;
  }else{
    cwal_array * ar;
    cwal_tuple * tp = 0;
    char const isKeyAnInt = cwal_value_is_integer(key);
    assert(key);
    xrv = 0;
    if(isKeyAnInt
       && (ar=cwal_value_array_part(se->e,self))){
      cwal_int_t const i = cwal_value_get_integer(key);
      if(i<0){
        rc = s2_engine_err_set(se, CWAL_RC_RANGE,
                               "Array indexes may not be negative.");

      }else{
        xrv = cwal_array_get(ar, (cwal_size_t)i);
        rc = 0;
      }
    }else if(isKeyAnInt
             && (tp = cwal_value_get_tuple(self))){
      cwal_size_t const tlen = cwal_tuple_length(tp);
      cwal_int_t const n = cwal_value_get_integer(key);
      if(n<0 || n>=(cwal_int_t)tlen){
        return s2_engine_err_set(se, CWAL_RC_RANGE,
                                 "Index %d is out of range "
                                 "for a length-%d tuple.",
                                 (int)n, (int)tlen);
      }
      xrv = cwal_tuple_get(tp, (cwal_size_t)n);
      rc = 0;
    }
    else {
      if(isKeyAnInt && cwal_value_is_string(self)){
        /* string[index] */
        if(cwal_value_get_integer(key)<0){
          rc = s2_engine_err_set(se, CWAL_RC_RANGE,
                                 "String indexes may not be negative.");
        }else if(!(xrv = s2_get_v_proxy( se, self, key ))){
          rc = CWAL_RC_OOM;
        }else if( (rc = s2__get_check_exception(se, rc)) ){
          cwal_refunref(xrv);
          xrv = 0;
        }
      }else{
        /* Object property lookup... */
        xrv = s2_get_v_proxy( se, self, key );
        if( (rc = s2__get_check_exception(se, rc)) ){
          cwal_refunref(xrv);
          xrv = 0;
        }
      }
    }
    if(!rc && !xrv){
      uint16_t const selfFlags = cwal_container_client_flags_get(self);
      if(S2_VAL_F_DISALLOW_UNKNOWN_PROPS & selfFlags){
        cwal_size_t keyLen = 0;
        char const * keyStr = cwal_value_get_cstr(key,&keyLen);
        rc = keyLen
          ? s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                              /* DISALLOW_NEW_PROPERTIES? */
                              "Unknown property '%.*s'.",
                              (int)keyLen, keyStr)
          : s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                              "Unknown property with key type '%s'.",
                              cwal_value_type_name(key));
      }
    }
  }
  if(!rc) *rv = xrv;
  return rc;
}

int s2_get( s2_engine * se, cwal_value * self,
            char const * key, cwal_size_t keyLen,
            cwal_value ** rv ){
  int rc = 0;
  cwal_value * v = 0;
  if(!key) return CWAL_RC_MISUSE;
  else if(self){
    v = s2_get_proxy( se, self, key, keyLen );
    rc = s2__get_check_exception(se, rc);
    if(!rc){
      if(v){
        *rv = v;
      }
      else{
        uint16_t const selfFlags = cwal_container_client_flags_get(self);
        if(S2_VAL_F_DISALLOW_UNKNOWN_PROPS & selfFlags){
          rc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                                 "Unknown property '%.*s'.",
                                 (int)keyLen, key);
        }
      }
    }
  }else{
    v = s2_var_get( se, -1, key, keyLen );
    rc = s2__get_check_exception(se, rc);
    if(rc){
      assert(!v);
    }else{
      *rv = v;
    }
  }
  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 const * s2_rc_cstr(int rc){
  char const * s = s2_rc_cstr2(rc);
  return s ? s : "Non-cwal/s2 result code";
}

char const * s2_rc_cstr2(int rc){
  if(rc <= CWAL_RC_CLIENT_BEGIN) return cwal_rc_cstr2(rc);
  switch((enum s2_rc_t)rc){
#define CASE(X) case X: return #X
    CASE(S2_RC_placeholder);
    CASE(S2_RC_END_EACH_ITERATION);
    CASE(S2_RC_TOSS);
    CASE(S2_RC_end);
    CASE(S2_RC_CLIENT_BEGIN);
#undef CASE
  }
  return 0;
}

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

/** @internal

    Appends se->err with file/line column info based on (st ? st :
    se->currentScript).

    Returns se->err.code on success and CWAL_RC_OOM if allocating the
    script name part fails.
*/
int s2_err_ammend_flc(s2_engine * se, s2_ptoker const * st){
  int rc = 0;
  char const * name;
  cwal_size_t nameLen = 0;
  char const * errPos;
  s2_linecol_t line = 0, col = 0;
  assert(se->err.code);
  if(!st) st = se->currentScript;
  errPos = se->opErrPos
    ? se->opErrPos
    : s2_ptoker_err_pos(st);
  assert(errPos);
  name = s2_ptoker_name_first(st, &nameLen);
  s2_ptoker_count_lines(st, errPos, &line, &col);
  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 );
  }else if(se->err.script.capacity){
    se->err.script.mem[0] = 0;
  }
  return rc ? rc : se->err.code;
}

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 * 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
    : s2_ptoker_err_pos(st);
  s2_linecol_t line = 0, col = 0;
  assert(errPos);
  if(errPos>=s2_ptoker_end(st)
     && s2_ptoker_end(st) > s2_ptoker_begin(st)){
    /* Shameless workaround for syntax errors at
       the end of a script */
    errPos = s2_ptoker_end(st)-1;
  }
  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.
  */
  if(errPos>=s2_ptoker_begin(top) && errPos<s2_ptoker_end(top)){
    s2_ptoker_count_lines(st, errPos, &line, &col);
  }
  if(CWAL_RC_OOM==code){
    rc = CWAL_RC_OOM;
  }
  else if(!throwIt && errPos
     && !se->currentScript
     /*  ^^^^^^ elide location info from error string when it looks
         like a script-side exception is up-coming */
     ) {
    char const * tailPart = ": ";
    if(name){
      rc = cwal_buffer_printf( se->e, obuf, "%s:", name );
    }
    if(!rc && errPos < s2_ptoker_end(top)){
      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 == s2_ptoker_end(top)){
      rc = cwal_buffer_printf( se->e, obuf,
                               "@ EOF%s", tailPart);
    }else{
      rc = cwal_buffer_printf( se->e, obuf,
                               "@ unknown source position%s",
                               tailPart);
    }
  }

  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 s2_check_interrupted(se, 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_SquigglyBlock;
      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 = *s2_ptoker_putback_get(st);
  switch(opener){
    case S2_T_SquigglyOpen:
      /*
        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().
      */
      CWAL_SWITCH_FALL_THROUGH;
    case S2_T_ParenOpen:
    case S2_T_BraceOpen:{
      s2_ptoken errTok = st->token;
      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().
        */
        s2_ptoken const * tok = &s2_ptoker_token(st)
          /* Reminder to self: this "could" be lifted outside of the
             loop because tok's address is constant, but eventual API
             changes might break that. */;
        end = s2_ptoken_end(tok);
        if(s2_ttype_is_eof(tok->ttype)){
          end = 0;
          break;
        }else{
          if(opener == tok->ttype){
            ++level;
            errTok = *tok;
          }
          else if(closer == tok->ttype){
            assert(level>0);
            if(!--level) break;
          }
        }
      }
      if(!rc && 0 != level){
        s2_ptoker_errtoken_set(st, &errTok);
        assert(typeLabel);
        rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                            "Unexpected EOF while slurping %s block.",
                            typeLabel);
      }
      break;
    }/*Parens/Braces*/
  }/* switch */

  if(!rc && !end){
    s2_ptoker_errtoken_set(st, &origSrc);

#if 0
    MARKER(("slurped <<%.*s>>\n",
            /* (int)s2_ptoken_len2(out), s2_ptoken_adjbegin(out), */
            (int)(s2_ptoken_end(&s2_ptoker_token(st)) - s2_ptoken_begin(&origSrc)),
            s2_ptoken_begin(&origSrc)));
#endif

    rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                        "Unexpected EOF or mismatched braces "
                        "while slurping %s block.",
                        typeLabel);
  }else if(!rc){
    assert(end);
    out->ttype = adjustedOpener;
    s2_ptoken_begin_set(out, s2_ptoken_begin(&origSrc));
    s2_ptoken_adjbegin_set(out, s2_ptoken_begin(out));
    s2_ptoken_end_set(out, end);
    assert(s2_ptoken_len(out) >= 2);
    assert(opener == (int)*s2_ptoken_begin(out));
    assert(closer == (int)*(s2_ptoken_end(out)-1));
    /* Skip leading whitespaces */
    while(((s2_ptoken_adjbegin_incr(out, 1)) < (end-1))
          && s2_is_space((int)*s2_ptoken_adjbegin(out))){
    }
    /* Skip trailing whitespaces */
    s2_ptoken_adjend_set(out, end - 1/* == closer */);
    while( ((s2_ptoken_adjend_incr(out,-1)) > s2_ptoken_adjbegin(out))
           && s2_is_space((int)*s2_ptoken_adjend(out)) ){
    }
    s2_ptoken_adjend_set(out, (s2_ptoken_adjend(out)+1))/* possibly back to the '}' byte */;
    out->ttype = adjustedOpener;
    rc = 0;
#if 0
    MARKER(("slurped <<%.*s>> out->adjEnd=%d\n",
            /* (int)s2_ptoken_len2(out), s2_ptoken_adjbegin(out), */
            (int)s2_ptoken_len(out), s2_ptoken_begin(out),
            *s2_ptoken_adjend(out)));
#endif
    assert(opener == *s2_ptoken_begin(out));
    assert(closer == *(s2_ptoken_end(out)-1));
    assert( s2_ptoken_adjend(out) >= s2_ptoken_adjbegin(out) );
    assert( s2_ptoken_adjend(out) <= s2_ptoken_end(out) );
  }
  if(out != &st->token){
    s2_ptoker_token_set(st, &origSrc);
    s2_ptoker_putback_set(st, &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;
  s2_ptoken tId = s2_ptoken_empty;
  cwal_size_t idLen;
  int modeFlag = 0;
  assert(S2_T_HeredocStart==st->token.ttype);
  if(!tgt) tgt = &st->token;

  rc = s2_ptoker_next_token(st);
  if(rc) goto tok_err;

  if(S2_T_Colon==st->token.ttype){
    modeFlag = st->token.ttype;
    rc = s2_ptoker_next_token(st);
    if(rc) goto tok_err;
  }

  switch(st->token.ttype){
    case S2_T_Identifier:
    case S2_T_LiteralStringDQ:
    case S2_T_LiteralStringSQ:
      break;
    default:
      rc = CWAL_SCR_SYNTAX;
      st->errMsg = "Expecting identifier or "
        "quoted string "
        "at start of HEREDOC.";
      goto tok_err;
  }
  tId = st->token;
  idBegin = s2_ptoken_begin(&tId);
  idEnd = s2_ptoken_end(&tId);
  idLen = s2_ptoken_len(&tId);
  docBegin = idEnd;
  switch(modeFlag){
    case S2_T_Colon:
      if(('\n'==*docBegin || s2_is_space((int)*docBegin))
         || ('\r'==*docBegin && '\n'==docBegin[1])){
        docBegin += '\r'==*docBegin ? 2 : 1;
        ++st->currentLine;
        st->currentCol = 0;
      }
      break;
    default:
      while(s2_is_space((int)*docBegin)){
        if('\n'==*docBegin){
          ++st->currentLine;
          st->currentCol = 0;
        }else{
          ++st->currentCol;
        }
        ++docBegin;
      }
  }
  rc = CWAL_SCR_SYNTAX;
  for( docEnd = docBegin; docEnd < s2_ptoker_end(st); ++docEnd ){
    if(*docEnd != *idBegin){
      if('\n'==*docEnd){
        ++st->currentLine;
        st->currentCol = 0;
      }else{
        ++st->currentCol;
      }
      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:
          for( ; back>=docBegin && s2_is_space((int)*back); --back) --docEnd;
      }
      rc = 0;
      st->currentCol += (s2_linecol_t)idLen;
      break;
    }
  }
  if(rc){
    s2_ptoker_errtoken_set(st, &tId);
    rc = s2_err_ptoker(se, st,
                       rc, "Did not find end of HEREDOC "
                       "starting with '%.*s'.",
                       (int)idLen, idBegin);
  }else{
    s2_ptoken_begin_set(tgt, s2_ptoken_begin(&origin));
    s2_ptoken_end_set(tgt, theEnd);
    tgt->ttype = S2_T_Heredoc;
    s2_ptoken_adjbegin_set(tgt, docBegin);
    s2_ptoken_adjend_set(tgt, docEnd);
    tgt->line = origin.line;
    tgt->column = origin.column;
    assert(docEnd>=docBegin);
#if 0
    MARKER(("HEREDOC<%.*s> body: %.*s\n",
            (int)(idEnd-idBegin), idBegin,
            (int)(docEnd-docBegin), docBegin));
#endif
  }
  return rc;
  tok_err:
  assert(rc);
  assert(st->errMsg);
  rc = s2_err_ptoker(se, st, rc, "%s", st->errMsg);
  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){
      s2_ptoker_token_set(pr, &next);
    }
    return ttype;
  }else{
    s2_ptoker_next_token_set(pr, &next);
    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 = *s2_ptoker_putback_get(st);
  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;
  /* s2_engine_err_reset(se); */
  rc = s2_ptoker_lookahead_skip( st, &tt, skipper);
  rc = s2_check_interrupted(se, rc);
  if(rc){
    assert(st->errMsg || se->flags.interrupted);
    return se->flags.interrupted
      ? se->flags.interrupted
      : (se->err.code
         ? se->err.code
         : s2_err_ptoker( se, st, rc, "%s", st->errMsg ))
      ;
  }else{
    s2_ptoker_token_set(st, &tt);
    switch((S2_NEXT_NO_POSTPROCESS & flags) ? 0 : tt.ttype){
      case S2_T_Semicolon:
        st->token.ttype = S2_T_EOX;
        break;
      case S2_T_CR:
        assert(!"skipped by the junk-skipper!");
        CWAL_SWITCH_FALL_THROUGH;
      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 open/close
           parens/braces handling from the eval side 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);
        assert(rc ? 1 : S2_T_Heredoc==st->token.ttype);
        break;
      default:
        break;
    }
  }
  if(rc && !s2_ptoker_errtoken_has(st)){
    s2_ptoker_errtoken_set(st, &st->token);
  }
  if(tgt){
    *tgt = st->token;
    s2_ptoker_token_set(st, &oldT);
    s2_ptoker_putback_set(st, &oldP);
  }else if(!rc){
    s2_ptoker_putback_set(st, &oldT);
  }
  return s2_check_interrupted(se, rc);
}


int s2_ptoken_create_value( s2_engine * se,
                            s2_ptoker const * pr,
                            s2_ptoken const * t,
                            cwal_value ** rv ){
  int rc = CWAL_RC_TYPE;
  cwal_value * v = NULL;
  cwal_engine * e = se->e;
#define RC rc = v ? CWAL_RC_OK : CWAL_RC_OOM
  switch(t->ttype){
    case S2_T_LiteralIntBin:
    case S2_T_LiteralIntDec:
    case S2_T_LiteralIntHex:
    case S2_T_LiteralIntOct:{
      cwal_int_t dd = 0;
      char const check = s2_ptoken_parse_int(t, &dd);
      assert(check &&
             "If this is false, then there's a mismatch "
             "between the tokenizer and converter.");
      if(check){/*avoid unused var warning in non-debug builds*/}
      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;
      cwal_size_t newLen;
      char const * begin;
      cwal_kvp * kvp = 0;
      assert(s2_ptoken_len(t)>=2 /* for the quotes */);
      rc = s2_unescape_string(e,
                              s2_ptoken_begin(t) + 1 /*quote*/,
                              s2_ptoken_end(t) - 1 /*quote*/,
                              escapeBuf );
      if(rc){
        rc = s2_err_ptoker(se, pr, CWAL_RC_RANGE==rc ? CWAL_SCR_SYNTAX : rc,
                           /*      allow catch() to block ^^^^^ this! */
                           "Unescaping string failed with rc=%s, "
                           "likely due to non-UTF8 content "
                           "or an unknown \\Uxxxxxxxx sequence.",
                           s2_rc_cstr(rc))
          /**
             20191220: whether or not this should trigger
             s2_throw_ptoker() (exception) or s2_err_ptoker() (fatal)
             is debatable. It seems unfortunate that an invalid \U
             should outright kill a script, especially if it arrives
             via input from outside the script. Treating it as a
             non-exception using the code CWAL_SCR_SYNTAX, we enable
             catch{...} to optionally downgrade it to an exception.
          */;
        escapeBuf->used = oldUsed;
        break;
      }
      assert(escapeBuf->used >= oldUsed);

      /*MARKER("STRING: [%.*s]\n", (int)(escapeBuf->used - oldUsed),
        (char const *)escapeBuf->mem+oldUsed);*/
      assert(0 == escapeBuf->mem[escapeBuf->used]);
      newLen = escapeBuf->used - oldUsed;
      begin = newLen ? (char const *)(escapeBuf->mem+oldUsed) : 0;
      /* Check if we have a stashed string with this value. If so, use it.*/
      kvp = (begin && newLen<=10
             /*minor ^^^^^^^^^^^ optimization: max length of any of the
              * core cached strings*/)
        ? s2_stash_get2_kvp(se, begin, newLen)
        : 0;
      if(kvp){
        v = cwal_kvp_key(kvp) /* the KEY, not the value, else we end up
                                 doing really stupid stuff! */;
#if 0
        MARKER(("Reusing stashed string: %.*s\n", (int)newLen, begin ));
#endif
      }
      else{
        v = cwal_new_string_value(e, begin, newLen);
      }
      escapeBuf->used = oldUsed;
      /* s2_dump_val(v,"string literal"); */
      RC;
      break;
    }
    case S2_T_SquigglyBlock:{
      cwal_size_t const len = s2_ptoken_len2(t);
      char const * beg = s2_ptoken_adjbegin(t);
      char const * end = s2_ptoken_adjend(t);
      assert(beg);
      assert(end);
      assert(beg <= end);
      v = cwal_new_string_value(e, beg, len);
      /*MARKER(("SquigglyBlock ==> cwal_string: '{' %s '}'\n",
        cwal_value_get_cstr(v, 0)));*/
      RC;
      break;
    }
    default:{
      /* Use the raw token bytes as a string... */
      cwal_size_t const len = s2_ptoken_len2(t);
      char const * begin = s2_ptoken_begin2(t);
      char const * end = s2_ptoken_end2(t);
      cwal_kvp * kvp;
      assert(begin && end && begin <= end);
      /* If se->stash has a matching string in it, simply return
         that. If code calling this is too lax/lazy with refs, this
         will backfire horribly, as they fetch a value from here,
         thinking it's new, then deref it. */
      /* MARKER("STRING: [%.*s]\n", (int)(end - begin), begin); */
      /* MARKER("STRING: len=%ld\n", (long) (end - begin) ); */
      kvp = len ? s2_stash_get2_kvp(se, begin, len) : 0;
      if(kvp){
        v = cwal_kvp_key(kvp);
        /* MARKER(("got stashed string: %.*s\n", (int)len, begin)); */
        /* s2_dump_val(v,"got stashed string?"); */
      }
      else if(!v){
        /* didn't find a stashed copy, so create a new string... */
        v = cwal_new_string_value(e, begin, len);
      }
      /* s2_dump_val(v,"stringified token"); */
      RC;
      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(pos>buf->mem && '0' == *pos && '.' != *(pos-1)) {
          *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:
    case CWAL_TYPE_TUPLE:{
      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:
    case CWAL_TYPE_UNIQUE:
      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;
    int cyclesIndex = -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;
    cyclesIndex = indentIndex + 1;
    assert(indentIndex >= 0);
    vIndent = (indentIndex >= (int)args->argc)
      ? NULL
      : args->argv[indentIndex];
    outOpt.cyclesAsStrings = (cyclesIndex < (int)args->argc)
      ? cwal_value_get_bool(args->argv[cyclesIndex])
      : 0;
    selfBuf = jb ? 1 : 0;
    if(!selfBuf) jb = &buf;
    oldUsed = jb->used;
    if(vIndent){
      /* Accept indentation as either a string, an integer, or an enum
         entry which wraps one of those.
      */
      vIndent = s2_value_unwrap(vIndent);
      if(!(outOpt.indentString.str =
           cwal_value_get_cstr(vIndent, &outOpt.indentString.len))){
        outOpt.indent = cwal_value_get_integer(vIndent);
      }
    }
    rc = cwal_json_output_buffer( args->engine, v,
                                  jb, &outOpt );
    if(CWAL_RC_CYCLES_DETECTED==rc){
      rc = cwal_exception_setf(args->engine, rc,
                               "Cycles detected in JSON output.");
    }else 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,
                                  ((char const *)jb->mem + oldUsed),
                                  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;
  cwal_value * lhs;
  cwal_value * rhs;
  if(!args->argc || (args->argc>3)){
    return cwal_exception_setf( args->engine, CWAL_RC_MISUSE,
                                "Expecting (value [,value [,bool typeStrict]]) arguments.");
  }
  lhs = (args->argc > 1)
    ? args->argv[0]
    : args->self;
  rhs = (args->argc > 1)
    ? args->argv[1]
    : args->argv[0];
  if(lhs==rhs){
    *rv = cwal_new_integer(args->engine, 0) /* does not allocate */;
    return 0;
  }
  if((3==args->argc) && cwal_value_get_bool(args->argv[2])){
    cwal_type_id const tL = cwal_value_type_id(lhs);
    cwal_type_id const tR = cwal_value_type_id(rhs);
    if(tL != tR){
      rc = (int)(tL-tR);
      *rv = cwal_new_integer(args->engine, (cwal_int_t)(tL<tR ? -1 : 1))
        /* does not allocate */;
      return 0;
    }
  }
  rc = cwal_value_compare(lhs, rhs);
  /* 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.
  */
  *rv = cwal_new_integer(args->engine, (cwal_int_t)(rc>0 ? 1 : (rc < 0 ? -1 : 0)))
    /* does not allocate */;
  return 0;
}

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


void s2_engine_subexpr_restore(s2_engine * e,
                               s2_subexpr_savestate const * from){
  e->ternaryLevel = from->ternaryLevel;
}

int s2_scope_push_with_flags( s2_engine * se, cwal_scope * tgt,
                              uint16_t scopeFlags ){
  int rc = s2_check_interrupted(se, 0);
  if(rc) return rc;
  assert(se);
  assert(se->e);
  assert(tgt);
  assert(!tgt->level);
  assert(!tgt->e);
  if(!se || !se->e || !tgt || tgt->e || tgt->level) return CWAL_RC_MISUSE;
  se->scopes.nextFlags = scopeFlags;
  rc = cwal_scope_push( se->e, &tgt );
  if(!rc){
    assert(tgt->level);
    assert(s2__scope_current(se)->flags == scopeFlags);
    assert(tgt == s2__scope_for_level(se, tgt->level)->cwalScope);
    assert(0 == se->scopes.nextFlags);
  }else{
    se->scopes.nextFlags = 0;
  }
  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;
  if(isFilename && (rc = s2_cb_disable_check(args, S2_DISABLE_FS_READ))){
    return rc;
  }
  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).",
                                 (cwal_size_t)pInfo.length,
                                 (cwal_size_t)pInfo.line,
                                 (cwal_size_t)pInfo.col,
                                 (int)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;
  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;
  }

  cwal_value_ref(mod);
  {
    s2_func_def const funcs[] = {
    /* S2_FUNC2("experiment", s2_cb_container_experiment), */
      S2_FUNC2("parse", s2_cb_json_parse_string),
      S2_FUNC2("parseFile", s2_cb_json_parse_file),
      S2_FUNC2("stringify", s2_cb_arg_to_json_token),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, mod, funcs, 0);
    if(!rc){
      /* Install mod.clone() convenience method */
      rc = s2_eval_cstr_with_var(se, "J", mod, "JSON module init",
                                 "J.clone=proc()"
                                 "using.{P:J.parse,S:J.stringify}"
                                 "{return using.P(using.S(argv.0))}",
                                 -1, 0);
    }
  }
  if(rc){
    if(tgt==mod) cwal_value_unhand(mod);
    else cwal_value_unref(mod);
  }else{
    cwal_value_unhand(mod);
  }
  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_rc_hash( cwal_callback_args const * args, cwal_value **rv ){
  s2_engine * se = s2_engine_from_args(args);
  int rc;
  cwal_int_t i;
  cwal_hash * h;
  cwal_value * hv;
  static const char * stashKey = "RcHash";
  assert(se);
  hv = s2_stash_get(se, stashKey);
  if(hv){
    *rv = hv;
    return 0;
  }
  h = cwal_new_hash(args->engine, 137)
    /*the installed set has 110 entries as of 20171013*/;
  if(!h) return CWAL_RC_OOM;
  hv = cwal_hash_value(h);
  cwal_value_ref(hv);
  for( i = 0; i <= (cwal_int_t)S2_RC_CLIENT_BEGIN; ++i ){
    char const * str = s2_rc_cstr2(i);
    if(str){
      cwal_value * k;
      cwal_value * v = cwal_new_xstring_value(args->engine,
                                              str, cwal_strlen(str));
      if(!v){
        rc = CWAL_RC_OOM;
        break;
      }
      cwal_value_ref(v);
      k = cwal_new_integer(args->engine, i);
      if(!k){
        cwal_value_unref(v);
        rc = CWAL_RC_OOM;
        break;
      }
      cwal_value_ref(k);
      rc = cwal_hash_insert_v( h, k, v, 0 );
      cwal_value_unref(k);
      cwal_value_unref(v);
      if(rc) break;
#if 1
      /* reverse mapping. */
      rc = cwal_hash_insert_v( h, v, k, 0 );
      if(rc) break;
#endif
    }
  }
  if(rc){
    cwal_value_unref(hv);
  }else{
    *rv = hv;
    s2_stash_set( se, stashKey, hv )
      /* if this fails, no big deal */;
    cwal_value_unhand(hv);
  }
  return 0;
}

int s2_cb_new_unique( cwal_callback_args const * args, cwal_value **rv ){
  *rv = cwal_new_unique(args->engine, args->argc ? args->argv[0] : 0);
  return *rv ? 0 : CWAL_RC_OOM;
}

int s2_interrupt( s2_engine * se ){
  s2_ptoker const * pt = se->currentScript;
  if(pt){
    /* fake an error pos which s2_err_ptoker() will see... */
    se->opErrPos = s2_ptoken_begin(&pt->token)
      ? s2_ptoken_begin(&pt->token) : s2_ptoker_begin(pt);
    s2_err_ptoker( se, pt, CWAL_RC_INTERRUPTED,
                   "Interrupted by s2_interrupt().");
    se->opErrPos = 0;      
  }else{
    s2_engine_err_set(se, CWAL_RC_INTERRUPTED,
                      "Interrupted by s2_interrupt().");
  }
  return se->flags.interrupted = CWAL_RC_INTERRUPTED;
}

#if S2_USE_SIGNALS
/**
   s2_engine interruption via signals...

   Potential TODO: instead of manipulating an s2_engine instance
   directly from the signal handler, which has potential
   timing-related issues vis-a-vis s2_engine lifetime, simply set a
   global flag here and check/consume it from the s2_engine APIs. The
   disadvantage to that is that we get less precise interruption
   location information (limited to where an engine explicitly checks
   it), but that seems like a small price to pay for "more correct"
   s2_engine lifetime interaction.
*/
static void s2_sigc_handler(int s){
  if(s2Interruptable && !s2Interruptable->flags.interrupted){
    s2_engine * se = s2Interruptable;
    s2_ptoker const * pt = se ? se->currentScript : 0;
    s2Interruptable = 0 /* disable concurrent interruption */;
    if(pt){
      MARKER(("Interruping via signal handler!\n"));
      /* fake an error pos which s2_err_ptoker() will see... */
      se->opErrPos = s2_ptoken_begin(&pt->token)
        ? s2_ptoken_begin(&pt->token) : s2_ptoker_begin(pt)
        /* kludge for the following call to get an error location */;
      s2_err_ptoker( se, pt, CWAL_RC_INTERRUPTED,
                     "Interrupted by signal #%d.", s);
      se->opErrPos = 0;
      if(se->err.line>0){
        /* We do this to help track down locations where interruption is
           triggered but does not preempt the normal result code of the
           interrupted operation like it should.
        */
        MARKER(("Interrupt was at line %d, column %d of '%.*s'.\n",
                se->err.line, se->err.col,
                (int)se->err.script.used, (char const *)se->err.script.mem));
      }
    }else if(se){
      s2_engine_err_set(se, CWAL_RC_INTERRUPTED,
                        "Interrupted by signal #%d.", s);
    }
    if(se){
      se->flags.interrupted = CWAL_RC_INTERRUPTED;
    }
    assert(!s2Interruptable);
    s2Interruptable = se /* re-enable interruption */;
  }
}
#endif
/* S2_USE_SIGNALS */

int s2_check_interrupted( s2_engine * se, int rc ){
  switch(rc){
    case CWAL_RC_ASSERT:
    case CWAL_RC_CANNOT_HAPPEN:
    case CWAL_RC_EXIT:
    case CWAL_RC_FATAL:
    case CWAL_RC_INTERRUPTED:
    case CWAL_RC_OOM:
      return rc;
    default:
        return se->e->fatalCode
          ? (se->flags.interrupted = se->e->fatalCode)
          : (se->flags.interrupted
             ? se->flags.interrupted
             : rc);
  }
}

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
}

char s2_cstr_to_rc(char const *str, cwal_int_t len, int * code){
#if 0
  /**
     2020-02-03: TODO (hash collisions permitting): this could be
     implemented much more efficiently using the same hashing
     mechanism we use for several pieces over in s2_eval.c. There's a
     risk of hash collisions with a list that long, but it's worth a
     try...
  */
  if(len<0) len = (cwal_int_t)cwal_strlen(str);
  if(str){ /* If passed a string, try (the hard way) to
              find the integer code. */
    if((len>8 && 'C'==*str && 'W'==str[1]) /* CWAL_RC_... */
       || (len>6 && 'S'==*str && '2'==str[1]) /* S2_RC_... */
       ){
      int i = ('C'==*str)
        ? 0 : CWAL_RC_CLIENT_BEGIN;
      cwal_int_t const max = ('C'==*str)
        ? CWAL_RC_CLIENT_BEGIN : S2_RC_CLIENT_BEGIN;
      cwal_size_t elen;
      char const * estr;
      for( ; i <= max;
           ++i ){
        estr = s2_rc_cstr2(i);
        if(estr && ((elen=cwal_strlen(estr)) == (cwal_size_t)len)
           && 0==memcmp(str,estr,elen)
           ){
          if(code) *code = i;
          return 1;
        }
      }
    }
  }
#else
  cwal_size_t const n = (len<0) ? cwal_strlen(str) : (cwal_size_t)len;
  uint32_t const h = s2_hash_keyword(str, n);
  if(n<10/*length of shortest entry: CWAL_RC_OK*/) return 0;
  switch(h){
#define W(RC) if((cwal_size_t)sizeof(#RC)-1 == n                \
                     && 0==cwal_compare_cstr(str, n, #RC, n))   \
    { *code = RC; return 1; } else return 0
    /* Generated by s2_eval-keyword-hasher.s2 (or equivalent): */

    case 0x00014848: W(CWAL_RC_OK);
    case 0x000a4d67: W(CWAL_RC_ERROR);
    case 0x000292ae: W(CWAL_RC_OOM);
    case 0x000a4761: W(CWAL_RC_FATAL);
    case 0x005266b4: W(CWAL_RC_CONTINUE);
    case 0x000a47d0: W(CWAL_RC_BREAK);
    case 0x0014a578: W(CWAL_RC_RETURN);
    case 0x000525e8: W(CWAL_RC_EXIT);
    case 0x00a4e143: W(CWAL_RC_EXCEPTION);
    case 0x00149814: W(CWAL_RC_ASSERT);
    case 0x0014a19c: W(CWAL_RC_MISUSE);
    case 0x00a552b9: W(CWAL_RC_NOT_FOUND);
    case 0x1493fc9a: W(CWAL_RC_ALREADY_EXISTS);
    case 0x000a4d4e: W(CWAL_RC_RANGE);
    case 0x00052a2e: W(CWAL_RC_TYPE);
    case 0x029615ff: W(CWAL_RC_UNSUPPORTED);
    case 0x001488a0: W(CWAL_RC_ACCESS);
    case 0x02953c70: W(CWAL_RC_IS_VISITING);
    case 0x52a80e30: W(CWAL_RC_IS_VISITING_LIST);
    case 0x5259accb: W(CWAL_RC_DISALLOW_NEW_PROPERTIES);
    case 0x5259b77c: W(CWAL_RC_DISALLOW_PROP_SET);
    case 0x5259c2c8: W(CWAL_RC_DISALLOW_PROTOTYPE_SET);
    case 0x2938b265: W(CWAL_RC_CONST_VIOLATION);
    case 0x00149b62: W(CWAL_RC_LOCKED);
    case 0x2936e803: W(CWAL_RC_CYCLES_DETECTED);
    case 0x526013da: W(CWAL_RC_DESTRUCTION_RUNNING);
    case 0x00a4b185: W(CWAL_RC_FINALIZED);
    case 0x149b07dc: W(CWAL_RC_HAS_REFERENCES);
    case 0x02941f9f: W(CWAL_RC_INTERRUPTED);
    case 0x00a469dd: W(CWAL_RC_CANCELLED);
    case 0x00014804: W(CWAL_RC_IO);
    case 0x0a48d963: W(CWAL_RC_CANNOT_HAPPEN);
    case 0x5297a321: W(CWAL_RC_JSON_INVALID_CHAR);
    case 0x5297ae5a: W(CWAL_RC_JSON_INVALID_KEYWORD);
    case 0x5297c0c2: W(CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE);
    case 0x5297c884: W(CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE);
    case 0x5297adcb: W(CWAL_RC_JSON_INVALID_NUMBER);
    case 0x529835fe: W(CWAL_RC_JSON_NESTING_DEPTH_REACHED);
    case 0x529901c6: W(CWAL_RC_JSON_UNBALANCED_COLLECTION);
    case 0x52979d2b: W(CWAL_RC_JSON_EXPECTED_KEY);
    case 0x5297a088: W(CWAL_RC_JSON_EXPECTED_COLON);
    case 0x293361be: W(CWAL_SCR_CANNOT_CONSUME);
    case 0x029429d7: W(CWAL_SCR_INVALID_OP);
    case 0x52a35f1b: W(CWAL_SCR_UNKNOWN_IDENTIFIER);
    case 0x5266c911: W(CWAL_SCR_CALL_OF_NON_FUNCTION);
    case 0x5289396d: W(CWAL_SCR_MISMATCHED_BRACE);
    case 0x528deb5d: W(CWAL_SCR_MISSING_SEPARATOR);
    case 0x52a00aa1: W(CWAL_SCR_UNEXPECTED_TOKEN);
    case 0x294ffd4d: W(CWAL_SCR_UNEXPECTED_EOF);
    case 0x0527f4f2: W(CWAL_SCR_DIV_BY_ZERO);
    case 0x00295497: W(CWAL_SCR_SYNTAX);
    case 0x0005251c: W(CWAL_SCR_EOF);
    case 0x52a959aa: W(CWAL_SCR_TOO_MANY_ARGUMENTS);
    case 0x52859352: W(CWAL_SCR_EXPECTING_IDENTIFIER);
    case 0x512547c8: W(S2_RC_END_EACH_ITERATION);
    case 0x000146f8: W(S2_RC_TOSS);

#undef W
  }
#endif
  return 0;
}

void s2_value_upscope( s2_engine * se, cwal_value * v ){
  s2_scope * sc = s2__scope_current(se);
  assert(sc);
  if(sc && sc->cwalScope->parent){
#if 1
    cwal_value_upscope(v);
#else
    cwal_value_rescope(sc->cwalScope->parent, v);
#endif
  }
}

int s2_immutable_container_check_cb( cwal_callback_args const * args, cwal_value const * v ){
  return s2_immutable_container_check( s2_engine_from_args(args), v, 1 );
}

int s2_immutable_container_check( s2_engine * se, cwal_value const * v, int throwIt ){
  cwal_flags16_t const containerFlags = cwal_container_flags_get(v);
  assert(se && se->e);
  if(CWAL_CONTAINER_DISALLOW_PROP_SET & containerFlags){
    char const * fmt = "Setting/clearing properties is "
      "disallowed on this container (of type '%s').";
    char const * typeName = cwal_value_type_name(v);
    if(!typeName) typeName = "<? ? ?>";
    if(throwIt){
      return s2_throw(se, CWAL_RC_DISALLOW_PROP_SET,
                      fmt, typeName);
    }else{
      return s2_engine_err_set(se, CWAL_RC_DISALLOW_PROP_SET,
                               fmt, typeName);
    }
  }
  return 0;
}

int s2_install_value( s2_engine *se, cwal_value * tgt,
                      cwal_value * v,
                      char const * name,
                      int nameLen,
                      uint16_t propertyFlags ){
  int rc;
  cwal_value * vname;
  if(nameLen<0) nameLen = (int)cwal_strlen(name);
  if(!se || !v || !name) return CWAL_RC_MISUSE;
  else if(!nameLen) return CWAL_RC_RANGE;
  else if(tgt && !cwal_props_can(tgt)) return CWAL_RC_TYPE;
  vname = cwal_new_string_value(se->e, name, (cwal_size_t)nameLen);
  if(!vname) return 0;
  /**
     We use s2_set_xxx() to honor the dot-like-object flag on hashes.

     OTOH: 1) that's the only place in this function we use the
     s2_engine object and 2) the dot-like-object bits have been
     removed from the public scripting API and are internally used
     only for enums (which are immutable, so we can't install
     functions into them). 3) we can derive an s2_engine from a
     cwal_engine with s2_engine_from_state(). That argues for
     changing this signature to take a cwal_engine instead. We'll
     try that one day.
  */
  cwal_value_ref(vname);
  rc = tgt
    ? s2_set_with_flags_v( se, tgt, vname, v, propertyFlags )
    : cwal_scope_chain_set_with_flags_v(cwal_scope_current_get(se->e),
                                        0, vname, v, propertyFlags);
  cwal_value_unref(vname);
  return rc;
}

int s2_install_callback( s2_engine *se, cwal_value * tgt,
                         cwal_callback_f callback,
                         char const * name,
                         int nameLen, 
                         uint16_t propertyFags,
                         void * state,
                         cwal_finalizer_f stateDtor,
                         void const * stateTypeID ){
  int rc = 0;
  cwal_value * fv;
  if(!se || !callback) return CWAL_RC_MISUSE;
  else if(tgt && !cwal_props_can(tgt)) return CWAL_RC_TYPE;
  fv = cwal_new_function_value(se->e, callback, state, stateDtor,
                               stateTypeID);
  if(fv){
    cwal_value_ref(fv);
    rc = s2_install_value( se, tgt, fv, name, nameLen, propertyFags );
    cwal_value_unref(fv);
  }else{
    rc = CWAL_RC_OOM;
  }
  return rc;
}
                          

int s2_install_functions( s2_engine *se, cwal_value * tgt,
                          s2_func_def const * defs,
                          uint16_t propertyFlags ){
  int rc = 0;
  if(!se || !defs) return CWAL_RC_MISUSE;
  else if(tgt && !cwal_props_can(tgt)) return CWAL_RC_TYPE;
  for( ; !rc && defs->name; ++defs ){
    cwal_value * fv = cwal_new_function_value( se->e, defs->callback,
                                               defs->state,
                                               defs->stateDtor,
                                               defs->stateTypeID);
    if(!fv){
      rc = CWAL_RC_OOM;
      break;
    }
    cwal_value_ref(fv);
    rc = s2_install_value( se, tgt, fv, defs->name, -1,
                           propertyFlags );
    if(!rc && defs->cwalContainerFlags){
      cwal_container_client_flags_set( fv, defs->cwalContainerFlags );
    }
    cwal_value_unref(fv);
  }
  return rc;
}

int s2_container_config( cwal_value * v, char allowPropSet,
                         char allowNewProps,
                         char allowGetUnknownProps ){
  if(!cwal_props_can(v)) return CWAL_RC_TYPE;
  else{
    cwal_flags16_t containerFlags = cwal_container_flags_get(v);
    cwal_flags16_t clientFlags = cwal_container_client_flags_get(v);
    if(allowPropSet){
      containerFlags &= ~CWAL_CONTAINER_DISALLOW_PROP_SET;
    }else{
      containerFlags |= CWAL_CONTAINER_DISALLOW_PROP_SET;
    }
    if(allowNewProps){
      containerFlags &= ~CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES;
    }else{
      containerFlags |= CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES;
    }
    if(allowGetUnknownProps){
      clientFlags &= ~S2_VAL_F_DISALLOW_UNKNOWN_PROPS;
    }else{
      clientFlags |= S2_VAL_F_DISALLOW_UNKNOWN_PROPS;
    }
    cwal_container_flags_set(v, containerFlags);
    cwal_container_client_flags_set(v, clientFlags);
    return 0;
  }
}


/** @internal 

    Expects cachedKey to be an entry from se->cache or NULL. ckey/keyLen describe
    an input string to compare against the final argument. This function returns
    true (non-0) if the final argument compares string-equivalent to (ckey,keyLen)
    OR is the same pointer address as the 2nd argument, else it returns false.
*/
static char s2_value_is_cached_string( s2_engine const * se,
                                       cwal_value const * cachedKey,
                                       char const * ckey, cwal_size_t keyLen,
                                       cwal_value const * key ){
  if(cachedKey == key) return 1;
  else{
      cwal_size_t xkeyLen = 0;
      char const * xkey = cwal_value_get_cstr(key, &xkeyLen);
      if(se){/*avoid unused param warning*/}
      return (xkeyLen==keyLen && *ckey==*xkey && 0==cwal_compare_cstr( ckey, keyLen,
                                                                       xkey, keyLen))
        ? 1 : 0;
  }
}

char s2_value_is_value_string( s2_engine const * se, cwal_value const * key ){
  return s2_value_is_cached_string( se, se->cache.keyValue, "value", 5, key );
}

char s2_value_is_prototype_string( s2_engine const * se, cwal_value const * key ){
  return s2_value_is_cached_string( se, se->cache.keyPrototype, "prototype", 9, key );
}


void s2_hash_dot_like_object( cwal_value * hv, int dotLikeObj ){
  /* cwal_hash * hash = cwal_value_get_hash(hv); */
  uint16_t f = cwal_container_client_flags_get(hv);
  cwal_container_client_flags_set(hv,
                                  dotLikeObj
                                  ? (S2_VAL_F_DOT_LIKE_OBJECT | (f & S2_VAL_F_COMMON_MASK))
                                  : (~S2_VAL_F_DOT_LIKE_OBJECT & f));
#if !defined(NDEBUG)
  assert(dotLikeObj
         ? (S2_VAL_F_DOT_LIKE_OBJECT & cwal_container_client_flags_get(hv))
         : !(S2_VAL_F_DOT_LIKE_OBJECT & cwal_container_client_flags_get(hv))
         );
  f = cwal_container_client_flags_get(hv);
  if(dotLikeObj){
    assert(S2_VAL_F_MODE_DOT == (f & S2_VAL_F_MODE_MASK));
    assert((S2_VAL_F_DOT_LIKE_OBJECT & 0xFF)  == (f & 0xFF));
  }else{
    assert(S2_VAL_F_MODE_DOT != (f & S2_VAL_F_MODE_MASK));
    assert((S2_VAL_F_DOT_LIKE_OBJECT & 0xFF) != (f & 0xFF));
  }
#endif
}

/**
  Internal impl for s2_minify_script() and friends.
*/
static int s2_minify_script_impl( s2_engine * se, cwal_buffer const * src,
                                  cwal_output_f dest, void * destState,
                                  unsigned int * nlCount ){
  int rc = 0;
  s2_ptoker pr = s2_ptoker_empty;
  s2_ptoken prev = s2_ptoken_empty;
  s2_ptoken const * t = &pr.token;
  s2_ptoker const * oldScript = se->currentScript;
  static const unsigned int nlEveryN = 3 /* output approx. 1 of every nlEveryN EOLs */;

  rc = s2_ptoker_init_v2( se->e, &pr, (char const *)src->mem,
                          (cwal_int_t)src->used,
                          0 );
  if(rc) goto end;
  for( ; !rc
         && !(rc = s2_next_token(se, &pr, S2_NEXT_NO_SKIP_EOL, 0))
         && !s2_ptoker_is_eof(&pr);
       prev = pr.token){
    assert(!s2_ttype_is_junk(t->ttype));
    /*
      We have to keep (some) newlines so that block constructs which
      optionally use EOL as EOX will work :/.
    */
    if(s2_ttype_is_eol(t->ttype)){
      ++(*nlCount);
      if(t->ttype==prev.ttype){
        /* elide runs of EOLs */
        continue;
      }
    }
    else if(S2_T_SquigglyBlock==t->ttype
       || S2_T_BraceGroup==t->ttype
       || S2_T_ParenGroup==t->ttype){
      cwal_buffer kludge = cwal_buffer_empty;
      s2_ptoker sub = s2_ptoker_empty;
      char const * opener;
      char const * closer;
      s2_ptoker_sub_from_token(&pr, t, &sub);
      kludge.mem = (unsigned char *)s2_ptoker_begin(&sub);
      kludge.used = kludge.capacity = (cwal_size_t)s2_ptoker_len(&sub);
      if(S2_T_BraceGroup==t->ttype){
        opener = "[";
        closer = "]";
      }else if(S2_T_ParenGroup==t->ttype){
        opener = "(";
        closer = ")";
      }else{
        opener = "{";
        closer = "}";
      }
      rc = dest( destState, opener, 1 );
      if(!rc) rc = s2_minify_script_impl( se, &kludge,
                                          dest, destState, nlCount );
      if(!rc) rc = dest( destState, closer, 1 );
      continue;
    }
    if(s2_ttype_is_eol(t->ttype)
       && S2_T_SquigglyBlock!=prev.ttype
       && (*nlCount % nlEveryN)){
      continue; /* we need EOLs intact after _some_ {blocks}, but
                   we can elide all others. We'll insert some now
                   and then, though, just for readability.*/
    }
    if((s2_ptoken_empty.ttype != prev.ttype /* first pass */)
       && !s2_is_space(t->ttype)
       && !s2_ttype_is_eol(t->ttype)
       && !s2_is_space(prev.ttype)
       && !s2_ttype_op(prev.ttype)
       && !s2_ttype_op(t->ttype)
       && (int)':' != t->ttype /* for 2nd half of ternary if */
       /*&& s2_ttype_is_pod(t->ttype)
         && s2_ttype_is_pod(prev.ttype)*/
       ){
      rc = dest( destState, " ", 1);
    }
    if(!rc){
      assert(s2_ptoken_begin(t) && s2_ptoken_end(t));
      assert(s2_ptoken_end(t) > s2_ptoken_begin(t));
      rc = dest( destState, s2_ptoken_begin(t), s2_ptoken_len(t) );
    }
  }
  end:
  s2_ptoker_finalize(&pr);
  se->currentScript = oldScript;
  return rc;
}

int s2_minify_script_buffer( s2_engine * se, cwal_buffer const * src,
                             cwal_buffer * dest ){
  if(src == dest) return CWAL_RC_MISUSE;
  else{
    unsigned int nlCount = 0;
    cwal_output_buffer_state job = cwal_output_buffer_state_empty;
    job.e = se->e;
    job.b = dest;
    return s2_minify_script_impl( se, src, cwal_output_f_buffer, &job,
                                  &nlCount );
  }
}

int s2_minify_script( s2_engine * se, cwal_input_f src, void * srcState,
                      cwal_output_f out, void * outState ){
  cwal_buffer buf = cwal_buffer_empty;
  int rc = cwal_buffer_fill_from(se->e, &buf, src, srcState);
  if(!rc){
    unsigned int nlCount = 0;
    cwal_buffer_clear(se->e, &buf);
    rc = s2_minify_script_impl( se, &buf, out, outState, &nlCount );
  }
  cwal_buffer_clear(se->e, &buf);
  return rc;
}

int s2_cb_minify_script( cwal_callback_args const * args, cwal_value ** rv ){
  s2_engine * se = s2_engine_from_args(args);
  int rc = 0;
  char const * src = 0;
  cwal_size_t srcLen = 0;
  cwal_buffer * dest = 0;
  cwal_value * destV = 0;
  assert(se);
  src = args->argc
    ? cwal_value_get_cstr(args->argv[0], &srcLen)
    : 0;
  if(!src){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting one string or Buffer argument.");
  }
  if(args->argc>1){
    dest = cwal_value_get_buffer(args->argv[1]);
  }
  if(!dest){
    dest = cwal_new_buffer(args->engine, srcLen/2);
    if(!dest) return CWAL_RC_OOM;
  }
  destV = cwal_buffer_value(dest);
  assert(destV);
  cwal_value_ref(destV);
  if(srcLen && *src){
    cwal_buffer kludge = cwal_buffer_empty;
    kludge.mem = (unsigned char *)src;
    kludge.used = srcLen;
    kludge.capacity = srcLen;
    rc = s2_minify_script_buffer(se, &kludge, dest);
  }
  if(rc){
    cwal_value_unref(destV);
  }else{
    *rv = destV;
    cwal_value_unhand(destV);
  }
  return rc;
}

int s2_ctor_method_set( s2_engine * se, cwal_value * container, cwal_function * method ){
  if(!se || !container || !method) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(container)) return CWAL_RC_TYPE;
  else{
    return s2_set_with_flags_v( se, container, se->cache.keyCtorNew,
                                cwal_function_value(method),
                                CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN );
  }
}

int s2_ctor_callback_set( s2_engine * se, cwal_value * container, cwal_callback_f method ){
  int rc;
  cwal_function * f;
  cwal_value * fv;
  if(!se || !container || !method) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(container)) return CWAL_RC_TYPE;
  fv = cwal_new_function_value(se->e, method, 0, 0, 0);
  f = fv ? cwal_value_get_function(fv) : NULL;
  if(f){
    cwal_value_ref(fv);
    rc = s2_ctor_method_set( se, container, f );
    cwal_value_unref(fv);
  }else{
    rc = CWAL_RC_OOM;
  }
  return rc;
    
}

char s2_value_is_newing(cwal_value const * v){
  return (cwal_container_client_flags_get( v )
          & S2_VAL_F_IS_NEWING) ? 1 : 0;
}


int s2_ctor_fetch( s2_engine * se, s2_ptoker const * pr,
                   cwal_value * operand,
                   cwal_function **rv,
                   int errPolicy ){
  cwal_value * vtor = 0;
  vtor = s2_get_v_proxy2( se, operand, se->cache.keyCtorNew, 0 );
  if(!vtor){
    static char const * msgfmt =
      "Construction requires a container "
      "with a constructor function property named '%s'.";
    if(!errPolicy) return CWAL_RC_NOT_FOUND;
    else if(errPolicy<0){
      return pr
        ? s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt,
                          cwal_value_get_cstr(se->cache.keyCtorNew,0))
        : s2_throw(se, CWAL_RC_NOT_FOUND, msgfmt,
                   cwal_value_get_cstr(se->cache.keyCtorNew,0))
        ;
    }else{
      return pr
        ? s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt,
                            cwal_value_get_cstr(se->cache.keyCtorNew,0))
        : s2_engine_err_set(se, CWAL_RC_NOT_FOUND, msgfmt,
                            cwal_value_get_cstr(se->cache.keyCtorNew,0))
      ;
    }
  }else{
    static const char * msgfmt =
      "Construction requires a container with "
      "a constructor function property named '%s', "
      "but the ctor resolves to type '%s'.";
    cwal_function * f = cwal_value_function_part(se->e, vtor);
    if(f){
      if(rv) *rv = f;
      return 0;
    }
    else if(!errPolicy) return CWAL_RC_NOT_FOUND;
    else if(errPolicy<0){
      return pr
        ? s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt,
                          cwal_value_get_cstr(se->cache.keyCtorNew,0),
                          cwal_value_type_name(vtor))
        : s2_throw(se, CWAL_RC_NOT_FOUND, msgfmt,
                   cwal_value_get_cstr(se->cache.keyCtorNew,0),
                   cwal_value_type_name(vtor))
      ;
    }else{
      return pr
        ? s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND, msgfmt,
                        cwal_value_get_cstr(se->cache.keyCtorNew,0),
                        cwal_value_type_name(vtor))
        : s2_engine_err_set(se, CWAL_RC_NOT_FOUND, msgfmt,
                            cwal_value_get_cstr(se->cache.keyCtorNew,0),
                            cwal_value_type_name(vtor))
        ;
    }
  }
}

/**
   Returns true if v is legal for use as an operand do the "new"
   keyword. This arguably should return true only if the property
   is found in v itself, not in prototypes. Hmmm.
*/
char s2_value_is_newable( s2_engine * se, cwal_value * v ){
#if 0
  cwal_value * vtor = 0;
  /* see notes in s2_keyword_f_new() */
  return (s2_get_v( se, v, se->cache.keyCtorNew, &vtor ))
    ? 0
    : !!cwal_value_function_part(se->e, vtor ? vtor : v);
#eif 0
  cwal_value * vtor = 0;
  return (s2_get_v( se, v, se->cache.keyCtorNew, &vtor ))
    ? 0
    : vtor ? !!cwal_value_function_part(se->e, vtor) : 0;
#else
  if(!cwal_props_can(v)) return 0;
  else{
    cwal_function * f = 0;
    s2_ctor_fetch(se, NULL, v, &f, 0);
    return f ? 1 : 0;
  }
#endif
}

unsigned int s2_sizeof_script_func_state(){
  return (unsigned int)sizeof(s2_func_state);
}

void s2_value_to_lhs_scope( cwal_value const * lhs, cwal_value * v){
  assert(lhs);
  assert(v);
  assert(!cwal_value_is_builtin(lhs)
         && "or else a caller is misusing this function.");
  if(lhs != v){
    cwal_scope * const s = cwal_value_scope(lhs);
    assert(s && "Very bad. Corrupt value.");
    if(s) cwal_value_rescope(s, v);
  }
}

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

cwal_value * s2_propagating_take( s2_engine * se ){
  return cwal_propagating_take(se->e);
}

cwal_value * s2_propagating_set( s2_engine * se, cwal_value * v ){
  return cwal_propagating_set(se->e, v);
}

int s2_typename_set_v( s2_engine * se, cwal_value * container, cwal_value * name ){
  return s2_set_with_flags_v(se, container, se->cache.keyTypename, name,
                             CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN);
}

int s2_typename_set( s2_engine * se, cwal_value * container,
                     char const * name, cwal_size_t nameLen ){
  int rc;
  cwal_value * v = cwal_new_string_value(se->e, name, nameLen);
  if(v){
    cwal_value_ref(v);
    rc = s2_typename_set_v( se, container, v);
    cwal_value_unref(v);
  }else{
    rc = CWAL_RC_OOM;
  }
  return rc;
}


char const * s2_strstr( char const * haystack, cwal_size_t hayLen,
                        char const * needle, cwal_size_t needleLen ){
  char const * pos = haystack;
  char const * end = haystack + hayLen - needleLen+1;
  if(hayLen<needleLen || !needleLen || end <= pos) return 0;
  for( ; pos >= haystack && pos < end; ++pos){
    if(0==memcmp(pos, needle, needleLen)) return pos;
  }
  return 0;                          
}


int s2_set_from_script_v( s2_engine * se, char const * src,
                          int srcLen, cwal_value * addResultTo,
                          cwal_value * propName ){
  if(!se || !src || !addResultTo || !propName){
    return CWAL_RC_MISUSE;
  }
  else if(!cwal_props_can(addResultTo)) return CWAL_RC_TYPE;
  else{
    int rc;
    cwal_value * rv = 0;
    char const * pn = cwal_value_get_cstr(propName, 0);
    if(srcLen<0) srcLen = (int)cwal_strlen(src);
    rc = s2_eval_cstr( se, 1, pn ? pn : "s2_set_from_script_v",
                       src, srcLen, &rv );
    switch(rc){
      case CWAL_RC_RETURN:
        rv = s2_propagating_take(se);
        s2_engine_err_reset(se);
        rc = 0;
        break;
    }
    if(!rc){
      cwal_value_ref(rv);
      rc = s2_set_v( se, addResultTo, propName,
                     rv ? rv : cwal_value_undefined() );
      cwal_value_unref(rv);
    }
    return rc;
  }  
}


int s2_set_from_script( s2_engine * se, char const * src,
                        int srcLen, cwal_value * addResultTo,
                        char const * propName,
                        cwal_size_t propNameLen ){
  if(!se || !src || !addResultTo || !propName){
    return CWAL_RC_MISUSE;
  }
  else if(!propNameLen) return CWAL_RC_RANGE;
  else if(!cwal_props_can(addResultTo)) return CWAL_RC_TYPE;
  else{
    int rc;
    cwal_value * prop = cwal_new_string_value(se->e, propName,
                                              propNameLen);
    cwal_value_ref(prop);
    rc = prop
      ? s2_set_from_script_v( se, src, srcLen, addResultTo,
                              prop )
      : CWAL_RC_OOM;
    cwal_value_unref(prop);
    return rc;
  }  
}

/**
   The following LIKE/GLOB comparison bits were all ripped directly
   from sqlite3. The names have been changed only to fit this project,
   not to obfuscate their origins.
*/

/*
** A structure defining how to do GLOB-style comparisons.
** Taken from sqlite3.
*/
struct s2_compareInfo {
  unsigned char matchAll;          /* "*" or "%" */
  unsigned char matchOne;          /* "?" or "_" */
  unsigned char matchSet;          /* "[" or 0 */
  unsigned char noCase;            /* true to ignore case differences */
};

static const struct s2_compareInfo s2_compareInfo_glob = {'*', '?', '[', 0};
static const struct s2_compareInfo s2_compareInfo_like = {'%', '_', 0, 0};
static const struct s2_compareInfo s2_compareInfo_likenc = {'%', '_', 0, 1};

/**
   Optimized cwal_utf8_read_char1() which skips the function call if
   A[0] is an ASCII character.
*/
#define Utf8Read(A)               (A[0]<0x80?*(A++):cwal_utf8_read_char1(&A))
/*
** Assuming zIn points to the first byte of a UTF-8 character,
** advance zIn to point to the first byte of the next UTF-8 character.
*/
#define Utf8SkipChar(zIn) \
  if( (*(zIn++))>=0xc0 ){                              \
    while( (*zIn & 0xc0)==0x80 ){ zIn++; }             \
  } (void)0

/*
** Taken from sqlite3.
**
** Compare two UTF-8 strings for equality where the first string is
** a GLOB or LIKE expression.  Return values:
**
**    CWAL_RC_OK:            Match
**    CWAL_RC_NOT_FOUND:          No match
**    CWAL_RC_NOT_FOUND:  No match in spite of having * or % wildcards.
**
** Globbing rules:
**
**      '*'       Matches any sequence of zero or more characters.
**
**      '?'       Matches exactly one character.
**
**     [...]      Matches one character from the enclosed list of
**                characters.
**
**     [^...]     Matches one character not in the enclosed list.
**
** With the [...] and [^...] matching, a ']' character can be included
** in the list by making it the first character after '[' or '^'.  A
** range of characters can be specified using '-'.  Example:
** "[a-z]" matches any single lower-case letter.  To match a '-', make
** it the last character in the list.
**
** Like matching rules:
** 
**      '%'       Matches any sequence of zero or more characters
**
***     '_'       Matches any one character
**
**      Ec        Where E is the "esc" character and c is any other
**                character, including '%', '_', and esc, match exactly c.
**
** The comments within this routine usually assume glob matching.
**
** This routine is usually quick, but can be N**2 in the worst case.
*/
static int patternCompare(
  const unsigned char *zPattern,              /* The glob pattern */
  const unsigned char *zString,               /* The string to compare against the glob */
  const struct s2_compareInfo *pInfo, /* Information about how to do the compare */
  unsigned int matchOther                   /* The escape char (LIKE) or '[' (GLOB) */
){
  unsigned int c, c2;                       /* Next pattern and input string chars */
  const unsigned int matchOne = pInfo->matchOne;  /* "?" or "_" */
  const unsigned int matchAll = pInfo->matchAll;  /* "*" or "%" */
  const unsigned char noCase = pInfo->noCase;       /* True if uppercase==lowercase */
  const unsigned char *zEscaped = 0;          /* One past the last escaped input char */
  
  while( (c = Utf8Read(zPattern))!=0 ){
    if( c==matchAll ){  /* Match "*" */
      /* Skip over multiple "*" characters in the pattern.  If there
      ** are also "?" characters, skip those as well, but consume a
      ** single character of the input string for each "?" skipped */
      while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){
        if( c==matchOne && cwal_utf8_read_char1(&zString)==0 ){
          return CWAL_RC_NOT_FOUND/*SQLITE_NOWILDCARDMATCH*/;
        }
      }
      if( c==0 ){
        return CWAL_RC_OK;   /* "*" at the end of the pattern matches */
      }else if( c==matchOther ){
        if( pInfo->matchSet==0 ){
          c = cwal_utf8_read_char1(&zPattern);
          if( c==0 ) return CWAL_RC_NOT_FOUND /*SQLITE_NOWILDCARDMATCH*/;
        }else{
          /* "[...]" immediately follows the "*".  We have to do a slow
          ** recursive search in this case, but it is an unusual case. */
          assert( matchOther<0x80 );  /* '[' is a single-byte character */
          while( *zString ){
            int bMatch = patternCompare(&zPattern[-1],zString,pInfo,matchOther);
            if( bMatch!=CWAL_RC_NOT_FOUND ) return bMatch;
            Utf8SkipChar(zString);
          }
          return CWAL_RC_NOT_FOUND;
        }
      }

      /* At this point variable c contains the first character of the
      ** pattern string past the "*".  Search in the input string for the
      ** first matching character and recursively continue the match from
      ** that point.
      **
      ** For a case-insensitive search, set variable cx to be the same as
      ** c but in the other case and search the input string for either
      ** c or cx.
      */
      if( c<=0x80 ){
        unsigned int cx;
        int bMatch;
        if( noCase ){
          cx = cwal_utf8_char_toupper(c);
          c = cwal_utf8_char_tolower(c);
        }else{
          cx = c;
        }
        while( (c2 = *(zString++))!=0 ){
          if( c2!=c && c2!=cx ) continue;
          bMatch = patternCompare(zPattern,zString,pInfo,matchOther);
          if( bMatch!=CWAL_RC_NOT_FOUND ) return bMatch;
        }
      }else{
        int bMatch;
        while( (c2 = Utf8Read(zString))!=0 ){
          if( c2!=c ) continue;
          bMatch = patternCompare(zPattern,zString,pInfo,matchOther);
          if( bMatch!=CWAL_RC_NOT_FOUND ) return bMatch;
        }
      }
      return CWAL_RC_NOT_FOUND;
    }
    if( c==matchOther ){
      if( pInfo->matchSet==0 ){
        c = cwal_utf8_read_char1(&zPattern);
        if( c==0 ) return CWAL_RC_NOT_FOUND;
        zEscaped = zPattern;
      }else{
        unsigned int prior_c = 0;
        int seen = 0;
        int invert = 0;
        c = cwal_utf8_read_char1(&zString);
        if( c==0 ) return CWAL_RC_NOT_FOUND;
        c2 = cwal_utf8_read_char1(&zPattern);
        if( c2=='^' ){
          invert = 1;
          c2 = cwal_utf8_read_char1(&zPattern);
        }
        if( c2==']' ){
          if( c==']' ) seen = 1;
          c2 = cwal_utf8_read_char1(&zPattern);
        }
        while( c2 && c2!=']' ){
          if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){
            c2 = cwal_utf8_read_char1(&zPattern);
            if( c>=prior_c && c<=c2 ) seen = 1;
            prior_c = 0;
          }else{
            if( c==c2 ){
              seen = 1;
            }
            prior_c = c2;
          }
          c2 = cwal_utf8_read_char1(&zPattern);
        }
        if( c2==0 || (seen ^ invert)==0 ){
          return CWAL_RC_NOT_FOUND;
        }
        continue;
      }
    }
    c2 = Utf8Read(zString);
    if( c==c2 ) continue;
    if( noCase  && cwal_utf8_char_tolower(c)==cwal_utf8_char_tolower(c2)
        && c<0x80 && c2<0x80 ){
      continue;
    }
    if( c==matchOne && zPattern!=zEscaped && c2!=0 ) continue;
    return CWAL_RC_NOT_FOUND;
  }
  return *zString==0 ? CWAL_RC_OK : CWAL_RC_NOT_FOUND;
}

#undef Utf8Read
#undef Utf8SkipChar

char s2_glob_matches_str(const char *zGlob,
                         const char *z,
                         enum s2_glob_style policy){
  unsigned int esc;
  struct s2_compareInfo const * ci;
  if(!zGlob || !*zGlob) return 0;
  switch(policy){
    case S2_GLOB_LIKE_NOCASE:
      ci = &s2_compareInfo_likenc;
      esc = 0;
      break;
    case S2_GLOB_WILDCARD:
      ci = &s2_compareInfo_glob;
      esc = '[';
      break;
    case S2_GLOB_LIKE:
      ci = &s2_compareInfo_like;
      esc = 0;
      break;
    default:
      ci = 0;
      esc = 0;
      assert(!"invalid s2_glob_style");
      return 0;
  }
  return patternCompare((unsigned char const *)zGlob,
                        (unsigned char const *)z,
                        ci, esc)
    ? 0 : 1;
}

int s2_cb_glob_matches_str( cwal_callback_args const * args,
                            cwal_value ** rv ){
  int rc = 0;
  cwal_int_t policy = S2_GLOB_WILDCARD;
  char const * glob =
    (args->argc>0) ? cwal_value_get_cstr(args->argv[0], NULL) : NULL;
  char const * str =
    (args->argc>1) ? cwal_value_get_cstr(args->argv[1], NULL) : NULL;
  char const * sSelf = cwal_value_get_cstr(args->self, NULL);
  if(sSelf){
    str = glob;
    glob = sSelf;
    if(args->argc>1) policy = cwal_value_get_integer(args->argv[1]);
  }else{
    if(args->argc>2) policy = cwal_value_get_integer(args->argv[2]);
  }
  if(!glob || !str){
    rc = s2_cb_throw(args, CWAL_RC_MISUSE,
                     "Expecting (glob,string) arguments "
                     "OR globStringInstance.thisFunc(otherString) "
                     "usage.");
  }
  else if(!*glob){
    return s2_cb_throw(args, CWAL_RC_RANGE,
                       "Glob string may not be empty.");
  }
  else{
    *rv = s2_glob_matches_str(glob, str,
                              policy<0
                              ? S2_GLOB_WILDCARD
                              : (policy>0
                                 ? S2_GLOB_LIKE
                                 : S2_GLOB_LIKE_NOCASE))
      ? cwal_value_true() : cwal_value_false();
  }
  return rc;
}

int s2_errno_to_cwal_rc(int errNo, int dflt){
  switch(errNo ? errNo : errno){
    case 0: return CWAL_RC_OK;
    case EACCES: return CWAL_RC_ACCESS;
    case EEXIST: return CWAL_RC_ALREADY_EXISTS;
    case EFBIG: return CWAL_RC_RANGE;
    case EINTR: return CWAL_RC_INTERRUPTED
      /* potential semantic conflict with
         s2_engine::flags::interrupted */
      ;
    case EINVAL: return CWAL_RC_MISUSE;
    case EIO: return CWAL_RC_IO;
    case EISDIR: return CWAL_RC_TYPE;
    case ELOOP: return CWAL_RC_CYCLES_DETECTED;
    case EMLINK: return CWAL_RC_RANGE;
    case ENAMETOOLONG: return CWAL_RC_RANGE;
    case ENFILE: return CWAL_RC_RANGE;
    case ENOENT: return CWAL_RC_NOT_FOUND;
    case ENOMEM: return CWAL_RC_OOM;
    case ENOTDIR: return CWAL_RC_TYPE;
    case ENOTTY: return CWAL_RC_TYPE;
    case EMFILE: return CWAL_RC_RANGE;
    case EPIPE: return CWAL_RC_IO;
    case ERANGE: return CWAL_RC_RANGE;
    case EROFS: return CWAL_RC_ACCESS;
    case ESPIPE: return CWAL_RC_UNSUPPORTED;
    default: return dflt;
  }
}

int s2_cb_tokenize_line(cwal_callback_args const * args, cwal_value ** rv){
  cwal_array * ar = 0;
  cwal_value * arV = 0;
  s2_ptoker pt = s2_ptoker_empty;
  cwal_size_t lineLen = 0;
  s2_engine * se = s2_engine_from_args(args);
  char const * line = args->argc
    ? cwal_value_get_cstr(args->argv[0], &lineLen)
    : 0;
  int rc;
  if(!line) {
    return s2_cb_throw(args, CWAL_RC_MISUSE, "Expecting a single string argument.");
  }
  rc = s2_ptoker_init_v2( args->engine, &pt, line, (cwal_int_t)lineLen, 0 );
  if(rc) goto toss;
  pt.name = "tokenize line input";
#if 0
  ar = cwal_new_array(args->engine);
  if(!ar){
    rc = CWAL_RC_OOM;
    goto toss;
  }
  arV = cwal_array_value(ar);
  cwal_value_ref(arV);
#endif
  /* pt.parent = se->currentScript; */
  while( !(rc=s2_next_token(se, &pt, 0, 0))
         && !s2_ptoker_is_eof(&pt) ){
    cwal_value * v = 0;
    /* if(s2_ttype_is_junk(pt.token.ttype)) continue; */
#if 1
    if(!ar){
      ar = cwal_new_array(args->engine);
      if(!ar){
        rc = CWAL_RC_OOM;
        goto toss;
      }
      arV = cwal_array_value(ar);
      cwal_value_ref(arV);
    }
#endif
    v = s2_ptoken_is_tfnu(&pt.token);
    if(!v){
      rc = s2_ptoken_create_value( se, &pt, &pt.token, &v );
      if(rc) break;
    }
    cwal_value_ref(v);
    rc = cwal_array_append(ar, v);
    cwal_value_unref(v);
    if(rc) break;
  }
  if(rc) goto toss;
  assert(!rc);
  cwal_value_unhand(arV);
  *rv = arV;
  return rc;
  toss:
  cwal_value_unref(arV);
  assert(rc);
  switch(rc){
    case CWAL_RC_EXCEPTION:
    case CWAL_RC_OOM:
    case CWAL_RC_INTERRUPTED:
      break;
    default:
      if(s2_ptoker_errtoken_has(&pt)){
        rc = s2_throw_ptoker(se, &pt, rc,
                             "Failed with code %d (%s).",
                             rc, s2_rc_cstr(rc));
      }else{
        rc = s2_cb_throw(args, rc, "Failed with code %d (%s).",
                         rc, s2_rc_cstr(rc));
      }
      break;
  }
  s2_ptoker_finalize( &pt );
  return rc;
}

int s2_eval_hold( s2_engine * se, cwal_value * v ){
  s2_scope * sc = s2__scope_current(se);
  return (v && sc->evalHolder && !cwal_value_is_builtin(v))
    ? cwal_array_append(sc->evalHolder, v)
    : 0;
}

int s2_stash_hidden_member( cwal_value * who, cwal_value * what ){
  int rc = cwal_props_can(who) ? 0 : CWAL_RC_TYPE;
  cwal_value * key;
  cwal_engine * e = cwal_value_engine(who);
  if(rc) return rc;
  assert(e && "if who can props then who has an engine.");
  if(!e) return CWAL_RC_MISUSE;
  key = cwal_new_unique( e, 0 );
  if(!key) return CWAL_RC_OOM;
  cwal_value_ref(key);
  rc = cwal_prop_set_with_flags_v( who, key, what,
                                   CWAL_VAR_F_HIDDEN
                                   | CWAL_VAR_F_CONST );
  cwal_value_unref(key);
  return rc;
}

int s2_trigger_exit( cwal_callback_args const * args, cwal_value * result ){
  cwal_propagating_set(args->engine,
                       result ? result : cwal_value_undefined());
  return CWAL_RC_EXIT;
}

cwal_value * s2_value_unwrap( cwal_value * v ){
  return cwal_value_is_unique(v)
    ? cwal_unique_wrapped_get(v)
    : v;
}

cwal_value const * s2_value_unwrap_c( cwal_value const * v ){
  return cwal_value_is_unique(v)
    ? cwal_unique_wrapped_get(v)
    : v;
}

char const * s2_value_cstr( cwal_value const * v, cwal_size_t * len ){
  if(cwal_value_is_unique(v)) v = cwal_unique_wrapped_get(v);
  return cwal_value_get_cstr(v, len);
}

int s2_seal_container( cwal_value * v, char sealIt ){
  if(!v) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(v)) return CWAL_RC_TYPE;
  else{
    cwal_flags16_t const sealFlags =
      CWAL_CONTAINER_DISALLOW_PROP_SET
      | CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET;
    cwal_flags16_t flags = cwal_container_flags_get(v);
    if(sealIt) flags |= sealFlags;
    else flags &= ~sealFlags;
    cwal_container_flags_set(v, flags);
  }
  return 0;
}

int s2_cb_seal_object( cwal_callback_args const * args,
                       cwal_value ** rv ){
  uint16_t i = 0;
  cwal_value * last = 0;
  char doSeal = 1;
  if(!args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting one or more Container-type arguments.");
  }
#if 0
  /* This enables setting the seal/unseal flag by passing
     a bool first argument, but allowing script code to unseal
     objects will lead to madness. */
  if(cwal_value_is_bool(args->argv[0])){
    doSeal = cwal_value_get_bool(args->argv[0]);
    i = 1;
  }
#endif
  for(; i < args->argc; ++i ){
    cwal_value * const c = args->argv[i];
    if(!cwal_props_can(c)){
      return s2_cb_throw(args, CWAL_RC_TYPE,
                         "Argument #%d is not a Container type.",
                         (int)i);
    }
    s2_seal_container(c, doSeal);
    last = c;
  }
  *rv = last;
  return 0;
}

int s2_tokenize_path_to_array( cwal_engine * e, cwal_array ** tgt, char const * path,
                               cwal_int_t pathLen ){
  int rc = 0;
  cwal_array * ar = *tgt ? *tgt : cwal_new_array(e);
  cwal_value * arV;
  char const * t = 0;
  cwal_size_t tLen = 0;
  s2_path_toker pt = s2_path_toker_empty;
  if(!ar){
    return CWAL_RC_OOM;
  }
  arV = cwal_array_value(ar);
  cwal_value_ref(arV);
  s2_path_toker_init(&pt, path, pathLen);
  while(0==s2_path_toker_next(&pt, &t, &tLen)){
    cwal_value * const v = cwal_new_string_value(e, t, tLen);
    if(!v){
      rc = CWAL_RC_OOM;
      break;
    }
    cwal_value_ref(v);
    rc = cwal_array_append(ar, v);
    cwal_value_unref(v);
    if(rc) break;
  }
  if(rc){
    if(ar == *tgt) cwal_value_unhand(arV);
    else cwal_value_unref(arV);
  }else{
    *tgt = ar /* maybe a no-op */;
    cwal_value_unhand(arV);
  }
  return rc;
}


int s2_cb_tokenize_path( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  cwal_array * tgt = 0;
  cwal_value * tgtV = 0;
  char const * path;
  cwal_size_t pathLen;
  if(!args->argc || args->argc>2) goto misuse;
  path = cwal_value_get_cstr(args->argv[0], &pathLen);
  if(!path) goto misuse;
  if(2==args->argc){
    tgtV = args->argv[1];
    tgt = cwal_value_get_array(tgtV);
    if(!tgt) goto misuse;
  }else{
    tgt = cwal_new_array(args->engine);
    if(!tgt) return CWAL_RC_OOM;
    tgtV = cwal_array_value(tgt);
  }
  cwal_value_ref(tgtV);
  rc = pathLen
    ? s2_tokenize_path_to_array(args->engine, &tgt,
                                path, (cwal_int_t)pathLen)
    : 0;
  if(rc){
    cwal_value_unref(tgtV);
  }else{
    cwal_value_unhand(tgtV);
    *rv = tgtV;
  }
  return rc;
  misuse:
  return s2_cb_throw(args, CWAL_RC_MISUSE,
                     "Expecting (string path[, array target]) argument(s).");
}

char const * s2_home_get(s2_engine * se, cwal_size_t * len){
  char const * e = getenv("S2_HOME");
  S2_UNUSED_ARG(se);
  if(e && len) *len = cwal_strlen(e);
  return e;
}

void s2_disable_set( s2_engine * se, cwal_flags32_t f ){
  se->flags.disable = f;
}

int s2_disable_set_cstr( s2_engine * se, char const * str, cwal_int_t strLen,
                         cwal_flags32_t * result ){
  cwal_flags32_t f = 0;
  if(strLen<0) strLen = (cwal_int_t)cwal_strlen(str);
  if(strLen>0){
    char const * t = 0;
    cwal_size_t tLen = 0;
    s2_path_toker pt = s2_path_toker_empty;
    s2_path_toker_init(&pt, str, strLen);
    pt.separators = " ,";
    while(0==s2_path_toker_next(&pt, &t, &tLen)){
#define CHECK(KEY,F) if(0==cwal_compare_cstr(KEY,tLen,t,tLen)){ f|=F; continue; }(void)0
      switch(tLen){
        case 4:
          if(0==cwal_compare_cstr("none",tLen,t,tLen)){
            f = 0;
            continue;
          }
          break;
        case 5:
          CHECK("fs-io", S2_DISABLE_FS_IO);
          break;
        case 6:
          CHECK("fs-all", S2_DISABLE_FS_ALL);
          break;
        case 7:
          CHECK("fs-read", S2_DISABLE_FS_READ);
          CHECK("fs-stat", S2_DISABLE_FS_STAT);
          break;
        case 8:
          CHECK("fs-write", S2_DISABLE_FS_READ);
          break;
        default:
          break;
      }
#undef CHECK
      return s2_engine_err_set(se, CWAL_RC_RANGE,
                               "Unknown feature-disable flag: %.*s",
                               (int)tLen, t);
    }
  }
  se->flags.disable = f;
  if(result) *result = f;
  return 0;
}


cwal_flags32_t s2_disable_get( s2_engine const * se ){
  return se->flags.disable;
}

/**
   Returns the name of the first s2_disabled_features flag which
   matches f, checking from lowest to highest value.
*/
static char const * s2_disable_first_flag_name( cwal_flags32_t f ){
#define CHECK(F) if(f & F) return #F
  CHECK(S2_DISABLE_FS_STAT);
  else CHECK(S2_DISABLE_FS_READ);
  else CHECK(S2_DISABLE_FS_WRITE);
  else return "???";
#undef CHECK
}

int s2_disable_check( s2_engine * se, cwal_flags32_t f ){
  int rc = 0;
  if(f & se->flags.disable){
    rc = s2_engine_err_set(se, CWAL_RC_ACCESS,
                           "Feature flag(s) 0x%08x disallowed by "
                           "s2_disabled_features flag %s.",
                           (unsigned)f,
                           s2_disable_first_flag_name(f & se->flags.disable));
  }
  return rc;
}

int s2_disable_check_throw( s2_engine * se, cwal_flags32_t f ){
  int rc = s2_disable_check(se, f);
  if(rc){
    rc = s2_throw_err(se, 0, 0, 0, 0);
    assert(rc != 0);
  }
  return rc;
}

int s2_cb_disable_check( cwal_callback_args const * args, cwal_flags32_t f ){
  return s2_disable_check_throw(s2_engine_from_args(args), f);
}

#if S2_TRY_INTERCEPTORS
/**
   Script usages:

   1) f( obj, key, func )

   Flags func as an interceptor and installs func as obj[key].

   2) Function f(func)

   Flags func as an interceptor. For the first form, it also adds the
   function as a property of the given object.
*/
int s2_cb_add_interceptor( cwal_callback_args const * args, cwal_value **rv ){
  cwal_value * tgt = 0;
  cwal_value * key = 0;
  cwal_value * fv = 0;
  cwal_function * f = 0;
  if(1==args->argc){
    fv = args->argv[0];
  }else if(3==args->argc){
    tgt = args->argv[0];
    key = args->argv[1];
    fv = args->argv[2];
  }
  f = fv ? cwal_value_function_part(args->engine, fv) : 0;
  if(!f || (tgt && !cwal_props_can(tgt))){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting (Function) or "
                       "(Object, mixed, Function) arguments.");
  }else{
    s2_engine * se = s2_engine_from_args(args);
    /* uint16_t const flags = cwal_container_client_flags_get(fv); */
    int const rc = key
      ? s2_set_with_flags_v( se, tgt, key, fv,
                             CWAL_VAR_F_HIDDEN
                             | CWAL_VAR_F_CONST )
      : 0;
    if(!rc){
#if 0
      s2_func_state * fst = rc ? 0 : s2_func_state_for_func(f);
      if(fst){
        /* A script func. Optimization: if it contains the text
           se->cache.keyInterceptee then set a flag to tell downstream
           code to set the se->cache.keyInterceptee var when calling
           the interceptor. */
        char const * str = cwal_value_get_cstr(fst->vSrc, 0);
        char const * key = cwal_value_get_cstr(se->cache.keyInterceptee,
                                               0);
        if(strstr(str, key)){
          fst->flags |= S2_FUNCSTATE_F_INTERCEPTEE;
        }
      }
#endif
      /* cwal_container_client_flags_set(fv, flags | S2_VAL_F_FUNC_INTERCEPTOR); */
      cwal_container_flags_set(fv, cwal_container_flags_get(fv)
                               | CWAL_CONTAINER_INTERCEPTOR);
      *rv = 1==args->argc
        ? fv
        : cwal_function_value(args->callee);
    }
    return rc;
  }
}
#endif/*S2_TRY_INTERCEPTORS*/

#undef MARKER
#undef HAS_DOTLIKE_FLAG
#undef HAS_ENUM_FLAG
#undef s2__scope_current
#undef s2__scope_for_level
#undef S2_USE_SIGNALS
/* 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_engine_from_args(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_ctor( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  cwal_array * ar;
  ar = cwal_new_array( args->engine );
  if(!ar) rc = CWAL_RC_OOM;
  else if(args->argc){
    cwal_int_t i = ((cwal_int_t)args->argc)-1;
    /* insert end-first to get all allocation out of the way
       up front. */
    for( ; !rc && i >= 0; --i ){
      rc = cwal_array_set(ar, (cwal_size_t)i, args->argv[i]);
    }
  }
  if(!rc) *rv = cwal_array_value(ar);
  else cwal_value_unref(cwal_array_value(ar));
  return rc;
}

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 s2_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 = args->self;
  }else{
    *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;
}

/**
   Internal impl for array.push(), array.'operator+=', and
   array.'operator+'. If isPush is true, it operators as push(), else
   as += or binary +.
*/
static int s2_cb_array_push_impl( cwal_callback_args const * args,
                                  cwal_value **rv, int isPush ){
  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) break;
      else if(isPush) *rv = args->argv[i];
    }
    if(rc){
      rc = s2_throw(se, rc, "Appending to array failed.");
    }else if(!isPush){
      *rv = args->self;
    }
  }
  return 0;
}


static int s2_cb_array_push( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_array_push_impl(args, rv, 1);
}
static int s2_cb_array_operator_pluseq( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_array_push_impl(args, rv, 0);
}
#if 0
/* Still unsure about whether (array + X)===array is really valid. */
static int s2_cb_array_operator_plus( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_array_push_impl(args, rv, 0);
}
#endif

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 = 0;
  cwal_int_t i = 0, n = 1;
  THIS_ARRAY;
  if(args->argc && cwal_value_is_integer(args->argv[0])){
    n = cwal_value_get_integer(args->argv[0]);
    if(n<=0){
      return cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                                 "Integer argument to shift() must be >0.");
    }
  }
  for( ; i < n; ++i ){
    *rv = 0;
    rc = cwal_array_shift( self, rv );
    if(CWAL_RC_RANGE==rc){
      rc = 0;
      *rv = cwal_value_undefined();
    }else if(rc) break;
    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 * const 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 ){
    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;
    cbRv = 0;
    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.
    */
    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_false()==cbRv/*literal false, not another falsy*/){
      /* Treat a "real" false return value as signal to end the
         loop. */
      break;
    }
    if(ndx!=cbRv) cwal_refunref(cbRv);
  }
  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;
  }
}


static int s2_cb_array_remove_index( cwal_callback_args const * args, cwal_value **rv ){
  cwal_int_t ndx;
  THIS_ARRAY;
  if(!args->argc || (0>(ndx=cwal_value_get_integer(args->argv[0])))){
    *rv = cwal_value_false();
  }else {
    cwal_size_t const max = cwal_array_length_get(self);
    if((cwal_size_t)ndx>=max){
      *rv = cwal_value_false();
      return 0;
    }else{
      int rc = 0;
      cwal_size_t i = ndx;
      for( ; i < max; ++i ){
        rc = cwal_array_set(self, i,
                            cwal_array_get(self, i+1));
        assert(!rc && "Cannot fail in this case.");
        if(!rc) *rv = args->argv[i];
        else break;
      }
      if(!rc){
        cwal_array_length_set(self, max-1);
        *rv = cwal_value_true();
      }
    }
  }
  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;
  char const strictCompare = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 1;
  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, strictCompare);
  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;
  cwal_tuple * tp = 0;
  cwal_array * ar = 0;
  ARGS_SE;
  ar = cwal_value_array_part(args->engine, args->self);
  tp = ar ? 0 : cwal_value_get_tuple(args->self);
  if(!ar && !tp){
    return s2_cb_throw( args, CWAL_RC_TYPE,
                       "'this' is-not-an Array or Tuple." );
  }
  buf = &se->buffer;
  oldUsed = buf->used;
  if(!args->argc){
    misuse:
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                    "Expecting string argument.");
  }
  joiner = cwal_value_get_cstr(args->argv[0], &jLen);
  if(!joiner) goto misuse;
  aLen = ar
    ? cwal_array_length_get(ar)
    : cwal_tuple_length(tp);
  for( i = 0; !rc && i < aLen; ++i ){
    cwal_value * v = ar
      ? cwal_array_get(ar, i)
      : cwal_tuple_get(tp, (uint16_t)i);
    if(i>0){
      rc = cwal_buffer_append(args->engine, buf, joiner, jLen);
    }
    if(v && !rc){
      rc = s2_value_to_buffer(args->engine, buf, v);
    }
  }
  if(!rc){
    cwal_size_t const ln = buf->used - oldUsed;
    assert(oldUsed <= buf->used);
    *rv = cwal_new_string_value(args->engine,
                                 (char const *)(buf->mem+oldUsed),
                                 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;
    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;
    }
    cwal_value_ref(proto);
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_ARRAY, proto );
    cwal_value_unref(proto) /* on success ^^^, the core now owns a ref */;
    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")); */

    {
      s2_func_def const funcs[] = {
      S2_FUNC2("clear", s2_cb_array_clear),
      S2_FUNC2("eachIndex", s2_cb_array_each),
      S2_FUNC2("getIndex", s2_cb_array_get_index),
      S2_FUNC2("indexOf", s2_cb_array_index_of),
      S2_FUNC2("isEmpty", s2_cb_array_isempty),
      S2_FUNC2("join",s2_cb_array_join),
      S2_FUNC2("length",s2_cb_array_length),
      S2_FUNC2("operator+=", s2_cb_array_operator_pluseq),
      /*S2_FUNC2("operator+", s2_cb_array_operator_plus),*/
      S2_FUNC2("push", s2_cb_array_push),
      S2_FUNC2("pop", s2_cb_array_pop),
      S2_FUNC2("removeIndex", s2_cb_array_remove_index),
      S2_FUNC2("reserve", s2_cb_array_reserve),
      S2_FUNC2("reverse", s2_cb_array_reverse),
      S2_FUNC2("setIndex", s2_cb_array_set_index),
      S2_FUNC2("shift", s2_cb_array_shift),
      S2_FUNC2("slice", s2_cb_array_slice),
      S2_FUNC2("sort", s2_cb_array_sort),
      S2_FUNC2("toString", s2_cb_value_to_string),
      S2_FUNC2("unshift", s2_cb_array_unshift),
      s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0);
      if(!rc) rc = s2_ctor_callback_set(se, proto,
                                        s2_cb_array_ctor);
      if(rc) goto end;
    }
    
    {
      /* Array.filter() impl. */
      char const * src =
        "proc(f,invert=false){"
          "affirm typeinfo(iscallable f);"
          "affirm typeinfo(islist this);"
          "const a = [];"
          "foreach(@this=>v) f(v) ? (invert ? 0 : a[]=v) : (invert ? a[]=v : 0);"
          "return typeinfo(istuple this) ? [#@a] : a;"
        "}";
      rc = s2_set_from_script(se, src, (int)cwal_strlen(src),
                              proto, "filter", 6);
      if(rc) goto end;
    }

    end:
    return rc ? NULL : proto;
}

static int s2_cb_tuple_ctor( cwal_callback_args const * args, cwal_value **rv ){
  cwal_int_t n = (1==args->argc) ? cwal_value_get_integer(args->argv[0]) : -1;
  if(1 != args->argc){
    goto misuse;
  }else if(cwal_value_is_array(args->argv[0])){
    /* Tuple(array): Copy elements from the array. */
    cwal_array const * ar = cwal_value_get_array(args->argv[0]);
    cwal_size_t an = cwal_array_length_get(ar);
    uint16_t i;
    int rc = 0;
    cwal_tuple * tp;
    if(an > (uint16_t)-1){
      goto toomany;
    }
    tp = cwal_new_tuple(args->engine, an);
    if(!tp) rc = CWAL_RC_OOM;
    else{
      *rv = cwal_tuple_value(tp);
      for(i = 0; i < an; ++i ){
        cwal_value * v = cwal_array_get(ar, i);
        rc = cwal_tuple_set(tp, i, v);
        assert(!rc && "No possible error conditions here.");
      }
    }
    return rc;
  }else if(cwal_value_is_tuple(args->argv[0])){
    /* Tuple(tuple): Copy elements from the tuple. */
    cwal_tuple const * t1 = cwal_value_get_tuple(args->argv[0]);
    uint16_t const an = cwal_tuple_length(t1);
    uint16_t i;
    int rc = 0;
    cwal_tuple * tp = cwal_new_tuple(args->engine, an);
    if(!tp) rc = CWAL_RC_OOM;
    else{
      *rv = cwal_tuple_value(tp);
      for(i = 0; i < an; ++i ){
        cwal_value * v = cwal_tuple_get(t1, i);
        rc = cwal_tuple_set(tp, i, v);
        assert(!rc && "No possible error conditions here.");
      }
    }
    return rc;
  }else if(n>=0){
    /* Tuple(integer size) */
    if(n > (cwal_int_t)((uint16_t)-1)){
      goto toomany;
    }
    *rv = cwal_new_tuple_value(args->engine, (cwal_size_t)n);;
    assert(*rv
           ? (s2_prototype_tuple(s2_engine_from_args(args))
              == cwal_value_prototype_get(args->engine, *rv))
           : 1);

    assert((cwal_size_t)n == cwal_tuple_length(cwal_value_get_tuple(*rv)));
    return *rv ? 0 : CWAL_RC_OOM;
  }
  misuse:
  return s2_cb_throw(args, CWAL_RC_MISUSE,
                      "Expecting an (array | tuple | integer>=0).");
  toomany:
  return s2_cb_throw(args, CWAL_RC_RANGE, "Too many items for a tuple!");
}

static int s2_cb_tuple_length( cwal_callback_args const * args, cwal_value **rv ){
  cwal_tuple const * tp = cwal_value_get_tuple(args->self);
  if(!tp){
    return s2_cb_throw(args, CWAL_RC_TYPE,
                      "'this' is-not-a Tuple.");
  }else if(args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                      "Tuple length cannot be changed after construction.");
  }else{
    *rv = cwal_new_integer(args->engine, cwal_tuple_length(tp));
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

/**
   Impl for Tuple's operator!=, <=, <, >, >=, ==.

   direction: <0 means lt, >0 means gt, 0 means eq.

   eq: 0 for (<, >, !=), non-0 for (>=, <=, ==).

   TODO: we can refactor this in a generic routine which injects these
   overloads for any type. Alternately, make the op overloading away
   of the compare() method and use it (if availble) to implement these
   ops. That would cost 6 fewer overloads.
*/
static int s2_cb_tuple_cmp_impl( cwal_callback_args const * args, cwal_value **rv,
                                 int direction, char eq ){
  cwal_value * rhs = args->argc ? args->argv[0] : 0;
  assert(1==args->argc);
  if(1!=args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                      "Internal error: expecting operator "
                      "to be called with 1 argument.");
  }
  else if(!cwal_value_is_tuple(rhs)){
    return s2_cb_throw(args, CWAL_RC_TYPE,
                      "Cannot compare a tuple to a non-tuple.");
  }else{
    int const cmp = cwal_value_compare(args->self, rhs);
    if(!direction){
      if(!eq){ /* != op */
        *rv = cmp ? cwal_value_true() : cwal_value_false();
      }else{ /* == op */
        *rv = cmp ? cwal_value_false() : cwal_value_true();
      }
    }else if(!cmp){
      *rv = eq ? cwal_value_true() : cwal_value_false();
    }else{
      *rv = (direction<0 && cmp<0)
        ? cwal_value_true()
        : ((direction>0 && cmp>0)
           ? cwal_value_true()
           : cwal_value_false())
        ;
    }
    return 0;
  }
}

static int s2_cb_tuple_op_lt( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_tuple_cmp_impl(args, rv, -1, 0);
}


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

static int s2_cb_tuple_op_gt( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_tuple_cmp_impl(args, rv, 1, 0);
}

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

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

static int s2_cb_tuple_op_eq( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_tuple_cmp_impl(args, rv, 0, 1);
}

cwal_value * s2_prototype_tuple( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    proto = 1
      ? s2_prototype_stashed(se, "tuple")
      : cwal_prototype_base_get( se->e, CWAL_TYPE_TUPLE );
    if(proto) return proto;
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    cwal_value_prototype_set(proto, NULL);
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_TUPLE, proto );
    if(!rc) rc = s2_prototype_stash(se, "tuple", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_TUPLE));

    {
      s2_func_def const funcs[] = {
        S2_FUNC2("compare", s2_cb_value_compare),
        S2_FUNC2("join",s2_cb_array_join),
        S2_FUNC2("length", s2_cb_tuple_length),
        S2_FUNC2("toJSONString", s2_cb_this_to_json_token),
        S2_FUNC2("toString", s2_cb_value_to_string),
        S2_FUNC2("operator<=", s2_cb_tuple_op_le),
        S2_FUNC2("operator<", s2_cb_tuple_op_lt),
        S2_FUNC2("operator>=", s2_cb_tuple_op_ge),
        S2_FUNC2("operator>", s2_cb_tuple_op_gt),
        S2_FUNC2("operator!=", s2_cb_tuple_op_neq),
        S2_FUNC2("operator==", s2_cb_tuple_op_eq),
        s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0);
      if(!rc) rc = s2_ctor_callback_set(se, proto, s2_cb_tuple_ctor);
      if(!rc){
        cwal_value * arrayProto = cwal_prototype_base_get( se->e, CWAL_TYPE_ARRAY );
        cwal_kvp * kvp;
        assert(arrayProto && "Array prototype must have been set up already!");
        kvp = cwal_prop_get_kvp( arrayProto, "filter", 6, 0, NULL );
        assert(kvp && "array.filter() must have been installed already!");
        rc = cwal_prop_set_v( proto, cwal_kvp_key(kvp), cwal_kvp_value(kvp) );
      }
      if(rc) goto end;
    }
    /* s2_dump_val(proto,"Pair prototype"); */
    assert(!rc);
    end:
    return rc ? NULL : proto;
}

#undef MARKER
#undef ARGS_SE
#undef THIS_ARRAY
/* end of file s2_array.c */
/* start of file s2_enum.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

#define HAS_ENUM_FLAG(ClientFlags) \
  ((S2_VAL_F_MODE_CLASS & ClientFlags) \
   && (S2_VAL_F_CLASS_ENUM & ClientFlags))

enum s2_enum_builder_state {
S2_ENUM_STATE_UNINITED = 0,
S2_ENUM_STATE_INITED = 1,
S2_ENUM_STATE_SEALED = 2
};

int s2_enum_builder_init( s2_engine * se, s2_enum_builder * eb,
                          char const * typeName,
                          cwal_size_t entryCountHint){
  int rc = 0;
  s2_enum_builder_cleanup(eb);
  eb->se = se;
  if(!entryCountHint) entryCountHint = 6;
  entryCountHint = cwal_next_prime(2 * (entryCountHint
                                        ? entryCountHint-1 : 6) );
  eb->entries = cwal_new_hash_value(se->e, entryCountHint);
  if(eb->entries){
    s2_hash_dot_like_object(eb->entries, 1);
  }else{
    return CWAL_RC_OOM;
  }
  cwal_value_ref(eb->entries) /* we'll hold this ref for the whole
                                 enum build process */;
  if(typeName && *typeName){
    cwal_value * key = cwal_new_string_value(se->e, typeName,
                                             cwal_strlen(typeName));
    if(key){
      cwal_value_ref(key);
      rc = cwal_prop_set_with_flags_v( eb->entries, se->cache.keyTypename,
                                       key,
                                       CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN );
      cwal_value_unref(key);
    }else{
      rc = CWAL_RC_OOM;
    }
    if(rc) goto err;
  }
  eb->flags = S2_ENUM_STATE_INITED;
  cwal_value_make_vacuum_proof(eb->entries,1);
  return 0;
  err:
  s2_enum_builder_cleanup(eb);
  assert(rc);
  return rc;
}

void s2_enum_builder_cleanup( s2_enum_builder * eb ){
  if(eb->entries){
    assert(eb->se);
    assert(cwal_value_refcount(eb->entries));
    cwal_value_make_vacuum_proof(eb->entries,0);
    cwal_value_unref(eb->entries);
  }
  *eb = s2_enum_builder_empty;  
}

int s2_enum_builder_append_v( s2_enum_builder * eb,
                              cwal_value * key,
                              cwal_value * wrappedVal ){
  cwal_value * uval;
  int rc;
  if(S2_ENUM_STATE_INITED != eb->flags){
    return CWAL_RC_MISUSE;
  }else if(!key || !wrappedVal){
    return CWAL_RC_MISUSE;
  }
  assert(eb->flags>0);
  assert(eb->entries);
  uval = cwal_new_unique(eb->se->e, wrappedVal);
  if(!uval){
    rc = CWAL_RC_OOM;
  }else{
    cwal_value_ref(uval);
    rc = s2_set_with_flags_v( eb->se, eb->entries, key,
                              uval, CWAL_VAR_F_CONST );
    if(!rc){
      rc = s2_set_with_flags_v( eb->se, eb->entries, uval, key,
                                CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN );
    }
    cwal_value_unref(uval);
    if(!rc){
      ++eb->entryCount;
    }
  }
  return rc;
}

int s2_enum_builder_append( s2_enum_builder * eb,
                            char const * entryName,
                            cwal_value * val){
  cwal_value * key = 0;
  cwal_engine * e = eb->se->e;
  if(S2_ENUM_STATE_INITED != eb->flags){
    return CWAL_RC_MISUSE;
  }else if(!entryName){
    return CWAL_RC_MISUSE;
  }
  key = cwal_new_string_value(e, entryName,
                              cwal_strlen(entryName));
  if(!key) return CWAL_RC_OOM;
  else{
    int rc;
    cwal_value_ref(key);
    rc = s2_enum_builder_append_v(eb, key, val);
    cwal_value_unref(key);
    return rc;
  }
}

int s2_enum_builder_seal( s2_enum_builder * eb, cwal_value **rv ){
  int rc = 0;
  cwal_value * enumProto;
  if(S2_ENUM_STATE_INITED != eb->flags){
    return CWAL_RC_MISUSE;
  }else if(!eb->entryCount){
    return CWAL_RC_RANGE;
  }
  enumProto = s2_prototype_enum(eb->se);
  if(!enumProto) return CWAL_RC_OOM;
  assert(eb->entries);
  assert(eb->flags > 0);
#if 0
  v = cwal_new_integer(eb->se->e, eb->entryCount);
  if(!v) rc = CWAL_RC_OOM;
  else{
    cwal_value_ref(v);
    rc = cwal_prop_set_with_flags(eb->entries, "enumEntryCount", 14, v,
                                  CWAL_VAR_F_CONST);
    cwal_value_unref(v);
    v = 0;
  }
  if(rc) goto end;
#endif
  rc = cwal_value_prototype_set(eb->entries, enumProto);
  if(!rc){
    cwal_container_flags_set(eb->entries,
                             CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES
                             | CWAL_CONTAINER_DISALLOW_PROP_SET
                             /* TODO:???
                                | CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET */
                             );
    cwal_container_client_flags_set(eb->entries, S2_VAL_F_ENUM);
    eb->flags = S2_ENUM_STATE_SEALED;
    if(rv){
      *rv = eb->entries;
      cwal_value_ref(*rv);
      s2_enum_builder_cleanup(eb) /* will unref() and de-vacuum-safe
                                     eb->entries */;
      assert(!cwal_value_is_vacuum_proof(*rv));
      cwal_value_unhand(*rv);
    }
  }
  /* end: */
  return rc;
}


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

/* KVP Visitor for enums */
static int s2_enum_visitor_each( cwal_kvp const * kvp, void * state_ ){
  /* Reminder: the (unique==>string) pairs are hidden, so they're
     not iterated over. */
  if(cwal_value_is_unique(cwal_kvp_value(kvp))){
    return cwal_value_is_string(cwal_kvp_key(kvp))
      /* so we pass (V,K) to the callback, and only for (Unique==>Name)
         pairs. */
      ? s2_kvp_visitor_prop_each(kvp, state_)
      : 0;
  }else{
    return 0;
  }
}

static int s2_cb_enum_each( cwal_callback_args const * args, cwal_value **rv ){
  cwal_function * f;
  f = args->argc
    ? cwal_value_function_part(args->engine, args->argv[0])
    : NULL;
  if(!f){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting a Function argument, "
                       "but got a %s.",
                       args->argc
                       ? cwal_value_type_name(args->argv[0])
                       : "<NULL>");
  }else{
    s2_kvp_each_state state = s2_kvp_each_state_empty;
    int rc;
    cwal_hash * h = cwal_value_hash_part(args->engine, args->self);
    state.e = args->engine;
    state.callback = f;
    state.self = args->self;
    /* state.valueArgFirst = 1; */
    rc = cwal_hash_visit_kvp( h, s2_enum_visitor_each, &state );
    if(S2_RC_END_EACH_ITERATION==rc) rc = 0;
    if(!rc) *rv = args->self;
    return rc;
  }
}

cwal_value * s2_value_enum_part( s2_engine * se, cwal_value * v ){
    do{
      if(HAS_ENUM_FLAG(cwal_container_client_flags_get(v))) return v;
      else v = cwal_value_prototype_get(se->e, v);
    }while(v);
    return 0;
}

int s2_value_is_enum( const cwal_value * v ){
  return HAS_ENUM_FLAG(cwal_container_client_flags_get(v));
}

/** Internal helper for s2_enum_from_object() (==> efo) */
static int cwal_kvp_visitor_f_efo( cwal_kvp const * kvp, void * state ){
  s2_enum_builder * eb = (s2_enum_builder *)state;
  return s2_enum_builder_append_v( eb, cwal_kvp_key(kvp), cwal_kvp_value(kvp) );
}

/**
   UNTESTED!

   Copies all properties from src to a new enum value.

   The typeName parameter is interpreted as documented for
   s2_enum_builder_init(). It may be NULL.

   On success, *rv is assigned to the new enum value. On error, *rv is
   not modified and non-0 is returned. Returns CWAL_RC_MISUSE if any
   pointer argument is NULL, CWAL_RC_RANGE if src has no properties
   (empty enums are not allowed), CWAL_RC_OOM if an allocation fails,
   and possibly other CWAL_RC_xxx codes from the underlying
   s2_enum_builder API calls.

   On success, *rv becomes the caller's responsibility: it is a new
   value with a refcount of 0.
*/
int s2_enum_from_object( s2_engine * se, cwal_value * src,
                         char const * typeName, cwal_value **rv ){
  int rc;
  cwal_value * enu = 0;
  cwal_size_t nKeys = 0;
  s2_enum_builder eb = s2_enum_builder_empty;
  if(!se || !src || !rv) return CWAL_RC_MISUSE;
  nKeys = cwal_props_count(src);
  if(0==nKeys){
    return s2_engine_err_set(se, CWAL_RC_RANGE,
                             "Empty enum is not permitted.");
  }
  rc = s2_enum_builder_init( se, &eb, typeName, nKeys );
  if(!rc){
    rc = cwal_props_visit_kvp( src, cwal_kvp_visitor_f_efo, &eb );
    if(!rc){
      rc = s2_enum_builder_seal( &eb, &enu );
      cwal_value_ref(enu);
    }
  }
  s2_enum_builder_cleanup(&eb);
  if(rc){
    cwal_value_unref(enu);
  }else{
    assert(enu);
    cwal_value_unhand(enu);
    *rv = enu;
  }
  return rc;
}

#define THIS_ENUM                                                  \
  s2_engine * se = s2_engine_from_args(args);                      \
  cwal_value * eObj = s2_value_enum_part(se, args->self);          \
  cwal_hash * eHash = eObj ? cwal_value_get_hash(eObj) : 0;        \
  if(!eHash) return cwal_exception_setf( args->engine, CWAL_RC_TYPE, \
                                         "Expecting an enum as 'this'." )

/**
   Specialized enum prop search which only searches in the enum part
   of theEnum (which may be an enum-derived value). Returns the value
   it finds or 0 if it does not find one.
*/
static cwal_value * s2_enum_search( s2_engine * se, cwal_value * theEnum,
                                    cwal_value * key){
  cwal_hash * h;
  theEnum = s2_value_enum_part(se, theEnum);
  h = theEnum ? cwal_value_get_hash(theEnum) : 0;
  return h ? cwal_hash_search_v(h, key) : 0;
}

/**
   Internal helper for s2_cb_enum_keys().
*/
static int s2_kvp_visit_enum_keys_to_array( cwal_kvp const * kvp, void * state ){
  return cwal_value_is_unique(cwal_kvp_value(kvp))
    ? cwal_array_append((cwal_array *)state, cwal_kvp_key(kvp))
    : 0;
}

/**
   Returns an array containing all enum entry keys for
   obj.
*/
static int s2_cb_enum_keys( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_array * ar;
  THIS_ENUM;
  ar = cwal_new_array(args->engine);
  if(!ar) return CWAL_RC_OOM;
  rc = cwal_hash_visit_kvp( eHash, s2_kvp_visit_enum_keys_to_array, ar );
  if(!rc) *rv = cwal_array_value(ar);
  else cwal_array_unref(ar);
  return rc;
}

static int s2_cb_enum_contains( cwal_callback_args const * args, cwal_value **rv ){
  THIS_ENUM;
  if(1 != args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting exactly one argument.");
  }
  *rv = cwal_new_bool( !!s2_enum_search(se, eObj, args->argv[0]) );
  return 0;
}

static int s2_cb_enum_op_arrow( cwal_callback_args const * args, cwal_value **rv ){
  THIS_ENUM;
  assert(1==args->argc && "expecting operator call usage.");
  if(!(*rv = s2_enum_search( se, eObj, args->argv[0] ))){
    *rv = cwal_value_undefined();
  }
  return 0;  
}

static int s2_cb_enum_op_dotdot( cwal_callback_args const * args, cwal_value **rv ){
  cwal_value * entry;
  cwal_value * key;
  int rc = 0;
  THIS_ENUM;
  assert(1==args->argc && "expecting operator call usage.");
  key = args->argv[0];
  if(!cwal_value_is_string(key)
     /* && !cwal_value_is_number(key) */){
	return s2_cb_throw(args, CWAL_RC_TYPE,
		              "Invalid type for enum 'operator::' RHS: "
		              "expecting a string/identifier");
  }
  entry = s2_enum_search( se, eObj, key );
  if(entry){
    if(cwal_value_is_unique(entry)){
	  if(!(*rv = cwal_unique_wrapped_get(entry))) *rv = cwal_value_undefined();
    }else{
      cwal_size_t len = 0;
      char const * cKey = cwal_value_get_cstr(key, &len);
      rc = s2_cb_throw(args, CWAL_RC_TYPE,
                      "Property '%.*s' is (somehow) not an enum entry.",
	                  (int)len, cKey);
    }
  }else{
    cwal_size_t len = 0;
    char const * cKey = cwal_value_get_cstr(key, &len);
    rc = s2_cb_throw(args, CWAL_RC_NOT_FOUND,
	                "No such enum entry: '%.*s'",
	                (int)len, cKey);
  }
  return rc;  
}


cwal_value * s2_prototype_enum(s2_engine * se){
  static char const * protoKey = "Enum";
  cwal_value * proto = s2_prototype_stashed( se, protoKey );
  if(proto) return proto;
  proto = cwal_new_object_value(se->e);
  if(!proto) return 0;
  else{
    int rc;
    cwal_value_ref(proto);
    cwal_value_prototype_set(proto, 0)
      /* so it does not derive from Object */;
    rc = s2_typename_set(se, proto, "enum", 4);
    if(!rc){
      const s2_func_def funcs[] = {
      S2_FUNC2("eachEnumEntry", s2_cb_enum_each),
      S2_FUNC2("getEnumKeys", s2_cb_enum_keys),
      S2_FUNC2("hasEnumEntry", s2_cb_enum_contains),
      S2_FUNC2("operator->", s2_cb_enum_op_arrow),
      S2_FUNC2("operator::", s2_cb_enum_op_dotdot),
      s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs,
                                CWAL_VAR_F_CONST);
    }
    if(rc || (rc=s2_prototype_stash(se, protoKey, proto))){
      cwal_value_unref(proto);
      proto = 0;
    }else{
      cwal_value_unhand(proto);
      assert(cwal_value_refcount(proto) && "But... the s2_stash ref?");
    }
    return proto;
  }
}


#undef MARKER
#undef HAS_ENUM_FLAG
#undef THIS_ENUM
/* end of file s2_enum.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():\t",__FILE__,__LINE__); if(1) printf pfexp
/**
   Dumps info about (s2_ptoken const*) TP, prefixed by the (char const *) lbl.
*/
#define s2__dump_token(lbl,TP) \
  MARKER(("%s: token type %d/%s: %.*s\n", lbl,                          \
          (TP)->ttype, s2_ttype_cstr((TP)->ttype),                      \
          (int)s2_ptoken_len(TP), s2_ptoken_begin(TP)))
#else
#define MARKER(pfexp) (void)0
#define s2__dump_token(lbl,TP) (void)0
#endif

/** @internal
   Internal representation of a stack trace entry.
*/
struct s2_strace_entry {
  /**
     The next entry "up" (older) in the stack.
  */
  s2_strace_entry * up;
  /**
     The next entry "down" (newer) in the stack.
  */
  s2_strace_entry * down;
  /**
     The tokenizer active when this entry is created.
   */
  s2_ptoker * pr;
  /**
     Active token when this entry is created.
  */
  s2_ptoken pos;
};

/**
   Empty-initilized s2_strace_entry object.
*/
#define s2_strace_entry_empty_m {                \
    0/*up*/, 0/*down*/, 0/*pr*/, s2_ptoken_empty_m/*pos*/   \
      }

/**
   Empty-initilized s2_strace_entry object.
*/
static const s2_strace_entry s2_strace_entry_empty = s2_strace_entry_empty_m;


const s2_func_state s2_func_state_empty = {
0/*vSrc*/,0/*vImported*/,0/*vName*/,
0/*keyScriptName*/,
0/*next*/,
0/*line*/,0/*col*/,
0/*flags*/
};

#define s2__ukwd(SE) ((s2_ukwd*)se->ukwd)
#define s2__ukwd_key "s2_engine::ukwd"
static void s2_ukwd_free2(s2_engine * se, s2_ukwd * u){
  if(u){
    cwal_free2(se->e, u->list, u->alloced * sizeof(s2_ukwd));
    u->h = 0/* owned by s2 stash */;
    cwal_free2(se->e, u, sizeof(s2_ukwd));
  }
}

void s2_ukwd_free(s2_engine * se){
  s2_ukwd_free2(se, se->ukwd);
  se->ukwd = 0;
}


cwal_hash * s2_fstash( s2_engine * se ){
  if(!se->funcStash){
    se->funcStash = cwal_new_hash(se->e, 53);
    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;
}

static int s2_strace_push_pos( s2_engine * se,
                               s2_ptoker * pr,
                               s2_ptoken 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);
  }
  if(!srcPos) srcPos = &pr->token;
  /**
     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){
    assert(!ent->down);
    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 ){
    /* Generate array of stack trace entries */
    cwal_value * c;
    if(!ar){
      ar = cwal_new_array(se->e);;
      if(!ar){
        rc = CWAL_RC_OOM;
        break;
      }
      cwal_value_ref(cwal_array_value(ar));
#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;
    }
    cwal_value_ref(c);
    rc = cwal_array_append(ar, c);
    cwal_value_unref(c);
    if(!rc){
      s2_ptoken const et = *s2_ptoker_errtoken_get(ent->pr);
      s2_ptoker_errtoken_set(ent->pr, &ent->pos);
      rc = s2_add_script_props(se, c, ent->pr);
      s2_ptoker_errtoken_set(ent->pr, &et);
    }
  }

  se->strace.count = oldCount;
  if(rc) cwal_array_unref(ar);
  else{
    cwal_value_unhand(cwal_array_value(ar));
    *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; 
  }else{
    rc = (s2_func_state *)cwal_malloc2(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;
    }
  }
  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(fs->vSrc){
    cwal_value_unref(fs->vSrc);
    fs->vSrc = 0;
    }
  if(fs->vImported){
    cwal_value_unref(fs->vImported);
    fs->vImported = 0;
  }
  if(fs->vName){
    cwal_value_unref(fs->vName);
    fs->vName = 0;
  }
  if(se->funcStash){
    /* cwal_value_unref(cwal_hash_value(se->funcStash)); */
#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_v( se->funcStash, fs->keyScriptName );
    }
#endif
    /* if(fs->keyName) cwal_hash_remove_v( 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->vSrc); */
    /* assert(!fs->keyName); */
  }
  if(fs->keyScriptName){
    cwal_value_unref(fs->keyScriptName);
    fs->keyScriptName = 0;
  }
  *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_free2(se->e, fs, sizeof(s2_func_state));
    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;
  s2_engine * se = s2_engine_from_state(e);
  assert(m);
  assert(se);
  s2_func_state_free( se, fs );
}

s2_func_state * s2_func_state_for_func(cwal_function * f){
  return (s2_func_state *)cwal_function_state_get(f, &s2_func_state_empty);
}


static int s2_keyword_f_FLC( 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_breakpoint( 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_continue( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_define( 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_echo( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_enum( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_eval( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_exception( 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_foreach( 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_if( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_import( 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_new( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_pragma( 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_reserved( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_s2out( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_typeinfo( 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_unset( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_ukwd( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_using( 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_while( 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 binary-search it.

  When adding new keywords, update all of:

  - Add a S2_T_KeywordXXX entry for it.

  - Add an entry in this struct for it, as well as an initializer in
    the array which follows.

  - Add it to the following functions:

  -- s2_ttype_cstr()
  -- s2_ttype_keyword()
  -- s2_ptoken_keyword()

  And to s2_eval-keyword-hasher.s2 (to generate the body of s2_ptoken_keyword()
  and test the keyword hash algo for collisions).
*/
  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 class_;
  s2_keyword const const_;
  s2_keyword const continue_;
  s2_keyword const define;
  s2_keyword const defined_;
  s2_keyword const delete_;
  s2_keyword const doWhile;
  s2_keyword const echo;
  s2_keyword const enum_;
  s2_keyword const eval;
  s2_keyword const exception_;
  s2_keyword const exit_;
  s2_keyword const false_;
  s2_keyword const fatal_;
  s2_keyword const for_;
  s2_keyword const foreach_;
  s2_keyword const function_;
  s2_keyword const if_;
  s2_keyword const import;
  s2_keyword const include;
  s2_keyword const inherits;
  s2_keyword const interface_;
  s2_keyword const is_;
  s2_keyword const isa_;
  s2_keyword const nameof;
  s2_keyword const new_;
  s2_keyword const null_;
  s2_keyword const pragma;
  s2_keyword const private_;
  s2_keyword const proc_;
  s2_keyword const protected_;
  s2_keyword const public_;
  s2_keyword const refcount_;
  s2_keyword const return_;
  s2_keyword const s2out;
  s2_keyword const scope;
  s2_keyword const static_;
  s2_keyword const throw_;
  s2_keyword const true_;
  s2_keyword const try_;
  s2_keyword const typeInfo;
  s2_keyword const typeName;
  s2_keyword const undef_;
  s2_keyword const unset;
  s2_keyword const using;
  s2_keyword const var;
  s2_keyword const while_;
  s2_keyword const _sentinel_;
} S2_KWDS = {
/*{ id                      word,         wordLen,  call(), allowEOLAsEOXWhenLHS } */
  { S2_T_KeywordBREAKPOINT, "__BREAKPOINT", 12, s2_keyword_f_breakpoint, 0 },
  { S2_T_KeywordCOLUMN,     "__COLUMN",   8, s2_keyword_f_FLC,           0 },
  { S2_T_KeywordFILE,       "__FILE",     6, s2_keyword_f_FLC,           0 },
  { S2_T_KeywordFILEDIR,    "__FILEDIR",  9, s2_keyword_f_FLC,           0 },
  { S2_T_KeywordSRCPOS,     "__FLC",      5, s2_keyword_f_FLC,           0 },
  { S2_T_KeywordLINE,       "__LINE",     6, s2_keyword_f_FLC,           0 },

  { S2_T_KeywordAffirm,     "affirm",     6, s2_keyword_f_assert,        0 },
  { S2_T_KeywordAssert,     "assert",     6, s2_keyword_f_assert,        0 },
  { S2_T_KeywordBreak,      "break",      5, s2_keyword_f_eval,          0 },
  { S2_T_KeywordCatch,      "catch",      5, s2_keyword_f_eval,          0 },
  { S2_T_KeywordClass,      "class",      5, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordConst,      "const",      5, s2_keyword_f_var,           0 },
  { S2_T_KeywordContinue,   "continue",   8, s2_keyword_f_continue,      0 },
  { S2_T_KeywordDefine,     "define",     6, s2_keyword_f_define,        0 },
  { S2_T_KeywordDefined,    "defined",    7, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordDelete,     "delete",     6, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordDo,         "do",         2, s2_keyword_f_dowhile,       1 },
  { S2_T_KeywordEcho,       "echo",       4, s2_keyword_f_echo,          0 },
  { S2_T_KeywordEnum,       "enum",       4, s2_keyword_f_enum,          0 },
  { S2_T_KeywordEval,       "eval",       4, s2_keyword_f_eval,          1 },
  { S2_T_KeywordException,  "exception",  9, s2_keyword_f_exception,     0 },
  { S2_T_KeywordExit,       "exit",       4, s2_keyword_f_eval,          0 },
  { S2_T_KeywordFalse,      "false",      5, s2_keyword_f_builtin_vals,  0 },
  { S2_T_KeywordFatal,      "fatal",      5, s2_keyword_f_eval,          0 },
  { S2_T_KeywordFor,        "for",        3, s2_keyword_f_for,           1 },
  { S2_T_KeywordForEach,    "foreach",    7, s2_keyword_f_foreach,       1 },
  { S2_T_KeywordFunction,   "function",   8, s2_keyword_f_function,      0 },
  { S2_T_KeywordIf,         "if",         2, s2_keyword_f_if,            1 },  
  { S2_T_KeywordImport,     "import",     6, s2_keyword_f_import,        0 },
  { S2_T_KeywordInclude,    "include",    7, s2_keyword_f_reserved,      0 },
  { S2_T_OpInherits,        "inherits",   8, 0 /* handled as an operator */, 0 },
  { S2_T_KeywordInterface,  "interface",  9, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordIs,         "is",         2, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordIsA,        "isa",        3, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordNameof,     "nameof",     6, s2_keyword_f_nameof,        0 },
  { S2_T_KeywordNew,        "new",        3, s2_keyword_f_new,           0 },
  { S2_T_KeywordNull,       "null",       4, s2_keyword_f_builtin_vals,  0 },
  { S2_T_KeywordPragma,     "pragma",     6, s2_keyword_f_pragma,        0 },
  { S2_T_KeywordPrivate,    "private",    7, s2_keyword_f_reserved,      0 },  
  { S2_T_KeywordProc,       "proc",       4, s2_keyword_f_function,      0
    /*
      20171115: Interesting: while using require.s2 to import a proc
      from a file which contained only that proc (and thus evaluates
      to that proc), the allowEOLAsEOXWhenLHS handling here uncovered,
      for the first time, that proc does not like a trailing EOL when
      it's the left-most part of an expression. That usage never
      happens except in the case of import()ing or
      s2_set_from_script()'ing a proc, and was never witnessed until
      today. If we enable allowEOLAsEOXWhenLHS for procs, though,
      then we'll almost cerainly get bitten someday by something like:

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

      Which terminates the expression after the proc and leads to
      "illegal operator '.' at start of expression" before the
      next line.

      So... for that corner case we'll just have to use semicolons
      after LHS-most procs or (if the context allows) add a return
      statement before the proc keyword. An LHS proc which is
      immediately called does not demonstrate this problem because the
      proc itself is not both the LHS and final expression component:

      proc(){...}()
     */
  },
  { S2_T_KeywordProtected,  "protected",  9, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordPublic,     "public",     6, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordRefcount,   "refcount",   8, s2_keyword_f_refcount,      0 },
  { S2_T_KeywordReturn,     "return",     6, s2_keyword_f_eval,          0 },
  { S2_T_KeywordS2Out,      "s2out",      5, s2_keyword_f_s2out,         0 },
  { S2_T_KeywordScope,      "scope",      5, s2_keyword_f_eval,          1 },
  { S2_T_KeywordStatic,     "static",     6, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordThrow,      "throw",      5, s2_keyword_f_eval,          0 },
  { S2_T_KeywordTrue,       "true",       4, s2_keyword_f_builtin_vals,  0 },
  { S2_T_KeywordTry,        "try",        3, s2_keyword_f_reserved,      0 },
  { S2_T_KeywordTypeinfo,   "typeinfo",   8, s2_keyword_f_typeinfo,      0 },
  { S2_T_KeywordTypename,   "typename",   8, s2_keyword_f_typename,      0 },
  { S2_T_KeywordUndefined,  "undefined",  9, s2_keyword_f_builtin_vals,  0 },
  { S2_T_KeywordUnset,      "unset",      5, s2_keyword_f_unset,         0 },
  { S2_T_KeywordUsing,      "using",      5, s2_keyword_f_using,         0 },
  { S2_T_KeywordVar,        "var",        3, s2_keyword_f_var,           0 },
  { S2_T_KeywordWhile,      "while",      5, s2_keyword_f_while,         1 },

  {/*_sentinel_*/0,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_KeywordClass,class_);
    WORD(S2_T_KeywordConst,const_);
    WORD(S2_T_KeywordContinue,continue_);
    WORD(S2_T_KeywordDefine,define);
    WORD(S2_T_KeywordDefined,defined_);
    WORD(S2_T_KeywordDelete,delete_);
    WORD(S2_T_KeywordDo,doWhile);
    WORD(S2_T_KeywordEcho,echo);
    WORD(S2_T_KeywordEnum,enum_);
    WORD(S2_T_KeywordEval,eval);
    WORD(S2_T_KeywordException,exception_);
    WORD(S2_T_KeywordExit,exit_);
    WORD(S2_T_KeywordFalse,false_);
    WORD(S2_T_KeywordFatal,fatal_);
    WORD(S2_T_KeywordFor,for_);
    WORD(S2_T_KeywordForEach,foreach_);
    WORD(S2_T_KeywordFunction,function_);
    WORD(S2_T_KeywordIf,if_);
    WORD(S2_T_KeywordImport,import);
    WORD(S2_T_KeywordInclude,include);
    WORD(S2_T_OpInherits,inherits);
    WORD(S2_T_KeywordInterface,interface_);
    WORD(S2_T_KeywordIs,is_);
    WORD(S2_T_KeywordIsA,isa_);
    WORD(S2_T_KeywordNameof,nameof);
    WORD(S2_T_KeywordNew,new_);
    WORD(S2_T_KeywordNull,null_);
    WORD(S2_T_KeywordPragma,pragma);
    WORD(S2_T_KeywordPrivate,private_);
    WORD(S2_T_KeywordProc,proc_);
    WORD(S2_T_KeywordProtected,protected_);
    WORD(S2_T_KeywordPublic,public_);
    WORD(S2_T_KeywordRefcount,refcount_);
    WORD(S2_T_KeywordReturn,return_);
    WORD(S2_T_KeywordS2Out,s2out);
    WORD(S2_T_KeywordScope,scope);
    WORD(S2_T_KeywordThrow,throw_);
    WORD(S2_T_KeywordTrue,true_);
    WORD(S2_T_KeywordTry,try_);
    WORD(S2_T_KeywordTypeinfo,typeInfo);
    WORD(S2_T_KeywordTypename,typeName);
    WORD(S2_T_KeywordUndefined,undef_);
    WORD(S2_T_KeywordUnset,unset);
    WORD(S2_T_KeywordUsing,using);
    WORD(S2_T_KeywordVar,var);
    WORD(S2_T_KeywordWhile,while_);

#undef WORD
    default:
      return 0;
  }
}

#define s2__keyword_perfect_hash( PTOKEN ) \
  s2_hash_keyword(s2_ptoken_begin(PTOKEN), s2_ptoken_len(PTOKEN))
uint32_t s2_hash_keyword( char const * input, cwal_size_t len ){
  cwal_size_t i = 0;
  uint64_t h = 0;
  char const * pos = input;
  for( ; i < len; ++i, ++pos ){
    /*All of these variations have worked out so far...
      h = (h << 1) + 100*i + i*pt->begin[i] - i*35;
      h = (h << 1) + 100*i + i*pt->begin[i] - i*35;
      h = (h << 1) + 45*i + pt->begin[i] - 35;

      Note that the magic 35==ASCII '$'-1, the lowest-numbered
      character allowed in an s2 keyword.

      ************************************************************
      WARNING: this algo MUST be kept in sync with the one in
      s2_eval-keyword-hasher.s2, as that script generates the C code
      for our keyword/typeinfo/pragma bits.
      ************************************************************
    */
    if(*pos > 'z' || *pos < '$') return 0;
    h = (h << 1) + (i+1) * (*pos - 35/*==>ASCII '$'-1*/);
    while(h > (uint64_t)0x7fffffff)
      h = h>>1
        /* With a uint64_t hash, trim hash to within 32 bits because
           we need to be able to use these values in switch/case, and
           64 bits are not portable for that. We *have* to use 64-bit
           integers for the calculation because they're also
           calculated in script-space, where we use (by and large)
           64-bit builds.
        */;
  }
  return (uint32_t)h;
}


/**
   If pt's [begin,end) range corresponds to a keyword, its entry from
   S2_KWDS is returned, else NULL is returned.

   This is an O(1) search, requiring, at most, generation of 1 hash
   code and (on a hash match) 1 string comparison.
*/
static s2_keyword const * s2_ptoken_keyword( s2_ptoken const * pt ){
  const cwal_size_t tlen = s2_ptoken_len(pt);
  if(tlen > sizeof("__BREAKPOINT"/*must be the longest keyword!*/)-1) return NULL;
  switch(s2__keyword_perfect_hash(pt)){
#define W(X,M) return tlen==(cwal_size_t)sizeof(X)-1 &&     \
      0==cwal_compare_cstr(s2_ptoken_begin(pt), tlen, X, sizeof(X)-1)   \
      ? &S2_KWDS.M : NULL
    /* Generated by s2_eval-keyword-hasher.s2 (or equivalent): */

    case 0x0609ce: W("__BREAKPOINT",_breakpoint);
    case 0x0061bc: W("__COLUMN",_col);
    case 0x00170e: W("__FILE",_file);
    case 0x00c013: W("__FILEDIR",_filedir);
    case 0x000b0c: W("__FLC",_flc);
    case 0x0017b2: W("__LINE",_line);
    case 0x001f9a: W("affirm",affirm);
    case 0x00225c: W("assert",assert_);
    case 0x000f50: W("break",break_);
    case 0x000f05: W("catch",catch_);
    case 0x000f88: W("class",class_);
    case 0x001059: W("const",const_);
    case 0x008ee4: W("continue",continue_);
    case 0x001f82: W("define",define);
    case 0x0040cb: W("defined",defined_);
    case 0x00200e: W("delete",delete_);
    case 0x00011a: W("do",doWhile);
    case 0x0006de: W("echo",echo);
    case 0x00077c: W("enum",enum_);
    case 0x000740: W("eval",eval);
    case 0x011e4b: W("exception",exception_);
    case 0x0007a0: W("exit",exit_);
    case 0x000f46: W("false",false_);
    case 0x000f39: W("fatal",fatal_);
    case 0x000329: W("for",for_);
    case 0x00448b: W("foreach",foreach_);
    case 0x009058: W("function",function_);
    case 0x000112: W("if",if_);
    case 0x0022f4: W("import",import);
    case 0x0044a2: W("include",include);
    case 0x008cb6: W("inherits",inherits);
    case 0x01211a: W("interface",interface_);
    case 0x00012c: W("is",is_);
    case 0x000312: W("isa",isa_);
    case 0x0020ba: W("nameof",nameof);
    case 0x000330: W("new",new_);
    case 0x0007c2: W("null",null_);
    case 0x0021e8: W("pragma",pragma);
    case 0x0048f2: W("private",private_);
    case 0x0007a8: W("proc",proc_);
    case 0x012d65: W("protected",protected_);
    case 0x002294: W("public",public_);
    case 0x008bd2: W("refcount",refcount_);
    case 0x0023b0: W("return",return_);
    case 0x000da5: W("s2out",s2out);
    case 0x001042: W("scope",scope);
    case 0x00233c: W("static",static_);
    case 0x001118: W("throw",throw_);
    case 0x0007f4: W("true",true_);
    case 0x000382: W("try",try_);
    case 0x0098e2: W("typeinfo",typeInfo);
    case 0x009884: W("typename",typeName);
    case 0x011f6d: W("undefined",undef_);
    case 0x001135: W("unset",unset);
    case 0x001114: W("using",using);
    case 0x000331: W("var",var);
    case 0x00106a: W("while",while_);

    default: break;
#undef W
  }
  return NULL;
}


/**
   Comparison for bsearch() which compares keywords
   by name.
*/
static int cmp_ukwd_kw(void const * lhs, void const * rhs){
  s2_keyword const * l = (s2_keyword const *)lhs;
  s2_keyword const * r = (s2_keyword const *)rhs;
  return cwal_compare_cstr(l->word, l->wordLen,
                           r->word, r->wordLen);
}

/**
   A variant of s2_ptoken_keyword() which first calls that function,
   and if it returns NULL then this function looks for user-defined
   keywords, returning one if found, else returning NULL.
*/
static s2_keyword const *
s2_ptoken_keyword2( s2_engine * se, s2_ptoken const * pt ){
  s2_keyword const * rc = s2_ptoken_keyword(pt);
  if(!rc){
    s2_keyword dummy;
    cwal_size_t tLen = 0;
    s2_ukwd * const uk = s2__ukwd(se);
    if(!uk || !uk->count) return 0;
    dummy.word = s2_ptoken_cstr(pt, &tLen);
    if(tLen>(unsigned short)-1/*overflow*/) return 0;
    dummy.wordLen = (unsigned short)tLen;
    ++se->metrics.ukwdLookups;
    if(1==uk->count){
      /* Fast-track it! */
      rc = 0==cmp_ukwd_kw(&uk->list[0], &dummy)
        ? &uk->list[0]
        : 0;
    }else{
      rc = (s2_keyword const*)bsearch(&dummy, uk->list, uk->count,
                                      sizeof(s2_keyword), cmp_ukwd_kw);
    }
    if(rc) ++se->metrics.ukwdHits;
  }
  return rc;
}



#if 0
/**
   If ttype matches a keyword's token type, ttype is returned, else 0
   is returned. Note that this returns non-0 for S2_T_OpInherits: the
   "inherits" keyword is partially a keyword, partially an operator.
 */
int s2_ttype_is_keyword( int ttype );
int s2_ttype_is_keyword( int ttype ){
  return s2_ttype_keyword(ttype) ? ttype : 0;
}
#endif

/**
   Internal-only flags for s2_eval_expr() and friends.
*/
enum s2_eval_flags3_t {
/*
   Maintenance reminder: flags 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' and
   typeinfo(name ...).
*/
S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED = 1 << S2_EVAL_flag_bits,
/**
   Tells s2_eval_expr() not to skip the first EOL token.
*/
S2_EVAL_NO_SKIP_FIRST_EOL = 2 << S2_EVAL_flag_bits,
/**
   Unused/untested: tells s2_eval_expr() to sweep up before
   evaluation.
*/
S2_EVAL_PRE_SWEEP = 4 << S2_EVAL_flag_bits,
/**
   Tells s2_eval_expr() that an empty parens group
   is legal. Only used to support allow return(),
   so it's a candidate for removal.
*/
S2_EVAL_EMPTY_GROUP_OK = 8 << S2_EVAL_flag_bits,
/**
   Experimental, doesn't work/do anything useful.
*/
S2_EVAL_KEEP_STACK = 0x10 << S2_EVAL_flag_bits,
/**
   To assist in the 'new' keyword. Tells s2_eval_expr_impl() to stop
   at a call-like operation, as new() handles the arguments itself.
*/
S2_EVAL_STOP_AT_CALL = 0x20 << S2_EVAL_flag_bits,
/**
   Experimental: tells s2_eval_expr_impl() to not clear
   s2_dotop_state() before returning (which it normally does unless a
   fromLhsOp argument is passed to it). Used by (unset x.y) so that it
   can get ahold of the 'this' part of the expression.
*/
S2_EVAL_RETAIN_DOTOP_STATE = 0x40 << S2_EVAL_flag_bits,
/**
   Tells eval that a trailing semicolon is not allowed. This is
   primarily to allow [array] and {object} literals to catch
   semicolons after the value parts of their inner expressions.

   e.g. without this then:

   [1;,2] evals to [1,2] because the semicolon legally terminates the
   1. Similarly, {a:1;, b:2;} is legal without this flag.

   Whether or not this is really a fix is arguable, as a semicolon
   legally ends any expression. It was added more for pedandicness'
   sake than to fix a problem. (It was only noticed by accident one
   day that a trailing semicolon in an array literal was not being
   caught as a syntax error.)
*/
S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED = 0x80 << 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);

/**
   An eval-level helper to check for interruption, which should trump
   any result of just-performed evaluation.

   If rc is not 0...

   - if rv then *rv is set to 0.

   - potential todo: if *rv then *rv is passed to cwal_refunref(),
   which may or may not clean it up immediately.

   If rc is 0, it rescopes *rv (if both rv and *rv are not NULL) to sc
   (if not NULL).

   Always returns rc except when se->flags.interrupted trumps it,
   in which case that non-0 code is returned.
*/
static int s2_rv_maybe_accept( s2_engine * se, cwal_scope * sc,
                                int rc, cwal_value ** rv ){
  rc = s2_check_interrupted(se, rc);
  switch(rc){
    case 0:
      assert(se->e->current);
      if(rv && *rv && sc) cwal_value_rescope(sc, *rv);
      break;
    default:
      break;
  }
  if(rc && rv){
#if 0
    if(*rv){
      /* This can theoretically (if all *rv-setters are well-behaved)
         only happen when s2_check_interrupted() trumps an otherwise
         successful operation. This block is not triggering in test
         code, and would be difficult to trigger for testing, so it's
         currently disabled. */
      assert(!"untested");
      cwal_refunref(*rv);
    }
#endif
    *rv = 0;
  }
  return rc;
}


int s2_err_ammend_flc(s2_engine * se, s2_ptoker const * st) /* Declared in s2.c */;

/**
   Internal helper for ammending stack-machine-level errors with
   file/line/column info. The 3rd argument is the error code of the op
   which just failed.

   Preconditions:

   - A stack-processing op must just have failed.

   - rc must be non-0.

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

   If !pr, se->currentScript is used. It asserts() that at least one
   of those is non-NULL.

   Returns, on success, se->err.code, and some other non-0 value if
   ammending the FLC info led to a more serious error (e.g. out of
   memory or an interrupt). 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_ammend_op_err(s2_engine * se, s2_ptoker const * pr, int rc){
  int const rcInterrupt = s2_check_interrupted(se,0); 
  assert(pr || se->currentScript);
  if(rcInterrupt) return rcInterrupt;
  switch(rc){
    case CWAL_RC_ASSERT:
    case CWAL_RC_EXCEPTION:
    case CWAL_RC_EXIT:
    case CWAL_RC_FATAL:
    case CWAL_RC_INTERRUPTED:
    case CWAL_RC_OOM:
      break;
    case CWAL_SCR_SYNTAX: /* Not QUITE the condition i want, but it
                             keeps existing tests running for the time
                             being. */
      assert(se->err.code);
      rc = s2_err_ammend_flc(se, pr ? pr : se->currentScript);
      break;
    default:
      /* Translate it to an exception... */
      assert(se->err.code);
      /* MARKER(("opErrPos=%p\n", (void const *)se->opErrPos)); */
      rc = s2_throw_err_ptoker(se, pr ? pr : se->currentScript);
      break;
  }
  se->opErrPos = 0
    /* Avoid a stale pointer in some rare cases (namely s2sh REPL loop).
       Stale pointer led to an assert() in s2_t10n's line-counting code
       via this s2sh session:

       var e = enum e{a}
       e#'a'                    // (1) op error here
       e = enum e{#a}
       catch e = enum e{#a}
       catch { e = enum e{#a} } // syntax error here referenced (1).
     */;
  return rc;

}


void s2_dotop_state( s2_engine * se, cwal_value * self,
                     cwal_value * lhs, cwal_value * key ){
    
#if 0
  /* Leave this around in case we have to go ref hunting
     sometime. */
    se->dotOp.self = self;
    se->dotOp.lhs = lhs;
    se->dotOp.key = key;
#else
    /* Reminder: the validity checks here point to, essentially,
       misuse in s2, where we've not cleaned up this state before it
       goes stale.

       A potential future problem is vacuuming-up of these pointers,
       which case we can't solve without a per-s2_scope
       array/container to hold these and make them vacuum-proof.
    */
    cwal_value * oldSelf = se->dotOp.self;
    cwal_value * oldLhs = se->dotOp.lhs;
    cwal_value * oldKey = se->dotOp.key;
    /**
       Assert that all of them appear to still be valid references
       (because it's easy to mess that up). These assertions are not
       guaranteed to trigger in all error cases, but they catch the
       most common one that we've prematurely unref'd a value and he
       have a pointer to its cwal-side recycling bin.
    */
    /* MARKER(("Setting se->dotOpXXX...\n")); */
    if(oldSelf){
        assert( cwal_value_scope(oldSelf)
                || cwal_value_is_builtin(oldSelf) );
    }
    if(oldLhs){
        assert( cwal_value_scope(oldLhs)
                || cwal_value_is_builtin(oldLhs));
    }
    if(oldKey){
        assert( cwal_value_scope(oldKey)
                || cwal_value_is_builtin(oldKey) );
    }
    /**
       Because any of self/lhs/key can refer to or contain/own any
       other, as well as be the same instance of oldSelf/oldLhs/oldKey
       (in any combination!), we have to ref them all before we unref
       any of them.
    */
    if(self) cwal_value_ref(self);
    if(lhs) cwal_value_ref(lhs);
    if(key) cwal_value_ref(key);

    se->dotOp.self = self;
    se->dotOp.lhs = lhs;
    se->dotOp.key = key;

    if(oldSelf) cwal_value_unref(oldSelf);
    if(oldLhs) cwal_value_unref(oldLhs);
    if(oldKey) cwal_value_unref(oldKey);
    /* MARKER(("Done setting se->dotOpXXX\n")); */
#endif
}

/**
   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
   (i.e. in s2's operator stack). Returns 0 on success (which includes
   it doing nothing of note).

   Specifically: if se->st has a pending operator (and operand(s))
   with a higher priority than op, or the same priority but the
   operator is left-associative, then that pending operator is
   processed. This repeats, if needed, to resolve all pending LHS ops
   until se->st is out of ops or we hit an op with a lower precedence
   than the given op (or equal precedence but not left-associative).
*/
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 = s2_check_interrupted(se, 0);
  assert(op);
  if(rc) return rc;
  else 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.traceTokenStack){
      MARKER(("Processing ge-precedent op '%s' to "
              "the left of '%s'.\n",
              topOp->sym, op->sym));
      s2_dump_val(se->dotOp.lhs,"se->dotOp.lhs");
      s2_dump_val(se->dotOp.key,"se->dotOp.key");
    }
    rc = s2_process_top(se);
    if(rc){
      rc = s2_ammend_op_err(se, 0, rc);
      assert(rc);
      break;
    }
    assert(se->st.vals.size>0);
    topOpTok = s2_engine_peek_op(se);
    topOp = topOpTok ? s2_stoken_op(topOpTok) : 0;
  }
  return rc;
}

/**
   Evaluates the contents of pr->token, treating it like a sub-parser.

   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_token( s2_engine * se, s2_ptoker * pr,
                                  char asExpr,
                                  int evalFlags,
                                  cwal_value **rv ){
  int rc = s2_check_interrupted(se, 0);
  s2_ptoker_errtoken_set(pr, 0);
  /* assert(!name || nameLen>0); */
  if(rc) return rc;
  else if(!s2_ptoken_has_content(&pr->token)){
    /* we know it's empty, so don't bother.*/
    if(rv) *rv = 0
             /* not the undefined value, so that the caller can
                differentiate empty expressions/bodies (which might
                not be legal in a given context). */;
    return 0;
  }else{
    s2_ptoker sub = s2_ptoker_empty;
    int rc = s2_ptoker_sub_from_toker(pr, &sub);
    assert(sub.parent == pr);
    /* 20200902: breaks stuff: assert(pr->e); */
    assert(sub.e == pr->e);
    if(rc){
      s2_ptoker_errtoken_set(pr, &pr->token) /* TODO: remove this (and test it). */;
      goto end;
    }
    else if(asExpr){
      rc = s2_eval_expr_impl( se, &sub, 0, evalFlags, rv );
    }
    else{
      s2_subexpr_savestate save = s2_subexpr_savestate_empty_m;
      s2_engine_subexpr_save(se, &save)
                      /* Saves/resets ternary level */;
      rc = s2_eval_ptoker( se, &sub,
                           evalFlags ? evalFlags : S2_EVALP_F_PROPAGATE_RETURN,
                           rv );
      s2_engine_subexpr_restore(se, &save);
    }
    if(rc) s2_ptoker_errtoken_set(pr, s2_ptoker_errtoken_get(&sub));
    pr->capture = sub.capture;
    end:
    s2_ptoker_finalize(&sub);
    return rc;
  }
}

/**
   Intended to be called at the end of a subexpression if:

   a) that subexpression generated an error which _might_ be one of
   CWAL_RC_BREAK/RETURN/CONTINUE.

   b) wants to report that as an error, using the tokenizer's current
   location information.

   Alternately, they can let it propagate and hope that it's handled
   higher up.

   The 2nd parameter is the tokenizer in which (or through which) the
   error was triggered.

   The 3rd parameter is the result code of the subexpression which
   failed.  If it is one of CWAL_RC_BREAK/RETURN/CONTINUE then this
   function clears any propagating value (if needed) and triggers a
   tokenizer error. All other values of rc have no side-effects.

   The 4th parameter is a string snippet used for the error report.
   It should be a brief description of the context, suitable for
   appending to a string in the form "Unhandled 'break' in ...".

   The "new" rc is returned, which will be either rc itself or, on
   allocation error while setting the error state, CWAL_RC_OOM.
*/
static int s2_check_brc( s2_engine * se,
                         s2_ptoker * pr,
                         int rc,
                         char const * contextDescr){
  s2_keyword const * kword = 0;
  switch(rc){
    case CWAL_RC_RETURN:
      kword = s2_ttype_keyword(S2_T_KeywordReturn);
      assert(kword);
      s2_propagating_set(se, 0);
      /* s2_engine_err_reset(se); */
      break;
    case CWAL_RC_BREAK:
      kword = s2_ttype_keyword(S2_T_KeywordBreak);
      assert(kword);
      s2_propagating_set(se, 0);
      /* s2_engine_err_reset(se); */
      break;
    case CWAL_RC_CONTINUE:
      kword = s2_ttype_keyword(S2_T_KeywordContinue);
      assert(kword);
      /* s2_engine_err_reset(se); */
      break;
    default: break;
  }
  if(kword){
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       /* if we pass on the same rc, error reporting
                          won't DTRT. */
                       "Unhandled '%s' in %s.",
                       kword->word, contextDescr);
  }
  return rc;
}

/**
   Like s2_eval_current_token(se, pr, 0, 0, rv), but pushes a scope and
   sets a local "this" variable to the self value. It is intended to
   be called only for the context of the code block in:

   new X() {this block}

   pr->token MUST of type S2_T_SquigglyBlock: this function assert()s
   it (only to simplify skip-mode handling, though we could extend it
   to handle other block-level constructs as well).

   This function treats script-propagated return/break/continue
   conditions as an error.

   ACHTUNG: self must not be a temporary value: it MUST have a ref
   or this will kill it. Exception: in skip-mode, self is not
   evaluated.
*/
static int s2_eval_current_token_with_this( s2_engine * se, s2_ptoker * pr,
                                            cwal_value * self,
                                            cwal_value **rv ){
  assert( S2_T_SquigglyBlock==pr->token.ttype );
  if(se->skipLevel){
    if(rv) *rv = cwal_value_undefined();
    return 0;
  }else{
    cwal_scope sc = cwal_scope_empty;
    cwal_value * xrv = 0;
    int rc = cwal_scope_push2(se->e, &sc);
    if(rc) return rc;
    rc = s2_var_decl_v(se, se->cache.keyThis, self, 0);
    if(!rc){
      rc = s2_eval_current_token(se, pr, 0,
                                 0, rv ? &xrv : NULL);
    }
    cwal_scope_pop2(se->e, rc ? 0 : xrv);
    if(!rc && rv) *rv = xrv
                    /* will be NULL on an empty expr except in
                       skip-mode */;
    if(rc){
      rc = s2_check_brc( se, pr, rc,
                         "'new' post-ctor script block" );
    }
    return 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. Empty expressions and a stray trailing comma are not
   allowed. Evaluates each token and appends it to dest. Returns 0 on
   success. Some errors are thrown as exceptions, but syntax errors
   are not and propagated errors might not be exceptions.

   If allowAtExpansion is true then it allows a minor syntax expansion:

   Any _array value_ (including deriving from Array) in the list may
   start with the @ symbol. Its slot in the array is replaced with the
   entries from the array value. e.g.

   print(@[1,2,@[3]]) ==> print(1,2,3)

   If a non-array comes after a @, an exception is triggered. An empty
   array causes no slots to be filled.
*/
static int s2_eval_to_array( s2_engine * se, s2_ptoker * pr, cwal_array * dest,
                             int allowAtExpansion){
  int rc = s2_check_interrupted(se, 0);
  s2_op const * opComma = s2_ttype_op(S2_T_Comma);
  s2_ptoken prev = s2_ptoken_empty;
  char const * errMsg = 0;
  assert(!se->skipLevel);
  /* s2_dotop_state(se, 0, 0, 0); */
  while( !rc ){
    s2_ptoken next = s2_ptoken_empty;
    cwal_value * v = 0;
    char hadAtPrefix = 0;
    /* if((int)'@' == pr->token; */
    if(allowAtExpansion){
#if 0
      rc = s2_ptoker_next_token_skip_junk(pr)
        /* ^^^ cheaper than s2_next_token() */;
      /**
         20190820... it turns out that the cheap option breaks when we do:

         func(a, b, c,
         @blah)

         with a newline. Hmm. Interestingly, i only found the solution
         so quickly because of the comment above that
         s2_ptoker_next_token_skip_junk() is "cheaper than
         s2_next_token()". Had that comment not been there, i'd have
         been searching for years.
       */
#else
      rc = s2_next_token( se, pr, 0, 0 );
#endif
      if(rc) break;
      else if(S2_T_At==pr->token.ttype){
        hadAtPrefix = 1;
      }else{
        s2_ptoken const tmp = pr->token;
        s2_ptoker_putback(pr) /* rewind to just before non-junk
                                 token */;
        s2_ptoker_next_token_set(pr, &tmp)
          /* this one has a relatively large effect (~20% of the 10k
             nextToken hits in the current tests) */;
      }
    }
    if( (rc = s2_eval_expr_impl( se, pr, opComma,
                                 S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED,
                                 &v)) ){
      break;
    }
    else if(!v){
      if(s2_ptoker_is_eof(pr)){
        if(S2_T_Comma==prev.ttype){
          s2_ptoker_errtoken_set(pr, &prev);
          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{
        rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Unexpected empty expression result.");
        break;
      }
    }else{ /* v === non-NULL result from eval */
      if(hadAtPrefix){
        /* Expand arrays/tuples prefixed with '@' into the destination array... */
        cwal_size_t i, n;
        cwal_array * av = 0;
        cwal_tuple * tp = 0;
        cwal_value_ref(v);
        av = cwal_value_array_part(se->e, v);
        if(!av) tp = cwal_value_get_tuple(v);
        if(!av && !tp){
          cwal_value_unref(v);
          return s2_throw_ptoker(se, pr, CWAL_RC_TYPE,
                                 "Illegal (non-array/tuple) value following '@'.");
        }
        n = av ? cwal_array_length_get(av) : cwal_tuple_length(tp);
        for( i = 0; !rc && i < n; ++i ){
          rc = av
            ? cwal_array_append(dest, cwal_array_get(av, i))
            : cwal_array_append(dest, cwal_tuple_get(tp, (uint16_t)i));
        }
        cwal_value_unref(v);
      }else{
        cwal_value_ref(v);
        rc = cwal_array_append(dest, v);
        cwal_value_unref(v);
      }
      if(rc) 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){
          /* s2_ptoker_errtoken_set(pr, &next); */
          errMsg = "Unexpected, comma at end, of list,";
          goto bad_comma;
        }
        else{
          /*???*/ /*pr->nextToken = next;*/
          break;
        }
      }
      else if(S2_T_Comma != next.ttype){
        errMsg = "Expecting ',' after list element.";
        goto bad_comma;
      }else{
        prev = next;
        s2_ptoker_token_set(pr, &next) /* consume comma */;
      }
    }
    rc = s2_check_interrupted(se, rc);
  }
  return rc;
  bad_comma:
  assert(errMsg);
  return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, "%s", errMsg);
}

/**
   Parser for [array, literals] and [#tuple 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 or Tuple value.

   TODO: the tuple impl is rather inefficient because it first creates
   an array. We've got to do that (or traverse the tokens twice) so
   that we have the size of the tuple (required for construction). We
   could think about adding a tuple factory to cwal which transfers
   list memory from an array to a tuple.
*/
static int s2_eval_array_literal( s2_engine * se, s2_ptoker * pr, cwal_value ** rv ){
  s2_ptoker sub = s2_ptoker_empty;
  int rc = s2_check_interrupted(se, 0);
  assert(S2_T_BraceGroup==pr->token.ttype);
  if(rc) return rc;
  else if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    cwal_array * ar = 0;
    cwal_value * av = 0;
    cwal_scope scope = cwal_scope_empty;
    int gotTuple = 0;
    if(!s2_ptoken_has_content(&pr->token)){
      return (*rv = cwal_new_array_value(se->e)) ? 0 : CWAL_RC_OOM;
    }
    else if((rc = cwal_scope_push2(se->e, &scope))) return rc;
    rc = s2_ptoker_init_v2( se->e, &sub, s2_ptoken_adjbegin(&pr->token),
                            (int)s2_ptoken_len2(&pr->token), 0 );
    if(rc) goto end;
    sub.parent = pr;
    gotTuple = s2_ptoker_next_is_ttype(se, &sub, S2_NEXT_NO_POSTPROCESS, S2_T_OpHash, 1);
    if((rc = s2_check_interrupted(se, 0))) goto end;
    else if(!(ar = cwal_new_array(se->e))){
      rc = CWAL_RC_OOM;
      goto end;
    }
    av = cwal_array_value(ar);
    cwal_value_ref(av);
    rc = s2_eval_to_array( se, &sub, ar, 1 );
    if(!rc && gotTuple){
      cwal_size_t const nA = cwal_array_length_get(ar);
      uint16_t const nT = (uint16_t)nA;
      cwal_tuple * tp = 0;
      if((cwal_size_t)nT != nA){
        s2_ptoker_errtoken_set(pr, &sub.token);
        rc = s2_throw_ptoker(se, pr, CWAL_RC_RANGE, "Too many entries for a tuple.");
      }else{
        tp = cwal_new_tuple(se->e, nT);
        if(!tp){
          rc = CWAL_RC_OOM;
        }else{
          uint16_t i = 0;
          for( ; i < nT && !rc; ++i ){
            rc = cwal_tuple_set(tp, i, cwal_array_get(ar, i));
            assert(!rc && "No known error conditions here. All memory has been allocated.");
          }
          ar = 0;
          cwal_value_unref(av);
          av = cwal_tuple_value(tp);
          cwal_value_ref(av);
        }
      }
    }
    rc = s2_check_interrupted(se, rc);
    if(rc) rc = s2_check_brc(se, &sub, rc,
                             gotTuple
                             ? "tuple literal"
                             : "array literal");
    if(!rc){
      assert(av);
      cwal_value_unhand(av);
      *rv = av;
    }else if(av){
      cwal_value_unref(av);
    }
    end:
    s2_ptoker_finalize(&sub);
    cwal_scope_pop2(se->e, rc ? 0 : *rv);
    return rc;    
  }
}

/** Internal helper for s2_eval_to_object() */
static int s2_eval_object_prop_set( s2_engine * se, s2_ptoker * pr,
                                    cwal_value * dest,
                                    cwal_value * key, cwal_value * val,
                                    cwal_flags16_t flags ){
  int rc;
  cwal_function * f = 0;
  if(0==cwal_value_compare(key, se->cache.keyCtorNew) &&
     (f = cwal_value_function_part(se->e, val))){
    /* If the key is '__new' and v is call()able, set it as
       hidden/const, simply for consistency across classes (as
       opposed to it being hidden/const in C-created cases but not
       script-created). We should arguably do this in
       s2_set_with_flags_v(), but that code is complex enough as
       it is :/. Maybe someday.

       2020-02-20: with the addition of {@anObject} property copying
       syntax, the flagging of constructors as hidden *might* be
       considered a misbehaviour, since {@x} will not copy a ctor
       property to the target object. Feature or bug?
    */
    rc = s2_ctor_method_set(se, dest, f);
  }else{
    /* use s2_set_v() for the 'prototype' and object-mode-hash
       handling, as well as se->err error reporting. */
    rc = s2_set_with_flags_v(se, dest, key, val, flags);
  }
  return s2_handle_set_result(se, pr, rc);
}

/** State for use with cwal_kvp_visitor_props_copy_s2() */
typedef struct {
  s2_engine * se;
  s2_ptoker * ptoker;
  cwal_value * store;
} S2CopyPropsState;

/** Internal helper for s2_eval_to_object() */
static int cwal_kvp_visitor_props_copy_s2( cwal_kvp const * kvp,
                                           void * state ){
  S2CopyPropsState * const sps = (S2CopyPropsState*)state;
  return s2_eval_object_prop_set(sps->se, sps->ptoker, sps->store,
                                 cwal_kvp_key(kvp), cwal_kvp_value(kvp),
                                 cwal_kvp_flags(kvp));
}


/**
   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.

   The resulting container is written to *rv on success. On error
   *rv is not modified.

   If propCount is not 0 then it is set to the number of properties
   parsed on success.

   If inputHasHashMarker is not 0 then:

   - *inputHasHashMarker is set to 0 before starting.

   - the syntax is extended a little bit: it allows, at the start
   (only) one # symbol, *inputHasHashMarker to be incremented by
   one. If one is seen then this functon creates a cwal_hash,
   otherwise it creates a cwal_object. If a # is encountered while
   inputHasHashMarker is NULL then it is a syntax error.

   Extensions to conventional JS-style syntax:

   1) { [expression-as-a-key]: value }, where the expression's value
   becomes the key.

   2) { @otherContainer } is essentially the same as JS's spread
   syntax, importing the iterable (non-hidden) properties from
   @otherContainer into the literal. This syntax is not yet permitted
   for hashes because of potential semantic ambiguities in handling of
   hash vs. object properties.

*/
static int s2_eval_to_object( s2_engine * se, s2_ptoker * pr, cwal_value ** rv,
                              cwal_size_t * propCount,
                              int *inputHasHashMarker){
  s2_ptoken prev = s2_ptoken_empty;
  s2_op const * opComma = s2_ttype_op(S2_T_Comma);
  cwal_size_t count = 0;
  char seenHash = 0;
  cwal_value * store = 0;
  int rc = s2_check_interrupted(se, 0);
  cwal_value * v = 0;
  cwal_value * vKey = 0;
  assert(!se->skipLevel);
  assert(opComma);
  if(inputHasHashMarker) *inputHasHashMarker = 0;
  while( !rc && !s2_ptoker_is_eof(pr)){
    s2_ptoken ident = s2_ptoken_empty;
    s2_ptoken startPos = pr->token;
    cwal_flags16_t fSetFlags = 0 /* flags for cwal_prop_set_with_flags_v()
                                    and friends. */;
    char gotAtSign = 0 /* true if we get a @-style key */;
    assert(!v);
    assert(!vKey);
    /*pr->flags |= S2_T10N_F_IDENTIFIER_DASHES;
      ^^^ potentially interesting. */
    rc = s2_next_token(se, pr, 0, 0);
    /*pr->flags &= ~S2_T10N_F_IDENTIFIER_DASHES;*/
    if( rc ) break;
    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){
        s2_ptoker_errtoken_set(pr, &prev);
        goto bad_comma;
      }
      else break;
    }else if(inputHasHashMarker && !seenHash
             && !count && (int)'#'==pr->token.ttype){
      ++seenHash;
      ++*inputHasHashMarker;
      continue;
    }else if((int)'#'==pr->token.ttype){
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Extra '#' in input.");
      break;
    }
#if 0
    MARKER(("OBJ KEY: %s %.*s\n", s2_ttype_cstr(pr->token.ttype),
            (int)s2_ptoken_len(&pr->token),
            s2_ptoken_begin(&pr->token)));
#endif
    else if(S2_T_BraceGroup==pr->token.ttype){
      /* 20191117: check for {[key expr]: value}
         a.k.a. Expression-as-a-Key (EaaK, pronounced like "eek").
      */
      s2_ptoker sub = s2_ptoker_empty;
      rc = s2_ptoker_sub_from_toker(pr, &sub);
      if(rc){
        s2_ptoker_finalize(&sub);
        break;
      }
      rc = s2_eval_ptoker( se, &sub, 0, &vKey );
      s2_ptoker_finalize(&sub);
      if(rc){
        assert(!vKey);
        break;
      }
      else if(!vKey){
        rc = s2_err_ptoker(se, pr, CWAL_RC_MISUSE,
                           "Unexpected NULL eval result while processing "
                           "[...] object literal key.");
        break;
      }
      cwal_value_ref(vKey);
      /*s2_dump_val(vKey, "vKey");*/
    }
    else if(S2_T_At == pr->token.ttype){
      gotAtSign = 1;
    }
    else if(!s2_ttype_is_object_keyable(pr->token.ttype)){
      s2_ptoker_errtoken_set(pr, &startPos);
      rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Expecting identifier or 'simple' "
                           "value as object key.");
      break;
    }
    ident = pr->token;
    /*MARKER(("ident =%.*s\n", (int)s2_ptoken_len(&pr->token), s2_ptoken_begin(&pr->token)));*/
    if(!gotAtSign){
      rc = s2_next_token(se, pr, 0, 0);
      if(rc) break;
      else if(S2_T_OpColonEqual == pr->token.ttype){
        fSetFlags = CWAL_VAR_F_CONST;
      }else if(S2_T_Colon != pr->token.ttype){
        /* check for {standaloneIdentifier} ... */
        if(vKey){
          rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             "{[object key]} syntax requires a "
                             "following colon.");
          break;
        }
        else if(S2_T_Identifier==ident.ttype
                && (S2_T_Comma == pr->token.ttype
                    || s2_ptoker_is_eof(pr/* virtual EOF at end of object: {a:1,...,b} */))
                ){
          /* JS-like {a,b} ==> {a: a, b: b} */
          s2_ptoker_putback(pr) /* comma/eof will be handled downstream */;
          v = s2_var_get(se, -1, s2_ptoken_begin(&ident), s2_ptoken_len(&ident));
          if(!v){
            rc = s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND,
                                 "Could not resolve identifier '%.*s'.",
                                 (int)s2_ptoken_len(&ident),
                                 s2_ptoken_begin(&ident));
            break;
          }
        }else{
          rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                               "Expecting ':' after object key.");
          break;
        }
      }
    }
    if(!v && (rc = s2_eval_expr_impl( se, pr, opComma,
                                      S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED,
                                      &v)) ){
      break;
    }
    else if(!v){
      rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "An empty expression is not allowed here.");
      break;
    }else if( gotAtSign ){
      if(!cwal_props_can(v) || s2_value_is_enum(v)){
        /**
          The "problem" with enums is that they can use either object
          or hash storage internally, so we would need to
          differentiate between the two in the @ expansion.  We don't
          currently do that, but maybe someday will.
        */
        rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE,
                             "Value after @ must be a non-enum "
                             "container type.");
        break;
      }
      assert(!vKey);
      vKey = v /* simplifies handling below */;
      cwal_value_ref(vKey);
    }
    cwal_value_ref(v);
    {
      s2_ptoken next = s2_ptoken_empty;
      /*s2_dump_val(v, "object entry value");*/
      if( (rc = s2_next_token( se, pr, 0, &next )) ) break;
      else if(!s2_ttype_is_eof(next.ttype) && S2_T_Comma != next.ttype){
        rc = s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                               "Got token type %s, but expected ',' or "
                               "end-of-object after object entry.",
                               s2_ttype_cstr(next.ttype));
        break;
      }

      if(!store){
        /* Create the property storage: Object or Hash */
        store = seenHash
          ? cwal_new_hash_value(se->e, 7)
          : cwal_new_object_value(se->e);
        if(!store){
          rc = CWAL_RC_OOM;
          break;
        }else{
          cwal_value_make_vacuum_proof(store, 1);
          cwal_value_ref(store);
          if(seenHash){
            s2_hash_dot_like_object(store, 1
                                    /* Needed for remainder of the loop
                                       so that s2_set_v() will DTRT.
                                       Might get overwritten afterwards*/);
          }
        }
      }

      /* And finally... insert the key/value... */
      if( !vKey ){
        assert(!gotAtSign);
        if((rc = s2_ptoken_create_value(se, pr, &ident, &vKey))) break;
        assert(vKey);
        /* s2_dump_val(vKey, "object key"); */
        cwal_value_ref(vKey);
      }
      if( gotAtSign ){
        if(seenHash){
          rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE,
                               "@ property expansion is not currently "
                               "permitted for hash literals.");
          break;
        }else{
          S2CopyPropsState sps;
          assert(vKey == v);
          sps.se = se;
          sps.ptoker = pr;
          sps.store = store;
          rc = cwal_props_visit_kvp( v, cwal_kvp_visitor_props_copy_s2,
                                     &sps );
        }
      }else{
        rc = s2_eval_object_prop_set(se, pr, store, vKey, v, fSetFlags);
      }
      cwal_value_unref(vKey);
      vKey = 0;
      cwal_value_unref(v);
      v = 0;
      if(rc) break;
      ++count;
      prev = next;
      s2_ptoker_token_set(pr, &next) /* consume comma */;
    }
    assert(!rc);
    rc = s2_check_interrupted(se, rc);
  }/* loop over object body */

  if(!rc && seenHash){
    /* Resize the hashtable if it doesn't appear adequately sized... */
    cwal_hash * h = cwal_value_get_hash(store);
    if(!h){
      assert(!count);
      cwal_value_unref(store);
      store = cwal_new_hash_value(se->e, 5);
      if(!store){
        rc = CWAL_RC_OOM;
      }else{
        cwal_value_ref(store);
      }
    }else{
      rc = cwal_hash_grow_if_loaded( h, 0.75 );
    }
  }

  end:
  cwal_value_unref(v);
  cwal_value_unref(vKey);
  if(store){
    cwal_value_make_vacuum_proof(store, 0);
  }
  if(rc){
    cwal_value_unref(store);
    rc = s2_check_brc( se, pr, rc, seenHash
                       ? "hash literal"
                       : "object literal" );
  }else{
    if(propCount) *propCount = count;
    if(!store){
      assert(!count);
      store = seenHash
        ? cwal_new_hash_value(se->e, 5)
        : cwal_new_object_value(se->e);
      if(!store){
        rc = CWAL_RC_OOM;
      }else{
        cwal_value_ref(store);
      }
    }
    if(store){
      assert(!rc);
      cwal_container_client_flags_set(store, seenHash>1
                                      ? S2_VAL_F_DOT_LIKE_OBJECT
                                      : 0);
      cwal_value_unhand(store);
      *rv = store;
    }else{
      assert(rc);
    }
  }
  return rc;
  bad_comma:
  rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                     "Unexpected comma in %s literal.",
                     seenHash ? "hash" : "object");
  goto end;
}

/**
   Evaluator for object literal blocks. Asserts that
   S2_T_SquigglyBlock ==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 ){
  int rc = s2_check_interrupted(se, 0);
  assert( S2_T_SquigglyBlock == pr->token.ttype );
  if(rc) return rc;
  else if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    cwal_value * ov = 0;
    cwal_scope scope = cwal_scope_empty;
    if(!s2_ptoken_has_content(&pr->token)){
      return (*rv = cwal_new_object_value(se->e)) ? 0 : CWAL_RC_OOM;
    }
    else if((rc = cwal_scope_push2(se->e, &scope))) return rc;
    else{
      s2_ptoker sub = s2_ptoker_empty;
      cwal_size_t propCount = 0;
      int doHash = 0;
      rc = s2_ptoker_sub_from_toker(pr, &sub);
      if(!rc){
        rc = s2_eval_to_object( se, &sub, &ov, &propCount, &doHash );
      }
      s2_ptoker_finalize(&sub);
    }
    if(!rc){
      assert(ov);
      *rv = ov;
    }
    cwal_scope_pop2(se->e, rc ? 0 : *rv);
    return rc;
  }
}


/**
   Iternal impl for ternary op: (IF ? THEN : ELSE). Expects the top of
   the value stack to hold the LHS value (the IF part). On success it
   replaces that value with the result of either its THEN or ELSE
   parts. On success, pr->token will be set up so that its next token
   will be the one after the ELSE expression. If rv is not 0 then the
   result is also placed in *rv.
*/
static int s2_eval_ternary( s2_engine * se, s2_ptoker * pr,
                            cwal_value **rv){
  int rc;
  cwal_value * lhs /* the IF part of (IF ? THEN : ELSE) */;
  cwal_value * rhs1 = 0 /* the THEN part */,
    * rhs2 = 0 /* the ELSE part */;
  cwal_value ** rhs = 0 /* bool(lhs) ? &rhs1 : &rhs2 */;
  char buul;
  s2_ptoken pos = pr->token;
  s2_ptoken const origin = pos;
  s2_op const * fromLhsOp = s2_ttype_op(S2_T_Question);
  int const oldTernaryLevel = se->ternaryLevel;
  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 X?Y:Z op");
  }
  buul = cwal_value_get_bool(lhs);
  assert(cwal_value_is_builtin(lhs)
         || cwal_value_refcount(lhs)/*expecting eval holder ref*/);
  cwal_value_ref(lhs);
  rhs = buul ? &rhs1 : &rhs2;
  ++se->ternaryLevel;

  /* Eval the THEN part... */
  rc = s2_eval_expr_impl(se, pr, fromLhsOp/*0?*/,
                         buul ? 0 : S2_EVAL_SKIP,
                         &rhs1);
  /*MARKER(("Got rhs1 rc=%d: [[[%.*s]]\n", rc,
    (int)(s2_ptoken_begin(&pr->capture.end)-s2_ptoken_end(&pr->capture.begin)),
    s2_ptoken_begin(&pr->capture.begin)));*/
  cwal_value_ref(rhs1);
  if(rc) goto end;
  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 ternary expression "
                       "(X ? Y : Z)");
  }
  if(rc) goto end;

  /* Eval the ELSE part... */
  pos = pr->token;
  rc = s2_eval_expr_impl(se, pr, fromLhsOp,
                         buul ? S2_EVAL_SKIP : 0,
                         &rhs2);
  cwal_value_ref(rhs2);
  /*MARKER(("Got rhs2 rc=%d: [[[%.*s]]\n", rc,
    (int)(s2_ptoken_begin(&pr->capture.end)-s2_ptoken_begin(&pr->capture.begin)),
    s2_ptoken_begin(&pr->capture.begin)));*/
  end:
  if(!rc){
    assert(s2_engine_peek_value(se)==lhs);
    s2_engine_pop_token(se, 0) /* ==> lhs (still in the eval holder) */;
    assert(se->ternaryLevel == oldTernaryLevel+1);
  }
  se->ternaryLevel = oldTernaryLevel;
  assert(cwal_value_is_builtin(lhs)
         || cwal_value_refcount(lhs)>1/*expecting eval holder ref and ours*/);
  cwal_value_ref(*rhs);
  cwal_value_unref(lhs);
  cwal_value_unref(rhs1);
  cwal_value_unref(rhs2);
  rc = s2_check_interrupted(se, rc);
  if(!rc && (!rhs1 || !rhs2)){
    static char const * emptyMarker = "<EMPTY>";
    s2_ptoker_errtoken_set(pr, &pos);
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Unexpected empty expression "
                       "in (X ? %s : %s)",
                       rhs1 ? "Y" : emptyMarker,
                       rhs2 ? "Z" : emptyMarker)
      /* Reminder: rhs1/rhs2 pointers are semantically invalid now
         (unref'd above), but their addresses are still usable
         here */;
  }
  if(rc){
    cwal_value_unref(*rhs);
  }else{
    if(rv) *rv = *rhs;
    cwal_value_unhand(*rhs);
    /* s2_dump_val(*rhs,"Ternary result"); */
    s2_engine_push_val(se, *rhs)->srcPos = s2_ptoken_begin(&origin)
      /* Trivia: that push cannot fail as long as token recycling is
         enabled, because the pop (above) just freed one up for us.

         The main eval loop will stuff that *rhs into the eval holder.
      */;
  }
  return rc;
}

#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 );
  if(!(rc = s2_ttype_is_assignment(tgt.ttype))){
    rc = s2_ttype_is_assignment_combo(tgt.ttype);
  }
  if(!rc) pr->nextToken = tgt;
  return rc;
}
#endif

static int s2_next_wants_identifier( s2_engine * se, s2_ptoker * pr ){
  s2_ptoken tgt = s2_ptoken_empty;
  int rc;
  rc = s2_next_token( se, pr, S2_NEXT_NO_POSTPROCESS, &tgt );
  if(rc) return 0;
  s2_ptoker_next_token_set(pr, &tgt)
    /* This pr->nextToken adjustment appears to have the
       single-biggest effect in triggering the s2_ptoker_next_token()
       nextToken optimization: ~75% of the 10k hits in the current
       tests. */;
  /* 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_dotop_state( se, lhs, lhs, rhs ) when run, so that assignments 
   can work and 'this' can get bound if the following operation is
   a call() on the lhs[rhs] result.
   
   i.e. the token type must be, or behave just like, the dot operator,
   S2_T_OpDot.

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

/**
  If op->id is of a type which should treat its RHS identifier as a 
  property key, rather than immediately resolving the identifier, 
  op->id is returned, else 0 is returned.
*/
static int s2_op_rhs_as_propkey( s2_op const * op ){
  switch(op ? op->id : 0){
    /*case S2_T_OpDotDot:*/
    case S2_T_OpColon2:
    case S2_T_OpDot:
      return op->id;
    default:
      return 0;
  }
}

/**
   Returns ttype if it is a dot-op-like id or (special case) parens
   group, else returns 0.
*/
static int s2_ttype_is_dotish( int ttype ){
  switch(ttype){
    case S2_T_OpArrow:
    case S2_T_OpDotDot:
    case S2_T_OpColon2:
    case S2_T_OpHash:
    case S2_T_OpDot:
    /*case S2_T_OpDotPrototype:*/
    case S2_T_BraceGroup:
    case S2_T_BraceOpen:
    case S2_T_ParenGroup:
    case S2_T_ParenOpen:
      /* 20170323: ParenGroup/Open added so that:

         ++f().x

         can work.
       */
      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;
  int const rc = s2_next_token( se, pr, S2_NEXT_NO_POSTPROCESS, &tgt )
    ? 0
    : s2_ttype_is_dotish(tgt.ttype);
  if(!rc){
    s2_ptoker_next_token_set(pr, &tgt);
  }
  return rc;
}

/**
   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.

   If allowNonCallableContainer then this function returns true if the
   LHS is a Container type, regardless of whether or not it is
   callable. This is intended for use with the 'new' keyword.

   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.

   BUG:

   var f = proc(){return o} using {o:{a:1}};
   f().a += 1 // OK
   f().a++ // OK
   ++f().a // not OK: Exception: Non-function value type 'string' before a call() operator.

   Where the 'string' is really the identifier 'f'.

   That was fixed by adding S2_T_ParenGroup to
   s2_ttype_is_dotish(). Probably not the best fix, but the simplest
   and most expedient (and doesn't cause any regressions, curiously
   enough).
*/
static char s2_looks_like_fcall( s2_engine * se, s2_ptoker * pr,
                                 int allowNonCallableContainer,
                                 s2_op const * prevOp, int * rc ){
  assert(S2_T_ParenGroup==pr->token.ttype);
  *rc = 0;
  if(prevOp) return 0;
  else{
    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->dotOp.lhs && se->dotOp.key){
        /* So that (obj.prop)(...) can work */
        return 1;
      }else if(allowNonCallableContainer
               ? !cwal_props_can(v)
               : !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;
      }
    }
  }
}

/**
   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 * xrv = 0;
  cwal_value * vSelf = 0;
  cwal_scope scope = cwal_scope_empty;
  s2_strace_entry strace = s2_strace_entry_empty;
  cwal_function * theFunc;
  s2_func_state * fst = 0;
  char reachedTheCall = 0
    /* will be set to 1 if we reach the actual call() part. This
       distinction is needed to handle stray continue/break
       properly. */;
  assert(S2_T_ParenGroup==pr->token.ttype);
  if(!se->skipLevel){
    rc = s2_scope_push_with_flags(se, &scope, S2_SCOPE_F_IS_FUNC_CALL);
    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->dotOp.lhs
    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->dotOp.lhs 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'. */
    rc = s2_process_top(se);
    switch(rc){
      case 0:
        break;
      case CWAL_RC_EXCEPTION:
        goto end;
        break;
      default:
        assert(se->err.code);
        rc = s2_ammend_op_err(se, pr, rc);
        assert(rc);
        goto end;
    }
  }
  if(se->dotOp.lhs){
    /*
      To allow (obj.func)(...) to work...

      Reminder to self: we should be able(?) to do this in the
      assignment ops as well, so that (x.y)=3 could work. The problem,
      it seems, with that, is that (A) we'd end up having to special-case
      clearing of the dotop state for (...) groups and (B) we _might_
      end up leaking over dotop state like this:

      var x = (y.z);

      On the assignment, "this" _might_ (not certain) get picked up as
      'y' and se->dotOp.key as 'z'. No, it won't - the stack machine
      will only push 2 ops, and 'this' will get cleared at the end of
      that expression. Hmmm.
    */
    /* MARKER(("Possibly stealing a 'this'\n")); */
    assert(!fv);
    assert(!vSelf);
    vSelf = se->dotOp.self;
    /* s2_dump_val(vSelf, "se->dotOpThis"); */
    if(vSelf) cwal_value_ref(vSelf);
    fv = s2_engine_pop_value(se) /* presumably a dot-op result */;
    assert(fv);
    cwal_value_ref( fv );
  }else{
    fv = s2_engine_pop_value(se) /* hopefully a function */;
    cwal_value_ref( fv );
  }
  s2_dotop_state(se, 0, 0, 0);

  /* 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){
    xrv = cwal_value_undefined();
    goto end;
  }

  rc = s2_strace_push_pos(se, pr, &origin, &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)s2_ptoken_len(&pr->token),
     s2_ptoken_begin(&pr->token))); */
  /* s2_dump_val(vSelf,"call() vSelf"); */
  /* s2_dump_val(fv,"call() func"); */

  fst = s2_func_state_for_func(theFunc);
  {
    /* Process the args and call() the func... */
    s2_ptoker sub = s2_ptoker_empty;
    cwal_value * arV = 0;
    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);
    cwal_value_make_vacuum_proof(arV, 1);
    rc = s2_ptoker_sub_from_toker(pr, &sub);
    if(rc){
      cwal_value_unref(arV);
      arV = 0;
      ar = 0;
      goto end;
    }
    assert(pr==sub.parent);
    /* sub.name = "call(args)"; */
    rc = s2_eval_to_array( se, &sub, ar, 1 );
    s2_ptoker_finalize(&sub);
    if(rc){
      cwal_value_unref(arV);
      arV = 0;
      ar = 0;
      goto end;
    }
    oldArgV = se->callArgV;
    se->callArgV = 0;
    se->currentScriptFunc = 0 /* if this is a script-side function call,
                                 s2_callback_f(), which is about to trigger,
                                 will set this to the corresponding function. */;
    s2_ptoker_errtoken_set(pr, &origin) /* just in case, location for error reporting */;
    if(0){
      MARKER(("Function call()...\n"));
      s2_dump_val(vSelf ? vSelf : fv, "vSelf ? vSelf : fv");
      s2_dump_val(cwal_array_value(ar), "argv");
    }

    reachedTheCall = 1;
    /* Reminder to self:
       cwal makes fv sweep/vacuum-safe for the life of the call,
       if it was not already.
     */
    if(fst
       && (fst->flags & S2_FUNCSTATE_F_EMPTY_BODY)
       && (fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)
       ){
      /* No function body and no possibility of default parameter
         values triggering side effects, so we don't need to call() it
         (which involves setting up imported symbols, "argv", and
         "this").  We need to ensure that se->callArgV is 0, though,
         as that would normally be taken care of via the pre- and/or
         post-call() hooks.

         It's hypothetically possible that default parameter values
         want to trigger side-effects via imported symbols (including
         "this" and the function's call()-local name
         (fst->vName)). For that to work, we need to go through the
         whole call() process if the body is empty but the params list
         is not. It's hard to envision a real use case for that, but
         the language allows for such constructs, so let's behave
         properly if someone actually constructs such a weird thing.
      */
      assert(!se->callArgV);
    }else{
      se->callArgV = ar /* needed by internals we're about to pass
                           through via the call() */;
      rc =  cwal_function_call_array( &scope, theFunc,
                                      vSelf ? vSelf : fv, &xrv, ar )
      /* Still wavering on this point: when called without a propery
         access (vSelf===0), should the function's "this" be the
         undefined value or itself? i don't want "this" propagating in
         from an older scope.

         One argument for passing fv instead of undefined:

         var obj = { prototype: proc(x){ this.x = argv.x } };

         obj(1);

         'this' is undefined in that call.

         Another argument: it seems that i already wrote a chapter on
         how to use/abuse it in the docs.
       */;
    }
    se->currentScriptFunc = oldScriptFunc;
    /* assert(!se->callArgV && "Gets unset via the post-call() hook");
    
       interesting problem: on certain types of errors, first made
       possible with the introduction of interceptors, the pre()
       hook might not be called, and thus the post-hook won't. */
    if(se->callArgV){
      assert(rc && "Only expecting this in certain error cases.");
      se->callArgV = 0;
      /* s2_dotop_state( se, 0, 0, 0 ); necessary!!! ? */
    }
    se->callArgV = oldArgV;
    /* assert(0==oldArgV); */
    cwal_value_make_vacuum_proof(arV, 0);
    if(!rc && xrv){
      /* we have to ref xrv before destroying arV for the case that xrv==arV
         or arV contains (perhaps indirectly) xrv. */
        cwal_value_ref(xrv);
    }
    cwal_value_unref(arV);
  }
  end:
  rc = s2_check_interrupted(se, rc);
  switch(rc){
    case 0:
      break;
    case CWAL_RC_EXCEPTION:
      rc = s2_exception_add_script_props(se, pr)
        /* Needed to decorate calls to native functions. */;
      if(rc){
        /* lower-level error, e.g. CWAL_RC_OOM, while adding script
           location state. */
        break;
      }
      rc = CWAL_RC_EXCEPTION;
      CWAL_SWITCH_FALL_THROUGH;
    /* case CWAL_SCR_SYNTAX: */
    case CWAL_RC_OOM:
    case CWAL_RC_EXIT:
    case CWAL_RC_FATAL:
    case CWAL_RC_INTERRUPTED:
    case CWAL_RC_ASSERT:
      /* do we want assertion errors to translate to exceptions
         if they go up the call stack? Much later: no.*/
      break;
    case CWAL_RC_BREAK:
    case CWAL_RC_CONTINUE:
      /*
        If a break/continue are triggered while handling the
        arguments, that's okay: let them propagate so they can be
        caught by upstream code. If they passed through the call()
        itself, that's not okay: stop them from propagating further.
      */
      if(!reachedTheCall) break;
      CWAL_SWITCH_FALL_THROUGH;
    default:
      if(se->err.code){
        /* Likely a syntax-related error pending */
        /*MARKER(("[rc=%s] se->err.msg=%s\n", s2_rc_cstr(se->err.code), (char *)se->err.msg.mem));*/
        rc = s2_throw_err(se, 0, 0, 0, 0);
      }else{
        if(cwal_exception_get(se->e)){
          /* s2_exception_add_script_props(se, pr); why is this not needed? */
          rc = CWAL_RC_EXCEPTION /* keep propagating it and
                                    assume/hope that rc is encoded in
                                    exception.code. */;
        }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(!rc && xrv){
      assert(cwal_value_refcount(xrv)>0 /* we ref'd it above */
             || cwal_value_is_builtin(xrv));
  }
  if(vSelf) cwal_value_unref(vSelf);
  if(fv) cwal_value_unref(fv);
  if(!rv){
    cwal_value_unref(xrv);
    xrv = 0;
  }
  if(rc){
    cwal_value_unref(xrv);
    xrv = 0;
  }else{
    cwal_value_unhand(xrv);
    if(rv) *rv = xrv ? xrv : cwal_value_undefined();
    else{
      assert(!xrv && "was zeroed out above.");
    }
  }
  if(scope.parent){
    assert(&scope == se->scopes.current->cwalScope);
    cwal_scope_pop2(se->e, rc ? 0 : xrv);
  }
  return rc;
}

static int s2_eval_is_eox(s2_engine const * se,
                          s2_ptoker const * pr){
  int rc;
  assert(pr);
  rc = s2_ttype_is_eox(pr->token.ttype);
#if 1
  if(!rc && se->ternaryLevel>0
     && S2_T_Colon==pr->_nextToken.ttype ){
    /*
      huge kludge for:

      0 ? foreach(@[1]=>k,v) eval x : 1
    */
    rc = S2_T_Colon;
  }
#endif
  return rc;
}

/**
   If true, s2_eval_expr_impl() internally uses a local
   array to keep references active and vacuum-proofed.

   HOLY COW... when recycling is disabled, this causes alloc counts
   and total (not peak) memory to skyrocket (nearly 60% increase in
   the 20160131 unit tests). With recycling on alloc counts go up by
   roughly 3% but peak memory goes up anywhere from 1k to 7k (as the
   recycling bins accumulate) in those same unit tests.

   After changing this, MAKE SURE to run all the valgrind tests with
   various combinations of recycling and string interning!!!

   Interestingly... if we'd done the right thing at the start of s2's
   development and told the s2_stoken_stack to hold/release refs, we'd
   probably have no problems with refs wrt string interning and how
   s2_engine::dotOpXXX are managed. At the time, i had too much faith
   in temp values because i didn't fully understand the implications
   of some of the interactions :/. There, i've admitted it. IIRC, we
   also didn't yet have cwal_value_unhand(), which is what makes some
   of the current ref handling possible/sane.

   20171112 re-enabling EVAL_USE_HOLDER to work around a serious bug:

   ./s2sh --S --a -v --R crash.s2

   with this crash.s2:

   if((var d=s2.getenv('HOME')) && (d = s2.fs.realpath(d+'/fossil'))) 0;

   It's crashing in the && operator, and it seems to be a lifetime
   problem related to the 'd' value (possibly getting hosed via the
   2nd assignment?). Running s2sh with recycling ON (-R instead of
   --R) hides the problem but potentially causes Weird Broken Stuff to
   happen later (as is being witnessed in the CGI plugin, which was
   where this was initially triggered). This is the first serious
   lifetime-related bug we've seen the core in a couple years :`(. A
   decent solution, aside from the eval holder, is as yet unknown :`(.

   Other formulations and permutations:

   (Forewarning: these are often dependent on the exact recycling
   options used and the internal state of the recycling system(s)!)

   var d; if((d = s2.getenv('HOME')) && (d = s2.fs.realpath(d+'/fossil'))) 0;
   // ^^^ crashes

   var d = s2.getenv('HOME'); if((d) && (d = s2.fs.realpath(d+'/fossil'))) 0;
   // ^^^ works

   (var d = 'a b c d e f g') && (d = d + 'h i j k') // crashes
   (var a = 30) && (a = a + 10) // works
   (var a = '30') && (a = a + '10') // crashes
   (var d = 'a b c d e f g') && (d = 'h i j k') // crashes
   (var a = 'x') && (a = 'x') // crashes
   (var a = 'x') + (a = 'y') // crashes
   (var a = 'x'), (a = 'y'), a // works
   (var a = 'x'), (a = a + 'y'), a // crashes
   (var a = 30), (a = a + 20), a // works

   So... the common element appears to be assigning to the same string
   value on the LHS and RHS of a binary operator.

   With the "eval holder" enabled, all of the above work as expected
   because the holder keeps refs to all of the values created during
   those expressions, unref'ing them at the very end of the expression
   (taking care to not kill the expression's final result value, if
   it's needed).

   The problem can (at times) be triggered both with and without
   string interning and/or the chunk recycler, which rules out the
   easy solution of getting rid of string interning.

   As of 20171112, don't disable this until/unless a resolution
   for the above problem is implemented.

   2020-02-20: the above story disregards the fact that even if the
   stack machine held refs to everything, we'd still need the eval
   holder to ensure vacuum-safety of in-progress expressions. It is a
   seemingly unavoidable feature of the engine.
*/
#define EVAL_USE_HOLDER 1

/**
   Internal helper for s2_eval_expr_impl(), which performs
   a sub-eval on keywords.

   Returns 0 on success.

   *nextTokenFlags will be set to 0 or to S2_NEXT_NO_SKIP_EOL, which
   must be respected in the next iteration of the main eval loop,
   treating an immediately-subsequent EOL as an EOX.

   *tVal will be set to the result of the keyword.

   st will be advanced to the last of the keyword's tokens.

   fromPt must be the currently-eval'ing token, and this function
   may change its ttype.

   fromLhsOp needs to be the operation taking place on the LHS, if
   any. If not NULL, then EOL is _never_ treated like an EOX. This is
   almost always null, but some few callers will want to pass
   s2_ttype_op(S2_T_Comma) so that their (sub)expression stops eval'ing
   at the first comma.
*/
static int s2_eval_keyword_call( s2_keyword const * kword,
                                 s2_ptoken * fromPt,
                                 s2_op const * fromLhsOp,
                                 s2_engine * se, s2_ptoker * st,
                                 cwal_value ** tVal,
                                 int * nextTokenFlags ){
  int rc;
  assert(kword->call);
  fromPt->ttype = st->token.ttype = kword->id;
  rc = kword->call(kword, se, st, tVal);
  s2_dotop_state( se, 0, 0, 0 );
  rc = s2_check_interrupted(se, rc);
  *nextTokenFlags = 0;
  if(rc) return rc;
  else if(!tVal){
    s2_ptoker_errtoken_set(st, fromPt);
    return 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((S2_T_SquigglyBlock==st->token.ttype
           || S2_T_Heredoc==st->token.ttype
           /* ^^ heredocs handling here is historic */)
          && !fromLhsOp && !se->st.vals.size
          /* ^^ keyword is the only thing on the stack */){
    /* If the keyword ends in a {script} and its the only
       expression (so far), then possibly 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(kword->allowEOLAsEOXWhenLHS){
      *nextTokenFlags = S2_NEXT_NO_SKIP_EOL;
    }else{
      /* 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);
           
         If it fails tokenizing, ignore it - we'll catch
         it again in a moment.
      */
      s2_op const * opCheck = 0;
      s2_ptoken check = s2_ptoken_empty;
#if 0
      /* why doesn't this work the same? */
      s2_ptoker stMod = *st;
      do{
        rc = s2_ptoker_next_token_skip_junk(&stMod);
      }while(!rc && s2_ttype_is_eol(stMod.token.ttype));
      if(!rc){
        check = stMod.token;
      }
#else
      rc = s2_next_token( se, st, 0, &check );
#endif
      if(!rc
         && S2_T_ParenGroup!=check.ttype
         /* avoid that: proc (){}
            (1,2,3)
            unduly fails! This might come around and
            bite us, though. */
         && (!(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)); */
        s2_ptoker_next_token_set(st, &check);
      }
    }
  }
  assert(!rc);
  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){
  s2_ptoken pt = s2_ptoken_empty;
  s2_ptoken prevTok = s2_ptoken_empty;
  s2_ptoken const pOrigin = st->token;
  s2_ptoken const pbOrigin = *s2_ptoker_putback_get(st);
  int tlen = 0;
  int const oldSkipLevel = se->skipLevel;
  s2_op const * op = 0;
  s2_op const * prevOp = 0;
  s2_estack priorStack = s2_estack_empty;
  s2_ptoken captureBegin = s2_ptoken_empty
    /* starting point of capture (first non-junk token) */,
    captureEnd = s2_ptoken_empty
    /* one-after-the-end point of capture. It turns out that using a
       range of [captureBegin,captureEnd) simplifies handling notably
       over [captureBegin,captureEnd]. This is typically an
       expression's EOX, but will be the same as captureBegin for an
       empty expression (including one comprised solely of an EOX
       token, in which case both captureBegin and captureEnd will
       point to that EOX token). */;
  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
    (S2_EVAL_KEEP_STACK & evalFlags) ? 0 : 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;
  cwal_scope scope = cwal_scope_empty /* cwal stack, if (flags&S2_EVAL_PUSH_SCOPE) */;
#if EVAL_USE_HOLDER
  cwal_array * holder = 0 /* holds pending expression values to keep
                             them referenced and vacuum-safe. */;
  cwal_size_t holderLen = 0 /* length of
                               se->scopes.current->evalHolder at the
                               time this function is called */;
#endif
#ifdef DEBUG
  /* Just for sanity checking */
  int const oldValCount = se->st.vals.size;
  int const oldOpCount = se->st.ops.size;
#endif
  int rc = s2_check_interrupted(se, 0);

  if( rc ) return rc;
  else if(!fromLhsOp
          /* Explicitly NOT honoring !(S2_EVAL_RETAIN_DOTOP_STATE &
             evalFlags) at the start of eval, only at the end. */
          ) s2_dotop_state(se, 0, 0, 0);
  if(S2_EVAL_PUSH_SCOPE & evalFlags){
    MARKER(("Oh, this scope-pushing bit in eval_expr_impl IS used.\n"));
    assert(!"This isn't used, is it?");
    rc = cwal_scope_push2(se->e, &scope);
    if(rc) return rc;
    assert(scope.parent);
    assert(0==se->scopes.current->sguard.sweep);
    assert(0==se->scopes.current->sguard.vacuum);
    /* se->scopes.current->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(++se->metrics.subexpDepth > se->metrics.peakSubexpDepth){
    se->metrics.peakSubexpDepth = se->metrics.subexpDepth;
  }

  s2_ptoker_errtoken_set(st, 0);

  /* if(rv) *rv = 0; */
  if((S2_EVAL_PRE_SWEEP & evalFlags)
     && !fromLhsOp
     && !se->st.vals.size
     && !se->st.ops.size
     ){
    /* MARKER(("SWEEPING from eval_expr_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. */
    assert(!"unused/untested");
    ++se->scopes.current->sguard.vacuum;
    s2_engine_sweep(se);
    --se->scopes.current->sguard.vacuum;
  }
  if(!scope.parent){
    /* reminder to self: needed only when no scope has been pushed by
       this routine. If this routine pushed a scope (that feature is
       currently unused/untested) then there cannot be pending LHS
       expressions which could be affected by a sweep in the
       newly-pushed scope.
    */
    ++se->scopes.current->sguard.sweep;
  }

  se->currentScript = st;

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


#if EVAL_USE_HOLDER
#  define eval_hold_this_value(ARRAY, VALUE) (cwal_value_is_builtin(VALUE) \
                                    ? 0                                 \
                                    : cwal_array_append((ARRAY), (VALUE)))
#  define eval_hold(V) if( holder && (V) \
                           && (rc = eval_hold_this_value(holder, (V)))) goto end
  /*
    Create se->scopes.current->evalHolder to hold refs to our
    being-eval'd values. As an optimization, if we're in skip mode,
    don't bother... and let's hope that this function is never called
    in such a way that skip mode is disabled by a downstream sub-eval
    (because this optimization will hose us if that happens). That
    case shouldn't be possible, though.
  */
  if(!se->skipLevel && !(holder = se->scopes.current->evalHolder)){
    holder = cwal_new_array(se->e);
    if(!holder){
      rc = CWAL_RC_OOM;
      goto end;
    }else{
      cwal_value * hv = cwal_array_value(holder);
      cwal_value_ref(hv);
#if 0
      /* array_reserve() costs us inordinate amounts of memory
         when recycling is disabled. Tried values 0, 10, 15, 20,
         and 0 is the clear winner.*/
      rc = cwal_array_reserve(holder, 0/*guess!*/);
      if(rc){
        cwal_value_unref(hv);
        holder = 0;
        goto end;
      }
#endif
      se->scopes.current->evalHolder = holder;
      cwal_value_make_vacuum_proof(hv,1);
      cwal_value_rescope(se->scopes.current->cwalScope, hv)
        /* in case a cwal-level API has pushed a scope without us
           knowing */;
    }
  }else if(holder){
    holderLen = cwal_array_length_get(holder)
      /* Get the older (pre-eval) length so that we can trim holder
         back to that length when we're done, releasing any refs this
         invocation of eval() is holding. */;
  }
#else
#  define eval_hold(V)
#endif
/* ^^^^ EVAL_USE_HOLDER */
  
  pt = st->token;

  /*
    Adapted from:

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

    https://en.wikipedia.org/wiki/Shunting-yard_algorithm

    https://en.wikipedia.org/wiki/Operator-precedence_parser

    For later reference (but not used here): Pratt Parsing:
    https://en.wikipedia.org/wiki/Pratt_parser
    https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
  */
  for( ; !rc &&
         (
          (prevTok.ttype
           && !op
           && s2_ttype_is_eox(st->token.ttype)
           /*
             ^^^^ translation: if we had a previous token which was
             not an operator and we're currently at an EOX, then stop
             processing without an error (though we may trigger one in
             an up-coming step).

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

             This case appears once keywords and short-circuiting
             enter this mix (i.e. sub-parses/sub-evals).

             Note that we allow EOX's through in some cases just so
             that we can do some more error checking/reporting.
           */)
          ? 0
          : (0 == (rc = s2_next_token(se, st, nextTokenFlags, 0)))
          )
         ;
       prevOp = op, prevTok = pt
       ){
    cwal_value * tVal = 0 /* Value for the token, if any */;
    int doBreak = 0 /* set to non-0 to force a non-error multi-level
                       break in certain places below. */;
    if((rc=s2_check_interrupted(se, rc))) break;
    if(!s2_ptoken_begin(&captureBegin)){
      captureBegin = st->token;
    }

    if(s2_ttype_is_eox(st->token.ttype)){
      if(/*!fromLhsOp &&  <--- 20171205: why did we do that?
           having that triggers an assert() via: new X( y. )
           (note the stray '.'). */
         prevOp
         && ((prevOp->placement<0 && prevOp->arity>0)/*prefix op*/
             ||(prevOp->placement==0 && prevOp->arity>1)/*infix op*/)
         ){
        /* infix/prefix operator preceeding EOX */
        /* s2_ptoker_errtoken_set(st, s2_ptoken_begin(&prevTok) ? &prevTok : &st->token); */
        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)
              /* yes, checking previous token, not current one */){
        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");
        }
      }
      else if(S2_T_EOX == st->token.ttype
              && (evalFlags & S2_EVAL_EOX_SEMICOLON_NOT_ALLOWED)){
        /* Bugfix: catch these as syntax errors:

           [1,2,3;]
           [1,2,3;,4]
           {a:b, c:d ;}

           Without this check, those semicolons are silently ignored
           because they're the tail end of the values' expressions
           (and thus officially end the expressions). That's arguably
           not a bug, given the eval engine's nature, but we'll go
           ahead and flag it as an error just to be pedantic (even
           though it means adding more code just to catch this)..
        */
        rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                            "Unexpected semicolon.");
      }
      /*
        Would like to do this but is currently endlessly looping
        in s2_eval_ptoker():

        s2_ptoker_putback(st);
      */
      break;
    }else if(kword
             && kword->allowEOLAsEOXWhenLHS
             && (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;
    /*captureEnd = pt; Reminder to self: setting captureEnd here does
      not work with break/continue/return handling (and similar): it
      captures the keyword but not the optional expression after it
      (which arrives back in this loop via error handling). For most
      cases we have to wait until after this loop to set
      captureEnd. */
    tlen = s2_ptoken_len(&pt);
    assert(tlen>0);
    if(se->flags.traceTokenStack){
      MARKER(("Token: fromLhsOp=%s type=%s [%.*s]\n",
              fromLhsOp ? fromLhsOp->sym : "none",
              s2_ttype_cstr(pt.ttype), tlen, s2_ptoken_begin(&pt)));
    }
    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... */
        CWAL_SWITCH_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, s2_ptoken_begin(&pt));
        break;
      case S2_T_At:
        rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                           "'@' is not allowed here.");
        break;
      case S2_T_Question:{
        cwal_value * lhs;
        if(fromLhsOp && (S2_T_OpAnd==fromLhsOp->id
                         || S2_T_OpOr==fromLhsOp->id
                         || S2_T_OpOr3==fromLhsOp->id
                         || S2_T_OpElvis==fromLhsOp->id)
           ){
          /*
            Workaround to avoid short-circuiting the RHS of a ternary
            when its LHS is a short-circuiting logical op which is
            skipping over its own RHS:
             
            0 && 1 ? a : b;
            1 || 0 ? a : b;

            We put back this token and come back to it after the
            logical op resolves.

            This is a bit of a hack, admittedly, but i haven't got a
            better approach for the time being.
          */
          captureEnd = st->token;
          s2_ptoker_putback(st);
          doBreak = S2_T_Question;
          break;
        }
        lhs = prevOp && prevOp->placement<=0 ? 0 : s2_engine_peek_value(se);
        if(!lhs){
          rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                              "Unexpected LHS (%s%s) for %s operator.",
                              prevOp ? prevOp->sym
                              : (lhs ? cwal_value_type_name(lhs)
                                 : "<NULL>"),
                              prevOp ? " operation" : (lhs ? " value" : ""),
                              s2_ttype_op( S2_T_Question )->sym);
          break;
        }
        lhs = 0;
        rc = s2_eval_ternary(se, st, &lhs);
        if(rc) break;
        else{
          op = 0
            /* 20180507: avoid confusion in the next iteration.

               var a, b;
               a++ ? 1 : 2; b

               ==> Unexpected token after postfix operator: (OpIncrPost) ==> (Identifier)

               That triggers after the ternary expr completes, but
               adding a second semicolon after the ternary expr fixes
               it.

               Other postfix ops can also trigger it.
            */;
          assert(lhs);
          eval_hold(lhs);
          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_op_rhs_as_propkey(prevOp)){
          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){/* xxxx misdiagnosis as post-op on mis-continued lines */
          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;
    }
    rc = s2_check_interrupted(se,rc);
    if(rc || doBreak) break;

    op = s2_ttype_op( pt.ttype );
#if 0
    if(op && !fromLhsOp){
      s2_dotop_state( se, 0, 0, 0 )
        /* If these get stale, Very Bad Things happen in the cwal core
             (assertions, if we're lucky). */;
      MARKER(("op=%s, dotop state?\n",op->sym));
      /* se->dotOpId = 0 */ /* keep Other Bad Things from happening */;
      /* Reminder: this means that assignment ops cannot tell
         which dot-like op triggered them. */
    }
#else
    /*
      We need a better heuristic, as the above makes this fail:

      x.y++; // fine
      x.y++ >= 2; // "Invalid LHS..." from the incrdecr op because dotop state is cleared.
    */
    if(op && !fromLhsOp){
      if(!prevOp){
        /* MARKER(("op=%s, prevOp=%s, resetting dotop state\n",op->sym, prevOp ? prevOp->sym : "<NULL>")); */
        s2_dotop_state( se, 0, 0, 0 );
      }else{
        /* MARKER(("op=%s, prevOp=%s, keeping dotop state\n",op->sym, prevOp->sym)); */
      }
    }
#endif

#if 0
    if(fromLhsOp){
      MARKER(("fromLhsOp=%s, pt.type=%s\n", s2_ttype_cstr(fromLhsOp->id), s2_ttype_cstr(pt.ttype)));
    }
#endif

    if(S2_T_Colon==pt.ttype){
      if(se->ternaryLevel>0){
        /* s2_eval_ternary() will deal with it */
        captureEnd = st->token;
        s2_ptoker_putback(st)
          /* This putback causes certain grief for foreach(). */;
        s2_ptoker_next_token_set(st, &pt) /* part of a kludge in s2_eval_is_eox().
                                             Also incidentally useful here. */;
      }else{
        rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                           "Unexpected ':' token (no %s op in progress).",
                           s2_ttype_op(S2_T_Question)->sym);
      }
      break;
    }else if(fromLhsOp
             /* This eval_expr() call is fetching the RHS of a pending
                expression. */
             && 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 "
              "'%s' skipping would break off.\n",
              op->sym, fromLhsOp->sym));*/
      captureEnd = st->token;
      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. */
      switch(op->id){
        case S2_T_OpPlus:
        case S2_T_OpMinus:
            /* See if op can be made into a unary op. */
          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_OpHash:
            if(prevOp && S2_T_OpDot==prevOp->id){
                /* Convert the dot op in X.# to S2_T_OpDotLength. */
                s2_stoken * topTok = s2_engine_peek_op(se);
                assert(S2_T_OpDot==topTok->ttype);
                assert(topTok->ttype == prevOp->id);
                topTok->ttype = S2_T_OpDotLength;
                op = s2_ttype_op(topTok->ttype)
                    /* so that prevOp gets set for the next iteration */;
                assert(op);
                continue;
            }
            break;
        default:
          break;
      }
      if(!rc
         && op
         && 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 */
      kword = s2_ptoken_keyword2(se, &pt);
      if(prevOp
         && (!kword || (kword && kword->call/*non-operator-like keyword*/))
         && prevOp->placement>0/*postfix*/ && prevOp->arity>0/*non-nullary*/){
        /* 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));
        break;
      }
      if(!prevOp
         && /*prevTok.ttype*/ s2_engine_peek_value(se)
         && (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 */)
         ){
#if 0
        /* who is setting se->opErrPos to this (correct) location? */
        if(se->opErrPos){
          MARKER(("opErrPos=[%.30s...]\n", se->opErrPos));
          st->errPos = se->opErrPos;
        }else{
          se->opErrPos = st->errPos =  s2_ptoken_begin(&pt);
        }
#else
        /* se->opErrPos = 0; resetting this here causes 'catch' to misreport
         line numbers in some cases! */
        s2_ptoker_errtoken_set(st, &pt);
        se->opErrPos = s2_ptoken_begin(&pt)
          /* se->opErrPos trumps st->errPos in s2_err_ptoker()
             and is currently (for unknown reasons) being relied
             upon by a containing 'catch' for proper behaviour.
             That's a bug. */;
#endif
        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, s2_ptoken_begin(&pt) */
                           );
        break;
      }
    }

    /* Some (more) checks and pre-processing... */
    switch(pt.ttype){
      case S2_T_OpOr:
      case S2_T_OpOr3:
      case S2_T_OpElvis:
      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 or OpOr3 and topVal is truthy,
          - op==OpElvis and topVal !== undefined
          - op==OpAnd and topVal is falsy
        */
        cwal_value * lhs = 0;
        char buul = 0;
        char shortIt = 0;
        int const theOpId = pt.ttype;
        rc = s2_eval_lhs_ops(se, st, op);
        if( rc ) break;
        lhs = s2_engine_peek_value(se);
        if(!lhs) break /* will error out below */;
        else if(se->skipLevel>0) break;
        if(S2_T_OpElvis==pt.ttype){
          shortIt = (cwal_value_undefined() == lhs) ? 0 : 1;
        }else{
          buul = cwal_value_get_bool(lhs);
          if(!buul){
            if(pt.ttype==S2_T_OpAnd){
              shortIt = 1;
            }
          }else{
            if(pt.ttype!=S2_T_OpAnd){
              assert(S2_T_OpOr==pt.ttype
                     || S2_T_OpOr3==pt.ttype);
              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;
          /* int const oldOpSize = se->st.ops.size; */
          /* MARKER(("op %s should short circuit RHS here.\n", op->sym)); */
          rc = s2_eval_expr_impl(se, st, op,
                                 S2_EVAL_SKIP /* | S2_EVAL_KEEP_STACK */,
                                 &rhs)
            /* This causes the following to parse incorrectly:

               0&&1 ? 2 : 3; ===> false instead of 3

               _Effectively_ parses as:

               0 && (1 ? 2 : 3)

               Because of the sub-eval. It actually parses right, but the
               skipLevel is still set after the &&, so the end effect is that
               the whole ?: gets skipped.

               Contrast with:

               (0&&1) ? 2 : 3; ===> 3

               which works because of the expression boundary there.

               Anyway: worked around up above in the S2_T_Question
               handling.
            */
            ;
          /* MARKER(("after %d op: skipLevel=%d\n", pt.ttype, se->skipLevel)); */
          assert(lhs == s2_engine_peek_token(se)->value);
          if(rc){
            /*
              Reminder: lhs is now an errant value, possibly a temp.
              It's potentially orphaned for the time being.
              (Later: except that it's currently in the eval-holder.)
            */
            break;
          }
          /*MARKER(("rhs-eval rc=%s. capture=%.*s\n", s2_rc_cstr(rc),
            (int)(s2_ptoken_begin(&captureEnd)-s2_ptoken_begin(&captureBegin)),
            s2_ptoken_begin(&captureBegin)
            ));*/
          /*if( (rc = s2_check_interrupted(se, rc)) ) break;
            else */
          if(!rhs){
            s2_ptoker_errtoken_set(st, &pt);
            rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                               "Empty RHS for logic operator");
          }else{
            s2_stoken * topTok = s2_engine_pop_token(se, 1) /* the short-circuited value */;
            assert(topTok->value);
            assert(lhs == topTok->value);
            topTok->value = 0;
            s2_stoken_free(se, topTok, 1);
            assert(cwal_value_undefined()==rhs
                   && "Violation of current API convention for skip-mode evals");
            s2_engine_push_val(se, (S2_T_OpOr3==theOpId || S2_T_OpElvis==theOpId)
                               ? lhs
                               : cwal_new_bool(buul)
                               )->srcPos = s2_ptoken_begin(&pt)
              /* that allocation cannot fail b/c it will
                 recycle the popped one unless someone foolishly
                 disables the stack token recycler. */;
            /* 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(S2_T_OpOr3 != pt.ttype && S2_T_OpElvis != pt.ttype){
              /*
                Clean this up:
                s2> x = 0; for( var i = 0; i < 100; (i++, x++ || 1, ++i) );
                MARKER: s2.c:296:s2_engine_sweep():	Swept up 48 value(s) in sweep mode
              */
              cwal_refunref(lhs);
            }else{
              eval_hold(lhs);
            }
            continue;
          }
        }/*if(shortIt)*/
        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){
            /* Transform keywords which get treated as operators
               into those operators... */
            s2_op const * kOp = s2_ttype_op(kword->id);
            assert(kOp);
            if(/* check for !inherits (two tokens) and transform
                  them into a single operator, !inherits. */
               S2_T_OpInherits == kOp->id
               && prevOp
               && S2_T_OpNot==prevOp->id){
              assert(s2_engine_peek_op(se)
                     && s2_engine_peek_op(se)->ttype == prevOp->id);
              s2_engine_pop_op(se,0);
              pt.ttype = S2_T_OpNotInherits;
              kOp = s2_ttype_op(pt.ttype);
              assert(kOp);
              assert(S2_T_OpNotInherits == kOp->id);
              prevOp = 0 /* avoid dowstream confusion */;
            }else{
              pt.ttype = kword->id;
            }
            op = kOp;
          }/* end operator-like words*/
          else{
            rc = s2_eval_keyword_call(kword, &pt, fromLhsOp,
                                      se, st, &tVal, &nextTokenFlags);
          }
        }/* end keywords */
        else if(se->skipLevel>0){
          /*
            In skip mode, we are obligated to resolve as much as we
            optimally can, and we resolve everything to undefined in
            that case.
          */
          tVal = cwal_value_undefined();
        }else{
          /* Non-keyword identifier, not in skip mode, so resolve
             it... */
          s2_stoken const * opT = s2_engine_peek_op(se);
          s2_op const * topOp = opT ? s2_ttype_op(opT->ttype) : 0;
          assert(opT ? !!topOp : 1);
          tVal = s2_var_get(se, -1, s2_ptoken_begin(&pt), (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, s2_ptoken_begin(&pt));
            }
          }
          else if(topOp && s2_ttype_is_identifier_prefix(topOp->id)){
            /* Previous token was prefix ++/-- (or similar) */
            if(s2_next_is_dotish(se, st)){
              /* we'll put the resolved tVal in the stack. */
              pt.ttype = S2_T_Value;
            }else{
              /* put the unresolved identifier on the stack as a string-type
                 value. */
              rc = s2_ptoken_create_value(se, st, &pt, &tVal);
              if(!tVal){
                assert(rc);
                /* rc = CWAL_RC_OOM; */
              }
            }
          }
          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 = 0;
            rc = s2_ptoken_create_value(se, st, &pt, &tVal);
            /*
              Potential problem here: tVal might return to a shared/stashed
              string and we cannot add a ref here. If we later assume it's
              new and simply unref it, we will hose a valid Value being used
              somewhere else (e.g. in se->stash).
            */
            if(!rc){
              assert(tVal);
            }
          }else{
            pt.ttype = S2_T_Value /* So as to not confuse
                                     the result with its identifier */;
          }
        }
        break;
      }/*end Identifier*/
      case S2_T_SquigglyBlock:{
        rc = s2_eval_object_literal( se, st, &tVal );
        if( !rc ){
          assert(tVal);
          pt.ttype = S2_T_Value;
        }
        break;
      }
#if 0
        /*
          The DotPrototype op (..). While the code below works just
          fine, after having used this in scripts i find myself deleting
          it and typing ".prototype" to improve the readability. i'm
          going to disable the .. op for now and reserve it for something
          more useful (don't yet know what, except that numeric ranges
          come to mind).
         */
      case S2_T_OpDotDot:{
        /*
          Transform this token to:

          DOT "prototype"

          We do this here, instead of at the operator level, because
          this approach (A) required much less code and (B) works out
          of the box with assignment:

          x.. = bar;

          and, by extension, the ++/-- ops, without a lot of extra
          fiddling in the operator code.
        */
        if(prevOp && prevOp->arity>0 && prevOp->placement<=0){
          rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                             "Invalid operator '%s' preceeding '%s'.",
                             prevOp->sym, op->sym);
        }else if(0==(rc = s2_eval_lhs_ops(se, st, s2_ttype_op(S2_T_OpDot)))){
          s2_stoken * synthTok = s2_engine_push_ttype( se, S2_T_OpDot );
          if(!synthTok) rc = CWAL_RC_OOM;
          else{
            synthTok->srcPos = s2_ptoken_begin(&pt);
            pt.ttype = S2_T_Identifier;
            tVal = se->skipLevel ? cwal_value_undefined() : se->cache.keyPrototype;
            prevOp = 0;
            op = 0;
            /* ^^^^ that setup will cause "prototype" to be pushed
               onto the value stack as the RHS. The injected dot op
               will do the LHS checking. It turns out that .prototype
               is valid for the majority of expression results.
            */
          }
        }
        break;
      }
#endif
/* S2_T_OpDotDot */
      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 ){
            rc = s2_ammend_op_err(se, st, rc);
            assert(rc);
          }
          topOpTok = 0;
        }
        if(rc) break;
        lhs = s2_engine_peek_value(se);
#if 0
        /* Reminder to self: string interning can bite us here (again)
           with missing refs on lhs unless we eval_hold() lhs. Because
           this is a more general problem, that hold() was added to
           s2_process_op_impl() on 20171115. A case which can (but
           doesn't always) trigger a crash without this eval_hold()
           is: 'abc'[2][0][0][0][0][0]
        */
        eval_hold(lhs);
#endif
        if(prevOp || !totalValCount){
          /* Treat as array literal... */
          rc = s2_eval_array_literal( se, st, &tVal );
          if( !rc ){
            pt.ttype = S2_T_Value;
          }
          break /* avoid ParenGroup fall-through below */;
        }
        else if(!se->skipLevel
                && (!lhs
                    || prevOp
                    || (/* !prevOp && */
                        !cwal_value_is_unique(lhs) /* for EnumEntry['value'] */ &&
                        !cwal_value_container_part(se->e, lhs)))
                ){
          /* Syntax error */
          if(prevOp) s2_ptoker_errtoken_set(st, &prevTok);
          rc = s2_throw_ptoker( se, st, CWAL_RC_TYPE,
                                "Invalid LHS (%s %s) "
                                "preceeding [...] block.",
                                s2_ttype_cstr(prevTok.ttype),
                                prevOp
                                ? prevOp->sym
                                : (lhs
                                   ? cwal_value_type_name(lhs)
                                   : "<NULL>")
                                );
        }
        if(rc) break;
        CWAL_SWITCH_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. Call arguments for
          functions and 'new' are evaluated separately by
          s2_eval_fcall() resp. s2_keyword_f_new(), and arguments to
          function-like keywords are handled by the respective
          keywords. Thus some uses of parens never land here.
        */
        int const braceType = pt.ttype;
        char const braceOpen = *s2_ptoken_begin(&pt);
        char const braceClose = *(s2_ptoken_end(&pt)-1);
        char emptySet = 1;
        assert(S2_T_ParenGroup==pt.ttype || S2_T_BraceGroup==pt.ttype);
        assert(S2_T_ParenGroup==pt.ttype
               ? ('('==braceOpen && ')'==braceClose)
               : ('['==braceOpen && ']'==braceClose));
        assert(s2_ptoken_adjbegin(&pt));
        assert(s2_ptoken_adjend(&pt));
        assert(s2_ptoken_adjend(&pt) >= s2_ptoken_adjbegin(&pt));
        if(S2_T_ParenGroup==pt.ttype){
          doBreak = 0;
          if(s2_looks_like_fcall(se, st,
                                 (S2_EVAL_STOP_AT_CALL & evalFlags),
                                 prevOp, &rc) && !rc){
            if(S2_EVAL_STOP_AT_CALL & evalFlags){
              doBreak = S2_T_ParenGroup;
            }else{
              /* MARKER(("Looks like func call? rc=%s prevOp=%s\n",
                 s2_rc_cstr(rc), prevOp?prevOp->sym:"<NULL>")); */
              rc = s2_eval_fcall(se, st, &tVal);
            }
            break;
          }
          else if(rc) break;
        }
        if(s2_ptoken_adjend(&pt) > s2_ptoken_adjbegin(&pt)){
          /* 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(s2_ptoken_begin(&pt) == s2_ptoken_begin(&st->token));
          s2_ptoker_token_set(st, &pt) /* consume it */;
          rc = s2_eval_current_token(se, st, 0, 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){
          assert(!tVal);
          if(se->skipLevel
             || (!prevOp
                 && S2_T_BraceGroup==braceType
                 && cwal_value_array_part(se->e, 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.

              Minor bug (20160106):

              var v = []; v.blah = 1;
              assert v.blah === (v[] = v.blah);

              The parens should not be required. What happens is
              that this:

              assert v.blah === v[] = v.blah;

              thinks the LHS of the []= op is a boolean, i.e. it
              sees:

              (v.blah === v)[] = v.blah

              20160107: turns out this applies to all(?) assignments
              on the RHS of a binary comparison or logical op.

              Oh, wait... JavaScript fails in the exact same way. :-D
              Now i remember commenting about this elsewhere long
              ago.
            */
            s2_ptoken next = s2_ptoken_empty;
            op = s2_ttype_op( pt.ttype = S2_T_ArrayAppend );
            s2_next_token( se, st,
                           /* this flag is only safe so long as the
                              up-coming logic using 'next' is
                              compatible. */
                           S2_NEXT_NO_POSTPROCESS,
                           &next );
            if((rc=s2_check_interrupted(se,rc))){
                /* next-token might have set
                   error state we which want to ignore...
                   except for an interruption. */;
              break;
            }
            if(s2_ttype_is_assignment(next.ttype)){
              s2_ptoker_token_set(st, &next) /* consume/skip it */;
              /* Fall through and push this op */
            }else{
              s2_ptoker_errtoken_set(st, &pt);
              rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                                 "Missing assignment op "
                                 "after '%s' operator.",
                                 op->sym);
              break;
            }
          }else if(S2_T_ParenGroup==pt.ttype
                   && !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_err_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 = s2_ptoken_begin(&pt);
            /* 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:
      case S2_T_OpColonEqual
        /* op := is a special case: only supports ternary, not binary */:{
        /**
           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?*/
           ){
          s2_ptoker_errtoken_set(st, &pt);
          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
            case S2_T_OpColonEqual: newOp = S2_T_OpAssignConst3; break;
            default:
              assert(!"Missing operator mapping.");
              s2_fatal(CWAL_RC_FATAL, "Missing operator mapping: %s",
                       s2_ttype_cstr(pt.ttype));
          }
          assert(newOp);
          op = s2_ttype_op( pt.ttype = newOp );
          assert(op);
          s2_engine_pop_op(se,0)/*==topOpTok, a.k.a. OpDot */;
          /* Continue on and allow new op to be pushed to the stack */
        }else if(S2_T_OpColonEqual == pt.ttype){
          rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                             "The const-assign operator only works in "
                             "ternary form (X.Y:=Z).");
        }
        break;
      }/* /Assignment */
      case S2_T_LiteralInt:
      case S2_T_LiteralIntDec:
      case S2_T_LiteralIntHex:
      case S2_T_LiteralIntOct:
      case S2_T_LiteralIntBin:
      case S2_T_LiteralDouble:
      case S2_T_LiteralStringDQ:
      case S2_T_LiteralStringSQ:
      case S2_T_LiteralString:
      case S2_T_Heredoc:
      case S2_T_PropertyKey:
      case S2_T_Value:
      /* These will be picked up as Values below. */
        break;
      default:
          /*
            20171130: discovered by accident that a backtick character
            in a script was getting parsed as a string value (via
            s2_ptoken_create_value()). It was slipping through eval as
            a value token. s2_next_token() tags their ttype with their
            ASCII value, many of which overlap with various S2_T_OpXXX
            and such. It is only at this late point in the evaluation
            that we can really take note of them.
          */
        if(!op){
          rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                             "Unhandled token type %d (%s): %.*s",
                             pt.ttype, s2_ttype_cstr(pt.ttype),
                             (int)s2_ptoken_len(&pt), s2_ptoken_begin(&pt));
        }
        break;
    }/*switch(pt.ttype)*/

    /************************************************************/
    /**
       We're done with the handling of the token. We now either have a
       value (via tVal) or an operator (via op) to put onto the
       stack...
    */
    /************************************************************/
    if(rc || doBreak) 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 = s2_ptoken_begin(&pt);
            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.
              */
            }
          }
        }
      }
    }/* /if(op) */
    else{/* a Value token */
      if(!tVal){
        if(se->skipLevel>0) tVal = cwal_value_undefined();
        else{
          rc = s2_ptoken_create_value(se, st, &pt, &tVal);
          if(!rc){
            assert(tVal);
          }
        }
      }
      if(!rc){
        s2_stoken * vtok;
        assert(tVal);
        if((se->flags.traceTokenStack>0) && (S2_T_Identifier==pt.ttype)){
          MARKER(("Identifier token: %.*s\n", tlen, s2_ptoken_begin(&pt)));
        }
        vtok = s2_engine_push_tv(se, pt.ttype, tVal);
        if(!vtok){
          rc = CWAL_RC_OOM;
        }else{
          eval_hold(tVal);
          ++totalValCount;
          vtok->srcPos = s2_ptoken_begin(&pt);
          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(!rc) */
    }/* /if op else value */
  }/* /foreach token */

  if(rc && !s2_ptoker_errtoken_has(st)){
    s2_ptoker_errtoken_set(st, (s2_ptoken_begin(&pt)
                                && (s2_ptoken_begin(&pt) != s2_ptoken_begin(&pt)))
                           ? &pt : &prevTok);
  }else if(!rc){
    assert(s2_ptoken_begin(&captureBegin));
  }
  if(!s2_ptoken_begin(&captureBegin)) captureBegin = st->token;
  if(!s2_ptoken_begin(&captureEnd)){
    captureEnd = st->token;
    /*
      20200110: Capturing historically elides the eox token, but
      before today we captured the expression's contents as a simple
      [begin,end) byte range. For EOX-eliding to work with the
      two-token capture approach, we have 2 options:

      1) We need to massage the capture end if (and only if) st->token
      is-a EOX.

      2) We define the capture end token as the one-after-the-end,
      rather than the end token.

      It turns out that 2 is easier to implement here, easier to deal
      with downstream, and ever-so-slightly more efficient (a handful
      fewer CPU instructions).

      When capturing a completely empty expression with an EOX,
      e.g. ";", captureBegin will unavoidably point to that semicolon,
      which goes against our age-old policy of *not* including the
      semicolon in the capture, but captureEnd will *also* point to
      that token, so well-behaved code will not include the ';' in any
      handling.
    */
  }
  assert(s2_ptoken_begin(&captureEnd) >= s2_ptoken_begin(&captureBegin)
         && s2_ptoken_end(&captureEnd) >= s2_ptoken_end(&captureBegin));
  st->capture.begin = captureBegin;
  st->capture.end = captureEnd;
  /*MARKER(("captured: <<<%.*s>>>\n", (int)(captureEnd.begin - captureBegin.begin),
    captureBegin.begin));*/
  /* 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 && ownStack){
    while(!rc && se->st.ops.size){
      rc = s2_process_top(se);
    }
    if(rc){
      rc = s2_ammend_op_err(se, st, rc);
      assert(rc);
      goto end;
    }
  }
  /* MARKER(("Stack sizes: op=%d, token=%d\n", se->st.ops.size, se->st.vals.size)); */
  if(!rc && ownStack && (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){
      s2_ptoker_errtoken_set(st, &pt);
      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);
    }
  }/* /stack size error checking. */

  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.traceTokenStack>0 && !se->skipLevel){
      s2_dump_val(xrv, "eval_expr result");
    }
    if(rv) *rv = xrv;
    else cwal_refunref(xrv);
  }

  end:
#undef eval_hold
#undef eval_hold_this_value
  if(rc){
    rc= 1 ? rc : 0 /* put breakpoint here (the ?: is to avoid
                      assignment-to-self warning from clang).*/;
  }
  /* Clean up... */

  if(rc && !se->err.code && st->errMsg
     && (CWAL_RC_EXCEPTION!=rc)){
    /* Error from the tokenizer. */
    rc = s2_err_ptoker(se, st, rc, 0);
  }

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

#if EVAL_USE_HOLDER
  if(holder){
    assert(!se->skipLevel);
    /* --se->sguard->vacuum; */
    assert(1 == cwal_value_refcount(cwal_array_value(holder)));
    if(!rc && rv) cwal_value_ref(*rv);
    cwal_array_length_set( holder, holderLen )
      /* truncate to its old length, potentially freeing any refs this
         eval() added */;
    holder = 0;
    if(!rc && rv) cwal_value_unhand(*rv);
  }
#endif
/* ^^^^ EVAL_USE_HOLDER */

  if(/* !fromLhsOp && */ !(S2_EVAL_RETAIN_DOTOP_STATE & evalFlags)){
    /* We must clear these to avoid picking them up after they're
       stale.  However, they's needed by, e.g. (unset x.y).

       Reminder: this ref/unhand of *rv is critical to avoid a crash
       caused by too many unrefs here:

       scope { {## a: 3}.a }

       where the temporary LHS hash is in se->dotOp.self and and the
       result value of the expression will get destroyed by it when
       s2_dotop_state() resets it. Thus we need to ref it. Right here:
    */
    if(rv && *rv) cwal_value_ref(*rv);
    s2_dotop_state( se, 0, 0, 0 );
    if(rv && *rv) cwal_value_unhand(*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);
#ifdef DEBUG
    assert(oldOpCount == se->st.ops.size);
    assert(oldValCount == se->st.vals.size);
#endif
  }
  if(!consumeIt){
    s2_ptoker_token_set( st, &pbOrigin );
    s2_ptoker_putback_set(st, &pbOrigin);
  }else{
    s2_ptoker_putback_set(st, &pOrigin)
      /* yes, pOrigin (not pbOrigin) - we want to be able to putback
         the whole expression */;
  }
  --se->metrics.subexpDepth;

  se->currentScript = oldScript;
  if(S2_EVAL_PUSH_SCOPE & evalFlags){
    assert(scope.parent);
    assert(s2_scope_current(se)->cwalScope==&scope);
    cwal_scope_pop2(se->e, rc ? 0 : (rv ? *rv : 0));
  }else{
    if(!scope.parent){
      --se->scopes.current->sguard.sweep;
    }
  }
  return rc;
}

#undef EVAL_USE_HOLDER

int s2_eval_buffer( s2_engine * se,
                    char newScope,
                    char const * name,
                    cwal_buffer const * buf,
                    cwal_value **rv ){
  return s2_eval_cstr( se, newScope, name,
                       buf->used ? (char const *)buf->mem : "",
                       (int)buf->used,
                       rv );
}

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_v2( se->e, &pt, src, srcLen, 0 );
  if(!rc){
    cwal_scope SC = cwal_scope_empty;
    if(!newScope || !(rc=cwal_scope_push2(se->e, &SC))){
      cwal_value * xrv = 0;
      pt.name = (name && *name) ? name : 0;
      rc = s2_eval_ptoker( se, &pt, 0/*TODO: flags*/, rv ? &xrv : 0 );
      if(!rc && rv) *rv = xrv;
      else cwal_refunref(xrv);
      if(SC.parent){
        cwal_scope_pop2(se->e, rc ? 0 : (rv ? *rv : 0));
      }
    }
  }
  s2_ptoker_finalize( &pt );
  return rc;
}

int s2_eval_cstr_with_var( s2_engine * se,
                           char const * varName,
                           cwal_value * varValue,
                           char const * scriptName,
                           char const * src, int srcLen,
                           cwal_value **rv ){
  int rc;
  cwal_scope sc = cwal_scope_empty;
  rc = cwal_scope_push2(se->e, &sc);
  if(rc) return rc;
  cwal_value_ref(varValue);
  rc = cwal_var_decl(se->e, 0, varName, cwal_strlen(varName), varValue, 0);
  if(!rc){
    rc = s2_eval_cstr(se, 0, scriptName, src, srcLen, rv );
  }
  cwal_scope_pop2(se->e, rc ? (rv ? *rv : 0) : 0);
  cwal_value_unhand(varValue);
  return rc;
}


int s2_eval_ptoker( s2_engine * se, s2_ptoker * pr, int e2Flags, cwal_value **rv ){
  int const oldTStackSize = se->st.vals.size;
  cwal_value * xrv = 0 /* pending result value */;
  int hardEoxCount = 0;
  int const srcLen = (int)(s2_ptoker_end(pr) - s2_ptoker_begin(pr));
  s2_ptoker const * oldScript = se->currentScript;
  char xrvWasVacSafe = 0 /* whether not not xrv was vacuum-safe (if not, we need to make it so). */;
  int rc = s2_check_interrupted(se,0);
  if(rc) return rc;
  else if(srcLen<0){
    return s2_engine_err_set(se, CWAL_RC_MISUSE, "Invalid s2_ptoker range.");
  }else if(!srcLen || !*s2_ptoker_begin(pr)){
    if(rv) *rv = 0;
    return 0;
  }

  /*
    We have one 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.

    As of 20160228, cwal can make non-container instances
    vacuum-proof, so we use that to make the pending result safe from
    vacuuming (and a ref point to keep it safe from sweep-up) 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.
  */
  s2_engine_err_reset(se);
  se->currentScript = pr;
#if 0
  if(pr->name){
    MARKER(("Running script [%s]\n", pr->name))
      /* for helping in finding a script name bug in interactive
         mode. */
      ;
  }
#endif
  for( ; !rc ; ){
    int isEof, isEox;
    cwal_value * vrx = 0;
    s2_engine_sweep(se);

    rc = s2_eval_expr_impl(se, pr, 0, 0, rv ? &vrx : 0);
    if(rc) break;
#if 0
    MARKER(("Ran expr: value=%p, capture=[[[%.*s]]]\n",
            (void const *)vrx, 
            (int)(s2_ptoken_begin(&pr->capture.end)
                  - s2_ptoken_begin(&pr->capture.begin)),
            s2_ptoken_begin(&pr->capture.begin)));
#endif
    isEof = s2_ptoker_is_eof(pr);
    isEox = isEof ? isEof : s2_ttype_is_eox(pr->token.ttype);
    if(vrx){
      hardEoxCount = 0 /* tells us (later) to keep xrv at the next EOX. */;
      assert(rv && "Else all of this is a waste of time...");
      /* Swap out pending result... */
      cwal_value_ref(vrx);
      if(xrv){
        if(!xrvWasVacSafe){
          assert(cwal_value_is_vacuum_proof(xrv));
          cwal_value_make_vacuum_proof(xrv, 0);
        }
        assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv) /* will fail on too many unrefs */);
        cwal_value_unref(xrv);
      }
      /* Make vrx the new pending result... */
      xrv = vrx;
      vrx = 0;
      xrvWasVacSafe = xrv ? cwal_value_is_vacuum_proof(xrv) : 0;
      if(xrv){ /* formerly known as vrx */
        assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv) /* we ref'd it above */);
        if(!xrvWasVacSafe) cwal_value_make_vacuum_proof(xrv, 1);
        /* cwal_unique_wrapped_set(holder, xrv); */
        /* s2_dump_val(xrv, "s2_eval_expr result"); */
      }
    }
    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. We pass this NULL back to the caller so that
        they can differentiate between empty expression [as the final
        result] and the undefined value (which would be the
        alternative result). Splitting hairs, either way, in this
        context (but the distinction is important in/via
        s2_eval_expr_impl()).
      */
      if(rv && ++hardEoxCount>1 && xrv){
        assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv) /* we ref'd it above */);
        assert(cwal_value_is_vacuum_proof(xrv));
        if(!xrvWasVacSafe) cwal_value_make_vacuum_proof(xrv, 0);
        cwal_value_unref(xrv);
        xrv = 0;
      }
    }else if(!isEox){
      hardEoxCount = 0;
    }
    if(isEof) break;
  }/* for-each expression */
  /* 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));*/
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Unexpected stack size in s2_eval_ptoker(): %d",
                         se->st.vals.size);
    }else{
      rc = s2_check_interrupted(se, rc);
    }
  }

  if(xrv && !xrvWasVacSafe){
    assert(cwal_value_is_vacuum_proof(xrv));
    cwal_value_make_vacuum_proof(xrv, 0);
  }
  /* s2_dump_val(xrv, "s2_eval_ptoker result"); */
  /* s2_dump_val(xrv, "s2_eval_ptoker result"); */
  if(!pr->parent
     && CWAL_RC_RETURN==rc
     && !(e2Flags & S2_EVALP_F_PROPAGATE_RETURN)){
    /* Top-level parser shall accept a RETURN as a non-error, and
       stuff it in *rv. Unless e2Flags has the S2_EVALP_F_PROPAGATE_RETURN
       bit set.

       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);
    cwal_value_ref(rr);
    cwal_value_unref(xrv);
    cwal_value_unhand(rr);
    xrv = rr;
    rc = 0;
    /* s2_engine_err_reset(se); */
  }else if(xrv){
    assert(rv);
    if(!rc) cwal_value_unhand(xrv)
              /* we pass xrv on down below */;
    else{
      cwal_value_unref(xrv);
      xrv = 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.traceTokenStack){
      s2_dump_val(*rv, "s2_eval_ptoker result");
    }
  }
  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;
  S2_UNUSED_ARG kw;
  S2_UNUSED_ARG se;
  S2_UNUSED_ARG pr;
  *rv = cwal_value_undefined();
  rc = 0 /* place breakpoint here */;
  /**
     TODO:

     add an interactive breakpoint callback hook, which the client can
     optionally start running when the hook is called. e.g. s2sh might
     (only in interactive mode) switch (back) to interactive mode in
     the callback, returning here when it's done. We'd need to install
     some convenience symbols here:

     __SCOPE = vars
  */
  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_UNUSED_ARG kw;
        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){
  s2_linecol_t 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, s2_ptoken_begin(&pr->token), &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));
      break;
    }
    case S2_T_KeywordLINE:
      s2_ptoker_count_lines( pr, s2_ptoken_begin(&pr->token), &line, 0);
      *rv = cwal_new_integer(se->e, (cwal_int_t)line);
      break;
    case S2_T_KeywordCOLUMN:
      s2_ptoker_count_lines( pr, s2_ptoken_begin(&pr->token), 0, &col);
      *rv = cwal_new_integer(se->e, (cwal_int_t)col);
      break;
    case S2_T_KeywordSRCPOS:
      s2_ptoker_count_lines( pr, s2_ptoken_begin(&pr->token), &line, &col);
      script = s2_ptoker_name_first(pr, &scriptLen);
      *rv = (line>0)
        ? cwal_string_value(script
                            ? cwal_new_stringf(se->e, "%.*s:%d:%d",
                                               (int)scriptLen, script,
                                               line, col)
                            : cwal_new_stringf(se->e, "unnamed script:%d:%d",
                                               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_exception( s2_keyword const * kw, s2_engine * se,
                            s2_ptoker * pr, cwal_value **rv){
  int rc;
  cwal_array * args = 0;
  cwal_value * argsV = 0;
  cwal_size_t argc = 0;
  s2_ptoker sub = s2_ptoker_empty;
  s2_ptoken const origin = pr->token;
  rc = s2_next_token( se, pr, 0, 0 );
  if(rc) return rc;
  else if(pr->token.ttype != S2_T_ParenGroup){
    /* 20191228: if the exception keyword is followed by anything
       other than a'(', it resolves to the exception prototype. */
    s2_ptoker_putback(pr);
    *rv = se->skipLevel
      ? cwal_value_undefined()
      : cwal_prototype_base_get( se->e, CWAL_TYPE_EXCEPTION );
    return 0;
  }
  /*MARKER(("exception: %.*s\n",
    (int)s2_ptoken_len(&pr->token), s2_ptoken_begin(&pr->token))); */
  *rv = cwal_value_undefined();
  if(rc || se->skipLevel>0) goto end;

  args = cwal_new_array(se->e);
  if(!args){
    rc = CWAL_RC_OOM;
    goto end;
  }
  argsV = cwal_array_value(args);
  cwal_value_ref(argsV);
  cwal_value_make_vacuum_proof(argsV, 1);

  rc = s2_ptoker_sub_from_toker(pr, &sub);
  if(!rc) rc = s2_eval_to_array(se, &sub, args, 0);
  s2_ptoker_finalize(&sub);
  if(rc) goto end;
  argc = cwal_array_length_get(args);
  /* s2_dump_val(argsV,"argsV"); */
  if(argc==1 || argc==2){
    /* 
       (ARG) is equivalent to what we get with (throw ARG), namely
       that the 'message' property === ARG.

       (ARG1, ARG2) sets the 'code' property to ARG1 and the
       'message' property to ARG2.
    */
    int exceptionCode = CWAL_RC_EXCEPTION;
    if(argc>1){
      /* Try to parse result code from exception(CODE,message)...
         Accept an integer or string in the form "CWAL_RC_name"
         resp. "S2_RC_name".
      */
      cwal_value * arg0 = cwal_array_get(args, 0);
      cwal_value *hv = s2_stash_get(se, "RcHash")
        /* Optimization: if the stashed RcHash (set up in s2.c) is
           available, check it first. This avoids having to allocate
           x-strings which we know are already in that hash. It also
           incidentally supports a reverse mapping, such that passing in
           the string 'CWAL_RC_OOM' will return its integer value.
        */;
      *rv = 0;
      if(hv){
        cwal_hash * h = cwal_value_get_hash(hv);
        assert(h);
        *rv = cwal_hash_search_v(h, arg0);
        if(*rv){
          if(!cwal_value_is_integer(*rv)){
            *rv = cwal_hash_search_v(h, *rv) /* reverse mapping */;
            assert(*rv);
            assert(cwal_value_is_integer(*rv));
          }
          exceptionCode = (int)cwal_value_get_integer(*rv);
        }
      }
      if(!*rv){
        /* Try argument as a string (enum entry name) or integer (enum
           entry value)... */
        cwal_size_t nameLen = 0;
        char const * codeName = cwal_value_get_cstr(arg0, &nameLen);
        if(codeName && s2_cstr_to_rc(codeName, (cwal_int_t)nameLen, &exceptionCode)){
          if(! (*rv = cwal_new_integer(se->e, (cwal_int_t)exceptionCode)) ){
            rc = CWAL_RC_OOM;
          }
        }else{
          exceptionCode = (int)cwal_value_get_integer(arg0);
        }
      }
      *rv = 0;
    }
    switch(exceptionCode){
      case 0:
      case CWAL_RC_OOM: /* don't allow OOM passed in here to crash the script engine */
        exceptionCode = CWAL_RC_EXCEPTION;
        break;
    }
    s2_ptoker_errtoken_set(pr, &origin);
    rc = s2_throw_value( se, pr, exceptionCode,
                         cwal_array_get(args, argc==1 ? 0 : 1));
    if(CWAL_RC_EXCEPTION==rc){
      /* Success! Now take away the newly-populated exception. */
      cwal_value * exv = cwal_exception_get(se->e);
      assert(exv);
      /* s2_dump_val(exv,"exv"); */
      assert(cwal_value_exception_part(se->e,exv));
      cwal_value_ref(exv);
      cwal_exception_set(se->e, 0);
      cwal_value_unhand(exv);
      s2_engine_err_reset(se);
      *rv = exv;
      rc = 0;
    }
  }else{
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "%s(...) requires (Message) or "
                       "(int|string Code, Message) arguments.",
                       kw->word);
  }

  end:
  if(argsV){
    cwal_value_make_vacuum_proof(argsV, 0);
    cwal_value_unref(argsV);
  }
  return rc;
}

int s2_keyword_f_eval( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  cwal_scope scope_ = cwal_scope_empty;
  cwal_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 -> or => 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;
  int scopeFlags = S2_SCOPE_F_NONE;
  int isBlock = 0;
  switch(kw->id){
    /* 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:
#if 0
      /* i don't like this inconsistency */
      evalFlags = S2_EVAL_EMPTY_GROUP_OK;
#endif
      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,
    or whether it uses one of the -> or => modifiers...
  */
  {
    s2_ptoker next = *pr;
    next_token: /* modifiers cause us to goto here */
    rc = s2_next_token(se, &next, 0, 0);
    if(rc) return rc;
    else{
      switch(next.token.ttype){
        case S2_T_SquigglyBlock:
          sourceType = next.token.ttype;
          s2_ptoker_token_set(pr, &next.token)/*consume it*/;
          break;
        case S2_T_OpArrow2:
          switch(kw->id){
            case S2_T_KeywordBreak:
            case S2_T_KeywordEval:
            case S2_T_KeywordExit:
            case S2_T_KeywordFatal:
            case S2_T_KeywordReturn:
              break;
            default:
              return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                   "The => modifier can only be used "
                                   "with these keywords: "
                                   "break, eval, exit, fatal, return");
          }
          CWAL_SWITCH_FALL_THROUGH;
        case S2_T_OpArrow:
          if(!modifier){
            modifier = next.token.ttype;
            s2_ptoker_token_set(pr, &next.token) /* consume it */;
            goto next_token;
          }else{
            return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                 "Unexpected extra modifier token '%.*s'.",
                                 (int)s2_ptoken_len(&next.token),
                                 s2_ptoken_begin(&next.token));
          }
          break;
        default:
          s2_ptoker_next_token_set(pr, &next.token)
            /* hits fairly often: just over 400 out of 10457
               next-token requests in the current UNIT.s2
               amalgamation. */;
          break;
      }
    }
  }

  isBlock = (sourceType == S2_T_SquigglyBlock) ? 1 : 0
    /* treat heredocs as strings for eval purposes */;
  if(!isBlock){
    /*
      Consider: (1 ? catch 2 : 3)

      Without this flag, the ':' after 'catch 2' is seen as a
      standalone token with no '?'  counterpart (causing a syntax
      error). Thus we have to set a flag to tell downstream code that
      the ':' ends that (sub)expression.
    */
    scopeFlags = S2_SCOPE_F_KEEP_TERNARY_LEVEL;
  }

  if(scope){
    rc = s2_scope_push_with_flags(se, scope, scopeFlags);
    if(rc) return rc;
    assert(scope->parent);
  }

  exprPos = pr->token;
  /* As of here, don't use 'return': use goto end. */

  if(isBlock){
    if(se->skipLevel>0){
      /* Skip it! */
      goto end;
    }
    else if(S2_T_OpArrow2 == modifier){
      /* eval=>{block} result = the block body as a string. */
      char const * beg;
      cwal_size_t len;
      s2_ptoken const * block = &pr->token;
      assert(S2_T_SquigglyBlock==block->ttype);
      assert(s2_ptoken_adjbegin(block));
      assert(s2_ptoken_adjend(block) &&
             s2_ptoken_end(block) >= s2_ptoken_begin(block)+2);
      /* Should we keep leading/trailing spaces or not? */
#if 0
      /* Keep spaces... */
      beg = s2_ptoken_begin(block) + 1 /* skip opening '{' */;
      len = (cwal_size_t)s2_ptoken_len(block)-2
        /* skip trailing '}' */;
#else
      /* Strip spaces... */
      beg = s2_ptoken_cstr2( block, &len );
#endif
      xrv = cwal_new_string_value(se->e, beg, len);
      if(!xrv) rc = CWAL_RC_OOM;
      else cwal_value_ref(xrv);
    }
    else{
      /* Eval the block... */
      switch(kw->id){
        case S2_T_KeywordBreak:
        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.  We
             eval it as a normal expression, as opposed to an Object
             literal, to pick up object literals and any following
             operators. */
          s2_ptoker_next_token_set(pr, &pr->token) /* avoids re-tokenization effort... */;
          /* MARKER(("pr->nextToken = pr->token\n")); */
          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
          */
          cwal_value_ref(xrv);
          break;
        default:
          /* Treat {} as a script block */
          rc = s2_eval_current_token( se, pr, 0, 0, &xrv );
          cwal_value_ref(xrv);
          break;
      }
      /* s2_dump_val(xrv, "EVAL result"); */
      if(!rc && !xrv) xrv = cwal_value_undefined();
    }
  }else{
    /* Not a block construct. Behave mostly as if the keyword wasn't
       there (except that we ignore LHS operators). */
    s2_op const * pseudoOp =
#if 1
      0
#else
      (sourceType == S2_T_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
      ;
    char const isArrow2 = (S2_T_OpArrow2 == modifier) ? 1 : 0;
    /* MARKER(("%s'ing around %.10s ...\n", kw->word,
       s2_ptoken_begin(&exprPos))); */

    if(isArrow2){
        ++se->skipLevel
            /* so that we capture the full text of the expression
               without "really" executing it. */;
    }
    rc = s2_eval_expr_impl( se, pr, pseudoOp, evalFlags, &xrv );
    cwal_value_ref(xrv);
    if(isArrow2) --se->skipLevel;
    if(isArrow2 && !rc){
      /* Capture the expression's text as a string. For consistency
         with other eval contexts, treat an empty expression (as
         opposed to an empty script string) as an error. */
      assert(0==xrv || cwal_value_undefined()==xrv);
      /* Capture the text of the expression as the result. */
      if(se->skipLevel){
        xrv = cwal_value_undefined();
      }else{
        cwal_size_t capLen = 0;
        char const * cap = s2_ptoker_capture_cstr(pr, &capLen);
        assert(cap);
        /*MARKER(("OpArrow2 capture: <<<%.*s>>>\n",(int)capLen, cap));*/
        while(capLen>0 && s2_is_space(*cap)){
          /* Skip leading spaces for this specific case because
             "it just looks funny" otherwise. */
          /* Leading space hypothetically cannot happen with the
             two-token capture approach, but better safe than sorry
             (it's conceivable, but seems unlikely, that a newline
             could slip through this way). */
          ++cap;
          --capLen;
        }
        while(capLen>0 && s2_is_space(cap[capLen-1])){
          /* Skip trailing spaces for consistency with the {block} form.*/
          --capLen;
        }
        if(!capLen){
          rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             "Empty expression is not allowed here.");
          goto end;
        }
        assert(capLen);
        xrv = cwal_new_string_value(se->e, cap, capLen);
        if(!xrv) rc = CWAL_RC_OOM;
        else cwal_value_ref(xrv);
      }
    }
    /* s2_dump_val(xrv, "EVAL result"); */
    if(!rc && !xrv){
      assert(!isArrow2);
      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_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.";
          }
          s2_ptoker_errtoken_set(pr, &origin);
          rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             errMsg, kw->word);
        }
      }
    }
  }
  ++phase;

  if(!rc
     && !se->skipLevel
     && S2_T_OpArrow == modifier /* got -> modifier */
     && cwal_value_is_string(xrv)
     /* ^^^ We don't want this upcoming cwal_value_get_cstr() to get
        source code from a Buffer because (partly) of potentially
        fatal corner-cases if that Buffer is modified during/via the
        eval. */
     ){
    /* second-pass evaluation: eval -> expr */
    cwal_size_t vlen = 0;
    char const * vstr = cwal_value_get_cstr(xrv, &vlen);
    if(vstr){
      s2_ptoker sub = s2_ptoker_empty;
      assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv));
      rc = s2_ptoker_init_v2( se->e, &sub, vstr, (int)vlen, 0 );
      if(!rc){
        /* 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 * xrv2 = 0;
        ++se->scopes.current->sguard.vacuum;
        s2_ptoker_count_lines( pr, s2_ptoken_end(&exprPos),
                               &sub.lineOffset,
                               &sub.colOffset );
        sub.name = s2_ptoker_name_first(pr, 0)
          /* 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; wrong: sub is dynamic text */
        rc = s2_eval_ptoker(se, &sub,
                            S2_EVALP_F_PROPAGATE_RETURN,
                            &xrv2)
          /* ..._RETURN flag needed so that (eval -> 'return 1')
             behaves just like (eval -> return 1) does.*/
          ;
        --se->scopes.current->sguard.vacuum;
        cwal_value_ref(xrv2);
        cwal_value_unref(xrv);
        xrv = xrv2;
      }
      s2_ptoker_finalize(&sub);
      ++phase;
    }
  }/* end second-pass eval */
  end:
  if(se->skipLevel>0){
    if(xrv){
      assert(cwal_value_undefined() == xrv);
    }else{
      xrv = 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){
          if(xrv){
            assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv));
          }else{
            xrv = cwal_value_undefined();
          }
          s2_propagating_set(se, xrv);
          cwal_value_unref(xrv) /* ^^^ took a reference */;
          xrv = 0; 
          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;
            default:
              s2_fatal(CWAL_RC_ASSERT,
                       "Cannot happen: invalid nested switch() values.");
          }
          /*
            But we need to keep the error location...
          */
          {
            s2_ptoken err = origin;
            s2_ptoken_begin_set(&err, s2_ptoken_end(&origin));
            s2_ptoken_end_set(&err, s2_ptoken_end(&err));
            s2_ptoker_errtoken_set(pr, &err);
            rc = s2_err_ptoker(se, pr, rc, 0);
          }
        }
        break;
      case S2_T_KeywordThrow:
        if(!rc){
          if(xrv){
            assert(cwal_value_refcount(xrv) || cwal_value_is_builtin(xrv));
          }else{
            xrv = cwal_value_undefined();
          }
          s2_ptoker_errtoken_set(pr, &origin);
          rc = s2_throw_value( se, pr, CWAL_RC_EXCEPTION, xrv);
            /* rc will be CWAL_RC_EXCEPTION or something more serious
               (CWAL_RC_OOM) */;
          assert(rc);
          cwal_value_unref(xrv) /* throwing took a refrence to it. */;
          xrv = 0;
        }
        break;
      case S2_T_KeywordCatch:
        cwal_value_unref(xrv)
          /* discard any would-propagate result */;
        xrv = 0;
        switch(rc){ /* Convert certain errors to exceptions... */
          case 0:
            xrv = cwal_value_undefined();
            break;
          case CWAL_SCR_SYNTAX:
            /* MARKER(("pr->errPos=[%.30s...]\n", pr->errPos ? pr->errPos : "<NULL>")); */
            /* MARKER(("se->opErrPos=[%.30s...]\n", se->opErrPos ? se->opErrPos : "<NULL>")); */
            if(!se->err.code){
              break /* we have nothing to report */;
            }else if(phase < 2 && S2_T_SquigglyBlock != 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 are
              (ostensibly) 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.
            */
            CWAL_SWITCH_FALL_THROUGH;
          case CWAL_RC_EXCEPTION:
            xrv = cwal_exception_get(se->e);
            assert(xrv &&
                   "Downstream code failed to call cwal_exception_set().");
            /* 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->parent, xrv);
            }
            cwal_value_ref(xrv);
            cwal_exception_set(se->e, 0);
            cwal_value_unhand(xrv);
            /* s2_dump_val(xrv,"exception after set-exception 0"); */
            assert((cwal_value_scope(xrv) || cwal_value_is_builtin(xrv))
                   && "Premature cleanup on isle 7.");
            assert(!cwal_exception_get(se->e));
            rc = 0
              /* we will propagate xrv as the non-error result. */;
            break;
          default:
            break;
        }
        break;
        /* end S2_T_KeywordCatch */
      default:
        /* Propagate anything else... */
        if(!rc){
          /* we'll allow the NULL==>undefined translation here for
             sanity's sake of sanity: eval -> "". An empty string is a
             valid script, but results in NULL instead of the
             undefined value, and keywords are generally not supposed
             to set *rv to NULL.
          */
          if(S2_T_OpArrow == modifier && !xrv){
            xrv = cwal_value_undefined();
          }
        }
        rc = s2_rv_maybe_accept(se, 0, rc, 0);
        break;
    }
  }
  if(rc || !rv){
    cwal_value_unref(xrv);
    xrv = 0;
  }else{
    if(rv) *rv = xrv ? xrv : cwal_value_undefined();
    cwal_value_unhand(xrv);
  }
  if(scope){
    /* We pushed a scope */
    assert(se->scopes.current->cwalScope == scope);
    assert(s2_scope_current(se)->cwalScope == scope);
    cwal_scope_pop2(se->e, rc ? 0 : xrv);
    assert(!scope->e);
    assert(!scope->props);
  }
  return rc;
}

int s2_keyword_f_reserved( s2_keyword const * kw, s2_engine * se,
                           s2_ptoker * pr, cwal_value **rv){
  if(se->skipLevel>0){
    /* Workaround: so that if/else/etc blocks which traverse these
       don't cry. But then they cry about token ordering. Hmm.
    */
    *rv = cwal_value_undefined();
    return 0;
  }else{
    return s2_err_ptoker( se, pr,
                          CWAL_SCR_SYNTAX
                          /* need for ^^^^ 'catch' to intercept this */,
                          "'%s' is a reserved keyword.",
                          kw->word);
  }
}

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;
  cwal_value * xrv = 0;
  assert(S2_T_KeywordAssert==pos.ttype
         || S2_T_KeywordAffirm==pos.ttype);
  rc = s2_eval_expr_impl(se, pr, 0, 0, &xrv);
  if(rc){
    if(kw){/*avoid unused param warning*/}
    assert(!xrv && "expecting NULL result value on error.");
    return rc;
  }
  else if(!xrv){
    s2_ptoker_errtoken_set(pr, &pos);
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "Unexpected empty expression");
  }
  else if(se->skipLevel>0){
    assert(cwal_value_undefined()==xrv);
    *rv = cwal_value_undefined();
    return 0;
  }else{
    cwal_size_t capLen = 0;
    char const * capStr = s2_ptoker_capture_cstr(pr, &capLen);
    char const isAssert = (S2_T_KeywordAssert==pos.ttype) ? 1 : 0;
    char buul;
    cwal_value_ref(xrv);
    buul = cwal_value_get_bool(xrv);
    cwal_value_unref(xrv);
    xrv = 0;
    if(!buul){
      if(se->flags.traceAssertions > 1){
        cwal_outputf(se->e, "%s FAILED: %.*s\n",
                     isAssert ? "ASSERTION" : "AFFIRMATION",
                     (int)capLen, capStr);
      }
      s2_ptoker_errtoken_set(pr, &pos);
      return isAssert
        ? s2_err_ptoker(se, pr, CWAL_RC_ASSERT,
                        "Assertion failed: %.*s",
                        (int)capLen, capStr)
        : s2_throw_ptoker(se, pr, CWAL_RC_ASSERT,
                          "Affirmation failed: %.*s",
                          (int)capLen, capStr);
    }else{
      if(isAssert) ++se->metrics.assertionCount;
      if(se->flags.traceAssertions>1
         || (isAssert && se->flags.traceAssertions>0)){
#if 0
        /* testing token-level line/column count.
           These are off. */
        cwal_size_t slen = 0;
        char const * script = s2_ptoker_name_first(pr, &slen);
        int l = 0, c = 0;
        /* s2_ptoken_adjusted_lc(pr, &ast, &l, &c); */
        s2_ptoker_count_lines2( pr, &pr->capture.begin, &l, &c );
        cwal_outputf(se->e, "%.*s:%d:%d: %s passed: %.*s\n",
          (int)slen, script, l, c,
          isAssert ? "Assertion" : "Affirmation",
          (int)capLen, capStr);
#else
        cwal_outputf(se->e, "%s passed: %.*s\n",
          isAssert ? "Assertion" : "Affirmation",
          (int)capLen, capStr);
#endif
      }
      *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;
  s2_ptoken const origin = pr->token;
  cwal_value * xrv = 0;
  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,
                          &xrv );
  /* s2_dump_val(xrv, "TYPENAME result"); */
  if(rc) return rc;
  else if(!xrv){
    s2_ptoker_errtoken_set(pr, &origin);
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "%s requires a non-empty expression.",
                       kw->word);
  }else if(se->skipLevel>0){
    assert(cwal_value_undefined()==xrv);
    *rv = cwal_value_undefined();
  }else{
    cwal_value * vTn;
    cwal_value_ref(xrv);
    vTn = cwal_prop_get_v(xrv, se->cache.keyTypename)
      /* If interested, see the notes in s2_keyword_f_typeinfo(),
         about TYPEINFO_NAME, for why we first look for this
         property. */;
    if(!vTn){
      cwal_size_t tlen = 0;
      char const * tn;
      tn = cwal_value_type_name2( xrv, &tlen );
      if(!tn){
        assert(!"Can't really happen, can it?");
        tn = cwal_type_id_name(CWAL_TYPE_UNDEF);
        tlen = cwal_strlen(tn);
      }
      vTn = cwal_new_string_value(se->e, tn, tlen);
      if(!vTn) rc = CWAL_RC_OOM;
    }
    cwal_value_ref(vTn);
    cwal_value_unref(xrv);
    if(rc){
      assert(NULL == vTn);
      cwal_value_unref(vTn) /* noop, but for symmetry */;
    }else{
      assert(NULL != vTn);
      *rv = vTn;
      cwal_value_unhand(vTn);
    }
  }
  return rc;
}

int s2_keyword_f_refcount( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
#if 1
  S2_UNUSED_ARG(rv);
  return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX,
                        "'%s' is so gauche. Use pragma(refcount ...) "
                        "instead!", kw->word );
#else
  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){
    assert(!xrv);
  }else{
    if(!xrv){
      s2_ptoker_errtoken_set(pr, &origin);
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "%s requires an RHS expression",
                                kw->word);
    }else if(se->skipLevel>0){
      assert(cwal_value_undefined()==xrv);
      *rv = cwal_value_undefined();
    }else{
      *rv = cwal_new_integer(se->e, (cwal_int_t)cwal_value_refcount(xrv));
      cwal_refunref(xrv);
      rc = *rv ? 0 : CWAL_RC_OOM;
    }
  }
  return rc;
#endif
}

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){
    s2_ptoker_token_set(pr, &next);
    *rv = cwal_value_undefined();
    return 0;
  }else{
    cwal_value * xrv = 0;
    cwal_size_t tlen = s2_ptoken_len(&next);
    assert(s2_ptoken_begin(&next)<s2_ptoken_end(&next));
    rc = s2_get( se, 0, s2_ptoken_begin(&next), tlen, &xrv );
    if(rc) return rc;
    else if(!xrv){
      s2_ptoker_errtoken_set(pr, &next);
      return s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND,
                           "Cannot resolve identifier '%.*s'",
                           (int)tlen, s2_ptoken_begin(&next));
    }else{
      assert((S2_T_KeywordNameof==pr->token.ttype)
             && "lookahead broken?");
      s2_ptoker_token_set(pr, &next);
      *rv = cwal_new_string_value(se->e, s2_ptoken_begin(&next), 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 declFlags;
  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;
  declFlags = isConst ? CWAL_VAR_F_CONST : 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)){
      /* empty function argument list */
      return 0;
    }else{
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "'%s' list requires an identifier argument, "
                           "but got token type %d with contents [%.*s].",
                           kw->word, pr->token.ttype,
                           (int)s2_ptoken_len(&pr->token),
                           s2_ptoken_begin(&pr->token));
    }
  }
  gotComma = 0;
  ident = pr->token;
  if(!se->skipLevel){
    if(s2_ptoken_keyword2(se, &ident)){
      /* Confirm that it's not a keyword, as the user would not be
         able to use the var/param properly. Throw vs error:
         s2_err_ptoker() is ostensibly fatal to a script, but
         subscripts will, on error propagation, convert this to
         an exception, so a sub-/imported script won't kill a
         top-level script this way. Hmmm... if this is an Error
         then it's difficult to test (our current unit tests
         will abort when they hit it). We'll make this an
         Exception for the time being:
      */
      return s2_throw_ptoker(se, pr, CWAL_RC_ALREADY_EXISTS,
                             "Cannot use keyword '%.*s' "
                             "as a %s name.",
                             (int)s2_ptoken_len(&ident),
                             s2_ptoken_begin(&ident),
                             argv ? "parameter" : "variable");
      
    }else if(cwal_scope_search(/*&se->currentScope->scope
                                 ^^^^
                                 cwal_function_call() pushes a scope
                                 which s2 doesn't have an s2_scope
                                 for, so we need to look in
                                 se->e->current instead, else an
                                 overloaded operator's declared vars
                                 can report a collision where there is
                                 really none. Been there, done that.

                                 20191117: that "shouldn't" be an
                                 issue since the cwal API added the
                                 scope-push/pop hooks, as the scopes
                                 are now in sync. This setup continues
                                 to work, though, so we'll leave it as
                                 is.
                               */
                                se->e->current,
                                0, s2_ptoken_begin(&ident),
                                s2_ptoken_len(&ident),
                                0)){
      /* Check for dupe symbols before evaluating the RHS. This
         "need" was uncovered by the sqlite3 module. It's a
         small performance hit, though. */
      return s2_throw_ptoker(se, pr, CWAL_RC_ALREADY_EXISTS,
                             "Symbol '%.*s' is already declared "
                             "in the current scope.",
                             (int)s2_ptoken_len(&ident),
                             s2_ptoken_begin(&ident));
    }
  }
  rc = s2_next_token( se, pr, 0, &next);
  if(rc) return rc;
  switch(next.ttype){
    case S2_T_Comma:
      gotComma = 1;
      CWAL_SWITCH_FALL_THROUGH;
    case S2_T_EOF:
    case S2_T_EOX:
      v = isConst ? 0 : cwal_value_undefined();
      s2_ptoker_token_set( pr, &next ) /* consume it */;
      break;
    case S2_T_OpColonEqual:
      if(argv){
        return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             "The := operator is not permitted on function "
                             "parameters because of syntactic inconsistencies "
                             "with regards to parameters with resp. without "
                             "default values.");
      }
      declFlags = CWAL_VAR_F_CONST;
      CWAL_SWITCH_FALL_THROUGH;
    case S2_T_OpAssign:
      s2_ptoker_token_set(pr, &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 0
      if(rc){
        /* If we disallow return/break/continue here, then we cannot
           do:

           const ex = catch { ... return foo };

           and i think that should work.
        */
        return s2_check_brc(se, pr, rc,
                            isConst
                            ? "const declaration"
                            : "var declaration");
      }
      else
#else
      if(rc){
        assert(!v && "Internal API misuse.");
        return rc;
      }
      else
#endif
      if(!v /* && !s2_ptoker_is_eof(pr) */){
        if(!isConst && S2_T_OpColonEqual == next.ttype){
          return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                               "%s declaration with := requires a value.",
                               kw->word, (int)s2_ptoken_len(&ident),
                               s2_ptoken_begin(&ident));
        }else{
          return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                               "Empty expression is not permitted "
                               "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)s2_ptoken_len(&next),
                           s2_ptoken_begin(&next),
                           argv
                             ? "parameter"
                             : (isConst?"const":"var"),
                           (int)s2_ptoken_len(&ident),
                           s2_ptoken_begin(&ident));
  }
  if(!v){
    if(isConst){
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "%s decl '%.*s' requires a value.",
                           kw->word, (int)s2_ptoken_len(&ident),
                           s2_ptoken_begin(&ident));
    }
    v = cwal_value_undefined();
  }

  if(!se->skipLevel){
    cwal_size_t const tlen = s2_ptoken_len(&ident);
    /* s2_dump_val(v, "var decl before"); */
    if(argv && argPos<argc){
      /* Value at this index is already in argv. */
      v = cwal_array_get(argv, argPos);
    }
    rc = s2_var_decl(se, s2_ptoken_begin(&ident), tlen, v, declFlags);
    if(CWAL_RC_ALREADY_EXISTS==rc){
      assert(!"CWAL_RC_ALREADY_EXISTS is checked earlier now.");
      s2_ptoker_errtoken_set(pr, &ident);
      return s2_throw_ptoker(se, pr, rc,
                            "Symbol '%.*s' is already declared "
                            "in the current scope.",
                             (int)tlen, s2_ptoken_begin(&ident));
    }
    else if(rc){
      return rc;
    }
    /* MARKER(("Declared %s '%.*s'\n", kw->word, (int)tlen,
       s2_ptoken_begin(&ident))); */
    /* 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, S2_NEXT_NO_POSTPROCESS, S2_T_Comma, 1)){
      gotComma = 1;
    }
  }
  assert(!rc);
  rc = s2_check_interrupted(se, rc);
  if(!rc){
    if(gotComma) goto next_part;
    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;
  char gotComma;
  int identLen;
  assert(S2_T_KeywordUnset==pr->token.ttype);
  next_part:
  origin = pr->token;
  rc = s2_next_token( se, pr, 0, 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;
  ident = pr->token;
  identLen = (int)s2_ptoken_len(&ident);
  /* 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;
      s2_ptoker_token_set( pr, &next ) /* consume it */;
      CWAL_SWITCH_FALL_THROUGH;
    case S2_T_EOF: /* e.g. typing "unset x" in s2sh */
    case S2_T_EOX:{
      if(!se->skipLevel){
        cwal_kvp * kvp = cwal_scope_search_kvp(se->e->current, 0,
                                               s2_ptoken_begin(&ident),
                                               (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, s2_ptoken_begin(&ident));
        }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, s2_ptoken_begin(&ident));
        }
        rc = s2_handle_set_result(se, pr,
                                  s2_set( se, 0, s2_ptoken_begin(&ident),
                                          (cwal_size_t)identLen, 0));
        /* MARKER(("Unsetting ident [%.*s] rc=%d\n",
           (int)s2_ptoken_len(&ident), s2_ptoken_begin(&ident), 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->dotOp.lhs and se->dotOp.key;

        That's full of weird corner cases, though:

        unset a.b = c.d

        20191117: that ends up unsetting (without error) c.d because
        se->dotOp refers, at the point where the unset is resolved, to
        c.d instead of a.b. Hmmm. Maybe we need a new
        S2_EVAL_FOR_UNSET flag, which only allows identifiers and
        dot-op, throwing if any other operators are seen
        access... except that in order to know whether we're doing a
        dot-op, we first have to eval the LHS of that dot op
        (e.g. unset (someFuncCall()).foo is/needs to be legal). Hmmm.
        The only solution i currently see is to skip-mode the first
        eval phase, store its source position, then check for a
        dot. If it's a dot, go back and eval that LHS (whatever it
        is), else check if the LHS is an identifier, and fail if it's
        not. Hmmm.

        So far such a case has never happened in practice, so priority
        for "fixing" it is low, but it's still an unsightly behaviour.
      */
      s2_ptoker_token_set(pr, &origin);
      rc = s2_eval_expr_impl(se, pr, pseudoOp,
                             S2_EVAL_RETAIN_DOTOP_STATE, 0);
      if(rc) return rc;
      else if(!se->skipLevel){
        if(!se->dotOp.lhs || !se->dotOp.key){
          s2_ptoker_errtoken_set(pr, &ident);
          return s2_throw_ptoker( se, pr, CWAL_SCR_SYNTAX,
                                  "Illegal RHS for %s operation.",
                                  kw->word);
        }
        cwal_value_ref(se->dotOp.lhs);
        cwal_value_ref(se->dotOp.key);
        /* Do we want to support: unset hash # key? */
        rc = s2_handle_set_result(se, pr,
                                  s2_set_v( se, se->dotOp.lhs, se->dotOp.key, 0 ));
        cwal_value_unhand(se->dotOp.lhs);
        cwal_value_unhand(se->dotOp.key);
        s2_dotop_state(se, 0, 0, 0);
        if(rc) return rc;
      }
      if(s2_ptoker_next_is_ttype(se, pr, S2_NEXT_NO_POSTPROCESS, S2_T_Comma, 1)){
        goto next_part;
      }
      break;
    }
    default:
      s2_ptoker_errtoken_set(pr, &next);
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Unexpected token type %s in %s.",
                           s2_ttype_cstr(next.ttype),
                           kw->word
                           /*(int)s2_ptoken_len(&ident),
                             s2_ptoken_begin(&ident)*/);
  }
  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;
  cwal_scope _SCOPE = cwal_scope_empty;
  cwal_scope * scope = 0;
  cwal_value * xrv = 0;
  char buul = 0;
  char finalElse = 0;
  char hasTrued = 0;
  int runCount = 0;
  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_SquigglyBlock != tBody.ttype
        && S2_T_Heredoc/*historical behaviour*/ != tBody.ttype){
    if(s2_ttype_is_eox(tBody.ttype)){
      /* special case: empty body. */
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Empty non-block expression is not allowed.");
      goto end;
    }
    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){
        s2_ptoker_token_set(pr, &tCondition);
      }/* else the token is already placed where it needs to be */
      /* MARKER(("pr->token=%.*s...\n",10, s2_ptoken_begin(&pr->token))); */
      if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))){
        goto end;
      }else if(s2_eval_is_eox(se, pr)
              /* is an expression ending with an EOX */){
        /* FIXME: won't work as-is with #compiled tokens */
        tBody = pr->capture.begin;
        s2_ptoken_end_set(&tBody, s2_ptoken_begin(&pr->capture.end));
        /*MARKER(("CAPTURE: %.*s\n", (int)s2_ptoken_len(&tBody),
          s2_ptoken_begin(&tBody)));*/
        bodyIsExpr = 1;
        break;
      }else{
        MARKER(("???\n"));
        assert(!"what happens here?");
      }
    }
    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 = cwal_scope_push2(se->e, scope);
    if(rc) return rc;
  }

  /* Eval the condition... */
  s2_ptoker_token_set(pr, &tCondition);
  /* Reminder: we need to eval-sub, even in skip mode,
     for the corner case of an empty (). */
  rc = s2_eval_current_token( se, pr, 0, 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:
  /*MARKER(("tBody: %.*s\n", (int)s2_ptoken_len(&tBody),
    s2_ptoken_begin(&tBody)));*/

  /* assert(S2_T_SquigglyBlock==pr->token.ttype || bodyIsExpr); */
  if(!finalElse && (!buul || hasTrued || se->skipLevel>0)){
    s2_ptoker_token_set(pr, &tBody);
    if(bodyIsExpr){
      s2_next_token(se, pr, 0, 0)
        /* Slurp up the semicolon */;
      assert(s2_ttype_is_eox(pr->token.ttype));
    }
    goto check_else;
  }

  /*
    Arguably slightly broken, but really not:

    1 ? if(1){ 1; } else 1 : 1
    ----------------------^^^

    ==> Unexpected ':' token (no X?Y:Z op in progress).

    The problem is one of scope. The 'else' is happening in a
    different scope than the '?', and ternary state explicitly does
    not span scopes (it's saved/reset/restored when pushing/popping
    scopes). That construct can be resolved by using a {block} or
    explicitly ending the else's expression with a semicolon.
  */
  s2_ptoker_token_set(pr, &tBody);
  rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0);
  if(!rc && bodyIsExpr){
    /*
      var a = 0;
      if(0) throw __FLC; else if(1) a = 1; else throw __FLC;
      -------------------------------------^^^^

      Could not resolve identifier 'else'

      tBody = "a = 1". rc from this eval is 0.
      capture after eval of the 2nd 'if' is "a = 1".
      pr is not at EOX afterwards: current token is S2_T_INVALID
      (because tBody is from the capture, not a valid token type,
      and pr is not advanced by s2_eval_current_token()).

      The problem is that the next token in pr, in that case, is an
      EOX, which s2_eval_current_token() is not getting/consuming, so
      we are seeing is as the end of this keyword's work and we're
      propagating back up to the eval loop, which then picks up the
      'else' and thinks it's an unknown identifier.

      As a workaround, we'll check the next token and, if it's an EOX,
      consume it so that the next/upcoming check of an 'else' part can
      DTRT. If it's not an EOX, we'll put it back and let the next part
      deal with it.
    */
    rc = s2_next_token(se, pr, 0, &next);
    if(!rc){
      if(s2_ttype_is_eox(next.ttype)) s2_ptoker_token_set(pr, &next) /* consume it */;
      else s2_ptoker_next_token_set(pr, &next) /* give it back for the next caller */;
    }
  }
  if(rc) goto end;
  else if(!hasTrued && !finalElse){
    hasTrued = 1;
    ++se->skipLevel;
  }
 
  check_else:
  if(finalElse){
    goto end;
  }
  next = s2_ptoken_empty;
  rc = s2_next_token(se, pr, 0, &next);

  /*MARKER(("bodyIsExpr=%d\n", bodyIsExpr));*/
  /*MARKER(("next: <<<%.*s>>>\n", (int)s2_ptoken_len(&next),
    s2_ptoken_begin(&next)));*/
#define TOK_IS_ELSE(TOKP) (4==(int)(s2_ptoken_len(TOKP))            \
                           && 0==memcmp(s2_ptoken_begin(TOKP),"else",4))
  bodyIsExpr = 0;
  /*MARKER(("after IF: <<<%.*s>>>\n", (int)s2_ptoken_len(&next),
    s2_ptoken_begin(&next)));*/
  if(!rc && TOK_IS_ELSE(&next)){
    s2_keyword const * ifCheck;
    s2_ptoken const theElse = next;
    s2_ptoker_token_set(pr, &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)){
      s2_ptoker_token_set(pr, &next) /*consume it*/;
      goto next_if;
    }else{
      /*MARKER(("final else: %s <<<%.*s>>>\n",
        s2_ttype_cstr(next.ttype),
        (int)s2_ptoken_len(&next), s2_ptoken_begin(&next)));*/
      if(S2_T_SquigglyBlock==next.ttype
         || S2_T_Heredoc==next.ttype){
        s2_ptoker_token_set(pr, &next) /* make sure we've got the right token type */;
      }else{
        s2_ptoker_token_set(pr, &theElse);
      }
      finalElse = 1;
      goto capture_body;
    }
  }
#undef TOK_IS_ELSE 
  end:
  if(hasTrued) --se->skipLevel;
  if(scope){
    assert(scope->parent);
    cwal_scope_pop(se->e);
  }
  if(!rc){
    *rv = se->skipLevel
      ? cwal_value_undefined()
      : (hasTrued
         ? cwal_value_true()
         : cwal_value_false())
      ;
  }
  /*MARKER(("tokenizer at: %.*s\n",
    (int)(s2_ptoker_end(pr) - s2_ptoken_end(&pr->token)),
s2_ptoken_end(&pr->token)));*/
  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();
    if(kw){/*avoid unused param warning*/}
    return 0;
  }else{
    return s2_err_ptoker(se, pr, CWAL_RC_CONTINUE, 0);
  }
}

/**
   Internal code consolidator for do/while/for/foreach loops.

   kw is the keyword on whose behalf this is working.

   Preconditions: pr's must be set up such that the next call to
   s2_next_token() (which this function makes) will fetch what we
   believe/hope is the loop body. This function determines whether
   it's a {block} or a single expression and sets *tBody to contain
   the block/expression contents.

   *bodyIsExpr is assigned 0 if tBody represents a {block}, else it's
   set to non-0.

   All loop types except for foreach() allow an empty body but require
   that non-{} expression bodies be explicitly EOX-terminated or end
   on an implicit EOX (e.g. the end of a block construct).

   Returns 0 on success. Any error must be propagated back to the
   loop's caller and the state of pr, tBody, and bodyIsExpr is
   unspecified.
*/
static int s2_keyword_loop_get_body( s2_keyword const * kw, s2_engine * se,
                                     s2_ptoker * pr, s2_ptoken * tBody,
                                     char * bodyIsExpr){
  /*s2_ptoken const origin = pr->token;*/
  char const allowEmptyBody = S2_T_KeywordForEach==kw->id ? 0 : 1;
  int rc = s2_next_token(se, pr, 0, 0);
  if(rc) return rc;
  *tBody = pr->token;
  if(S2_T_SquigglyBlock == tBody->ttype
     || S2_T_Heredoc/*historical behaviour*/ == tBody->ttype){
    if(!allowEmptyBody && !s2_ptoken_has_content(tBody)){
      goto empty_body;
    }else{
      *bodyIsExpr = 0;
      return 0;
    }
  }
  else if(s2_ttype_is_eox(tBody->ttype)){
    /* special case: empty body. */
    if(allowEmptyBody){
      /* all but foreach() allow an empty body but require that it ends with
         an explicit EOX. */
      *bodyIsExpr = 1;
      return 0;
    }else{
      goto empty_body;
      /* foreach() disallows an empty body because it'd do nothing
         at all except iterate (no visible side-effects). */
    }
  }else{
    /* Check for an expression... */
    /* MARKER(("pr->token=[%.*s]\n",(int)s2_ptoken_len(&pr->token),
       s2_ptoken_begin(&pr->token))); */
    s2_ptoker_next_token_set(pr, tBody);
    if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))) return rc;
    else if(s2_eval_is_eox(se, pr)
            /* is an expression ending with an EOX */){
      *tBody = pr->capture.begin;
      s2_ptoken_end_set(tBody, s2_ptoken_begin(&pr->capture.end))
        /* 20200107 FIXME: this can't work with #compiled tokens. */;
      *bodyIsExpr = 1;
#if 0
      {
        cwal_size_t n = 0;
        char const * str = s2_ptoken_cstr(tBody, &n);
        MARKER(("expr tBody = %.*s\n", (int)n, str));
        str = s2_ptoker_capture_cstr(pr, &n);
        MARKER(("pr->capture = %.*s\n", (int)n, str));
      }
#endif
      return 0;
    }
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting {...} or a single "
                         "expression after %s(...).",
                         kw->word);
  }
  empty_body:
  return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "%s may not have an empty body.",
                       kw->word);
}


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;
  cwal_scope scope = cwal_scope_empty;
  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... */
  rc = s2_keyword_loop_get_body(kw, se, pr, &tBody, &bodyIsExpr);
  /* Run the tBody while the tCondition evaluates to truthy... */
  while(!rc){
    char buul = 0;
    if(scope.parent){
      cwal_scope_pop(se->e);
      assert(!scope.parent);
    }
    rc = cwal_scope_push2(se->e, &scope);
    if(rc) break;
    assert(scope.parent);
    s2_ptoker_token_set(pr, &tCondition);
    assert(!xrv);
    rc = s2_eval_current_token( se, pr, 0, 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;
    }
    buul = cwal_value_get_bool(xrv);
    /* ref()/unref() combo kills temporaries, regardless of
       their owning scope, but leaves others effectively
       untouched. This is the same hack the for() loop uses -
       see explanation in s2_keyword_f_for(). */
    cwal_refunref(xrv);
    xrv = 0;
    if(!buul){
      s2_ptoker_token_set(pr, &tBody)
        /* consume the body. err... you know what i mean. */;
      break;
    }
    else{
      int doBreak = 0;
      s2_ptoker_token_set(pr, &tBody) /* set body up for eval */;
      if(se->skipLevel>0){
        /* we only need to get this far to ensure
           syntax rules are met. */
        break;
      }
      s2_ptoker_token_set(pr, &tBody);
      rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0);
      switch(rc){
        case CWAL_RC_BREAK:
          doBreak = rc;
          xrv = s2_propagating_take(se);
          assert(xrv);
          assert(scope.parent);
          cwal_value_rescope(scope.parent, xrv);
          rc = 0;
          cwal_value_ref(xrv) /* required when xrv is created in tCondition! */;
          s2_engine_err_reset(se);
          break;
        case CWAL_RC_CONTINUE:
          rc = 0;
          s2_engine_err_reset(se);
          break;
        default:
          break;
      }
      if(doBreak) break;
    }/* end body eval */
  }/* end while(!rc) */
  
  if(scope.parent){
    cwal_scope_pop2(se->e, (rc && !se->skipLevel) ? 0 : xrv);
    assert(!scope.parent);
  }
  if(rc){
    cwal_value_unref(xrv);
  }else{
    cwal_value_unhand(xrv);
    *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; */
  cwal_scope scope = cwal_scope_empty;
  cwal_value * xrv = 0;
  char bodyIsExpr = 0;
  /* Get the body part... */
  rc = s2_keyword_loop_get_body(kw, se, pr, &tBody, &bodyIsExpr);
  if(rc) return rc;
  /* 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 tBody while tCondition evaluates to truthy... */
  do{
    char doBreak = 0;
    if(scope.parent){
      cwal_scope_pop(se->e);
      assert(!scope.parent);
    }
    rc = cwal_scope_push2(se->e, &scope);
    if(rc) break;
    assert(scope.parent);

    /* Run the body... */
    xrv = 0;
    s2_ptoker_token_set(pr, &tBody) /* set body up for eval */;
    rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0);
    switch(rc){
      case CWAL_RC_BREAK:
        xrv = s2_propagating_take(se);
        assert(xrv && "Expecting 'break' keyword to set this.");
        assert(scope.parent);
        cwal_value_ref(xrv);
        doBreak = 1;
        CWAL_SWITCH_FALL_THROUGH;
      case CWAL_RC_CONTINUE:
        rc = 0;
        s2_engine_err_reset(se);
        break;
      default:
        break /* other, more serious, error,
                 or a 'return' or similar */;
    }
    if(rc || doBreak) break;
    s2_ptoker_token_set(pr, &tCondition);
    xrv = 0;
    rc = s2_eval_current_token( se, pr, 0, 0, &xrv );
    if(rc){
      xrv = 0 /* just in case, so we don't misbehave below */;
      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)){
      /* Condition is falsy, end the loop. */
      xrv = 0;
      break;
    }else{
      /* Condition is truthy, so continue. ref/unref combo cleans up
         temps, regardless of owning scope, while not cleaning up
         non-temps.*/
      cwal_refunref(xrv);
      xrv = 0;
    }
  }while(!rc);
  
  if(scope.parent){
    cwal_scope_pop2(se->e, rc ? 0 : xrv);
    assert(!scope.parent);
  }

  if(rc){
    cwal_value_unref(xrv);
  }else{
    cwal_value_unhand(xrv);
    s2_ptoker_token_set(pr, &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;
  cwal_scope scopeOut = cwal_scope_empty;
  cwal_scope scopeIn = cwal_scope_empty;
  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);
  }
  rc = s2_ptoker_sub_from_toker(pr, &prParens);
  if(rc) return rc;

  /* Get the body part... */
  rc = s2_keyword_loop_get_body( kw, se, pr, &tBody, &bodyIsExpr );
  if(rc) goto end;

  /* 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) goto end;
  if( S2_T_EOX != prParens.token.ttype){
    s2_ptoker_errtoken_set(pr, &prParens.token);
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Expecting ';' at start of %s(...), "
                       "but got %s.",
                       kw->word,
                       s2_ttype_cstr(prParens.token.ttype));
    goto end;
  }
  /* 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) goto end;
  if( S2_T_EOX != prParens.token.ttype){
    s2_ptoker_errtoken_set(pr, &prParens.token);
    rc = 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));
    goto end;
  }

  /* 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) goto end;
  if( !s2_ttype_is_eof(prParens.token.ttype)){
    s2_ptoker_errtoken_set(pr, &prParens.token);
    rc = 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));
    goto end;
  }

  /* 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.
  */
  /* oldRefCount = s2_scope_refs_count(se); */
  rc = cwal_scope_push2(se->e, &scopeOut);
  if(rc) goto end;
  /* No RETURNs as of here... */

  /* Run the prefix part */
  s2_ptoker_token_set( &prParens, &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 * postRv = 0 */ /* a cleanup hack */
    cwal_value * condRv = 0;
    char condBuul;
    if(scopeIn.parent){
      /* Inner scope needs to be re-initialized on each
         iter.
      */
      cwal_scope_pop(se->e);
      assert(!scopeIn.parent);
    }
    assert(&scopeOut == se->scopes.current->cwalScope);
    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.

           Later on... after having looked into that as the cause, i
           do see an extra reference whose origin i have not yet
           located.

           20160206: i don't believe that's the case anymore.
        */;
    }
    rc = cwal_scope_push2(se->e, &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;
    condBuul = condRv /* Treat empty expr as true here */
      ? cwal_value_get_bool(condRv)
      : 1;
    if(condRv){
      cwal_refunref(condRv);
      condRv = 0;
    }
    if(!condBuul) break;

    /* Run the body ... */;
    xrv = 0;
    s2_ptoker_token_set(pr, &tBody);
    rc = s2_eval_current_token(se, pr, bodyIsExpr, 0, 0);
    if(rc){
      if(CWAL_RC_BREAK==rc){
        /* fall through */
      }
      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?
    */
    if(!rc){
      prParens.token = tPost;
      rc = s2_eval_expr_impl( se, &prParens, 0, 0, 0 /* &postRv */ );
    }
    if(CWAL_RC_BREAK==rc){
      /*
        There's little reason not to allow the tPost part to 'break'
        out.  Unconventional, yes, but so are keywords which resolve
        to values.
       */
        xrv = s2_propagating_take(se);
        assert(xrv && "else misuse of CWAL_RC_BREAK.");
        assert(scopeOut.parent);
        cwal_value_rescope(scopeOut.parent, xrv);
        rc = 0;
        cwal_value_ref(xrv);
        s2_engine_err_reset(se);
        break;
    }
#if 0
    /* 20160206: doesn't seem to be needed anymore. Tested with the
       example case described below and everything was cleaned up
       optimally. */
    if( postRv ){
      /* What this does: keeps lower-scope temporaries from
         surviving until the end of the loop:

         Without this hack:

         s2> var a=[100]
         result: array@0x104ca20[scope=#1@0x7fffdb851260 ref#=1] ==> [100]
         s2> for(a.0 = 100; a.0 > 0; a.0-- );
         MARKER: s2.c:264:s2_engine_sweep():	Swept up 99 value(s) in vacuum mode

         With this hack: no post-loop sweepup of those temps because
         they're cleaned up here. This only works (has a benefit) for
         the common case (a single var gets modified), not for a
         series of them. The comma op has a similar hack which cleans up
         the LHS side(s) of a comma-separated list of expressions.
      */
      cwal_refunref(postRv);
    }
#endif
  }

  end:
  if(scopeIn.parent){
    assert(&scopeIn == se->scopes.current->cwalScope);
    cwal_scope_pop(se->e);
    assert(!scopeIn.parent);
  }
  if(scopeOut.parent){
    assert(&scopeOut == se->scopes.current->cwalScope);
    cwal_scope_pop(se->e);
    assert(!scopeOut.parent);
  }
  if(!rc){
    s2_ptoker_token_set(pr, &tBody);
    cwal_value_unhand(xrv);
    *rv = se->skipLevel
      ? cwal_value_undefined()
      : (xrv ? xrv : cwal_value_undefined());
  }else{
    cwal_value_unref(xrv);
  }
  s2_ptoker_finalize( &prParens );
  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;
  cwal_scope _SCOPE = cwal_scope_empty;
  cwal_scope * scope = pushScope ? &_SCOPE : 0;
  cwal_size_t const nFname = (fnlen<0) ? cwal_strlen(fname) : (cwal_size_t)fnlen;
  if(!se || !fname) return CWAL_RC_MISUSE;
  else if( !*fname || !nFname) return CWAL_RC_RANGE;
  if(rv) *rv = 0;
#if defined(S2_OS_UNIX)
  if(nFname>1 || '-'!=*fname
     /* ^^^ upcoming call will interpret that as stdin, so skip stat() */){
    /*
      Check if fname is a directory. Trying to eval a dir entry will
      lead to (in my experience) confusing CWAL_RC_OOM error (though
      other weird errors are also possible). We only have code for
      doing this check on platforms which support stat(). We could
      arguably also fail for other device types, e.g. BLOCK, but we'll
      let those stay for now because they haven't caused any grief in
      practice.
    */
    s2_fstat_t fst = s2_fstat_t_empty;
    rc = s2_fstat( fname, nFname, &fst, 1 );
    if(CWAL_RC_UNSUPPORTED==rc){
      /* Assume that stat() is not available in this build, so go
         ahead and try to read the given filename without stat()'ing
         it first. This is needed for non-stat()-aware builds to be
         able to eval any external scripts.*/
      rc = 0;
      /* fall through... */
    }
    else if(rc){
      return s2_engine_err_set(se, rc, "stat(\"%.*s\") failed.",
                               (int)nFname, fname);
    }
    else if(S2_FSTAT_TYPE_DIR == fst.type){
      return s2_engine_err_set(se, CWAL_RC_TYPE,
                               "Cannot eval a directory.");
    }
  }
#endif
  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 = cwal_scope_push2(se->e, scope);
      if(!rc){
        assert(0==se->scopes.current->sguard.sweep);
        assert(0==se->scopes.current->sguard.vacuum);
      }
    }
    if(!rc){
      s2_ptoker pr = s2_ptoker_empty;
      rc = s2_ptoker_init_v2( se->e, &pr, (char const *)buf.mem,
                              (cwal_int_t)buf.used, 0 );
      if(!rc){
        pr.name = fname;
        rc = s2_eval_ptoker( se, &pr, 0, rv ? &xrv : 0 );
      }
      s2_ptoker_finalize(&pr);
      switch(rc){
        case CWAL_RC_INTERRUPTED:
        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:
          assert(!"This cannot happen anymore: s2_eval_ptoker() will catch this.");
          rc = 0;
          xrv = s2_propagating_take(se);
          /* s2_engine_err_reset(se); */
          CWAL_SWITCH_FALL_THROUGH;
        case 0:
          if(scope && xrv && rv) cwal_value_rescope(scope->parent, xrv);
          if(rv) *rv = xrv;
          break;
        default:
          if(se->err.code){
            /* Convert non-exception errors to exceptions so that
               eval'ing a sub-script does not kill the top-level
               script.
            */
            rc = s2_throw_err(se, 0, 0, 0, 0);
          }
          break;
      }
    }
    if(scope && scope->parent){
      cwal_scope_pop2(se->e, rc ? 0 : (rv ? *rv : 0));
    }
  }
  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_for_func(args->callee)
    /*(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 ? s2_engine_from_args(args) : 0;
  cwal_array * cbArgv = se ? se->callArgV : 0
    /* The array form of args->argv */
    ;
  char const * src = 0;
  cwal_size_t srcLen = 0;
  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(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. We "could" do a
       quick scan for a '=', as side effects cannot (legally) happen
       without an assignment to a default param value. If one is not
       found, we could take the empty-params route. A
       micro-optimization, in any case.
    */
    /* assert(!fst->lastCallReturned); */
    assert(!fst->keyScriptName);
    assert(!fst->vSrc);
    *rv = cwal_value_undefined();
    return 0;
  }
  assert(cbArgv || (fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS));

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

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

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

  rc = s2_ptoker_init_v2( se->e, pr, src, (cwal_int_t)srcLen, 0 );
  if(rc) goto end;

  se->currentScriptFunc = fst;
  se->currentScript = pr;
  pr->name = cwal_value_get_cstr(fst->keyScriptName, 0);
  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 /* we know the tokens are valid, but interruption
                     can trigger this */;

  /* Get the params */
  rc = s2_next_token( se, pr, 0, 0 );
  if(rc) goto end;
  else if(S2_T_Identifier==pr->token.ttype){ /* The name part */
    rc = s2_next_token( se, pr, 0, 0 );
    if(rc) goto end;
  }
  tParams = pr->token;
  assert(S2_T_ParenGroup==tParams.ttype);

  /* The body... */
  rc = s2_next_token( se, pr, 0, 0 );
  if(rc) goto end
           /* likely an interruption error, not tokenization (which we
              know works because it was parsed at decl-time). Yes, it
              happened when testing s2_interrupt().
           */;
  tBody = pr->token;
  if(S2_T_Identifier==tBody.ttype){
    /* Skip over using(...) resp. using{...} */
    assert((5U == s2_ptoken_len(&tBody)) && "Not 'using'?");
    rc = s2_next_token(se, pr, 0, 0);
    if(rc) goto end;
    if(S2_FUNCSTATE_F_NO_USING_DECL & fst->flags){
      assert(S2_T_OpDot==pr->token.ttype);
      rc = s2_next_token(se, pr, 0, 0);
      if(rc) goto end;
    }
    assert(S2_T_ParenGroup==pr->token.ttype
           || S2_T_SquigglyBlock==pr->token.ttype);
    rc = s2_next_token(se, pr, 0, 0);
    if(rc) goto end;
    tBody = pr->token;
  }
  assert(S2_T_SquigglyBlock==tBody.ttype);

  assert(s2_ptoker_begin(pr) < s2_ptoken_begin(&tParams));
  assert(s2_ptoker_begin(pr) < s2_ptoken_begin(&tBody));
  assert(s2_ptoker_end(pr) > s2_ptoken_end(&tParams));
  assert(s2_ptoker_end(pr) >= s2_ptoken_end(&tBody));
  /* fst->lastCallReturned = 0; */
  if(!(fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)){
    /* Collect parameter list */
    s2_ptoker sub = s2_ptoker_empty;
    s2_ptoker_token_set(pr, &tParams);
    rc = s2_ptoker_sub_from_toker(pr, &sub);
    /* MARKER(("PARAMS: %.*s\n", (int)s2_ptoken_len(&tParams),
       s2_ptoken_begin(&tParams))); */
    if(!rc){
      rc = s2_keyword_f_var_impl( s2_ttype_keyword(S2_T_KeywordFunction),
                                  se, &sub, 0, 0, cbArgv );
    }
    s2_ptoker_finalize(&sub);
    /* 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)){
    assert(!rc);
    s2_ptoker_token_set(pr, &tBody);
    rc = s2_eval_current_token( se, pr, 0, 0, NULL );
  }
  switch(rc){
    case 0:
      /* function ended without an explicit return. */
      *rv = cwal_value_undefined();
      break;
    case CWAL_RC_RETURN:
      rc = 0;
      *rv = s2_propagating_take(se);
      s2_engine_err_reset(se);
      assert(*rv && "s2_propagating_set() must be used with CWAL_RC_RETURN");
      if(!*rv) rc = s2_err_ptoker(se, pr, CWAL_RC_ASSERT,
                                  "internal misuse: only return CWAL_RC_RETURN "
                                  "in conjunction with s2_propagating_set().");
      break;
    case CWAL_RC_ASSERT:
    case CWAL_RC_CANNOT_HAPPEN:
    case CWAL_RC_EXCEPTION:
    case CWAL_RC_EXIT:
    case CWAL_RC_FATAL:
    case CWAL_RC_INTERRUPTED:
    case CWAL_RC_OOM:
    case CWAL_SCR_SYNTAX:
      break;
    case CWAL_RC_BREAK:
      /* Cosmetic: improve error messages for break/continue...  We
         really need this handling in a higher-level place, but to get
         a good stack trace we have to not BREAK/CONTINUE here.
      */
      assert(s2_propagating_get(se)
             && "s2_propagating_set() must be used with CWAL_RC_BREAK");
      s2_propagating_set(se, 0);
      CWAL_SWITCH_FALL_THROUGH /* ... to 'continue' */;
    case CWAL_RC_CONTINUE:
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Unhandled '%s' outside of a loop.",
                         (CWAL_RC_BREAK==rc) ? "break" : "continue");
      break;
    default:
      if(se->err.code == rc){
        /*MARKER(("se->err.code=%s: %.*s\n", s2_rc_cstr(se->err.code),
          (int)se->err.msg.used, (char *)se->err.msg.mem));*/
        /* Leave it! */
        /* rc = s2_err_ptoker(se, pr, rc, 0, 0); */
      }else{
        rc = s2_throw_ptoker(se, pr, rc,
                             "Script function call returned "
                             "non-exception error code #%d (%s).",
                             rc, s2_rc_cstr(rc));
      }
      break;
  }
  end:
    /* s2_dump_val(*rv, "call result"); */
  s2_ptoker_finalize( pr );
  se->currentScriptFunc = oldScriptFunc;
  se->currentScript = oldScript;
  return rc;
}

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

   this
   argv
   funcNameIfSetInFuncDecl

   and symbols imported via Function.importSymbols() or the
   'using' keyword.

   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 * stateUnused){
  s2_func_state * fs = args->callee
    ? s2_func_state_for_func(args->callee)
    : NULL;
  S2_UNUSED_ARG stateUnused;
  /* MARKER(("PRE-FUNC HOOK fstate=%p argc=%d!\n", (void const *)fs, (int)args->argc)); */
  if(
     !fs /* It's a native function */
     || (/* A function with neither body content nor params. */
         fs->flags & S2_FUNCSTATE_F_EMPTY_BODY
         && fs->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)
     ){
    s2_engine * se = s2_engine_from_state(args->engine);
    assert(se);
    se->callArgV = 0 /* Make sure this doesn't propagate through to a script
                        func that this native function calls. Been
                        there, debugged that! */;
    /* MARKER(("Non-script or empty function. Not injecting argv/this.\n")); */
    s2_dotop_state( se, 0, 0, 0 ) /* necessary!!! */;
    return 0;
  }
  else{
    /* Set up argv, callee name, "this", and imported symbols. */
    int rc = 0;
    cwal_array * ar = 0;
    cwal_value * av = 0;
    cwal_value * calleeV = 0;
    cwal_scope * s = args->scope;
    s2_engine * se = s2_engine_from_args(args);
    assert(fs && "Currently always true here.");
    assert(se);
    calleeV = cwal_function_value(args->callee);
    assert(calleeV);
    s2_dotop_state( se, 0, 0, 0 ) /* necessary!!! */;

    assert(!(/* empty function */
             fs->flags & S2_FUNCSTATE_F_EMPTY_BODY
             &&
             fs->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)
           && "optimizations should have skipped this call() overhead"
           );
    ar = se->callArgV;
    /* s2_dump_val(cwal_array_value(ar),"ar"); */
    if(ar){
      /* This call is EITHER:

         1) coming from a script-parsed call() point
         about to call into s2_callback_f().

         2) from a new T() call.

         In either case, we'll re-use this argv array.
      */
      av = cwal_array_value(ar);
      assert(cwal_value_refcount(av) && "Expecting a ref from our caller's end.");
      cwal_value_ref(av);
    }else{
      /* This is probably being called from C code, so we need to inject
         an argv array. */
      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);
      for( ; !rc && i > 0; --i ){
        /* insert in reverse order as a reallocation optimization */
        /* 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 = s2_var_set_v(se, 0, se->cache.keyArgv, av);
    cwal_value_unref(av);
    if(rc) return rc;

    /* Inject function's name (if any)... */
    if( fs->vName
        &&
        (rc = cwal_scope_chain_set_with_flags_v(s, 0, fs->vName, calleeV,
                                                CWAL_VAR_F_CONST)) ){
      return rc;
    }
    /* Inject "this"... */
    rc = s2_var_set_v( se, 0, se->cache.keyThis, args->self
                       ? args->self
                       : cwal_value_undefined())
      /* 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;
#if S2_TRY_INTERCEPTORS
    else if(args->propertyHolder){
      /* s2_dump_val(args->propertyHolder, "interceptee"); */
      rc = s2_var_set_v( se, 0, se->cache.keyInterceptee, args->propertyHolder );
      if(rc) return rc;
    }
#endif
    if(fs
       && fs->vImported
       && !(S2_FUNCSTATE_F_NO_USING_DECL & fs->flags)){
      /* Import imported properties (using/Function.importSymbols())
         into the current scope.
      */
      rc = cwal_scope_import_properties( s, fs->vImported );
    }
    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); */
  if(state || fRc || rv){/*avoid unused param warning*/}
  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. */;
  /**
     Reminder to self:

     We can potentially use this callback to communicate certain
     non-zero results back up the call chain, e.g. CWAL_RC_OOM could
     be stuffed into a dedicated error code propagation slot (the plan
     is to consolidate return/exit/etc. with the
     s2_engine::flags::interrupted for that purpose).
  */
#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,
                                      char const * endPos,
                                      s2_func_state * fst,
                                      cwal_value * func ){
  int tlen = (int)(endPos - s2_ptoken_begin(tOrigin));
  int rc = 0;
  cwal_hash * h = s2_fstash(se);
  cwal_value * v = 0;
  cwal_size_t nameLen = 0;
  char const * scriptName = s2_ptoker_name_first(pr, &nameLen);
  assert(endPos >= s2_ptoken_begin(tOrigin)+8
         && endPos <= s2_ptoker_end(pr)
         /* 8 == strlen("proc(){}"), shortest possible function
            decl */);
  if(!h){
    return CWAL_RC_OOM;
  }
  assert(tlen >=8 /*proc(){}*/);
  /* MARKER(("setting up srcinfo for:\n%.*s\n", tlen,
     s2_ptoken_begin(tOrigin))); */

  /* Calculate script/line/column info, as we need those for
     error reporting. */
  {
    s2_ptoker_count_lines( pr, s2_ptoken_begin(tOrigin),
                           &fst->line, &fst->col );
  }
  /* currenly used as the mechanism for holding a func's name. */
  if(s2_ptoken_begin(tName)){
    assert(s2_ptoken_end(tName) > s2_ptoken_begin(tName));
    assert(!fst->vName);
    rc = s2_ptoken_create_value( se, pr, tName, &fst->vName );
    if(rc) return rc;
    cwal_value_ref(fst->vName);
    s2_value_to_lhs_scope(func, fst->vName);
  }
  if(fst->vImported
     || !(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,
       as they're only used for error reporting...
    */
    v = cwal_new_string_value(se->e, s2_ptoken_begin(tOrigin),
                              (cwal_size_t)tlen);
    if(!v){
      return CWAL_RC_OOM;
    }
    fst->vSrc = v;
    cwal_value_ref(fst->vSrc);
    s2_value_to_lhs_scope( func, fst->vSrc );
    if(scriptName && *scriptName){
      v = cwal_hash_search( h, scriptName, nameLen );
      if(v){
        ++se->metrics.totalReusedFuncStash;
      }else{
        v = cwal_new_string_value(se->e, scriptName, nameLen);
        if(!v){
          return CWAL_RC_OOM;
        }
        cwal_value_ref(v);
        rc = cwal_hash_insert_v( h, v, v, 0 );
        if(!rc) rc = cwal_hash_grow_if_loaded(h, 0.8);
        cwal_value_unref(v);
        if(rc) return rc;
        /* hashtable now holds a ref to v (or v is a builtin)  */;
      }
      fst->keyScriptName = v;
      cwal_value_ref(fst->keyScriptName);
      /* s2_dump_val(v, "fst->keyScriptName"); */
    }
  }
  return 0;
}

/**
   A cwal_value_rescoper_f() intended for use with
   cwal_function instances holding s2_func_state native
   data.
*/
static int cwal_value_rescoper_f_func_state(cwal_scope * s,
                                            cwal_value * v){
  cwal_function * f = cwal_value_get_function(v);
  s2_func_state * fst =  s2_func_state_for_func(f);
  assert(f);
  assert(fst);
  if(fst->vSrc) cwal_value_rescope(s, fst->vSrc);
  if(fst->vImported) cwal_value_rescope(s, fst->vImported);
  if(fst->vName) cwal_value_rescope(s, fst->vName);
  if(fst->keyScriptName) cwal_value_rescope(s, fst->keyScriptName);
  return 0;
}


/**
   Internal helper to process the using(...) part of: proc()
   using(...) {}, resp. the {object} part of ... using {object} ...

   It stashes imported properties into fst->vImported.

   pr_->token must be the (...) or {object} token. fv must be the
   Function Value and fst must be fv's state.

   Returns 0 on success, and all that.

   Ownership/lifetime of se, fv, and fst are not modified.

   If passed an object literal, it imports all properties of
   that literal into the function. If passed (...) then:

   1) Any identifiers in that list are resolved immediately and
   that identifier's name/value are imported into the function.

   2) Any object literals are treated as mentioned above.


   This handling leads to a descrepancy with importSymbols(), which
   is best demonstrated with an example:

   const with = proc(obj, func){return proc(){return func()}.importSymbols(obj)()};
   assert 3 === with({a: 1, b:2}, proc(){return a+b});

   The difference is that 'using' currently (20171112) has no syntax
   for saying "import the properties of this non-literal object".

   One easy-to-implement approach would be to extend the (...) syntax
   just a small bit:

   using (->nonLiteralObject1, nonLiteralObject2)

   such that the first one would, due to the -> modifier, import the
   symbols of that object, whereas the second one would be imported
   as-is.
*/
static int s2_function_using( s2_engine * se, s2_ptoker const * pr_,
                              cwal_value * fv, s2_func_state * fst ){
  s2_ptoken next = s2_ptoken_empty;
  cwal_value * v = 0;
  int rc;
  char gotComma;
  cwal_size_t argPos = 0;
  s2_ptoker sub = s2_ptoker_empty;
  s2_ptoker * pr = &sub /* simplifies some copy/paste reuse of code :/ */;
  s2_ptoken const tUsing = pr_->token;
  cwal_value * importHolder = 0;
  s2_op const * commaOp = s2_ttype_op(S2_T_Comma);
  /* if(!importHolder) return CWAL_RC_OOM; */
  assert(cwal_value_is_function(fv));
  assert(commaOp);
  if(S2_T_SquigglyBlock==tUsing.ttype){
    /* using {object} ... */
    sub = *pr_;
    rc = s2_eval_object_literal( se, pr, &v );
    if(rc){
      assert(!v);
    }else{
      assert(v);
      cwal_value_ref(v);
      if(!cwal_value_is_object(v)){
        rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE,
                             "Expecting an Object literal but got a %s.",
                             cwal_value_type_name(v));
        cwal_value_unref(v);
      }
      else if(!fst->vImported){
        fst->vImported = v /* fst now owns the ref point */;
        cwal_value_prototype_set(v, NULL);
        s2_value_to_lhs_scope(fv, v);
      }else{
        rc = cwal_props_copy(v, fst->vImported);
        cwal_value_unref(v);
      }
      v = 0;
    }
    goto end;
  }/* end of {object}*/
  /* Else using (...) ... */
  assert(S2_T_ParenGroup == tUsing.ttype);
  assert(s2_ptoken_len(&tUsing) >= 3
         && "(...) emptiness was checked earlier");
  importHolder = s2_func_import_props(se, fv, fst);
  if(!importHolder) return CWAL_RC_OOM;
  rc = s2_ptoker_sub_from_toker(pr_, &sub);
  if(rc) return rc;
  next_part:
  gotComma = 0;
  v = 0;
  rc = s2_next_token( se, pr, 0, 0 );
  if(rc) goto end;
  else if(S2_T_Identifier==pr->token.ttype){
    if(!se->skipLevel){
      s2_ptoken const ident = pr->token;
      if(s2_ptoken_keyword2(se, &ident)){
        /* confirm that it's not a keyword, as the user would not
           be able to use such an imported var.
        */
        rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Cannot use keyword '%.*s' "
                           "as a 'using' identifier.",
                           (int)s2_ptoken_len(&ident),
                           s2_ptoken_begin(&ident));
        goto end;
      }
      s2_get(se, 0, s2_ptoken_begin(&ident), s2_ptoken_len(&ident), &v);
      if(!v){
        rc = s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND,
                           "Cannot resolve identifier '%.*s'.",
                           (int)s2_ptoken_len(&ident),
                           s2_ptoken_begin(&ident));
        goto end;
      }
      /* Import identifier's value... */
      cwal_value_ref( v );
      rc = cwal_prop_set( importHolder, s2_ptoken_begin(&ident),
                          s2_ptoken_len(&ident), v);
      cwal_value_unref( v );
      if(rc) goto end;
    }
  }else{
    /* See if we got an Object we can import props from... */
    s2_ptoker_putback(pr) /* for pending eval... */;
    rc = s2_eval_expr_impl(se, pr, commaOp, 0, &v);
    if(rc){
      assert(!v);
      goto end;
    }else if(!se->skipLevel){
      if(!v){
        rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "An empty expression is not allowed here.");
        goto end;
      }else if(!cwal_props_can(v)){
        rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Unexpected '%s' value in 'using' list. "
                           "Expecting identifier or object.",
                           cwal_value_type_name(v));
        cwal_refunref(v);
        goto end;
      }else{
        cwal_value_ref(v);
        rc = cwal_props_copy(v, importHolder);
        cwal_value_unref(v);
        if(rc) goto end;
      }
    }else{
      assert(cwal_value_undefined()==v
             && "Violation of current skip-mode conventions.");
    }
  }
  /* Check for trailing comma or EOX */
  rc = s2_next_token( se, pr, 0, &next);
  if(rc) goto end;
  switch(next.ttype){
    case S2_T_Comma:
      gotComma = 1;
      CWAL_SWITCH_FALL_THROUGH;
    case S2_T_EOF:
    case S2_T_EOX:
      s2_ptoker_token_set(pr, &next) /* consume it */;
      break;
    default:
      s2_ptoker_errtoken_set(pr, &next);
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Unexpected token '%.*s' in 'using' list.",
                         (int)s2_ptoken_len(&next),
                         s2_ptoken_begin(&next));
      goto end;
  }
  assert(!rc);
  if(!se->skipLevel){
    ++argPos;
  }
  if(!s2_ptoker_is_eof(pr) && !gotComma){
    /* Check for a follow-up token */
    if(s2_ptoker_next_is_ttype(se, pr, S2_NEXT_NO_POSTPROCESS, S2_T_Comma, 1)){
      gotComma = 1;
    }
  }
  assert(!rc);
  rc = s2_check_interrupted(se, rc);
  if(!rc && gotComma) goto next_part;
  end:
  s2_ptoker_finalize(&sub);
  return rc;
}

/**
   Part of s2_keyword_f_function():

   Requires that curToken->ttype==S2_T_Identifier and be the token at
   the X part of either (proc() X) or (proc(){} X). stage must be -1
   for former case and 1 for the latter. Both of these conditions are
   assert()ed.

   On error, non-0 is returned and s2_keyword_f_function() must
   propagated it.

   On success 0 is returned.

   If -1==stage:

   - An error is triggered if curToken is NOT the identifier "using"...

   - else *tUsing is set to the "using" clause's body: (...) or
   {...}.

   If 1==stage:

   - If curToken is an identifier other than "using", this function
   has no side effects and 0 is returned: this indicates that whatever
   follows the function body is not part of the function definition
   (it might be an error, but not one which we have enough information
   to diagnose from here). Neither tUsing, fsFlags, nor pr->token are
   modified in that case. If curToken is the "using" identifier then,
   on success, *tUsing is set to the "using" clause's body token:
   (...) or {...}.

   Both cases, on success:

   - If the "using" clause was followed immediately by a dot,
   *fsFlags gets OR'd with S2_FUNCSTATE_F_NO_USING_DECL.

   - pr->token is set to the last-consumed token (the body part of
   the "using" clause).
 */
static int s2_keyword_f_function_using(s2_keyword const * kw,
                                       s2_engine * se, s2_ptoker * pr,
                                       int stage, s2_ptoken const * curToken,
                                       s2_ptoken * tUsing,
                                       int * fsFlags){
  s2_ptoken check = s2_ptoken_empty;
  int rc = 0;
  int gotDot = 0;
  assert(S2_T_Identifier==curToken->ttype);
  assert(-1==stage || 1==stage);
  if(5 != s2_ptoken_len(curToken)
     || 0 != cwal_compare_cstr("using", 5, s2_ptoken_begin(curToken), 5)){
    if(1==stage){
      /* proc(){body} <curToken>: we do not/cannot know, from here, if
         this is legal, so we do not consume this token, leaving it
         for downstream to deal with. */
      return 0;
    }else{
      s2_ptoker_errtoken_set(pr, curToken);
      return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX,
                            "Unexpected token (type %s) after %s(...)%s.",
                            s2_ttype_cstr(curToken->ttype), kw->word,
                            1==stage ? "{...}" : ""
                            );
    }
  }
  /* Grab the (...) resp. {...} part... */
  s2_ptoker_token_set(pr, curToken)/*consume it*/;
  rc = s2_next_token( se, pr, 0, &check );
  if(rc) return rc;

  if(S2_T_OpDot == check.ttype){
    /* proc() using <PERIOD> ... indicates that we should not
       declare using/imported symbols as local symbols at
       call()-time. They can instead be accessed via the using()
       keyword. */
    /*fFlags |= S2_FUNCSTATE_F_NO_USING_DECL;*/
    s2_ptoker_token_set(pr, &check)/*consume it*/;
    rc = s2_next_token(se, pr, 0, &check);
    if(rc) return rc;
    ++gotDot;
  }

  /* Expecting a (...) or {...} part... */
  if(S2_T_ParenGroup != check.ttype
     && S2_T_SquigglyBlock != check.ttype){
    s2_ptoker_errtoken_set(pr, &check);
    return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX,
                          "Expecting (...) or {object} after 'using%s', "
                          "but got token type %s. Sub-parse stage: %d.",
                          gotDot ? "." : "",
                          s2_ttype_cstr(check.ttype), stage);
  }
  *tUsing = check /* the (...) or {...} part */;
  s2_ptoker_token_set(pr, &check)/*consume it*/;
  if(gotDot) *fsFlags |= S2_FUNCSTATE_F_NO_USING_DECL;
  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 /* function params list */;
  s2_ptoken tBody = s2_ptoken_empty /* function body */;
  s2_ptoken tName = s2_ptoken_empty /* function name */;
  s2_ptoken tUsing = s2_ptoken_empty /* the (...) part of: using (...) */;
  s2_ptoken tTail = s2_ptoken_empty /* either tBody or tUsing, whichever appears last */;
  s2_ptoken const tOrigin = pr->token;
  int rc;
  cwal_function * cf = 0;
  cwal_value * vf = 0;
  s2_func_state * fst = 0;
  int fFlags = 0;
  assert(S2_T_KeywordFunction==pr->token.ttype
         || S2_T_KeywordProc==pr->token.ttype);
  rc = s2_next_token( se, pr, 0, &tParams );
  if(rc) goto end;
  if(S2_T_Identifier==tParams.ttype){
    tName = tParams;
    s2_ptoker_token_set(pr, &tParams);
    rc = s2_next_token( se, pr, 0, &tParams );
    if(rc) goto end;
  }
  if(S2_T_ParenGroup != tParams.ttype){
    rc = s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX,
                        "Expecting [identifier](...) after '%s', "
                        "but got token type %s.",
                        kw->word,
                        s2_ttype_cstr(tParams.ttype));
    goto end;
  }
  s2_ptoker_token_set(pr, &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.  This would also let us count the arity (for potential use
    in function dispatching).

    TODO: consolidate the two checks for the 'using' modifier.
  */

  rc = s2_next_token( se, pr, 0, &tBody );
  if(rc) goto end;
  if(S2_T_Identifier == tBody.ttype){
    /* check for using(...) _before_ function body... */
    assert(!s2_ptoken_begin(&tUsing));
    rc = s2_keyword_f_function_using(kw, se, pr, -1,
                                     &tBody, &tUsing, &fFlags);
    if(rc) goto end;
    assert(s2_ptoken_begin(&tUsing));
    rc = s2_next_token( se, pr, 0, &tBody );
    if(rc) goto end;
  }

  /* Get/check the body... */
  if(S2_T_SquigglyBlock != tBody.ttype){
    s2_ptoker_errtoken_set(pr, &tBody);
    rc = s2_err_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;
  }
  s2_ptoker_token_set(pr, &tBody) /* consume it */;
  if(!s2_ptoken_begin(&tUsing)){
    /* Check for using(...) _after_ the body.  */
    s2_ptoken check = s2_ptoken_empty;
    rc = s2_next_token(se, pr, 0, &check);
    if(rc) goto end;
    else if(S2_T_Identifier==check.ttype){
      rc = s2_keyword_f_function_using(kw, se, pr, 1,
                                       &check, &tUsing, &fFlags);
      if(rc) goto end;
    }else{/* Roll it back, let downstream deal with it. */
      assert(s2_ptoken_begin(&pr->token) == s2_ptoken_begin(&tBody));
      /*s2_ptoker_token_set(pr, &tBody);*/
      s2_ptoker_next_token_set(pr, &check);
    }
  }
  assert(!rc);
  
  if(S2_T_ParenGroup==tUsing.ttype
     && !s2_ptoken_has_content(&tUsing)){
    s2_ptoker_errtoken_set(pr, &tUsing);
    rc = s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX,
                        "using (...) may not be empty.");
    /* We explicitly disallow using(<EMPTY>) but we do allow
       using{<EMPTY OBJECT>} because (A) that {} is parsed at a
       different level and (B) it turns out, with the addition of the
       using() keyword, to be potentially useful to have an empty
       imports object installed.
    */
    goto end;
  }

  tTail = (s2_ptoken_begin(&tUsing)
           && s2_ptoken_begin(&tUsing) > s2_ptoken_begin(&tBody))
    ? tUsing : tBody;

  if(se->skipLevel){
    *rv = cwal_value_undefined();
    rc = 0;
    s2_ptoker_token_set(pr, &tTail);
    goto end;
  }

  /*MARKER(("Got func:\n%.*s\n",
          (int)(s2_ptoken_end(&tBody) - s2_ptoken_begin(&tOrigin)),
          s2_ptoken_begin(&tOrigin)));*/
  *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 vf. */
  cwal_function_set_rescoper( cf, cwal_value_rescoper_f_func_state );
  fst->flags = fFlags;
  vf = cwal_function_value(cf);
  cwal_value_ref(vf);
  if(S2_T_INVALID != tUsing.ttype){
    s2_ptoken const tmp = pr->token;
    assert(S2_T_ParenGroup==tUsing.ttype
           || S2_T_SquigglyBlock == tUsing.ttype);
    s2_ptoker_token_set(pr, &tUsing);
    rc = s2_function_using( se, pr, vf, fst );
    s2_ptoker_token_set(pr, &tmp);
    if(rc) goto end;
  }
  assert(!rc);
  rc = s2_function_setup_srcinfo( se, pr, &tOrigin,
                                  &tName, s2_ptoken_end(&tTail),
                                  fst, vf );
  if(rc) goto end;

  *rv = vf;
  assert(s2_ptoken_begin(&tTail));
  s2_ptoker_token_set(pr, &tTail);
  goto end;

  end:
  if(vf){
    if(rc) cwal_value_unref(vf);
    else{
      assert(*rv == vf);
      cwal_value_unhand(vf);
    }
  }
  return rc;
}

int s2_ctor_apply( s2_engine * se, cwal_value * operand,
                   cwal_function * ctor, cwal_array * args,
                   cwal_value **rv ){
  cwal_value * newThis;
  int rc;
  cwal_scope sc = cwal_scope_empty;
  *rv = 0;
  if(!se || !operand || !rv) return CWAL_RC_MISUSE;
  else if(!ctor){
    rc = s2_ctor_fetch( se, NULL, operand, &ctor, 1 );
    if(rc) return rc;
  }
  rc = cwal_scope_push2(se->e, &sc);
  if(rc) return rc;
  if(args) cwal_value_ref(cwal_array_value(args));
  newThis = cwal_new_object_value(se->e);
  if(!newThis){
    rc = CWAL_RC_OOM;
    goto end;
  }
  cwal_value_ref(newThis);
  rc = s2_set_v( se, newThis, se->cache.keyPrototype, operand)
    /* instead of cwal_value_prototype_set(newThis, operand) so that
       we get consistent error reporting (via s2_set_v(), as opposed
       to an unadorned error code via the lower-level function). */;
  if(!rc){
    cwal_value * ctorResult = 0 /* the ctor result */;
    cwal_array * oldArgV = se->callArgV
      /* not _actually_ sure this is needed, but there might
         be a corner case or two without it. */;
    uint16_t const vFlags
      = cwal_container_client_flags_set( newThis, S2_VAL_F_IS_NEWING );
    se->callArgV = args;
    rc = args
      ? cwal_function_call_array(&sc, ctor, newThis, &ctorResult, args)
      : cwal_function_call_in_scope(&sc, ctor, newThis, &ctorResult,
                                    0, NULL)
      ;
    cwal_container_client_flags_set( newThis, vFlags );
    assert(!se->callArgV && "callArgV Gets unset via the call() hook(s)");
    se->callArgV = oldArgV;
    if(!rc && ctorResult
       && newThis != ctorResult
       && cwal_value_undefined() != ctorResult){
      /*
        If the ctor returns a non-undefined value, use that as the
        result of the construction.  It's up to the caller to set
        the prototype in that case, but he can fetch is via the
        ctor's args->self. This approach is needed when the client
        wants/needs to return a type other than Object.
      */
      /* s2_dump_val(ctorResult,"ctorResult"); */
      /* s2_dump_val(newThis,"newThis"); */
      cwal_value_ref(ctorResult);
      cwal_value_unref(newThis);
      newThis = ctorResult;
    }
  }
  end:
  assert(se->scopes.current->cwalScope == &sc);
  cwal_scope_pop2(se->e, newThis);
  assert(se->scopes.current->cwalScope != &sc);
  if(rc){
    cwal_value_unref(newThis);
  }else{
    assert(newThis);
    *rv = newThis;
    cwal_value_unhand(newThis);
  }
  if(args) cwal_value_unref(cwal_array_value(args));
  return rc;
}

/**
   Impl for the "new" keyword:

   new Operand(...args...) {optional post-ctor init block}

   Evaluates to the result of the Operand's constructor call (a new
   Object by default unless that ctor returns a non-undefined
   value). In skip mode, it always evaluates to the undefined value.

   On success pr will be set up such that the next token fetched will
   be the one right after the (...args...) or {post-ctor block}.
*/
int s2_keyword_f_new( s2_keyword const * kw, s2_engine * se,
                      s2_ptoker * pr, cwal_value **rv){
  int rc;
  cwal_size_t operandLen = 0;
  char const * operandStr = 0;
  cwal_value * operand = 0;
  cwal_function * ctor = 0;
  cwal_array * args = 0;
  cwal_value * vargs = 0;
  s2_ptoker ptArgs = s2_ptoker_empty;
  s2_ptoken tTail = s2_ptoken_empty;
  s2_ptoken tWithThis = s2_ptoken_empty;
  s2_strace_entry strace = s2_strace_entry_empty;
  s2_ptoken const origin = pr->token;
  cwal_value * xrv = 0;
  assert(rv);
  rc = s2_strace_push_pos(se, pr, &origin, &strace )
    /* treat new() like a function for stack trace purposes
       or exceptions may contain far-off location info.
    */;
  if(rc) return rc /* after this, no more 'return', only goto end */;
#if 0
  /* Reminder to self:

     1) please leave more descriptive comments to self in the future.

     2) this will not work with the #compiled token layer.
  */
  while(s2_is_space(*s2_ptoken_end(&pr->token))){
    /* Cosmetic workaround! */
    s2_ptoken_end_set(&pr->token, s2_ptoken_end(&pr->token)+1);
  }
#endif

  rc = s2_eval_expr_impl(se, pr, s2_ttype_op(S2_T_Comma),
                         S2_EVAL_STOP_AT_CALL,
                         &operand);
  if(rc) goto end;
  else if(operand) cwal_value_ref(operand);
  if(!operand || S2_T_ParenGroup!=pr->token.ttype){
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Expecting EXPR(...) after '%s'.",
                       kw->word);
    goto end;
  }
  tTail = pr->token;
  operandStr = s2_ptoker_capture_cstr(pr, &operandLen);
#if 0
  MARKER(("operand=%.*s\n", (int)operandLen, operandStr));
#endif
  if(se->skipLevel>0){
    xrv = cwal_value_undefined();
    goto check_tail;
  }else if(!cwal_props_can(operand)){
    /* s2_dump_val(operand,"new operand"); */
#if 0
    if(cwal_value_is_tuple(operand)){
      /* we just happen to know this has a ctor, so
         fall through and try it... */
    }
    else
#endif
    {
      rc = s2_throw_ptoker(se, pr, CWAL_RC_EXCEPTION,
                           "'%s' expects a container, "
                           "but '%.*s' resolves to type '%s'.",
                           kw->word, (int)operandLen, operandStr,
                           cwal_value_type_name(operand));
      goto end;
    }
  }
  rc = s2_ctor_fetch(se, pr, operand, &ctor, -1);
  if(rc) goto end;
  assert(ctor);
#if 0
  MARKER(("new'ing=%.*s\n", (int)operandLen, operandStr));
  MARKER(("Capture=%.*s\n", (int)(s2_ptoken_begin(&pr->capture.end)
                                  -s2_ptoken_begin(&pr->capture.begin)),
          s2_ptoken_begin(&pr->capture.begin)));
  MARKER(("Call bits=%.*s\n", (int)s2_ptoken_len(&pr->token),
          s2_ptoken_begin(&pr->token)));
  s2_dump_val(operand, "new() prototype");
#endif

  /* Eval the call params... */
  assert(S2_T_ParenGroup==pr->token.ttype);
  args = cwal_new_array(se->e);
  if(!args){
    rc = CWAL_RC_OOM;
    goto end;
  }
  vargs = cwal_array_value(args);
  assert(vargs);
  cwal_value_ref(vargs);
  cwal_value_make_vacuum_proof(vargs, 1);
  /* cwal_value_make_vacuum_proof(operand, 1); */
  /* cwal_value_ref(operand); */
  rc = s2_ptoker_sub_from_toker(pr, &ptArgs);
  if(!rc){
    rc = s2_eval_to_array( se, &ptArgs, args, 1 );
    /* cwal_value_unhand(operand); */
    /* cwal_value_make_vacuum_proof(operand, 0); */
    if(!rc){
      /* s2_dump_val(vargs,"call args"); */
      rc = s2_ctor_apply( se, operand, ctor, args, &xrv );
      /* s2_dump_val(xrv, "result from 'new'"); */
    }
  }
  s2_ptoker_finalize( &ptArgs );

  check_tail:
  if(rc){
    assert(!xrv);
  }else{
    if(xrv) cwal_value_ref(xrv);
    /* Check for a {body} part after the T(...), and treat it like
       an inlined extension to the ctor like in Java: new X() {{ ... }}.
    */
    s2_next_token(se, pr, 0, &tWithThis);
    if(S2_T_SquigglyBlock==tWithThis.ttype){
      tTail = tWithThis;
      s2_ptoker_token_set(pr, &tTail);
      /*MARKER(("Got post-new() body: %.*s\n",
        (int)s2_ptoken_len(&tTail), s2_ptoken_begin(&tTail)));*/
      if(!se->skipLevel){
        rc = s2_eval_current_token_with_this(se, pr, xrv, NULL);
      }
    }else{
      s2_ptoker_next_token_set(pr, &tWithThis);
    }
  }
  end:
  switch(rc){
    case CWAL_RC_EXCEPTION:
      s2_exception_add_script_props(se, pr)
        /* Needed to decorate calls to native functions. */;
      break;
    default:
      break;
  }
  if(strace.pr){
    s2_strace_pop(se);
  }
  if(operand){
    cwal_value_unref(operand);
    operand = 0;
  }
  if(vargs){
    cwal_value_make_vacuum_proof(vargs, 0);
    cwal_value_unref(vargs);
  }
  if(!rc){
    s2_ptoker_token_set(pr, &tTail);
    *rv = xrv;
    cwal_value_unhand(xrv);
  }else if(xrv){
    cwal_value_unref(xrv);
  }
  return rc;
}

static int s2_enum_is_legal_id( int ttype ){
  switch(ttype){
    case S2_T_Identifier:
    case S2_T_LiteralStringSQ:
    case S2_T_LiteralStringDQ:
    case S2_T_LiteralString:
#if 0
    /* reminder to self:

       The reason numbers as keys in enums is not a good idea is
       because lookups may or may not be type-strict, depending on
       whether the enum uses an object or hash for storage. Consider:

       var e = enum {1: 'one'};
       e::1; // fine
       e::'1'; // doh: will match for an object but not a hash

       So... we disallow them solely to eliminate that corner case :/.
    */
    case S2_T_LiteralIntBin:
    case S2_T_LiteralIntDec:
    case S2_T_LiteralIntHex:
    case S2_T_LiteralIntOct:
    case S2_T_LiteralDouble:
#endif
      return ttype;
    default:
      return 0;
  }
}

int s2_keyword_f_echo( s2_keyword const * kw, s2_engine * se,
                       s2_ptoker * pr, cwal_value **rv){
  return s2_keyword_f_reserved(kw, se, pr, rv);
}

int s2_keyword_f_enum( s2_keyword const * kw, s2_engine * se,
                       s2_ptoker * pr, cwal_value **rv){
  /* TODO: reimplement this using the s2_enum_builder API.
     The glaring problem is that the builder wants to know, in advance,
     whether we want a hash or object for property storage.

     2020-02-21: enums are now always hashes, so that's not an issue
     anymore.
 */
  /* Also TODO: this function is a mess. Clean it up! */
  int rc;
  s2_ptoken tName = s2_ptoken_empty;
  s2_ptoker ptBody = s2_ptoker_empty;
  s2_ptoker const * oldScript = se->currentScript;
  cwal_value * store = 0;
  cwal_value * key;
  cwal_size_t entryCount = 0, loopCount = 0;
  cwal_value * enumProto = s2_prototype_enum(se);
  int doBreak = 0;
  cwal_hash * hashStore = 0;
  int gotCustomProto = 0 /* If a custom prototype property is applied, we
                            need a couple special cases. */;
  if(!enumProto) return CWAL_RC_OOM;
  rc = s2_next_token(se, pr, 0, 0);
  if(rc) return rc;
  else if(S2_T_Identifier==pr->token.ttype){
    tName = pr->token;
    rc = s2_next_token(se, pr, 0, 0);
    if(rc) return rc;
  }

  if(S2_T_SquigglyBlock != pr->token.ttype){
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting {...} after '%s'.",
                         kw->word);
  }
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }
  if(!s2_ptoken_has_content(&pr->token)){
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Empty %s body is not permitted.",
                         kw->word);
  }

  rc = s2_ptoker_sub_from_toker(pr, &ptBody);
  /* As of here, NO RETURNs */
  if(rc) goto end;

#if 0  
  MARKER(("tName=%.*s\n", (int)nameLen, s2_ptoken_begin(&tName)));
  MARKER(("Body=%.*s\n", (int)s2_ptoken_len(&ptBody),
          s2_ptoken_begin(&ptBody)));
#endif

  assert(ptBody.parent == pr);
  se->currentScript = &ptBody;
  while(!rc && !doBreak){
    s2_ptoken tId = s2_ptoken_empty;
    s2_ptoken tValue = s2_ptoken_empty;
    cwal_value * uv = 0;
    cwal_value * wrappedVal = 0;
    char wrappedIsId = 0 /* true if the wrapped value is the
                            string form of the ID token, so that
                            we can know to re-use that string
                            in the entry-name-to-entry mapping. */;
    char isPrototype = 0 /* true if an entry's key is "prototype" */;
    key = 0;
    rc = s2_next_token(se, &ptBody, 0, 0);
    if(rc) goto loop_end;
    else if(1==++loopCount
            && S2_T_OpHash==ptBody.token.ttype){
      rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX,
                         "The '#' modifier for enums is no longer "
                         "needed nor permitted.");
      goto loop_end;
    }else if(!s2_enum_is_legal_id(ptBody.token.ttype)){
      rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX,
                         "Unexpected token type [%s] in %s body.",
                         s2_ttype_cstr(ptBody.token.ttype),
                         kw->word);
       goto loop_end;
    }
    tId = ptBody.token;
    if(9U==s2_ptoken_len(&tId)
       && 0==cwal_compare_cstr("prototype", 9,
                               s2_ptoken_begin(&tId), 9)){
      isPrototype = 1;
    }
    /* Check next token before continuing to alloc this value, to
       accommodate our switch-to-hash bits... */
    rc = s2_next_token(se, &ptBody, 0, &tValue);
    if(rc)  goto loop_end;
    s2_ptoker_token_set(&ptBody, &tValue) /* consume it */;
    doBreak = s2_ptoker_is_eof(&ptBody)
      /* Reminder: we want to fall through when doBreak
         is true so that store can be upgraded to a hash
         if needed.
      */;
    if(!doBreak){
      if(S2_T_Colon == tValue.ttype){ /* Entry: VALUE */
        if( (rc = s2_eval_expr_impl( se, &ptBody,
                                     s2_ttype_op(S2_T_Comma), 0,
                                     &wrappedVal)) ){
          assert(!wrappedVal);
          /*cwal_value_unref(wrappedVal);*/
          goto loop_end;
        }
        cwal_value_ref(wrappedVal);
        /* s2_dump_val(wrappedVal, "enum_entry = (value)"); */
        rc = s2_next_token(se, &ptBody, 0, 0);
        if(rc) goto loop_end;
        doBreak = s2_ptoker_is_eof(&ptBody);
      }
      else if(S2_T_Comma != tValue.ttype){
        rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX,
                           "Expecting comma in %s body, "
                           "but got token type %s.",
                           kw->word,
                           s2_ttype_cstr(tValue.ttype));
        goto loop_end;
      }
    }
    assert(!rc);
    if(!wrappedVal){
      /* treat an entry with no value as having its name as its
         value. That seems, in practice, to be more useful than the
         undefined value. */
      assert(!wrappedIsId);
      if(isPrototype){
        rc = s2_err_ptoker(se, &ptBody, CWAL_SCR_SYNTAX,
                           "%s: prototype property cannot be applied "
                           "without a value.",
                           kw->word);
        goto loop_end;
      }
      rc = s2_ptoken_create_value(se, pr, &tId, &wrappedVal);
      if(!wrappedVal){
        rc = CWAL_RC_OOM;
        goto loop_end;
      }
      cwal_value_ref(wrappedVal);
      wrappedIsId = 1;
    }
    if(!store){
      /* Create the property storage. */
      assert(!entryCount);
      hashStore = cwal_new_hash(se->e, 7);
      if(!hashStore){
        rc = CWAL_RC_OOM;
        goto loop_end;
      }else{
        store = cwal_hash_value(hashStore);
        s2_hash_dot_like_object(store, 1
                                /* Needed for remainder of the loop,
                                   so that s2_set_v() will DTRT.
                                   Will get overwritten afterwards*/);
        cwal_value_ref(store);
        cwal_value_make_vacuum_proof(store,1);
      }
    }
    assert(!key);
    if(wrappedIsId){
      assert(!isPrototype);
      key = wrappedVal;
    }else if(!isPrototype){
      rc = s2_ptoken_create_value(se, pr, &tId, &key);
      if(rc){
        assert(!key);
        goto loop_end;
      }
    }
    if(isPrototype){
      assert(!key);
      assert(wrappedVal);
      ++gotCustomProto;
      rc = s2_set_v( se, store, se->cache.keyPrototype, wrappedVal)
        /* ^^^^^^^^ for its non-trivial handling of prototype setting. */
        ;
      if(rc) goto loop_end;
    }else{
      cwal_value_ref(key);
      uv = cwal_new_unique(se->e, 0);
      if(!uv){
        cwal_value_unref(key);
        rc = CWAL_RC_OOM;
        goto loop_end;
      }
      cwal_unique_wrapped_set( uv, wrappedVal );
      cwal_value_ref(uv);
      rc = s2_set_with_flags_v( se, store, key, uv, CWAL_VAR_F_CONST );
      cwal_value_unref(uv);
      cwal_value_unref(wrappedVal);
      wrappedVal = 0;
      ++entryCount;
      if(rc){
        cwal_size_t keylen = s2_ptoken_len(&tId);
        char const * ckey = s2_ptoken_begin(&tId);
        cwal_value_unref(key);
        key = 0;
        s2_ptoker_token_set( &ptBody, &tId );
        rc = s2_err_ptoker(se, &ptBody, rc,
                           "Assignment to enum entry '%.*s' failed with "
                           "code %d (%s).",
                           (int)keylen, ckey, rc, s2_rc_cstr(rc));
        goto loop_end;
      }else{
        rc = s2_set_with_flags_v( se, store, uv, key,
                                  CWAL_VAR_F_CONST
                                  | CWAL_VAR_F_HIDDEN );
        cwal_value_unref(key);
        key = 0;
        if(rc) goto loop_end;
      }
    }
    loop_end:
    if(wrappedVal){
      cwal_value_unref(wrappedVal);
    }
    key = 0;
  }

  end:
  s2_ptoker_finalize( &ptBody );
  if(!rc && !entryCount){
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "%s requires at least one entry.",
                       kw->word);
  }
  
  if(!rc){
    assert(hashStore);
    rc = cwal_hash_grow_if_loaded( hashStore, 0.85 );
  }

  if(!rc && s2_ptoken_begin(&tName)){
    /* Set typename... */
    cwal_size_t const nameLen = s2_ptoken_len(&tName);
    assert(nameLen);
    key = cwal_new_string_value(se->e, s2_ptoken_begin(&tName), nameLen);
    if(!key) rc = CWAL_RC_OOM;
    else{
      cwal_value_ref(key);
      rc = cwal_prop_set_with_flags_v( store, se->cache.keyTypename, key,
                                       CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN )
        /* Note that this is an object-level property, not hash entry. */;
      cwal_value_unref(key);
      key = 0;
    }
  }

  se->currentScript = oldScript;
  if(store){
      cwal_value_make_vacuum_proof(store,0);
  }
  if(rc){
    if(store){
      cwal_value_unref(store);
    }
  }else{
    if(!gotCustomProto){
      cwal_value_prototype_set(store, enumProto);
    }
    cwal_container_flags_set(store,
                             CWAL_CONTAINER_DISALLOW_NEW_PROPERTIES
                             | CWAL_CONTAINER_DISALLOW_PROP_SET
                             /* TODO:???
                                | CWAL_CONTAINER_DISALLOW_PROTOTYPE_SET */
                             );
    cwal_container_client_flags_set(store, S2_VAL_F_ENUM);
    cwal_value_unhand(store);
    *rv = store;
  }
  return rc;
}

/**
   Tag type IDs for typeinfo(TAG ...).

   ACHTUNG: their order MUST match the entries of the s2TypeInfoWords
   array (defined below) because we reference those by index with these
   entries.
*/
enum s2_typeinfo_words {
  /* Sentinel value. Must be 0. */
  TYPEINFO_NONE = 0,
  /** True if operand can be used as an operand for the "new" keyword. */
  TYPEINFO_CANNEW,
  /** True if operand is or has an array in its prototype chain. */
  TYPEINFO_HASARRAY,
  /** True if operand is or has an buffer in its prototype chain. */
  TYPEINFO_HASBUFFER,
  /** True if operand is or has an enum in its prototype chain. */
  TYPEINFO_HASENUM,
  /** True if operand is or has an exception in its prototype chain. */
  TYPEINFO_HASEXCEPTION,
  /** True if operand is or has a hash in its prototype chain. */
  TYPEINFO_HASHASH,
  /** True if operand is or has a native in its prototype chain. */
  TYPEINFO_HASNATIVE,
  /** True if operand is or has an Object in its prototype chain. */
  TYPEINFO_HASOBJECT,
  /** True if operand has a prototype. */
  TYPEINFO_HASPROTOYPE,
  /** True if operand is an array. */
  TYPEINFO_ISARRAY,
  /** True if operand is a bool. */
  TYPEINFO_ISBOOL,
  /** True if operand is a buffer. */
  TYPEINFO_ISBUFFER,
  /** True if operand is callable (is a function or has a function
      in its prototype chain). */
  TYPEINFO_ISCALLABLE,
  /** True if operand is a container (can hold its own properties). */
  TYPEINFO_ISCONTAINER,
  /** True if operand is the name of a declared (currently in-scope) variable/const. */
  TYPEINFO_ISDECLARED,
  /** True if operand is legal for use with the dot operator. */
  TYPEINFO_ISDEREFABLE,
    /** True if operand is a double. */
  TYPEINFO_ISDOUBLE,
  /** True if operand is an enum. */
  TYPEINFO_ISENUM,
  /** True if operand is an exception. */
  TYPEINFO_ISEXCEPTION,
  /** True if operand is a function. */
  TYPEINFO_ISFUNCTION,
  /** True if operand is a hash. */
  TYPEINFO_ISHASH,
  /** True if operand is an integer . */
  TYPEINFO_ISINT,
  /** True if cwal_value_is_iterating_props(operand) OR cwal_value_is_iterating_list(operand). */
  TYPEINFO_ISITERATING,
  /** True if cwal_value_is_iterating_list(operand). */
  TYPEINFO_ISITERATINGLIST,
  /** True if cwal_value_is_iterating_props(operand). */
  TYPEINFO_ISITERATINGPROPS,
  /** True if operand is a tuple or an array. */
  TYPEINFO_ISLIST,
  /** True if operand is a var/const declared in the local scope. */
  TYPEINFO_ISLOCAL,
  /** True if operand is a native. */
  TYPEINFO_ISNATIVE,
  /** True if the current "this" is being initalized via the 'new' keyword. */
  TYPEINFO_ISNEWING,
  /** True if operand is an integer or a double. */
  TYPEINFO_ISNUMBER,
  /**
     True if operand is an integer, a double, a boolean,
     or a numeric-format string (one parseable by
     NumberPrototype.parseNumber()).
  */
  TYPEINFO_ISNUMERIC,
  /** True if operand is an Object */
  TYPEINFO_ISOBJECT,
  /** True if operand is a string. */
  TYPEINFO_ISSTRING,
  /** True if operand is a Tuple */
  TYPEINFO_ISTUPLE,
  /** True if operand is a Unique-type value. */
  TYPEINFO_ISUNIQUE,
  /** True if cwal_value_may_iterate(operand) OR cwal_value_may_iterate_list(operand). */
  TYPEINFO_MAYITERATE,
  /** True if cwal_value_may_iterate_list(operand). */
  TYPEINFO_MAYITERATELIST,
  /** True if cwal_value_may_iterate(operand). */
  TYPEINFO_MAYITERATEPROPS,
  /** Evaluates to the type's name (as as the typename
      keyword). */
  TYPEINFO_NAME,
  /** Evaluates to the operand's refcount. */
  TYPEINFO_REFCOUNT
};
enum s2_typeinfo_arg_type {
TYPEINFO_ARG_IDENTIFIER = -1,
TYPEINFO_ARG_NONE = 0,
TYPEINFO_ARG_EXPR = 1
};
typedef struct {
  enum s2_typeinfo_words type;
  /**
     Argument type.
  */
  enum s2_typeinfo_arg_type argType;
} s2_typeinfo_word;
static const s2_typeinfo_word s2TypeInfoWords[] = {
  /*
   ACHTUNG: their order MUST match the entries of
   the s2_typeinfo_words enum because we reference them
   by index that way!
  */
  { TYPEINFO_NONE,              TYPEINFO_ARG_NONE},
  { TYPEINFO_CANNEW,            TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASARRAY,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASBUFFER,         TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASENUM,           TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASEXCEPTION,      TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASHASH,           TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASNATIVE,         TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASOBJECT,         TYPEINFO_ARG_EXPR},
  { TYPEINFO_HASPROTOYPE,       TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISARRAY,           TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISBOOL,            TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISBUFFER,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISCALLABLE,        TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISCONTAINER,       TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISDECLARED,        TYPEINFO_ARG_IDENTIFIER},
  { TYPEINFO_ISDEREFABLE,       TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISDOUBLE,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISENUM,            TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISEXCEPTION,       TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISFUNCTION,        TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISHASH,            TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISINT,             TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISITERATING,       TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISITERATINGLIST,   TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISITERATINGPROPS,  TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISLIST,            TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISLOCAL,           TYPEINFO_ARG_IDENTIFIER},
  { TYPEINFO_ISNATIVE,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISNEWING,          TYPEINFO_ARG_EXPR
    /*special case: isnewing requires argType TYPEINFO_ARG_EXPR but
      optionally accepts no argument. */},
  { TYPEINFO_ISNUMBER,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISNUMERIC,         TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISOBJECT,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISSTRING,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISTUPLE,           TYPEINFO_ARG_EXPR},
  { TYPEINFO_ISUNIQUE,          TYPEINFO_ARG_EXPR},
  { TYPEINFO_MAYITERATE,        TYPEINFO_ARG_EXPR},
  { TYPEINFO_MAYITERATELIST,    TYPEINFO_ARG_EXPR},
  { TYPEINFO_MAYITERATEPROPS,   TYPEINFO_ARG_EXPR},
  { TYPEINFO_NAME,              TYPEINFO_ARG_EXPR},
  { TYPEINFO_REFCOUNT,          TYPEINFO_ARG_EXPR}
};

/**
   If the given ptoken resolves to a typeinfo(WORD) identifier, its
   entry is returned, else NULL is returned. This is an O(1) search.
   See s2_ptoken_keyword() for more details.
 */
static s2_typeinfo_word const * s2_ptoken_typeinfo( s2_ptoken const * pt ){
  cwal_size_t const tlen = s2_ptoken_len(pt);
  s2_typeinfo_word const * rc;
#define W(X,E) rc = tlen==(cwal_size_t)sizeof(X)-1 &&               \
    0==cwal_compare_cstr(s2_ptoken_begin(pt), tlen, X, sizeof(X)-1) \
    ? &s2TypeInfoWords[E] : NULL;                                   \
  assert(rc ? E==rc->type : 1); return rc
  switch(s2__keyword_perfect_hash(pt)){
    /* Generated by s2_eval-keyword-hasher.s2 (or equivalent): */

    case 0x00002004: W("cannew",TYPEINFO_CANNEW);
    case 0x00003a10: W("can-new",TYPEINFO_CANNEW);
    case 0x000088d4: W("hasarray",TYPEINFO_HASARRAY);
    case 0x0000f5ba: W("has-array",TYPEINFO_HASARRAY);
    case 0x000112eb: W("hasbuffer",TYPEINFO_HASBUFFER);
    case 0x0001ece2: W("has-buffer",TYPEINFO_HASBUFFER);
    case 0x000043ba: W("hasenum",TYPEINFO_HASENUM);
    case 0x00007a24: W("has-enum",TYPEINFO_HASENUM);
    case 0x0008c084: W("hasexception",TYPEINFO_HASEXCEPTION);
    case 0x000f9697: W("has-exception",TYPEINFO_HASEXCEPTION);
    case 0x000042db: W("hashash",TYPEINFO_HASHASH);
    case 0x00007920: W("has-hash",TYPEINFO_HASHASH);
    case 0x0001163a: W("hasnative",TYPEINFO_HASNATIVE);
    case 0x0001f102: W("has-native",TYPEINFO_HASNATIVE);
    case 0x00011411: W("hasobject",TYPEINFO_HASOBJECT);
    case 0x0001ee92: W("has-object",TYPEINFO_HASOBJECT);
    case 0x0008fe4e: W("hasprototype",TYPEINFO_HASPROTOYPE);
    case 0x000fe16a: W("has-prototype",TYPEINFO_HASPROTOYPE);
    case 0x0000466e: W("isarray",TYPEINFO_ISARRAY);
    case 0x00007814: W("is-array",TYPEINFO_ISARRAY);
    case 0x00002216: W("isbool",TYPEINFO_ISBOOL);
    case 0x00003abf: W("is-bool",TYPEINFO_ISBOOL);
    case 0x00008df4: W("isbuffer",TYPEINFO_ISBUFFER);
    case 0x0000f16b: W("is-buffer",TYPEINFO_ISBUFFER);
    case 0x00023026: W("iscallable",TYPEINFO_ISCALLABLE);
    case 0x0003bb16: W("is-callable",TYPEINFO_ISCALLABLE);
    case 0x00048a39: W("iscontainer",TYPEINFO_ISCONTAINER);
    case 0x0007a928: W("is-container",TYPEINFO_ISCONTAINER);
    case 0x0002317e: W("isdeclared",TYPEINFO_ISDECLARED);
    case 0x0003bcff: W("is-declared",TYPEINFO_ISDECLARED);
    case 0x00047176: W("isderefable",TYPEINFO_ISDEREFABLE);
    case 0x00078b66: W("is-derefable",TYPEINFO_ISDEREFABLE);
    case 0x00008f26: W("isdouble",TYPEINFO_ISDOUBLE);
    case 0x0000f2e6: W("is-double",TYPEINFO_ISDOUBLE);
    case 0x00002290: W("isenum",TYPEINFO_ISENUM);
    case 0x00003b5a: W("is-enum",TYPEINFO_ISENUM);
    case 0x00049271: W("isexception",TYPEINFO_ISEXCEPTION);
    case 0x0007b484: W("is-exception",TYPEINFO_ISEXCEPTION);
    case 0x00024c1e: W("isfunction",TYPEINFO_ISFUNCTION);
    case 0x0003de01: W("is-function",TYPEINFO_ISFUNCTION);
    case 0x000021d6: W("ishash",TYPEINFO_ISHASH);
    case 0x00003a7b: W("is-hash",TYPEINFO_ISHASH);
    case 0x00012407: W("isinteger",TYPEINFO_ISINT);
    case 0x0001ecea: W("is-integer",TYPEINFO_ISINT);
    case 0x00049bc0: W("isiterating",TYPEINFO_ISITERATING);
    case 0x0007c0fa: W("is-iterating",TYPEINFO_ISITERATING);
    case 0x0049f317: W("isiteratinglist",TYPEINFO_ISITERATINGLIST);
    case 0x00f86719: W("is-iterating-list",TYPEINFO_ISITERATINGLIST);
    case 0x0093f07e: W("isiteratingprops",TYPEINFO_ISITERATINGPROPS);
    case 0x01f0da02: W("is-iterating-props",TYPEINFO_ISITERATINGPROPS);
    case 0x000022fe: W("islist",TYPEINFO_ISLIST);
    case 0x00003bef: W("is-list",TYPEINFO_ISLIST);
    case 0x00004697: W("islocal",TYPEINFO_ISLOCAL);
    case 0x0000788c: W("is-local",TYPEINFO_ISLOCAL);
    case 0x00009072: W("isnative",TYPEINFO_ISNATIVE);
    case 0x0000f4ba: W("is-native",TYPEINFO_ISNATIVE);
    case 0x0000918a: W("isnewing",TYPEINFO_ISNEWING);
    case 0x0000f61c: W("is-newing",TYPEINFO_ISNEWING);
    case 0x0000932c: W("isnumber",TYPEINFO_ISNUMBER);
    case 0x0000f84b: W("is-number",TYPEINFO_ISNUMBER);
    case 0x00012a04: W("isnumeric",TYPEINFO_ISNUMERIC);
    case 0x0001f4bc: W("is-numeric",TYPEINFO_ISNUMERIC);
    case 0x00008e90: W("isobject",TYPEINFO_ISOBJECT);
    case 0x0000f291: W("is-object",TYPEINFO_ISOBJECT);
    case 0x00009662: W("isstring",TYPEINFO_ISSTRING);
    case 0x0000fc5c: W("is-string",TYPEINFO_ISSTRING);
    case 0x00004a2e: W("istuple",TYPEINFO_ISTUPLE);
    case 0x00007d16: W("is-tuple",TYPEINFO_ISTUPLE);
    case 0x0000954c: W("isunique",TYPEINFO_ISUNIQUE);
    case 0x0000fb0a: W("is-unique",TYPEINFO_ISUNIQUE);
    case 0x000243ae: W("mayiterate",TYPEINFO_MAYITERATE);
    case 0x00040cc2: W("may-iterate",TYPEINFO_MAYITERATE);
    case 0x00246da6: W("mayiteratelist",TYPEINFO_MAYITERATELIST);
    case 0x0081db28: W("may-iterate-list",TYPEINFO_MAYITERATELIST);
    case 0x0048e4dc: W("mayiterateprops",TYPEINFO_MAYITERATEPROPS);
    case 0x0103c160: W("may-iterate-props",TYPEINFO_MAYITERATEPROPS);
    case 0x0000070c: W("name",TYPEINFO_NAME);
    case 0x00008bd2: W("refcount",TYPEINFO_REFCOUNT);

    default: break;
  }
#undef W
  return NULL;
}

int s2_keyword_f_typeinfo( s2_keyword const * kw, s2_engine * se,
                           s2_ptoker * pr, cwal_value **rv){
  s2_ptoken tIdent = s2_ptoken_empty;
  s2_ptoker prBody = s2_ptoker_empty;
  s2_ptoker const * oldScript = se->currentScript;
  cwal_size_t idLen = 0;
  cwal_value * xrv = 0;
  int rc;
  int buul = -1 /* <0=unknown, 0=false, >0=true */;
  s2_typeinfo_word const * word = 0;
  *rv = 0;
  assert(pr->e);
  /**
     Ideas:

     typeinfo(FLAG EXPR)

     We use (...) to avoid potential precedence confusion for use
     cases such as:

     typeinfo iscallable X || throw "need a callable type"

     i.e. does the || belong the RHS of the typeinfo or not? We punt
     on that problem by adding the parenthesis, making it unambiguous.

     FLAG is one of the words defined in s2TypeInfoWords above.
  */
  rc = s2_next_token( se, pr, 0, 0 );
  if(rc) goto end;

  if(S2_T_ParenGroup!=pr->token.ttype){
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Expecting (IDENTIFIER ...) after '%s'.",
                       kw->word);
    goto end;
  }

  if(se->skipLevel>0){
    /**
       Reminder to self: someday we should arguably continue going
       in skip mode so that we can validate that the contents of (...)
       are syntactically valid.
    */
    *rv = cwal_value_undefined();
    goto end;
  }

  rc = s2_ptoker_sub_from_toker(pr, &prBody);
  assert(prBody.e == pr->e);
  if(!rc){
    prBody.flags |= S2_T10N_F_IDENTIFIER_DASHES;
    rc = s2_next_token(se, &prBody, 0, 0);
    prBody.flags &= ~S2_T10N_F_IDENTIFIER_DASHES;
  }
  if(rc) goto end;
  else if(S2_T_Identifier!=prBody.token.ttype){
    rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                       "Expected typeinfo tag in %s(TAG ...).",
                       kw->word);
    goto end;
  }

  se->currentScript = &prBody;
  tIdent = prBody.token;
  idLen = s2_ptoken_len(&tIdent);
  assert(idLen > 0);
  /* MARKER(("typeinfo tag: %.*s\n", (int)idLen, s2_ptoken_begin(&tIdent))); */

  /*
    Look for sub-keyword match...
  */
  word = s2_ptoken_typeinfo(&tIdent);

  assert(!rc);
  if(!word){
    /* Note that in skip mode we've skipped over the whole (...), so
       this won't be triggered in skip mode (an exception to the
       rule/guideline regarding "blatant syntax errors").  Whether or
       not that needs "fixing" (such that this error could be
       triggered in skip mode) is as yet undecided.
    */
    rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                       "Unknown idenitifier '%.*s' in %s().",
                       (int)idLen, s2_ptoken_begin(&tIdent),
                       kw->word);
    goto end;
  }

  switch(word->argType){
    case TYPEINFO_ARG_IDENTIFIER:
        /* typeinfo(tag IDENTIFIER) */
      rc = s2_next_token(se, &prBody, 0, 0);
      if(!rc && S2_T_Identifier != prBody.token.ttype){
          rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                             "The %s(%.*s ...) tag requires an IDENTIFIER.",
                             kw->word, (int)idLen, s2_ptoken_begin(&tIdent));
      }
      if(rc) goto end;
      switch(word->type){
        case TYPEINFO_ISLOCAL:
        case TYPEINFO_ISDECLARED:
          xrv = s2_var_get(se, TYPEINFO_ISLOCAL==word->type ? 0 : -1,
                           s2_ptoken_begin(&prBody.token),
                           s2_ptoken_len(&prBody.token));
          buul = xrv ? 1 : 0;
          xrv = 0;
          break;
        default:
            s2_fatal(CWAL_RC_ASSERT,
                     "Missing typeinfo() handler entry for "
                     "TYPEINFO_ARG_IDENTIFIER!");
            break;
      }
      break;
    case TYPEINFO_ARG_EXPR:
        /* typeinfo(tag EXPR) */
      rc = s2_eval_expr_impl(se, &prBody, 0,
                             (TYPEINFO_NAME==word->type)
                             ? S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED
                             : 0
                             , &xrv);
      if(rc){
        assert(!xrv);
        goto end;
      }else if(!xrv && word->type==TYPEINFO_ISNEWING){
        /* Special case: typeinfo(isnewing) ==> typeinfo(isnewing this) */
        xrv = s2_var_get_v( se, -1, se->cache.keyThis );
      }else if(!xrv){
        rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                           "Expecting non-empty expression after %s(%.*s ...).",
                           kw->word, (int)idLen, s2_ptoken_begin(&tIdent));
        goto end;
      }
      cwal_value_ref(xrv);
      break;
    case TYPEINFO_ARG_NONE:
        /* typeinfo(tag) */
        ++se->skipLevel;
        rc = s2_eval_expr_impl(se, &prBody, 0, 0, &xrv);
        --se->skipLevel;
        if(rc){
            assert(!xrv);
            goto end;
        }else if(xrv){
            assert(cwal_value_undefined() == xrv && "b/c of skipLevel");
            cwal_value_ref(xrv);
            rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                               "Got unexpected expression in %s(%.*s <HERE>).",
                               kw->word, (int)idLen, s2_ptoken_begin(&tIdent));
            goto end;
        }
        break;
  }
  assert(!rc && "Expecting that non-0 RC did 'goto end'");

  switch(word->type){

#define TYPEWORD(WORD,PREDICATE) \
    case WORD: buul = PREDICATE(xrv); break

    TYPEWORD(TYPEINFO_ISARRAY,cwal_value_is_array);
    TYPEWORD(TYPEINFO_ISBOOL,cwal_value_is_bool);
    TYPEWORD(TYPEINFO_ISBUFFER,cwal_value_is_buffer);
    TYPEWORD(TYPEINFO_ISDOUBLE,cwal_value_is_double);
    TYPEWORD(TYPEINFO_ISENUM,s2_value_is_enum);
    TYPEWORD(TYPEINFO_ISEXCEPTION,cwal_value_is_exception);
    TYPEWORD(TYPEINFO_ISFUNCTION,cwal_value_is_function);
    TYPEWORD(TYPEINFO_ISINT,cwal_value_is_integer);
    TYPEWORD(TYPEINFO_ISITERATINGLIST,cwal_value_is_iterating_list);
    TYPEWORD(TYPEINFO_ISITERATINGPROPS,cwal_value_is_iterating_props);
    TYPEWORD(TYPEINFO_ISNATIVE,cwal_value_is_native);
    TYPEWORD(TYPEINFO_ISNEWING,s2_value_is_newing);
    TYPEWORD(TYPEINFO_ISOBJECT,cwal_value_is_object);
    TYPEWORD(TYPEINFO_ISSTRING,cwal_value_is_string);
    TYPEWORD(TYPEINFO_ISTUPLE,cwal_value_is_tuple);
    TYPEWORD(TYPEINFO_ISUNIQUE,cwal_value_is_unique);
    TYPEWORD(TYPEINFO_MAYITERATELIST,cwal_value_may_iterate_list);
    TYPEWORD(TYPEINFO_MAYITERATEPROPS,cwal_value_may_iterate);
#undef TYPEWORD

    case TYPEINFO_CANNEW:
      buul = s2_value_is_newable(se, xrv);
      break;

    case TYPEINFO_HASARRAY:
      buul = !!cwal_value_array_part(se->e, xrv);
      break;

    case TYPEINFO_HASBUFFER:
      buul = !!cwal_value_buffer_part(se->e, xrv);
      break;

    case TYPEINFO_HASENUM:
      buul = !!s2_value_enum_part(se, xrv);
      break;

    case TYPEINFO_HASEXCEPTION:
      buul = !!cwal_value_exception_part(se->e, xrv);
      break;

    case TYPEINFO_HASHASH:
      buul = s2_value_is_enum(xrv)
        ? 0 : !!cwal_value_hash_part(se->e, xrv);
      break;

    case TYPEINFO_HASNATIVE:
      buul = !!cwal_value_native_part(se->e, xrv, 0);
      break;

    case TYPEINFO_HASOBJECT:
      buul = !!cwal_value_object_part(se->e, xrv);
      break;

    case TYPEINFO_HASPROTOYPE:
      buul = !!cwal_value_prototype_get(se->e, xrv);
      break;

    case TYPEINFO_ISCALLABLE:
      buul = !!cwal_value_function_part(se->e, xrv);
      break;

    case TYPEINFO_ISCONTAINER:
      buul = cwal_props_can(xrv);
      break;

    case TYPEINFO_ISHASH:
      buul = cwal_value_is_hash(xrv) && !s2_value_is_enum(xrv);
      break;

    case TYPEINFO_ISITERATING:
      buul = cwal_value_is_iterating_props(xrv) || cwal_value_is_iterating_list(xrv);
      break;

    case TYPEINFO_ISLIST:
      buul = cwal_value_is_array(xrv) || cwal_value_is_tuple(xrv);
      break;

    case TYPEINFO_ISLOCAL:
    case TYPEINFO_ISDECLARED:
      /* handled above */
      assert(buul >= 0);
      break;

    case TYPEINFO_ISNUMBER:
      buul = cwal_value_is_integer(xrv)
        || cwal_value_is_double(xrv)
        /* reminder: don't use cwal_value_is_number() here b/c that
           one considers booleans to be numeric. */;
      break;

    case TYPEINFO_ISNUMERIC:
      if(!(buul = cwal_value_is_number(xrv))
         && cwal_value_is_string(xrv)){
      /* check for numeric-format strings a-la
         0.prototype.parseNumber(). */
        cwal_size_t slen = 0;
        char const * src = cwal_value_get_cstr(xrv, &slen);
        buul = s2_cstr_parse_double(src, slen, 0)
            || s2_cstr_parse_int(src, slen, 0);
      }
      break;

    case TYPEINFO_MAYITERATE:
      buul = cwal_value_may_iterate(xrv) || cwal_value_may_iterate_list(xrv);
      break;

    case TYPEINFO_ISDEREFABLE:
      switch(cwal_value_type_id(xrv)){
        case CWAL_TYPE_UNDEF:
        case CWAL_TYPE_NULL:
        case CWAL_TYPE_BOOL:
          buul = 0;
          break;
        default:
          buul = 1;
          break;
      }
      break;

    case TYPEINFO_NAME:{
      /* This dual-pronged approach isn't strictly necessary, but it
         keeps us from effectively strdup()'ing typenames on each call
         if xrv has/inherits a __typename property. The cwal type-name
         API does not deal in non-string types, so it cannot pass us
         back the underlying cwal_value for a given __typename
         property. Maybe that API needs to be changed?
      */
      cwal_value * vTn = cwal_prop_get_v(xrv, se->cache.keyTypename);
      if(!vTn){
        cwal_size_t nlen = 0;
        char const * name = cwal_value_type_name2(xrv, &nlen);
        vTn = nlen
          ? cwal_new_string_value(se->e, name, nlen)
          : cwal_value_undefined();
        if(!vTn){
          rc = CWAL_RC_OOM;
          goto end;
        }
      }
      *rv = vTn;
      break;
    }
    case TYPEINFO_REFCOUNT:
      *rv = cwal_new_integer(se->e,
                             (cwal_int_t)cwal_value_refcount(xrv));
      if(!*rv){
        rc = CWAL_RC_OOM;
        goto end;
      }
      break;
      
    case TYPEINFO_NONE:
      s2_fatal(CWAL_RC_ASSERT,
               "This is not possible: unhandled case in typeinfo handler.");
      break;
  }

  if(rc) goto end;
  else if(buul>=0){
    *rv = cwal_new_bool(buul ? 1 : 0);
  }else{
    assert(*rv);
  }

  end:
  s2_ptoker_finalize(&prBody);
  se->currentScript = oldScript;
  if(xrv){
    if(!rc && *rv == xrv) cwal_value_unhand(xrv);
    else cwal_value_unref(xrv);
  }
  return rc;
}

/**
   Internal helper for s2_foreach_iterate(), processing one entry
   from the result set.

   body holds the whole for-each body. If bodyIsExp then body is
   assumed to be a single expression, otherwise it's assumed to
   be a block of expressions. If keyName is not NULL then it is the
   NAME part of:

   foreach(X => NAME, value)

   and the key parameter must be the associated property key or
   array index.

   If valName is not NULL, it is the VALUE part of:

   foreach(X => name, VALUE)

   The val param holds the associated property value or array
   entry.

   Combinations of NULL/non-NULL which are legal depends on the
   operand's type, but it is never legal for both to be NULL.

   Returns 0 on success.
*/
static int s2_foreach_one( s2_engine * se, s2_ptoker const * body,
                           char bodyIsExpr,
                           cwal_value * keyName, cwal_value * key,
                           cwal_value * valName, cwal_value * val ){
  s2_ptoker pr = *body;
  int rc = 0;
  cwal_scope scope = cwal_scope_empty;
  rc = cwal_scope_push2(se->e, &scope);
  if(rc) return rc;
  assert(keyName ? !!key : !key);
  assert(valName ? !!val : !!keyName);
  if(keyName && (rc = s2_var_decl_v( se, keyName, key, 0 ))){
    goto end;
  }
  if(valName && (rc = s2_var_decl_v( se, valName, val, 0 ))){
    goto end;
  }
  assert(body->e);
  assert(pr.e);
  /* FIXME: won't work with #compiled tokens: */
  s2_ptoken_begin_set(&pr.token, s2_ptoker_begin(body));
  s2_ptoken_end_set(&pr.token, s2_ptoker_end(body));
  rc = s2_eval_current_token(se, &pr, bodyIsExpr, 0, 0);
  rc = s2_check_interrupted(se, rc);
  switch(rc){
    /* case CWAL_RC_BREAK: is handled higher up */
    case CWAL_RC_CONTINUE:
      rc = 0;
      s2_engine_err_reset(se);
      break;
    default:
      break;
  }

  end:
  cwal_scope_pop(se->e);
  return rc;
}

/**
   State used by s2_foreach_iterate().
*/
struct s2_foreach_state {
  s2_engine * se;
  s2_ptoker * body;
  char bodyIsExpr;
  char isEnum;
  cwal_value * keyName;
  cwal_value * valName;
  cwal_value * breakRC;
  cwal_size_t index;
};
typedef struct s2_foreach_state s2_foreach_state;
static const s2_foreach_state s2_foreach_state_empty = {
0,0,0,0,0,0,0,0U
};

/* cwal_kvp_visitor_f() impl for s2_foreach_iterate(). */
static int s2_foreach_kvp_visitor( cwal_kvp const * kvp, void * state ){
  s2_foreach_state * fst = (s2_foreach_state*)state;
  int rc = 0;
  cwal_value * val = cwal_kvp_value(kvp);
  assert(fst && fst->se);
  assert(fst->keyName);
  if(fst->isEnum && !cwal_value_is_unique(val)){
    /* for enums, only iterate over the string=>unique mappings, not
       the reverse mappings nor the properties like enumEntryCount. Seems
       reasonable enough. */
  }
#if S2_TRY_INTERCEPTORS
  else if(!!s2_value_is_interceptor(val)){
    /* Don't iterate over interceptors. */
  }
#endif
  else{
    rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr,
                         fst->keyName, cwal_kvp_key(kvp),
                         fst->valName, fst->valName ? val : 0 );
    if(CWAL_RC_BREAK==rc){
      fst->breakRC = s2_propagating_take(fst->se);
      assert(fst->breakRC && "misuse of CWAL_RC_BREAK");
      cwal_value_ref(fst->breakRC);
      /* s2_engine_err_reset(fst->se); */
      /* s2_dump_val(fst->breakRC,"breakRC"); */
    }
  }
  return rc;
}

/* cwal_array_visitor_f() impl for s2_foreach_iterate(). */
static int s2_foreach_array_visitor( cwal_array * a, cwal_value * v,
                                     cwal_size_t index, void * state ){
  s2_foreach_state * fst = (s2_foreach_state*)state;
  int rc;
  cwal_engine * e;
  cwal_value * vIndex;
  assert(fst && fst->se);
  e = fst->se->e;
  vIndex = fst->keyName ? cwal_new_integer(e, (cwal_int_t)index) : 0;
  if(fst->keyName && !vIndex){
    if(a){/*avoid unused param warning*/}
    return CWAL_RC_OOM;
  }
  cwal_value_ref(vIndex);
  rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr,
                       fst->keyName, fst->keyName ? vIndex : 0,
                       fst->valName, v ? v : cwal_value_undefined() );
  cwal_value_unref(vIndex);
  if(CWAL_RC_BREAK==rc){
    fst->breakRC = s2_propagating_take(fst->se);
    assert(fst->breakRC && "misuse of CWAL_RC_BREAK");
    cwal_value_ref(fst->breakRC);
    /* s2_engine_err_reset(fst->se); */
  }
  return rc;
}

/* cwal_value_visitor_f() impl for s2_foreach_iterate(), for
   visiting tuples. */
static int s2_foreach_tuple_visitor( cwal_value * v, void * state ){
  s2_foreach_state * fst = (s2_foreach_state*)state;
  int rc;
  cwal_engine * e;
  cwal_value * vIndex;
  assert(fst && fst->se);
  e = fst->se->e;
  vIndex = fst->keyName ? cwal_new_integer(e, (cwal_int_t)fst->index++) : 0;
  if(fst->keyName && !vIndex){
    return CWAL_RC_OOM;
  }
  if(vIndex) cwal_value_ref(vIndex);
  rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr,
                       fst->keyName, fst->keyName ? vIndex : 0,
                       fst->valName, v ? v : cwal_value_undefined() );
  if(vIndex) cwal_value_unref(vIndex);
  if(CWAL_RC_BREAK==rc){
    fst->breakRC = s2_propagating_take(fst->se);
    assert(fst->breakRC && "misuse of CWAL_RC_BREAK");
    cwal_value_ref(fst->breakRC);
    /* s2_engine_err_reset(fst->se); */
  }
  return rc;
}

/**
   foreach impl for strings. vStr must be the X part of
   foreach(X=>...) and it must be of type string (that gets
   asserted). Unlike most places, a Buffer will not act like a string
   here (foreach will use the Object property path for Buffers).

   Note that iteration using this approach is far more efficient than
   iterating over its length, e.g.

   for(var i = 0, n = aString.#; i<n; ++i ){... aString[i] ...}

   because that approach has to (for non-ASCII strings) re-traverse
   the whole string to find the i'th character.

   Returns 0 on success.
*/
static int s2_foreach_string_char( cwal_value * vStr, s2_foreach_state * fst ){
  int rc = 0;
  cwal_size_t slen = 0;
  cwal_string * str = cwal_value_get_string(vStr);
  char unsigned const * cstr = (char unsigned const *)cwal_string_cstr2( str, &slen );
  char unsigned const * pos = cstr;
  char const isAscii = cwal_string_is_ascii(str);
  cwal_engine * const e = fst->se->e;
  cwal_value * vIndex = 0;
  cwal_value * vVal = 0;
  char unsigned const * const eof = pos + slen;
  assert(str);
  assert(cstr);
  for( ; !rc && pos < eof && fst->index < slen; ++fst->index ){
    unsigned char const * cend = pos;
    if(isAscii){
      pos = cstr + fst->index;
      cend = pos + 1;
    }else{
      cwal_utf8_read_char(pos, eof, &cend);
      if(!(cend-pos)) break /*???*/;
    }
    assert(cend <= eof);
    vVal = cwal_new_string_value(e, (char const*)pos, (cwal_size_t)(cend-pos));
    if(!vVal){
      rc = CWAL_RC_OOM;
      break;
    }
    cwal_value_ref(vVal);
    if(fst->keyName){
      vIndex = cwal_new_integer(e, (cwal_int_t)fst->index);
      if(!vIndex){
        cwal_value_unref(vVal);
        rc = CWAL_RC_OOM;
        break;
      }
      cwal_value_ref(vIndex);
    }
    rc = s2_foreach_one( fst->se, fst->body, fst->bodyIsExpr,
                         fst->keyName, fst->keyName ? vIndex : 0,
                         fst->valName, vVal );
    if(vIndex) cwal_value_unref(vIndex);
    cwal_value_unref(vVal);
    pos = cend;
  }
  if(CWAL_RC_BREAK==rc){
    fst->breakRC = s2_propagating_take(fst->se);
    assert(fst->breakRC && "misuse of CWAL_RC_BREAK");
    cwal_value_ref(fst->breakRC);
    /* s2_engine_err_reset(fst->se); */
  }
  return rc;
}

/**
   Part of the foreach() loop mechanism. This part loops over the
   XXX part of foreach(...) XXX;

   arguments:

   kw: the keyword this function is working for (used for error
   reporting).

   body: the token holding the complete body of the loop.

   bodyIsExpr: if true, body is assumed to contain only a single
   expression, else is it assumed to be a {script block}.

   container: the container or string to iterate over.

   opMode: what to iterate over: 0 = object props, enum entries, or
   tuple entries. S2_T_At = array and tuple entries. S2_T_OpHash =
   hash entries. S2_T_LiteralString = a string (iterate over its
   characters), noting that it need not be a literal (we just use this
   token type ID because it's convenient to do so). Anything else is
   illegal and may trigger an assert().

   keyName: the symbol name of the key part of foreach(X=>key,val).

   valName: the symbol name of the val part of foreach(X=>key,val).

   rv: on success, the result value is put here (rescoped if needed)
   if it's not NULL.
*/
static int s2_foreach_iterate( s2_keyword const * kw, s2_engine * se,
                               s2_ptoker * body, char bodyIsExpr,
                               cwal_value * container, int opMode,
                               cwal_value * keyName, cwal_value * valName,
                               cwal_value ** rv){
  int rc = 0;
  s2_foreach_state fst = s2_foreach_state_empty;
  assert(0==opMode
         || S2_T_At==opMode
         || S2_T_OpHash==opMode
         || S2_T_LiteralString);
  fst.se = se;
  fst.body = body;
  fst.bodyIsExpr = bodyIsExpr;
  fst.keyName = keyName;
  fst.valName = valName;
  assert(!se->skipLevel);
  /* s2_dump_val(container,"container"); */
  /* s2_dump_val(keyName,"keyName"); */
  /* s2_dump_val(valName,"valName"); */

  /* Special case: for enums, when no mode is specified, choose its
     enum hash entries, but filter so that only the "forward"
     mappings, not its value-to-key reverse mappings, are
     traversed... */
  if( !opMode && s2_value_is_enum(container)){
    opMode = S2_T_OpHash;
    fst.isEnum = 1;
    assert(cwal_value_is_hash(container) && "Enums are always hashes since 2020-02-21.");
  }

  if(rv) *rv = cwal_value_undefined();
  switch(opMode){
    case S2_T_OpHash: /* Hash entries */
      assert(cwal_value_get_hash(container));
      CWAL_SWITCH_FALL_THROUGH;
    case 0: /* Object properties */{
      assert(cwal_props_can(container));
      rc = 0==opMode
        ? cwal_props_visit_kvp( container, s2_foreach_kvp_visitor,
                                &fst )
        : cwal_hash_visit_kvp( cwal_value_get_hash(container),
                               s2_foreach_kvp_visitor, &fst )
        ;
      break;
    }/* Objects/Hashes */
    case S2_T_At: /* Array/Tuple entries */{
      cwal_array * ar = cwal_value_array_part(se->e, container);
      cwal_tuple * tp = ar ? 0 : cwal_value_get_tuple(container);
      if(ar){
        rc = cwal_array_visit2( ar, s2_foreach_array_visitor, &fst );
      }else{
        assert(tp);
        rc = cwal_tuple_visit(tp, s2_foreach_tuple_visitor, &fst);
      }
      break;
    }
    case S2_T_LiteralString:
      rc = s2_foreach_string_char( container, &fst );
      break;
    default:
      if(kw){/*avoid unused param warning*/}
      assert(!"Unknown opMode!");
      rc = CWAL_RC_CANNOT_HAPPEN;
      break;
  }
  /* Reminder to self (20180620): there's seeminly a race condition
     between the break- and interruption handling here, in that a
     Break can potentially hide/trump a poorly-timed interrupt. We
     check for interruption in the top-level foreach impl. */
  if(CWAL_RC_BREAK==rc){
    rc = 0;
    assert(fst.breakRC && "should have been set by iterator");
    if(fst.breakRC){
      assert((cwal_value_is_builtin(fst.breakRC)
              || cwal_value_refcount(fst.breakRC))
             && "We're expecting the ref we held.");
      if(rv) {
        *rv = fst.breakRC;
        cwal_value_unhand(fst.breakRC);
      }else{
        cwal_value_unref(fst.breakRC);
      }
    }
  }else{
    if(fst.breakRC/* hypothetically possible on an interrupt */){
      assert((cwal_value_is_builtin(fst.breakRC)
              || cwal_value_refcount(fst.breakRC))
             && "We're expecting the ref we held.");
      cwal_value_unref(fst.breakRC);
    }
  }
  return rc;
}


/**
   foreach(X => v)
   foreach(X => k, v)

   Where X may be:

   container: iterate over non-hidden properties (never inherited properties).

   @array: iterate over array entries.

   #hash: iterate over hash entries.

   enum: iterate over (key=>enumEntry) pairs.

   tuple: iterate over tuple entries. May optionally, for consistency
   with @array, be prefixed with @, but the meaning is the same.

   string: iterate over each character.

   If 1 operand is passed after =>, then its interpretation depends on
   the data type:

   - Arrays, tuples, strings: the value of the element
   - Everything else: the property key

   If 2 operands are passed after =>, they are the property key/index
   and value, in that order.

   The value to the left of the => is evaluated only once, so it may,
   e.g. be the result of a function call.

   Reminder to self: results are undefined if passed a non-NULL final
   argument which points to uninitialized memory. i.e. *rv must, when
   this is called, either be 0 or point to a value. In practice,
   though, it must never point to a value, as we will (on success)
   overwrite it without unreferencing it. We never, in day-to-day
   work, leave such values uninitialized, but if this routine crashes
   near the end, it may be an indication of such.
*/
int s2_keyword_f_foreach( s2_keyword const * kw, s2_engine * se,
                          s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_ptoker prParens = s2_ptoker_empty /* the whole (X => ...) part */;
  s2_ptoker prBody = s2_ptoker_empty /* the ... part of (X => ...) */;
  s2_ptoken tBody = s2_ptoken_empty /* body block/expression */;
  s2_ptoken tKey = s2_ptoken_empty /* (x => KEY, value) */;
  s2_ptoken tVal = s2_ptoken_empty /* (x => VALUE) (X => key, VALUE) */;
  cwal_value * operand = 0/*the value at the X part of foreach(X=>...)*/;
  char bodyIsExpr = 0 /* true if the loop body is a standalone expr,
                         false if it's a block. */;
  int operandMode = 0
    /*
      0 = object props, enum entries, or tuple entries

      S2_T_At = array and tuple entries

      S2_T_OpHash = hash entries

      S2_T_LiteralString = chars of a string
    */;
  
  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);
  }
  rc = s2_ptoker_sub_from_toker(pr, &prParens);
  if(!rc){
    rc = s2_keyword_loop_get_body(kw, se, pr, &tBody, &bodyIsExpr);
  }
  if(rc) goto end;

  if(se->skipLevel){
    if(rv) *rv = cwal_value_undefined();
    goto end;
  }
  else{
    /* Parse the (X => ...) part */
    s2_ptoker prEntry = prParens
      /* (X => ...) part */
      /* This will break if/when we add #compiled tokens */
      ;
    s2_ptoken tmpTok = s2_ptoken_empty;
    s2_op const * opArrow = s2_ttype_op(S2_T_OpArrow2);
    assert(opArrow);
    /* Check for a leading '@' or '#' ... */
    rc = s2_next_token(se, &prEntry, 0, 0);
    if(rc){
      /*rc = s2_err_ptoker(se, &prEntry, rc, "Tokenization error.");*/
      goto end;
    }
    switch(prEntry.token.ttype){
      case S2_T_At:
      case S2_T_OpHash:
        operandMode = prEntry.token.ttype;
        break;
      default:
        s2_ptoker_next_token_set(&prEntry, &prEntry.token);
        break;
    }

    rc = s2_eval_expr_impl(se, &prEntry, opArrow, 0, &operand);
    if(operand) cwal_value_ref(operand);
    if(rc){
      assert(!operand && "but... how?");
      /*
        20181119: let's allow a break in the operand expression, for
        the sake of:

        foreach((blah ||| break)=>x){...}

        Yeah, it's a corner case, but i just tried to do it in a script
        and it didn't work, so... here it is.
      */
      if(CWAL_RC_BREAK==rc){
        *rv = s2_propagating_take(se);
        assert(*rv && "Else internal misuse of CWAL_RC_BREAK.");
        rc = 0;
      }
      goto end;
    }
    else if(!operand){
      rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX,
                         "Empty expression not allowed in the X part of "
                         "%s(X=>...).", kw->word);
      goto end;
    }
    tmpTok = prEntry.capture.begin;
    /* s2_dump_val(operand,"operand"); */
    /*MARKER(("(X => ...) operandMode=%s, X=<<<%.*s>>>\n",
            s2_ttype_cstr(operandMode),
            (int)s2_ptoken_len(&tmpTok), s2_ptoken_begin(&tmpTok)));*/
    rc = s2_next_token(se, &prEntry, 0, 0);
    if(rc) goto end;
    /* Confirm operand type is okay... */
    switch(operandMode){
      case 0: /* default mode and mode for Object properties */{
        if(cwal_value_is_tuple(operand)){
          /* Force iteration over entries */
          operandMode = S2_T_At;
        }
        else if(cwal_value_is_string(operand)){
          operandMode = S2_T_LiteralString;
        }
        else if(!cwal_props_can(operand)){
          s2_ptoker_errtoken_set(&prEntry, &tmpTok);
          rc = s2_err_ptoker(se, &prEntry, CWAL_RC_TYPE,
                             "Expecting a string or a value capable of "
                             "holding properties.");
        }
        else if(!cwal_props_has_any(operand)
                && !s2_value_is_enum(operand)
                ){
          /* Simply checking for no properties isn't sufficient in the
             case of hashed enums unless the enum has the
             enumEntryCount property (which was removed 20171204). */

          /* Nothing to do! */
          /* *rv set is important, else the eval loop
             ignores what we parsed and takes "foreach" (the string)
             as the next token! */
          *rv = cwal_value_undefined();
          goto end;
        }
        break;
      }
      case S2_T_At: /* Array and tuple entries */{
        cwal_array * ar = cwal_value_array_part(se->e, operand);
        cwal_tuple * tp = ar ? 0 : cwal_value_get_tuple(operand);
        if(!ar && !tp){
          s2_ptoker_errtoken_set(&prEntry, &tmpTok);
          rc = s2_err_ptoker(se, &prEntry, CWAL_RC_TYPE,
                             "Expecting an Array or Tuple value.");
        }else if(0==
                 (ar ? cwal_array_length_get(ar) : cwal_tuple_length(tp))){
          /* Nothing to do! */
          *rv = cwal_value_undefined();
          goto end;
        }
        break;
      }
      case S2_T_OpHash: /* Hash entries */{
        cwal_hash * h = cwal_value_hash_part(se->e, operand);
        if(!h){
          s2_ptoker_errtoken_set(&prEntry, &tmpTok);
          rc = s2_err_ptoker(se, &prEntry, CWAL_RC_TYPE,
                             "Expecting a Hash value.");
        }else if(!cwal_hash_entry_count(h)){
          /* Nothing to do! */
          *rv = cwal_value_undefined();
          goto end;
        }
        break;
      }
      default:
        rc = CWAL_RC_ASSERT;
        assert(!"Wrong opMode!");
    }
    if(rc) goto end;

    if(S2_T_OpArrow2 != prEntry.token.ttype){
      rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX,
                         "Expecting => after X in "
                         "%s(X => ...).",
                         kw->word);
      goto end;
    }
    rc = s2_next_token(se, &prEntry, 0, 0);
    if(rc) goto end;
    else if(S2_T_Identifier != prEntry.token.ttype){
      rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX,
                         "Expecting identifier after => "
                         "in %s(X => ...).",
                         kw->word);
      goto end;
    }else{
      tKey = prEntry.token;
    }
    /* Check for a comma then a value identifier... */
    rc = s2_next_token(se, &prEntry, 0, 0);
    if(rc) goto end;
    else if(S2_T_Comma == prEntry.token.ttype){
      rc = s2_next_token(se, &prEntry, 0, 0);
      if(rc) goto end;
      else if(S2_T_Identifier != prEntry.token.ttype){
        rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX,
                           "Expecting identifier after comma "
                           "in %s(X => Y,...).",
                           kw->word);
        goto end;
      }else{
        tVal = prEntry.token;
      }
    }else if(!s2_ttype_is_eox(prEntry.token.ttype)){
      rc = s2_err_ptoker(se, &prEntry, CWAL_SCR_SYNTAX,
                         "Unexpected token after Y in "
                         "%s(X => Y...).",
                         kw->word);
      goto end;
    }else{
      if(S2_T_At==operandMode || S2_T_LiteralString==operandMode){
      /* (@X=>V) and ("string"=>V) pass the index's value, not the
         index, to each loop iteration. */
        tVal = tKey;
        tKey = s2_ptoken_empty;
      }
    }
  }

  /*MARKER(("tBody (isExpr=%d, opMode=%s): <<<%.*s>>>\n", bodyIsExpr,
          s2_ttype_cstr(operandMode), (int)s2_ptoken_len(&tBody),
          s2_ptoken_begin(&tBody)));*/
  /*MARKER(("tKey: <<<%.*s>>>\n", (int)s2_ptoken_len(&tKey), s2_ptoken_begin(&tKey)));*/
  /*MARKER(("tVal: <<<%.*s>>>\n", (int)s2_ptoken_len(&tVal), s2_ptoken_begin(&tVal)));*/

  /* And now, finally, create the key/value name parts and iterate
     over the body... */
  assert(!rc);
  if(!rc){
    cwal_value * key = 0;
    cwal_value * val = 0;
    cwal_value * xrv = 0;
    cwal_scope scope = cwal_scope_empty;
    assert(tVal.ttype || tKey.ttype);
    rc = cwal_scope_push2(se->e, &scope);
    if(rc) goto end;
    rc = s2_ptoker_sub_from_token(pr, &tBody, &prBody)
      /* 20200107 FIXME: this won't work with #compiled tokens when
         tBody is non-{} expression, nor can
         s2_ptoker_sub_from_token() accommodate underlying compiled
         tokens, whether or not tBody is a {} block. */;
    assert(!rc && "Cannot fail!");
    assert(pr->e);
    prBody.parent = pr;
    prBody.e = pr->e;
    if(tKey.ttype){
      rc = s2_ptoken_create_value(se, pr, &tKey, &key);
      if(rc){
        assert(!key);
        goto pop_scope;
      }
      assert(key);
      cwal_value_ref(key);
    }
    if(tVal.ttype){
      rc = s2_ptoken_create_value(se, pr, &tVal, &val);
      if(rc){
        if(key) cwal_value_unref(key);
        goto pop_scope;
      }
      assert(val);
      cwal_value_ref(val);
    }

    /* s2_dump_val(key,"foreach key"); */
    /* s2_dump_val(val,"foreach value name"); */
    ++se->scopes.current->sguard.vacuum /* protect key/val */;
    rc = s2_foreach_iterate( kw, se, &prBody, bodyIsExpr,
                             operand, operandMode,
                             key, val, &xrv);
    --se->scopes.current->sguard.vacuum;
    if(xrv) cwal_value_ref(xrv);
    if(key) cwal_value_unref(key);
    cwal_value_unref(val);
    val = 0;
    /* prBody.errPos = 0; */
    pop_scope:
    assert( &scope == se->scopes.current->cwalScope );
    cwal_scope_pop2( se->e, rc ? 0 : xrv );
    rc = s2_check_interrupted(se, rc);
    if(!rc && rv){
      *rv = xrv ? xrv : cwal_value_undefined();
      if(xrv) cwal_value_unhand(xrv);
    }else{
      if(xrv) cwal_value_unref(xrv);
    }
  }

  end:
  if(!rc){
    s2_ptoker_token_set(pr, &tBody)
      /* 20200107 FIXME: this won't work with #compiled tokens when
         tBody is non-{} expression. */;
    rc = s2_check_interrupted(se, rc);
  }
  if(operand){
    if(!rc && rv) cwal_value_ref(*rv);
    cwal_value_unref(operand);
    if(!rc && rv) cwal_value_unhand(*rv);
  }
  s2_ptoker_finalize( &prParens );
  return rc;
}

/**
   The using() function-like keyword is used for accessing
   "using"/importSymbols()-injected state.
*/
int s2_keyword_f_using( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc;
  cwal_value * imports = 0;
  cwal_value * xrv = 0;
  assert(S2_T_KeywordUsing == kw->id);
  rc = s2_next_token(se, pr, 0, 0);
  if(rc) return rc;
  if(S2_T_ParenGroup!=pr->token.ttype){
    if(se->currentScriptFunc){
      /* Accept "using" as shorthand for "using()" inside a script
         function body. */
      s2_ptoker_putback(pr);
    }else{
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Expecting (...) after %s keyword.",
                           kw->word);
    }
  }
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }
  if(S2_T_ParenGroup==pr->token.ttype){
    rc = s2_eval_current_token(se, pr, 0,
                               0/* S2_EVAL_PUSH_SCOPE seems like overkill,
                                   as the operand will almost always be empty
                                   or an identifier/property access, rather than
                                   a complex expression. Note that the empty-parens
                                   case gets optimized out (no eval, no scope),
                                   either way.*/,
                               &xrv);
    if(rc){
      assert(!xrv);
      return rc;
    }
  }else{
    assert(se->currentScriptFunc /* tested above */);
  }
  if(!xrv){
    /* using() is valid in the current script-side func to refer
       to its own imports. */
    if(!se->currentScriptFunc){
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "%s() is only valid in the body of a "
                         "script-implemented function.",
                         kw->word);
    }else{
      imports = se->currentScriptFunc->vImported;
      cwal_value_ref(imports);
    }
  }else{
    /* using(expr), where expr must be a script-side function. */
    cwal_function * f = cwal_value_get_function(xrv);
    s2_func_state * fst = f ? s2_func_state_for_func(f) : 0;
    cwal_value_ref(xrv);
    if(!fst){
      rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE,
                           "Expression passed to %s(...) is not a "
                           "script-defined function.",
                           kw->word)
        /* Note that this throws an exception, but using(<EMPTY>) in
           the wrong place is a syntax error. Feature or bug? */;
    }else{
      imports = fst->vImported;
      cwal_value_ref(imports);
    }
    cwal_value_unref(xrv);
    xrv = 0;
  }
  if(imports){
    assert(!rc);
#if 0
    cwal_value_rescope(se->scopes.current->cwalScope, imports)
      /* i don't think this is strictly necessary. i haven't (yet?)
         been able to work out a case where the imports would end up
         in a wrong scope without this. If the imports are accessed by
         an older scope, they'll get rescoped as soon as a local symbol
         references them, they're propagated, or similar.*/;
#endif
    cwal_value_unhand(imports);
    *rv = imports;
  }else if(!rc){
    *rv = cwal_value_undefined();
  }
  return rc;
}

/**
   The s2out keyword.
*/
int s2_keyword_f_s2out( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  cwal_value * v = se->skipLevel>0 ? cwal_value_undefined() : se->cache.s2out;
  if(!v){
    /*
      Initialize it on the first call...

      The API does not guaranty that s2.io.output ever gets installed,
      so we cannot simply reuse that object here (though we may at
      some point try that before falling back to creating a new
      object). Alternately, we could reverse that: initilialize
      s2.output to point to this object.
    */
    cwal_scope sc = cwal_scope_empty
      /*
        Reminder to self: we use a scope here in order to cleanly
        handle the 1-in-a-bazillion chance that the s2out.operator<<
        prop set succeeds but s2_stash_set() fails (out of memory). In
        that case we'd leave a cyclic structure laying around, which
        the scope will clean up for us.
      */;
    S2_UNUSED_ARG pr;
    rc = cwal_scope_push2(se->e, &sc);
    if(rc) return rc;
    v = cwal_new_function_value(se->e, s2_cb_write, 0, 0, 0);
    if(v){
      cwal_value_prototype_set(v, 0);
      /*
        We want s2out to be callable like s2out(...)  and
        s2out<<arg<<arg<<...; The easiest and most efficient way to do
        that is...
      */
      rc = cwal_prop_set_with_flags(
             v, "operator<<", 10, v,
             CWAL_VAR_F_CONST
             /* const is not strictly needed, but that's okay */
           );
      if(!rc){
        rc = s2_stash_set(se, kw->word, v )
          /* Moves it to the top scope and implicitly makes it
             vacuum-proof. */;
      }
    }else{
      rc = CWAL_RC_OOM;
    }
    cwal_scope_pop2( se->e, rc ? 0 : v );
    if(rc){
      v = 0 /* was destroyed by the popped scope */;
    }else{
      /* v is held by/in se->stash. */
      se->cache.s2out = v;
      /* We'll disallow new properties on this object for sanity's
         sake. We can reconsider this later if the need arises. One
         side-effect of this is that we cannot reasonably make
         s2.io.output an alias for this object because there's no good
         justification for locking that object this way. */
      s2_seal_container(v, 1);
    }
  }
  if(!rc) *rv = v;
  return rc;
}

/**
   The cwal_callback_f() part of the import keyword.
*/
static int s2_cb_keyword_import( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  s2_engine * const se = s2_engine_from_args(args);
  cwal_value * const vPf = cwal_prop_get(cwal_function_value(args->callee),
                                         "path", 4);
  s2_pf * const pf = s2_value_pf(vPf);
  if((rc=s2_cb_disable_check(args, S2_DISABLE_FS_STAT | S2_DISABLE_FS_READ))){
    return rc;
  }
  else if(!pf){
    rc = s2_cb_throw(args, CWAL_RC_ASSERT,
                     "The import.path property has gone missing!");
  }
  else if(1==args->argc && cwal_value_is_bool(args->argv[0])){
    /* import(bool): set args->self[se->cache.keyImportFlag] */;
    s2_seal_container(args->self, 0);
    rc = cwal_prop_set_v(args->self, se->cache.keyImportFlag,
                         args->argv[0]);
    s2_seal_container(args->self, 1);
    if(!rc) *rv = args->self;
  }else if(!args->argc || args->argc>2 ||
           (2==args->argc && (!cwal_value_is_bool(args->argv[0])))
           /* Non-stringy argv[1] case is handled below. */
           ){
    misuse:
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting ([bool doPathSearch,] string filename) "
                       "or (bool doPathSearch) arguments(s).");
  }else{
    /*
      import([bool doPathSearch,] string filename), where doPathSearch
      defaults to args->self[se->cache.keyImportFlag].
    */
    char const doPfSearch = args->argc>1
      ? cwal_value_get_bool(args->argv[0])
      : cwal_value_get_bool(cwal_prop_get_v(args->self,
                                            se->cache.keyImportFlag));
    cwal_value * const vFName = args->argv[args->argc>1 ? 1 : 0];
    cwal_size_t fLen;
    char const * fname = cwal_value_get_cstr(vFName, &fLen);
#define FMT_NOT_FOUND "File not found: %.*s", (int)fLen, fname
    if(!fname){
      goto misuse;
    }
    else if(doPfSearch){
      char const * fn = s2_pf_search(pf, fname, fLen, &fLen,
                                     S2_PF_SEARCH_FILES);
      if(fn) fname = fn;
      if(!fn){
        rc = s2_cb_throw(args, CWAL_RC_NOT_FOUND, FMT_NOT_FOUND);
        if(CWAL_RC_EXCEPTION==rc){
          /* Amend the exception with:
             exception.notFound = 
             {filename:name, path:array, extensions:array} */
          int rc2;
          cwal_value * obj;
          cwal_value * const ex = cwal_exception_get(args->engine);
          assert(ex && "But we *just* threw one?");
          obj = cwal_new_object_value(args->engine)
            /* Reminder to self: we're running in a callback scope, so
               cleanup on error is not _strictly_ necessary here. */;
          if(obj){
            cwal_value_ref(obj);
            rc2 = cwal_prop_set(obj, "filename", 8, vFName);
            if(!rc2) rc2 = cwal_prop_set(obj, "path", 4,
                                         cwal_array_value(s2_pf_dirs(pf)));
            if(!rc2) rc2 = cwal_prop_set(obj, "extensions", 10,
                                         cwal_array_value(s2_pf_exts(pf)));
            if(!rc2){
              rc2 = cwal_prop_set(ex, "notFound", 8, obj);
            }
            cwal_value_unref(obj);
          }else{
            rc2 = CWAL_RC_OOM;
          }
          if(rc2){
            rc = rc2;
          }
        }
      }
    }else if(!s2_file_is_accessible(fname, 0)){
      rc = s2_cb_throw(args, CWAL_RC_NOT_FOUND, FMT_NOT_FOUND);
    }
#undef FMT_NOT_FOUND
    if(!rc){
      rc = s2_eval_filename(se, 1, fname, fLen, rv);
    }
  }
  return rc;
}

/**
   The import keyword impl.
*/
int s2_keyword_f_import( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  cwal_value * v = se->skipLevel>0 ? cwal_value_undefined() : se->cache.import;
  if(!v){
    /*
      Initialize it on the first call...
    */
    cwal_scope sc = cwal_scope_empty
      /*
        Reminder to self: we use a scope here in order to simplify
        cleanup if something goes wrong.
      */;
    s2_pf * pf = 0;
    cwal_value * vImport = 0;
    S2_UNUSED_ARG pr;
    rc = cwal_scope_push2(se->e, &sc);
    if(rc) return rc;
    vImport = cwal_new_function_value(se->e, s2_cb_keyword_import, 0, 0, 0);
    if(!vImport){
      rc = CWAL_RC_OOM;
      goto end;
    }
    pf = s2_pf_new(se);
    rc = pf
      ? cwal_prop_set_with_flags(vImport, "path", 4,
                                 s2_pf_value(pf), CWAL_VAR_F_CONST)
      : CWAL_RC_OOM;
    if(!rc){
      rc = cwal_prop_set_v(vImport, se->cache.keyImportFlag,
                           cwal_value_true());
    }
    if(rc) goto end;
    else{/* Set path/extensions from environment, if available... */
      struct {
        char const * evar;
        char const * dflt;
        int (*adder)(s2_pf *, char const *, cwal_size_t);
        cwal_array * (*getter)(s2_pf*);
      } envs[] = {
        {"S2_IMPORT_PATH", NULL, s2_pf_dir_add, s2_pf_dirs
         /* Potential TODO: if s2_home_get() returns non-NULL
            and S2_IMPORT_PATH is not set, add S2_HOME/lib(?)
            (S2_HOME/import?) to the defeault import path. */
        },
        {"S2_IMPORT_EXTENSIONS", ".s2", s2_pf_ext_add, s2_pf_exts}
      };
      s2_path_toker pt = s2_path_toker_empty;
      char const * entry = 0;
      char const * env;
      cwal_size_t entryLen = 0;
      size_t i = 0;
      assert(pf);
      for(; !rc && i < sizeof(envs)/sizeof(envs[0]); ++i){
        env = getenv(envs[i].evar);
        if(!env){
          /* Note that empty env vars evaluate to "", not NULL.  We
             rely on that here to enable overriding of the default
             S2_IMPORT_EXTENSIONS value with an empty list. */
          env = envs[i].dflt;
        }
        if(env && *env){
          s2_path_toker_init(&pt, env, -1);
          while(!rc && !s2_path_toker_next(&pt, &entry, &entryLen)){
            rc = envs[i].adder(pf, entry, entryLen);
          }
        }
        if(!rc && !envs[i].getter(pf)){
          /* If there was no env var, or it had no entries, the
             current pf array member will be NULL. Calling the getter
             initializes that member if needed, and we want that for
             this use case. */
          rc = CWAL_RC_OOM;
          break;
        }
      }
      if(rc) goto end;
    }
    assert(!rc);
    rc = s2_stash_set(se, kw->word, vImport)
      /* Moves it to the top scope and implicitly makes it
         vacuum-proof. */;
    end:
    cwal_scope_pop( se->e )
      /* Remember that:
         1) on success, vImport is in the stash (so it's alive) and
         2) this pop also cleans up pf on error.
      */;
    if(rc){
       /* vImport was destroyed by the popped scope */
    }else{
      /* vImport is held by/in se->stash. */
      v = se->cache.import = vImport;
      s2_seal_container(vImport, 1);
    }
  }/* end of first-call initialization */
  if(!rc) *rv = v;
  return rc;
}


/**
   Tag type IDs for pragma(TAG ...).

   ACHTUNG: their order MUST match the entries of the s2PragmaWords
   array (defined below) because we reference those by index with
   these entries.
*/
enum s2_pragma_type {
  /* Sentinel value. Must be 0. */
  PRAGMA_NONE = 0,
  /**
     Get/set s2_engine::flags::exceptionStackTrace.

  */
  PRAGMA_EXCEPTION_STACKTRACE,
  /** Evaluates to the operand's refcount. */
  PRAGMA_REFCOUNT,
  /** Get/set s2_engine::sweepInterval */
  PRAGMA_SWEEP_INTERVAL,
  /** Get/set s2_engine::vacuumInterval */
  PRAGMA_VACUUM_INTERVAL,
  /** Get/set s2_engine::flags::traceSweeps */
  PRAGMA_TRACE_SWEEP,
  /** Get/set s2_engine::flags::traceAssertions */
  PRAGMA_TRACE_ASSERT,
  /** Get/set s2_engine::flags::traceTokenStack */
  PRAGMA_TRACE_TOKEN_STACK

};
/**
   Modes of operation for pragmas:
 */
enum s2_pragma_operand_mode {
/* pragma(TAG) */
PRAGMA_OPERAND_NONE = 0,
/* pragma(TAG EXPR) */
PRAGMA_OPERAND_EXPR,
/* pragma(TAG) or pragma(TAG EXPR) */
PRAGMA_OPERAND_OPT_EXPR,
/* pragma(TAG IDENTIFIER) */
PRAGMA_OPERAND_IDENTIFIER,
/* pragma(TAG) or pragma(TAG IDENTIFIER) */
PRAGMA_OPERAND_OPT_IDENTIFIER
};

/**
   Internal-only callback type for individual pragma() handlers.
*/
typedef struct s2_pragma_word s2_pragma_word;

/**
   Callback for pragma() handlers. The 2nd parameter is the
   pragma an whose behalf the handler is being called. The third
   is NULL unless...

   If the pragma has an `opMode` of PRAGMA_OPERAND_IDENTIFIER then the
   3rd parameter is the token which contains the identifier. If opMode
   is PRAGMA_OPERAND_OPT_IDENTIFIER then the 3rd parameter is NULL if
   no identifier was provided. In either case, the 4th parameter
   will have a value of NULL.

   If opMode is PRAGMA_OPERAND_EXPR then the 4th parameter is the
   non-NULL result of that expression. If opMode is
   PRAGMA_OPERAND_OPT_EXPR, the 4th parameter may be NULL, indicating
   that no expression was provided (an empty expression is not
   allowed, and this callback will never be called in that case).

   The pragma must write its result to the final parameter and return
   0 on success or a CWAL_RC_xxx or S2_RC_xxx value on error. Any
   non-0 result other than CWAL_RC_EXCEPTION will likely be fatal to
   the current script.
*/
typedef int (*s2_pragma_f)(s2_engine *, struct s2_pragma_word const *,
                           s2_ptoken const * tIdent,
                           cwal_value * ev, cwal_value **rv);
/**
   pragma(...) state.
*/
struct s2_pragma_word {
  enum s2_pragma_type type;
  /** Name of the pragma. */
  char const * word;
  /** Length of this->word, in bytes. */
  cwal_size_t wordLen;
  enum s2_pragma_operand_mode opMode;
  s2_pragma_f call;
};

#define PRAGMA_F_DECL(P) static int s2_pragma_f_##P\
  (s2_engine *, struct s2_pragma_word const *, \
   s2_ptoken const *, cwal_value *, cwal_value **)
PRAGMA_F_DECL(refcount);
PRAGMA_F_DECL(stacktrace);
PRAGMA_F_DECL(svinterval);
PRAGMA_F_DECL(trace_sweep);
PRAGMA_F_DECL(trace_assert);
PRAGMA_F_DECL(trace_token_stack);
#undef PRAGMA_F_DECL

static const struct s2_pragma_word s2PragmaWords[] = {
  /*
   ACHTUNG: their order **MUST** match the entries of
   the s2_pragma_word enum because we reference them
   by index that way!
  */
{ PRAGMA_NONE,                  "", 0,
    PRAGMA_OPERAND_NONE, 0},
{ PRAGMA_EXCEPTION_STACKTRACE,  "exception-stacktrace", 20,
    PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_stacktrace},
{ PRAGMA_REFCOUNT,              "refcount", 8,
    PRAGMA_OPERAND_EXPR, s2_pragma_f_refcount},
{ PRAGMA_SWEEP_INTERVAL,        "sweep-interval", 14,
    PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_svinterval},
{ PRAGMA_VACUUM_INTERVAL,       "vacuum-interval", 15,
    PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_svinterval},
{ PRAGMA_TRACE_SWEEP,           "trace-sweep", 11,
    PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_trace_sweep},
{ PRAGMA_TRACE_ASSERT,           "trace-assert", 12,
    PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_trace_assert},
{ PRAGMA_TRACE_TOKEN_STACK,      "trace-token-stack", 17,
    PRAGMA_OPERAND_OPT_EXPR, s2_pragma_f_trace_token_stack}
};

int s2_pragma_f_refcount(s2_engine *se, s2_pragma_word const *pw,
                         s2_ptoken const * tIdent,
                         cwal_value * ev, cwal_value **rv){
  cwal_value * c;
  S2_UNUSED_ARG(pw);
  S2_UNUSED_ARG(tIdent);
  assert(!tIdent);
  assert(ev);
  c = cwal_new_integer(se->e, (cwal_int_t)cwal_value_refcount(ev));
  if(c) *rv = c;
  return c ? 0 : CWAL_RC_OOM;
}

int s2_pragma_f_svinterval(s2_engine *se, s2_pragma_word const *pw,
                           s2_ptoken const * tIdent,
                           cwal_value * ev, cwal_value **rv){
  int getVal = 0;
  int * setVal = 0;
  S2_UNUSED_ARG(tIdent);
  assert(!tIdent);
  switch(pw->type){
    case PRAGMA_SWEEP_INTERVAL:
      if(ev) setVal = &se->sweepInterval;
      getVal = se->sweepInterval;
      break;
    case PRAGMA_VACUUM_INTERVAL:
      if(ev) setVal = &se->vacuumInterval;
      getVal = se->vacuumInterval;
      break;
    default:
      s2_fatal( CWAL_RC_ASSERT, "Not possible: invalid pragma type.");
  }
  if(setVal){
    cwal_int_t const x = cwal_value_get_integer(ev);
    if(x<0){
      return cwal_exception_setf(se->e, CWAL_RC_RANGE,
                                 "pragma(%s) does not allow negative values.",
                                 pw->word);
    }
    *setVal = (int)x;
  }
  *rv = cwal_new_integer(se->e, (cwal_int_t)getVal);
  return *rv ? 0 : CWAL_RC_OOM;
}


/**
   Impl. of pragmas which get/set an int-type flag. A pointer to that
   flag's origin is passed as the 2nd argument. The other parameters
   are as for the s2_pragma_f() typedef.

   If asBool is true, the *what value and result are treated as a
   booleans, rather than as integers.
*/
static int s2_pragma_f_int_impl(s2_engine *se,
                                int * what,
                                char asBool,
                                s2_pragma_word const *pw,
                                s2_ptoken const * tIdent,
                                cwal_value const * ev, cwal_value **rv){
  int const getVal = *what;
  S2_UNUSED_ARG(tIdent);
  assert(!tIdent);
  if(ev){
    cwal_int_t const x = asBool
      ? cwal_value_get_bool(ev)
      : cwal_value_get_integer(ev);
    if(x<0){
      return cwal_exception_setf(se->e, CWAL_RC_RANGE,
                                 "pragma(%s) does not allow negative values.",
                                 pw->word);
    }
    *what = (int)(asBool ? (x ? 1 : 0) : x);
  }
  if(asBool){
    *rv = getVal ? cwal_value_true() : cwal_value_false();
  }else{
    *rv = cwal_new_integer(se->e, (cwal_int_t)getVal);
  }
  return *rv ? 0 : CWAL_RC_OOM;
}

int s2_pragma_f_stacktrace(s2_engine *se, s2_pragma_word const *pw,
                           s2_ptoken const * tIdent,
                           cwal_value * ev, cwal_value **rv){
  return s2_pragma_f_int_impl(se, &se->flags.exceptionStackTrace, 1,
                              pw, tIdent, ev, rv);
}


int s2_pragma_f_trace_sweep(s2_engine *se, s2_pragma_word const *pw,
                            s2_ptoken const * tIdent,
                            cwal_value * ev, cwal_value **rv){
  return s2_pragma_f_int_impl(se, &se->flags.traceSweeps, 0,
                              pw, tIdent, ev, rv);
}

int s2_pragma_f_trace_assert(s2_engine *se, s2_pragma_word const *pw,
                            s2_ptoken const * tIdent,
                            cwal_value * ev, cwal_value **rv){
  return s2_pragma_f_int_impl(se, &se->flags.traceAssertions, 0,
                              pw, tIdent, ev, rv);
}

int s2_pragma_f_trace_token_stack(s2_engine *se, s2_pragma_word const *pw,
                                  s2_ptoken const * tIdent,
                                  cwal_value * ev, cwal_value **rv){
  return s2_pragma_f_int_impl(se, &se->flags.traceTokenStack, 0,
                              pw, tIdent, ev, rv);
}

/**
   If the given ptoken resolves to a pragma(WORD) identifier, its
   entry is returned, else NULL is returned. This is an O(1) search.
   See s2_ptoken_keyword() for more details.
 */
static s2_pragma_word const * s2_ptoken_pragma( s2_ptoken const * pt ){
  cwal_size_t const tlen = s2_ptoken_len(pt);
  s2_pragma_word const * rc;
#define W(X,E) rc = tlen==(cwal_size_t)sizeof(X)-1 &&                   \
    0==cwal_compare_cstr(s2_ptoken_begin(pt), tlen, X, sizeof(X)-1)     \
    ? &s2PragmaWords[E] : NULL;                                         \
  assert(rc ? E==rc->type : 1); return rc
  switch(s2__keyword_perfect_hash(pt)){
    /* Generated by s2_eval-keyword-hasher.s2 (or equivalent): */

    case 0x00008bd2: W("refcount",PRAGMA_REFCOUNT);
    case 0x09022910: W("exception-stacktrace",PRAGMA_EXCEPTION_STACKTRACE);
    case 0x00245262: W("sweep-interval",PRAGMA_SWEEP_INTERVAL);
    case 0x00045dbf: W("trace-sweep",PRAGMA_TRACE_SWEEP);
    case 0x004732eb: W("vacuum-interval",PRAGMA_VACUUM_INTERVAL);
    case 0x0008b1a6: W("trace-assert",PRAGMA_TRACE_ASSERT);
    case 0x0117cb00: W("trace-token-stack",PRAGMA_TRACE_TOKEN_STACK);

    default: break;
  }
#undef W
  return NULL;
}

int s2_keyword_f_pragma( s2_keyword const * kw, s2_engine * se,
                         s2_ptoker * pr, cwal_value **rv){
  int rc;
  s2_pragma_word const * ptag = 0;
  s2_ptoker prBody = s2_ptoker_empty;
  s2_ptoken tIdent = s2_ptoken_empty;
  s2_ptoken tPTag = s2_ptoken_empty;
  s2_ptoken const * pIdent = 0;
  s2_ptoker const * oldScript = se->currentScript;
  cwal_size_t pTagLen = 0;
  cwal_value * xrv = 0 /* pragma(tag EXPR) result for EXPR */;
  cwal_value * crv = 0 /* result of the pragma */;
  cwal_value * vHolder = 0;
  /**
     Ideas:

     pragma(blah)

     There's all sorts of stuff we could potentially do with (blah),
     potentially requiring different syntaxes, e.g. (x=y), (x), (x y).

     e.g.:

     pragma(stacktrace false)

     To enable/disable collection of stack traces (a major cause of
     line-counting, which is slow). For most testing purposes, stack
     traces are not needed - they're needed primarily in app-level
     code.

     It would seem we need both getter and setter forms for some
     constructs.
  */
  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);
  }
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }
  
  rc = s2_ptoker_sub_from_toker(pr, &prBody);
  if(!rc){
    prBody.flags |= S2_T10N_F_IDENTIFIER_DASHES;
    rc = s2_next_token(se, &prBody, 0, 0);
    prBody.flags &= ~S2_T10N_F_IDENTIFIER_DASHES;
  }
  if(rc) goto end;
  else if(S2_T_Identifier!=prBody.token.ttype){
    rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                       "Expected pragma command in %s(...).",
                       kw->word);
    goto end;
  }

  se->currentScript = &prBody;
  tPTag = prBody.token;
  pTagLen = s2_ptoken_len(&tPTag);
  assert(pTagLen > 0);
  ptag = s2_ptoken_pragma(&tPTag);
  if(!ptag){
    rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                       "Unknown pragma in %s(%.*s ...).",
                       kw->word, (int)pTagLen, s2_ptoken_begin(&tPTag));
    goto end;
  }
  assert(ptag->word && *ptag->word);
  assert(ptag->call);

  rc = s2_next_token(se, &prBody, 0, 0);
  if(rc) goto end;
  
  switch(ptag->opMode){
    case PRAGMA_OPERAND_NONE:
      /* pragma(tag) */
      if(!s2_ptoker_is_eof(&prBody)){
        rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                           "Unexpected token in pragma %s(%.*s ...).",
                           kw->word, (int)pTagLen, s2_ptoken_begin(&tPTag));
        break;
      }
      break;
    case PRAGMA_OPERAND_OPT_EXPR:
      /* pragma(tag) or pragma(tag EXPR) */
      if(s2_ptoker_is_eof(&prBody)){
        break;
      }
      CWAL_SWITCH_FALL_THROUGH;
    case PRAGMA_OPERAND_EXPR:{
      /* pragma(tag EXPR) */
      s2_ptoker_putback(&prBody);
      rc = s2_eval_expr_impl(se, &prBody, 0, 0, &xrv);
      if(rc) goto end;
      else if(!xrv){
        rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                           "Empty expression is not permitted here.");
        goto end;
      }
      break;
    }
    case PRAGMA_OPERAND_OPT_IDENTIFIER:
      /* pragma(tag) or pragma(tag IDENTIFIER) */
      if(s2_ptoker_is_eof(&prBody)){
        break;
      }
      CWAL_SWITCH_FALL_THROUGH;
    case PRAGMA_OPERAND_IDENTIFIER:
      /* pragma(tag IDENTIFIER) */
      if(S2_T_Identifier != prBody.token.ttype){
        rc = s2_err_ptoker(se, &prBody, CWAL_SCR_SYNTAX,
                           "The %s(%.*s ...) pragma requires an "
                           "IDENTIFIER argument",
                           kw->word, pTagLen, s2_ptoken_begin(&tPTag));
        goto end;
      }
      tIdent = prBody.token;
      pIdent = &tIdent;
      break;
    default:
      s2_fatal( CWAL_RC_ASSERT, "Cannot happen: unknown pragma operand type.");
  }

  if(xrv){
    vHolder = cwal_new_unique(se->e, xrv);
    if(!vHolder){
      rc = CWAL_RC_OOM;
      cwal_refunref(xrv);
      xrv = 0;
      goto end;
    }
    cwal_value_ref(vHolder);
    cwal_value_make_vacuum_proof(vHolder, 1);
  }
  rc = ptag->call(se, ptag, pIdent, xrv, &crv);

  end:
  s2_ptoker_finalize(&prBody);
  if(crv){
    cwal_value_ref(crv) /* needed in case crv == xrv */;
    if(rc) cwal_value_unref(crv);
    else *rv = crv;
    cwal_value_unref(vHolder);
    if(!rc) cwal_value_unhand(crv);
    crv = 0;
  }else{
    if(!rc){
      *rv = cwal_value_undefined();
    }
    cwal_value_unref(vHolder);
  }
  se->currentScript = oldScript;
  return rc;
}

int s2_keyword_f_ukwd( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_ukwd * uk = s2__ukwd(se);
  S2_UNUSED_ARG(pr);
  if(uk && uk->h){
    cwal_value * v;
    v = cwal_hash_search(uk->h, kw->word, kw->wordLen);
    if(v){
      *rv = v;
    }else{
      rc = cwal_exception_setf(se->e, CWAL_RC_NOT_FOUND,
                               "Could not find %.*s keyword object.",
                               (int)kw->wordLen, kw->word);
    }
  }
  return rc;
}

/**
   If pt's [begin,end) range corresponds to a keyword, its entry from
   S2_KWDS is returned, else NULL is returned.

   This is an O(1) search, requiring, at most, generation of 1 hash
   code and (on a hash match) 1 string comparison.
*/
static s2_keyword const * s2__cstr_keyword( char const * name,
                                            cwal_size_t nameLen){
  s2_ptoken pt = s2_ptoken_empty;
  s2_ptoken_begin_set(&pt, name);
  s2_ptoken_end_set(&pt, name + nameLen);
  return s2_ptoken_keyword(&pt)/*NOT keyword2()!*/;  
}

#if 0
static s2_keyword const * s2__value_keyword( s2_engine * se, cwal_value * v ){
  cwal_size_t nameLen = 0;
  char const * name = cwal_value_get_cstr(v, &nameLen);
  if(name){
      s2_ptoken pt = s2_ptoken_empty;
      s2_ptoken_begin_set(pt, name);
      s2_ptoken_end_set(&pt, name + nameLen);
      return s2_ptoken_keyword2(se, &pt);
  }else{
    return 0;
  }
}
#endif

static int s2_cstr_is_identifier(char const * z, cwal_size_t len){
  char const * tail = 0;
  if(!s2_read_identifier(z, z+len, &tail)) return 0;
  return tail==z+len;
}

/**
   Creates a UKWD entry for the given key/value.

   See the public API docs for s2_define_ukwd() for the details, with
   the addition that CWAL_RC_TYPE is returned if k is not a stringable
   type.
*/
static int s2_define_ukwd_v(s2_engine * se, cwal_value * k, cwal_value * v){
  s2_ukwd * uk = s2__ukwd(se);
  s2_keyword * kw = 0;
  int rc;
  cwal_size_t nameLen = 0;
  char const * name;
  cwal_size_t const maxNameLen = 100
    /*This upper limit is essentially arbitrary, but we *must* reject
      anything longer than an unsigned short because kw->wordLen will
      hold the length. Also, long names have longer comparison times,
      and we frequently check the UKWD list during evaluation, so
      keeping them shorter is a good thing.*/;
  assert(v);
  if(cwal_value_undefined()==v){
    return s2_engine_err_set(se, CWAL_RC_UNSUPPORTED,
                             "The undefined value is not legal for use as "
                             "a define() value.");
  }
  name = cwal_value_get_cstr(k, &nameLen);
  if(!name){
    return s2_engine_err_set(se, CWAL_RC_TYPE,
                             "define() requires a string key.");
  }else if(!nameLen){
    return s2_engine_err_set(se, CWAL_RC_RANGE,
                             "define() key name may not be empty");
  }else if(nameLen > maxNameLen){
    return s2_engine_err_set(se, CWAL_RC_RANGE,
                             "define() key name is too long (max length=%d).",
                             (int)maxNameLen);
  }else if(!s2_cstr_is_identifier(name, nameLen)){
    return s2_engine_err_set(se, CWAL_SCR_SYNTAX,
                             "define() key must be a legal identifier.");
  }else if(s2__cstr_keyword(name, nameLen)){
    return s2_engine_err_set(se, CWAL_RC_ACCESS,
                             "define() may not override a built-in "
                             "keyword.");
  }
  else if(!uk){
    uk = cwal_malloc( se->e, sizeof(s2_ukwd));
    if(!uk) return s2_engine_err_set(se, CWAL_RC_OOM, 0);
    memset(uk, 0, sizeof(s2_ukwd));
    se->ukwd = uk;
  }
  if(!uk->h){
    cwal_value * hv;
    uk->h = cwal_new_hash(se->e, 13);
    if(!uk->h) return s2_engine_err_set(se, CWAL_RC_OOM, 0);
    hv = cwal_hash_value(uk->h);
    cwal_value_ref(hv);
    rc = s2_stash_set_v(se, hv, hv)
        /* To give the hash permanent lifetime */;
    cwal_value_unref(hv);
    if(rc){
      uk->h = 0;
      return rc;
    }
  }
  if(uk->count<=uk->alloced){
    unsigned const n = uk->alloced ? uk->alloced*3/2 : 10;
    void * m = cwal_realloc(se->e, uk->list, n * sizeof(s2_keyword));
    if(!m) return s2_engine_err_set(se, CWAL_RC_OOM, 0);
    uk->list = (s2_keyword *)m;
    uk->alloced = n;
  }
  {
    rc = cwal_hash_insert_with_flags_v(uk->h, k, v, 0, CWAL_VAR_F_CONST);
    if(rc){
      return CWAL_RC_ALREADY_EXISTS==rc
        ? s2_engine_err_set(se, rc, "define() key already exists.")
        : s2_engine_err_set(se, rc, "Hash insertion of define() key "
                            "failed: %s",
                            s2_rc_cstr(rc))
        ;
    }
    kw = &uk->list[uk->count];
    kw->wordLen = (unsigned short)nameLen;
    kw->word = name
      /* We know those bytes now live forever in uk->h,
         so there's no lifetime issue. */;
    ++uk->count;
    kw->id = S2_T_KeywordUKWD;
    kw->word = name;
    kw->call = s2_keyword_f_ukwd;
    kw->allowEOLAsEOXWhenLHS = 0;
    qsort( uk->list, uk->count, sizeof(s2_keyword), cmp_ukwd_kw );
    cwal_hash_grow_if_loaded(uk->h, 0.7)/*ignore error - not fatal*/;
    return 0;
  }
}

int s2_define_ukwd(s2_engine * se, char const * name,
                   cwal_size_t nameLen, cwal_value * v){
  int rc;
  cwal_value * const k = cwal_new_string_value(se->e, name, nameLen);
  if(k){
    cwal_value_ref(k);
    rc = s2_define_ukwd_v(se, k, v);
    cwal_value_unref(k);
  }else{
    rc = s2_engine_err_set(se, CWAL_RC_OOM, 0);
  }
  return rc;
}

/**
   cwal_callback_f() impl which wraps s2_define_ukwd(). Script-side
   signature:


   mixed define(string name[, mixed value])

   If called with one argument, it returns the defined value for the
   given key (or the undefined value), else it defines the given
   key/value pair.

   It returns its 2nd argument.
*/
static int s2_cb_define_ukwd( cwal_callback_args const * args, cwal_value **rv ){
  s2_engine * se = s2_engine_from_args(args);
  if((!args->argc || args->argc>2)
     && !cwal_value_is_string(args->argv[0])){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting (string name[, value]) arguments.");
  }
  if(1==args->argc){
    s2_ukwd * uk = s2__ukwd(se);
    if(uk){
      cwal_value * v = cwal_hash_search_v(uk->h, args->argv[0]);
      *rv = v ? v : cwal_value_undefined();
    }else{
      *rv = cwal_value_undefined();
    }
    return 0;
  }else{
    int rc = s2_define_ukwd_v(se, args->argv[0], args->argv[1]);
    if(rc){
      if(se->err.code == rc){
        rc = s2_throw_err(se, 0, 0, 0, 0);
      }
    }
    else{
      *rv = args->argv[1];
    }
    return rc;
  }
}

int s2_keyword_f_define( s2_keyword const * kw, s2_engine * se,
                         s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  cwal_value * v = se->skipLevel>0
    ? cwal_value_undefined()
    : se->cache.define;
  if(!v){
    cwal_value * vDef = 0;
    S2_UNUSED_ARG pr;
    vDef = cwal_new_function_value(se->e, s2_cb_define_ukwd, 0, 0, 0);
    if(!vDef){
      return CWAL_RC_OOM;
    }
    cwal_value_ref(vDef);
    rc = s2_stash_set(se, kw->word, vDef)
      /* Moves it to the top scope and implicitly makes it
         vacuum-proof. */;
    cwal_value_unref(vDef);
    if(!rc){
      /* vDef is held by/in se->stash. */
      v = se->cache.define = vDef;
      s2_seal_container(vDef, 1);
    }
  }/* end of first-call initialization */
  if(!rc) *rv = v;
  return rc;
}
#undef s2__dump_token
#undef s2__ukwd
#undef s2__ukwd_key
#undef s2__keyword_perfect_hash
#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_engine_from_args(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

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 = fst->vSrc;
    if(src || fst->flags){
      if(src){
        *rv = src;
      }else{
        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)
          /* trivia: because this string's so short, we don't actually
             save any memory here by using an x-string. That
             particular string won't get interned, so we've got about
             the same memory cost using an x-string or a normal
             string */;
      }
      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;
  }
}

cwal_value * s2_func_import_props( s2_engine * se, cwal_value const *theFunc,
                                   s2_func_state * fst ){
  cwal_value * props;
  assert(se && theFunc && fst);
  props = fst->vImported;
  if(!props){
    props = cwal_new_object_value(se->e);
    if(!props) return 0;
    s2_value_to_lhs_scope(theFunc, props);
    cwal_value_ref(props);
    cwal_value_prototype_set(props, NULL);
    fst->vImported = props;
  }
  return props;
}

/**
   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 */;
  uint16_t i, startAt = 0;
  s2_func_state * fst;
  THIS_FUNCTION;
  if(!args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                      "Expecting at least one symbol name to import.");
  }
  fst = s2_func_state_for_func( self );
  if(!fst){
    return s2_cb_throw(args, CWAL_RC_TYPE,
                      "This is not a script function, "
                      "so cannot import symbols.");
  }
  props = s2_func_import_props(se, cwal_function_value(self), fst);
  if(!props) return CWAL_RC_OOM;
  if(cwal_value_is_bool(args->argv[0])){
    /* initial bool explicitly specifies whether or not to clear old
       properties before importing the new ones. */
    ++startAt;
    if(cwal_value_get_bool(args->argv[0])){
      cwal_props_clear( props );
    }
  }else{
    cwal_props_clear( props );
  }
  for( i = startAt; !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 ){
  cwal_value * fwdSelf /* args->argv[0], the 'this' object for the next call() */;
  cwal_array * theList = 0 /* args->argv[1], the list of arguments to apply() */;
  THIS_FUNCTION;
  if(!args->argc || args->argc>2){
    return s2_throw(se, CWAL_RC_MISUSE,
                    "'apply' expects (Value [, Array]) argument(s).");
  }
  fwdSelf = args->argv[0];
  if(args->argc>1){
    theList = cwal_value_array_part(se->e, args->argv[1]);
    if(!theList){
      return s2_throw(se, CWAL_RC_TYPE,
                      "Second argument to 'apply' must be an array.");
    }
  }
  return theList
    ? cwal_function_call_array( 0, self, fwdSelf, rv, theList )
    : cwal_function_call( self, fwdSelf, rv, 0, 0 );
}

static int s2_cb_func_call( cwal_callback_args const * args, cwal_value **rv ){
  THIS_FUNCTION;
  if(!args->argc){
    return s2_throw(se, CWAL_RC_MISUSE,
                    "'call' expects (Value [, ...]) argument(s).");
  }
  return cwal_function_call( self, args->argv[0], rv,
                             args->argc-1, args->argv+1 );
}


cwal_value * s2_prototype_function( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    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")); */

    {
      s2_func_def const funcs[] = {
        S2_FUNC2("sourceCode", s2_cb_func_source_code),
        S2_FUNC2("importSymbols", s2_cb_func_import_symbols),
        S2_FUNC2("call", s2_cb_func_call),
        S2_FUNC2("apply", s2_cb_func_apply),
        s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0);
    }

    {
      /* Function.bind() impl. */
      char const * src =
        "proc(x){"
          "affirm typeinfo(isfunction this);"
          "return proc(){ return f.apply(x,argv) }"
          "using {x, f:this} "
        "}";
      int const srcLen = (int)cwal_strlen(src);
      rc = s2_set_from_script(se, src, srcLen, proto, "bind", 4);
      if(rc) goto end;
    }
    
    end:
    return rc ? NULL : proto;
}


#undef MARKER
#undef THIS_FUNCTION
#undef ARGS_SE
/* end of file s2_func.c */
/* start of file s2_fs.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 S2_HAVE_REALPATH
#  if defined(_BSD_SOURCE)
#    define S2_HAVE_REALPATH 1
#  elif defined(_XOPEN_SOURCE) && _XOPEN_SOURCE>=500
#    define S2_HAVE_REALPATH 1
#  elif defined(_XOPEN_SOURCE) && defined(_XOPEN_SOURCE_EXTENDED)
#    if _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
#      define S2_HAVE_REALPATH 1
#    endif
#  endif
#  ifndef S2_HAVE_REALPATH
#    define S2_HAVE_REALPATH 0
#  endif
#endif
*/

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

#if S2_HAVE_STAT || S2_HAVE_MKDIR
#  include <sys/stat.h>
#  include <sys/types.h>
#endif


#ifdef S2_OS_UNIX
#  include <unistd.h> /* W_OK, R_OK */
#  include <sys/stat.h>
#  include <sys/types.h>
#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_getcwd( cwal_engine * e, cwal_buffer * tgt ){
#ifdef S2_OS_WINDOWS
  if(e || tgt){/*unused*/}
  return CWAL_RC_UNSUPPORTED;
#else
  cwal_size_t const bufSize = 1024 * 2;
  int rc = cwal_buffer_reserve( e, tgt, tgt->used + bufSize );
  if(rc) return rc;
  else if( ! getcwd((char *)(tgt->mem+tgt->used), (size_t)bufSize) ){
    rc = s2_errno_to_cwal_rc( errno, CWAL_RC_IO);
  }else{
    tgt->used += cwal_strlen( (char const *)(tgt->mem+tgt->used) );
    assert( tgt->used <= tgt->capacity );
  }
  return rc;
#endif
}

int s2_mkdir( char const * name, int mode, char const ** errMsg ){
#if !S2_HAVE_MKDIR
  if(name || mode){/*unused*/}
  if(errMsg) *errMsg = "mkdir() is not available in this build.";
  return CWAL_RC_UNSUPPORTED;
#else
  int rc = mkdir(name, (mode_t)mode);
  if(rc){
    if(errMsg) *errMsg = strerror(errno);
    rc = s2_errno_to_cwal_rc(errno, CWAL_RC_IO);
  }
  return rc;
#endif
}

int s2_mkdir_p( char const * name, int mode, char const ** errMsg ){
#if !S2_HAVE_MKDIR
  if(name || mode){/*unused*/}
  if(errMsg) *errMsg = "mkdir() is not available in this build.";
  return CWAL_RC_UNSUPPORTED;
#else
  int rc = 0;
  char const * t;
  cwal_size_t tLen = 0;
  s2_path_toker pt = s2_path_toker_empty;
  char buf[1024 * 2] = {0};
  char * bufPos = buf;
  char const * bufEnd = buf + sizeof(buf);
  int dirCount = 0;
  char const leadingSlash = '/'==*name ? 1 : 0;
  s2_path_toker_init(&pt, name, cwal_strlen(name));
  pt.separators = "/";
  while(0==s2_path_toker_next(&pt, &t, &tLen)){
    if(!tLen) continue /* adjacent dir separators */;
    else if(bufPos+tLen+1 >= bufEnd){
      if(errMsg) *errMsg = "Directory path is too long.";
      return CWAL_RC_RANGE;
    }
    if((0==dirCount && leadingSlash)
       || dirCount>0){
      *bufPos++ = '/';
    }
    ++dirCount;
    memcpy(bufPos, t, (size_t)tLen);
    bufPos += tLen;
    *bufPos = 0;
    if(!s2_is_dir(buf, 0)){
      rc = s2_mkdir(buf, mode, errMsg);
      if(rc) break;
    }
  }
  if(!rc && !dirCount){
    rc = CWAL_RC_MISUSE;
    if(errMsg) *errMsg = "No directory names found in the given string.";
  }
  return rc;
#endif
}

int s2_cb_mkdir( cwal_callback_args const * args, cwal_value ** rv ){
#if !S2_HAVE_MKDIR
  if(rv){/*unused*/}
  return s2_cb_throw(args, CWAL_RC_UNSUPPORTED,
                     "mkdir() is not available in this build.");
#else
  int rc;
  char const * p;
  cwal_size_t pLen = 0;
  cwal_int_t mode = 0750;
  char const *errMsg = 0;
  uint16_t ndxMode = 0/* args->argv index of the permissions mode argument, 0 for none */;
  char mkp = 0;
  char dirBuf[1024 * 2]
    /* We copy the given path to a char buffer only so that we can
       ensure that it is NUL-terminated (X-/Z-strings can be created
       without a NUL terminator). We "could" instead use the
       s2_engine::buffer for this purpose, rather than hard-coding a
       length limit. */;
  if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_WRITE | S2_DISABLE_FS_STAT)) ) return rc;
  else if(!args->argc || args->argc>3) goto misuse;
  else if(1==args->argc){
    /* (dir) */
  }else if(cwal_value_is_bool(args->argv[1])){
    /* (dir, bool [, mode]); */
    mkp = cwal_value_get_bool(args->argv[1]);
    ndxMode = args->argc>2 ? 2 : 0;
  }else{
    /* (dir, mode) */
    ndxMode = 1;
  }
  p = cwal_value_get_cstr(args->argv[0], &pLen);
  if(!p || !pLen) goto misuse;
  else if(pLen >= (cwal_size_t)sizeof(dirBuf)){
    return s2_cb_throw(args, CWAL_RC_RANGE, "Dir name is too long.");
  }
  if(ndxMode){
    if(!cwal_value_is_number(args->argv[ndxMode])) goto misuse;
    mode = cwal_value_get_integer(args->argv[ndxMode]);
  }
  *rv = 0;
  memcpy(dirBuf, p, (size_t)pLen);
  if(s2_is_dir(dirBuf, 0)){
    return 0;
  }
  rc = mkp
    ? s2_mkdir_p(dirBuf, (int)mode, &errMsg)
    : s2_mkdir(dirBuf, (int)mode, &errMsg);
  if(rc){
    rc = s2_cb_throw(args, s2_errno_to_cwal_rc(errno, CWAL_RC_IO),
                     "mkdir() failed with errno %d: %s",
                     errno, errMsg);
  }
  return rc;
  misuse:
  return s2_cb_throw(args, CWAL_RC_MISUSE,
                     "mkdir() requires (string dir [,bool createParentDirs] "
                     "[,int unixPermissions]) arguments, "
                     "with a non-empty directory name.");
#endif
}


int s2_cb_getcwd( cwal_callback_args const * args, cwal_value ** rv ){
  s2_engine * se = s2_engine_from_args(args);
  cwal_buffer * buf = &se->buffer;
  cwal_size_t const pos = se->buffer.used;
  int rc;
  char const addSep = args->argc ? cwal_value_get_bool(args->argv[0]) : 0;
  if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc;
  rc = s2_getcwd( args->engine, buf );
  if(rc) rc = s2_cb_throw(args, rc, "getcwd() failed with code %d (%s).",
                         rc, s2_rc_cstr(rc));
  else{
    if(addSep){
      rc = cwal_buffer_append( args->engine,
                               buf,
                               S2_DIRECTORY_SEPARATOR,
                               sizeof(S2_DIRECTORY_SEPARATOR)-
                               sizeof(S2_DIRECTORY_SEPARATOR[0]) );
    }
    if(!rc){
      *rv = cwal_new_string_value(args->engine,
                                  (char const *)(buf->mem + pos),
                                  (cwal_size_t)(buf->used - pos));
      rc = *rv ? 0 : CWAL_RC_OOM;
    }
  }
  if(buf->mem){
    buf->used = pos;
    buf->mem[buf->used] = 0;
  }
  return rc;
}


const s2_fstat_t s2_fstat_t_empty = s2_fstat_t_empty_m;

/*
   Use _stati64 rather than stat on windows, in order to handle files
   larger than 2GB.
*/
#if defined(S2_OS_WINDOWS) && (defined(__MSVCRT__) || defined(_MSC_VER))
# undef stat
# define stat _stati64
#endif
/*
   On Windows S_ISLNK always returns FALSE.
*/
#if !defined(S_ISLNK)
# define S_ISLNK(x) (0)
#endif

enum {
/**
   Internal buffer sized used for various filesystem-related
   routines.
*/
S2_FILENAME_BUF_SIZE = 1024 * 2
};

int s2_fstat( char const * filename,
              cwal_size_t fnLen,
              s2_fstat_t * tgt,
              char derefSymlinks ){
#if !S2_HAVE_STAT || defined(S2_OS_WINDOWS)
  if(filename || fnLen || tgt || derefSymlinks){/*unused params*/}
  return CWAL_RC_UNSUPPORTED;
#else
  int rc;
  if(!filename || !tgt) rc = CWAL_RC_MISUSE;
  else if(!*filename || !fnLen) rc = CWAL_RC_RANGE;
  else if(fnLen >= (cwal_size_t)S2_FILENAME_BUF_SIZE) rc = CWAL_RC_RANGE;
  else{
    struct stat buf;
    char isCwalRc = 0;
    char fnBuf[S2_FILENAME_BUF_SIZE] = {0};
    memcpy( fnBuf, filename, (size_t)fnLen );
    fnBuf[fnLen] = 0;
    if( derefSymlinks ){
      rc = stat(fnBuf, &buf);
    }else{
#if S2_HAVE_LSTAT
      rc = lstat(fnBuf, &buf);
#else
      rc = CWAL_RC_UNSUPPORTED;
      isCwalRc = 1;
#endif
    }
    if(rc){
      if(!isCwalRc){
        rc = s2_errno_to_cwal_rc(errno, CWAL_RC_IO);
      }
    }else if(tgt){
      *tgt = s2_fstat_t_empty;
      tgt->ctime = (uint64_t)buf.st_ctime;
      tgt->mtime = (uint64_t)buf.st_mtime;
      tgt->size = (uint64_t)buf.st_size;
      tgt->perm = buf.st_mode & 0777 /* Unix file permissions are only the bottom 9 bits */;
#define TCHECK(SMACRO, TYPE) if(SMACRO(buf.st_mode)) tgt->type = S2_FSTAT_TYPE_ ## TYPE
      TCHECK(S_ISDIR,DIR);
      else TCHECK(S_ISREG,REGULAR);
#if defined(S_ISLNK)
      else TCHECK(S_ISLNK,LINK);
#endif
#if defined(S_ISSOCK)
      else TCHECK(S_ISSOCK,SOCKET);
#endif
#if defined(S_ISCHR)
      else TCHECK(S_ISCHR,CHAR);
#endif
#if defined(S_ISBLK)
      else TCHECK(S_ISBLK,BLOCK);
#endif
#if defined(S_ISFIFO)
      else TCHECK(S_ISFIFO,FIFO);
#endif
      else tgt->type = S2_FSTAT_TYPE_UNKNOWN;
#undef TCHECK
    }
  }
  return rc;
#endif
}

int s2_fstat_to_object( s2_engine * se, s2_fstat_t const * fst, cwal_value ** rv ){
  cwal_value * obj = 0;
  cwal_value * v = 0;
  cwal_value * keysV = 0;
  cwal_array * keysA = 0;
  char const * stashKey = "stat()keys";
  int rc = 0;
  enum {
    keyUNKNOWN = 0,
    keyREGULAR,
    keyDIR,
    keyLINK,
    keyBLOCK,
    keyCHAR,
    keyFIFO,
    keySOCKET,
    keyMTIME,
    keyCTIME,
    keySIZE,
    keyPERM,
    keyTYPE,
    keyEND
  };
  keysV = s2_stash_get(se, stashKey);
  if(keysV){
    keysA = cwal_value_get_array(keysV);
  }else{
    /* set up key names cache... */
    cwal_value * key = 0;
    keysV = cwal_new_array_value(se->e);
    if(!keysV){
      rc = CWAL_RC_OOM;
      goto end;
    }
    keysA = cwal_value_get_array(keysV);
    cwal_value_ref(keysV);
    rc = s2_stash_set(se, stashKey, keysV);
    cwal_value_unref(keysV);
    if(rc) goto end;
    else{
      assert(cwal_value_refcount(keysV)>0 && "stash is holding a ref");
    }
    rc = cwal_array_reserve(keysA, (cwal_size_t)keyEND);
    if(rc) goto end;
#define KEY(TYPE,STR)                                                      \
    key = cwal_new_string_value(se->e, STR, cwal_strlen(STR));   \
    if(!key) { rc = CWAL_RC_OOM; goto end; }                            \
    cwal_value_ref(key);                                                \
    rc = cwal_array_set( keysA, (cwal_size_t)key##TYPE, key );          \
    cwal_value_unref(key);                                              \
    key = 0;                                                            \
    if(rc) goto end
    KEY(UNKNOWN,"unknown");
    KEY(REGULAR,"file");
    KEY(DIR,"dir");
    KEY(LINK,"link");
    KEY(BLOCK,"block");
    KEY(CHAR,"char");
    KEY(FIFO,"fifo");
    KEY(SOCKET,"socket");
    KEY(MTIME,"mtime");
    KEY(CTIME,"ctime");
    KEY(SIZE,"size");
    KEY(PERM,"perm");
    KEY(TYPE,"type");
#undef KEY    
  }/* end key stash setup */

  assert(!rc);

  obj = cwal_new_object_value(se->e);
  if(!obj) { rc = CWAL_RC_OOM; goto end; }
  cwal_value_ref(obj);

#define VCHECK 
#define VSET(KEY)                                                       \
  if(!v) { rc = CWAL_RC_OOM; goto end; }                                \
  cwal_value_ref(v);                                                    \
  rc = cwal_prop_set_v(obj, cwal_array_get(keysA, (cwal_size_t)key##KEY), v); \
  cwal_value_unref(v);                                                  \
  v = 0;                                                                \
  if(rc) goto end
#define INTVAL(KEY,IV) \
  v = cwal_new_integer(se->e, (cwal_int_t)(IV)); \
  VSET(KEY);
  
  INTVAL(MTIME,fst->mtime);
  INTVAL(CTIME,fst->ctime);
  INTVAL(SIZE,fst->size);
  INTVAL(PERM,fst->perm);
#undef INTVAL
#undef VCHECK
#undef VSET

  switch(fst->type){
#define CASE(TYPE) case S2_FSTAT_TYPE_##TYPE: \
    v = cwal_array_get(keysA, (cwal_size_t)key##TYPE); \
    assert(v); \
    rc = cwal_prop_set_v(obj, cwal_array_get(keysA, (cwal_size_t)keyTYPE), v); \
    break
    CASE(REGULAR);
    CASE(DIR);
    CASE(LINK);
    CASE(BLOCK);
    CASE(CHAR);
    CASE(FIFO);
    CASE(SOCKET);
    default:
      v = cwal_array_get(keysA, (cwal_size_t)keyUNKNOWN);
      assert(v);
      rc = cwal_prop_set_v(obj, cwal_array_get(keysA, (cwal_size_t)keyTYPE), v);
      break;
  }
#undef CASE
  end:
  if(rc){
    cwal_value_unref(obj);
  }else{
    *rv = obj;
    cwal_value_unhand(obj);
  }
  return rc;
}

int s2_cb_fstat( cwal_callback_args const * args, cwal_value ** rv ){
  int rc = 0;
  s2_fstat_t stbuf = s2_fstat_t_empty;
  cwal_size_t fnLen = 0;
  char const * fn = args->argc
    ? cwal_value_get_cstr( args->argv[0], &fnLen )
    : 0;
  char derefSymlinks = 1;
  char doQuickCheck = 0;
  if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc;
  *rv = 0;
  if(!fn) {
    rc = s2_cb_throw( args, CWAL_RC_MISUSE,
                     "Expecting string (filename) argument.");
    goto end;
  }
  if(args->argc>1){
    if(cwal_value_undefined()==args->argv[1]){
      doQuickCheck = 1;
      if(args->argc>2) derefSymlinks = cwal_value_get_bool(args->argv[2]);
    }else{
      derefSymlinks = cwal_value_get_bool(args->argv[1]);
    }
  }
  assert(!rc);
  rc = s2_fstat( fn, fnLen, &stbuf, derefSymlinks );
  if(rc){
    if(doQuickCheck){
      *rv = cwal_value_false();
      rc = 0;
    }else{
      rc = s2_cb_throw(args, rc, "stat(%.*s) failed with code %d (%s)",
                      (int)fnLen, fn, rc, s2_rc_cstr(rc));
    }
    goto end;
  }
  end:
  if(!rc){
    if(doQuickCheck){
      /* We just wanted to check for stat()ability */
      if(!*rv) *rv = cwal_value_true();
    }else{
      s2_engine * se = s2_engine_from_args(args);
      assert(se);
      rc = s2_fstat_to_object( se, &stbuf, rv );
      if(rc){
        assert(CWAL_RC_OOM == rc);
      }
    }
  }
  return rc;
}

int s2_chdir( char const * dir, cwal_size_t dirLen ){
#if S2_HAVE_CHDIR
  char fnBuf[S2_FILENAME_BUF_SIZE] = {0};
  int rc = 0;
  if(dirLen >= (cwal_size_t)S2_FILENAME_BUF_SIZE) return CWAL_RC_RANGE;
  memcpy( fnBuf, dir, dirLen );
  fnBuf[dirLen] = 0;
  if(chdir(fnBuf)) rc = s2_errno_to_cwal_rc(0, CWAL_RC_IO);
  return rc;
#else
  if(dir && dirLen){/* unused */}
  return CWAL_RC_UNSUPPORTED;
#endif
}

int s2_cb_chdir( cwal_callback_args const * args, cwal_value **rv ){
  cwal_size_t strLen = 0;
  int rc;
  char const * str = args->argc
    ? cwal_value_get_cstr(args->argv[0], &strLen)
    : 0;
  if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc;
  else if(!str) rc = s2_cb_throw(args, CWAL_RC_MISUSE,
                            "Expecting a string (directory) argument.");
  else{
    rc = s2_chdir( str, strLen );
    if(rc) rc = s2_cb_throw(args, rc,
                            "chdir() failed with code %d (%s).",
                            rc, s2_rc_cstr(rc));
    else *rv = cwal_value_undefined();
  }
  return rc;  
}

#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

char s2_file_is_accessible( char const * fn, char checkWrite ){
  return (0 == CHECKACCESS( fn, CHECKRIGHTS ));
}

char s2_is_dir( char const * fn, char checkWrite ){
#ifdef S2_OS_UNIX
  struct stat buf;
  if(checkWrite && 0!=CHECKACCESS(fn, CHECKRIGHTS)){
    return 0;
  }else if(stat(fn, &buf)) return 0;
  else return 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)
    source tree, which jumps through several hoops in that regard.
  */
  return 0;
#endif
}

#undef CHECKACCESS
#undef CHECKRIGHTS

static int s2_cb_fs_accessible( int isFile, cwal_callback_args const * args, cwal_value **rv ){
  char const * fn;
  {
    int const rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT);
    if( rc ) return rc;
  }
  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 = (isFile ? s2_file_is_accessible(fn, checkWrite) : s2_is_dir(fn, checkWrite))
      ? cwal_value_true()
      : cwal_value_false();
    return 0;
  }
}

int s2_cb_file_accessible( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_fs_accessible(1, args, rv);
}

int s2_cb_dir_accessible( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_fs_accessible(0, args, rv);
}


int s2_cb_realpath( cwal_callback_args const * args, cwal_value **rv ){
#if !S2_HAVE_REALPATH
  if(rv){/*unused*/}
  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;
  int rc;
  if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT)) ) return rc;
  if(!str) return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                      "Expecting a string argument.");
  p = realpath(str, buf);
  if(!p){
    switch(errno){
      case ENOENT:
        *rv = cwal_value_undefined();
        return 0;
      default:
        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
}

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

FILE *s2_fopen(const char *zName, const char *zMode){
  FILE *f;
  if(zName && ('-'==*zName && !zName[1])){
    f = (strchr(zMode, 'w')
         || strchr(zMode,'+')
         || strchr(zMode,'a'))
      ? stdout
      : stdin
      ;
  }else{
    f = fopen(zName, zMode);
  }
  return f;
}



int s2_passthrough_FILE( cwal_engine * e, FILE * file ){
  return cwal_stream( cwal_input_f_FILE, file,
                      cwal_output_f_cwal_engine, e );
}

int s2_passthrough_filename( cwal_engine * e, char const * filename ){
  int rc;
  FILE * f = s2_fopen(filename, "r");
  if(!f) rc = CWAL_RC_IO;
  else{
    rc = s2_passthrough_FILE(e, f);
    s2_fclose(f);
  }
  return rc;
}


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

   Script usage:

   passthrough(filename)
*/
static int s2_cb_file_passthrough( cwal_callback_args const * args,
                                   cwal_value **rv ){
  char const * fn;
  cwal_size_t len;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], &len)
    : NULL;
  if(!fn){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting non-empty string "
                       "argument.");
  }else if(!s2_file_is_accessible(fn, 0)){
    return s2_cb_throw(args, CWAL_RC_NOT_FOUND,
                       "Cannot find file: %s", fn);
  }else{
    FILE * fi = s2_fopen(fn, "r");
    int rc;
    if(!fi){
      rc = s2_cb_throw(args, CWAL_RC_IO,
                       "Could not open file for reading: %s",
                       fn);
    }else{
      rc = cwal_stream( cwal_input_f_FILE, fi,
                        cwal_output_f_cwal_engine,
                        args->engine );
      s2_fclose(fi);
    }
    if(!rc) *rv = args->self;
    return rc;
  }
}

int s2_install_fs( 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;
  }

  {
    s2_func_def const funcs[] = {
      S2_FUNC2("stat", s2_cb_fstat),
      S2_FUNC2("realpath", s2_cb_realpath),
      S2_FUNC2("passthrough", s2_cb_file_passthrough),
      S2_FUNC2("mkdir", s2_cb_mkdir),
      S2_FUNC2("getcwd", s2_cb_getcwd),
      S2_FUNC2("fileIsAccessible", s2_cb_file_accessible),
      S2_FUNC2("dirIsAccessible", s2_cb_dir_accessible),
      S2_FUNC2("chdir", s2_cb_chdir),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, sub, funcs, 0);
    if(rc) goto end;
  }

#define SET(NAME)                                                    \
  if(!v) { rc = CWAL_RC_OOM; goto end; }                             \
  cwal_value_ref(v);                                                 \
  rc = cwal_prop_set( sub, NAME, cwal_strlen(NAME), v );             \
  cwal_value_unref(v);                                               \
  v = 0;                                                             \
  if(rc) goto end;

  v = cwal_new_string_value(se->e,
                            S2_DIRECTORY_SEPARATOR,
                            sizeof(S2_DIRECTORY_SEPARATOR)-
                            sizeof(S2_DIRECTORY_SEPARATOR[0]) );
  SET("dirSeparator");

  end:
#undef SET
  return rc;
}

#undef MARKER
/* end of file s2_fs.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

static int s2_new_hash( cwal_engine * e, cwal_int_t hashSize,
                        int setDotFlag /* experiment! */, cwal_value ** rv ){
  cwal_hash * h = 0;
  cwal_value * hv = 0;
  if(hashSize<=0) hashSize = 17;
  else if(hashSize>7919) hashSize = 7919;
#if 0
  else hashSize = s2_hash_next_prime(hashSize);
#endif
  h = cwal_new_hash(e, hashSize);
  hv = h
    ? cwal_hash_value(h)
    : NULL;
  if(!hv){
    assert(!h);
    return CWAL_RC_OOM;
  }
  else {
    assert(cwal_value_is_hash(hv));
    assert(cwal_value_prototype_get(e,hv));
    if(setDotFlag){
      s2_hash_dot_like_object(hv, 1);
    }
    *rv = hv;
    return 0;
  }
}


#define ARGS_SE s2_engine * se = s2_engine_from_args(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;
  int const setDotFlag = 
#if 1
    0
#else
    (args->argc>1)
    ? cwal_value_get_bool(args->argv[1])
    : 0
#endif
    ;
  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, setDotFlag, rv );
}

/**
   Internal impl of s2_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_v( 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;
}

static int s2_cb_hash_take_props( cwal_callback_args const * args, cwal_value **rv ){
  int const overwritePolicy = (args->argc>1)
    ? (int)cwal_value_get_integer(s2_value_unwrap(args->argv[1]))
    : 1;
  cwal_value * src = args->argc ? args->argv[0] : 0;
  THIS_HASH;
  if(!src || !cwal_props_can(src)){
    return s2_cb_throw(args, src ? CWAL_RC_TYPE : CWAL_RC_MISUSE,
                       "Expecting a Container argument.");
  }
  *rv = args->self;
  return cwal_hash_take_props( h, src, overwritePolicy );
}



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

static int s2_cb_hash_resize( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_int_t hsz;
  THIS_HASH;
  hsz = args->argc
    ? cwal_value_get_integer(args->argv[0])
    : -1;
  if(hsz<1){
    return s2_cb_throw(args, args->argc ? CWAL_RC_RANGE : CWAL_RC_MISUSE,
                       "Expecting a positive hash table size value.");
  }
  rc = cwal_hash_resize(h, (cwal_size_t)hsz);
  if(!rc) *rv = args->self;
  return rc;
}

static int s2_cb_hash_grow_if_loaded( cwal_callback_args const * args,
                                      cwal_value **rv ){
  int rc;
  cwal_double_t load;
  THIS_HASH;
  load = args->argc
    ? cwal_value_get_double(args->argv[0])
    : 0.8;
  rc = cwal_hash_grow_if_loaded(h, load);
  if(!rc) *rv = args->self;
  return rc;
}

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));
  return *rv ? 0 : CWAL_RC_OOM;
}


static int s2_cb_hash_has_entries( cwal_callback_args const * args, cwal_value **rv ){
  THIS_HASH;
  *rv = cwal_new_bool( cwal_hash_entry_count(h) ? 1 : 0 );
  return 0;
}

cwal_value * s2_prototype_hash( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    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;

    {
      const s2_func_def funcs[] = {
      S2_FUNC2("clearEntries", s2_cb_hash_clear),
      S2_FUNC2("containsEntry", s2_cb_hash_has),
      S2_FUNC2("eachEntry", s2_cb_hash_each_entry),
      S2_FUNC2("entryCount", s2_cb_hash_entry_count),
      S2_FUNC2("entryKeys", s2_cb_hash_keys),
      S2_FUNC2("entryValues", s2_cb_hash_values),
      S2_FUNC2("growIfLoaded", s2_cb_hash_grow_if_loaded),
      S2_FUNC2("hashSize", s2_cb_hash_size),
      S2_FUNC2("insert", s2_cb_hash_insert),
      S2_FUNC2("hasEntries", s2_cb_hash_has_entries),
      S2_FUNC2("remove", s2_cb_hash_remove),
      S2_FUNC2("resize", s2_cb_hash_resize),
      S2_FUNC2("search", s2_cb_hash_get),
      S2_FUNC2("takeProperties", s2_cb_hash_take_props),
      S2_FUNC2("new", s2_cb_hash_create),
      s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0 );
      if(rc) goto end;
      else {
        cwal_value * fv = 0;
        s2_get(se, proto, "new", 3, &fv);
        assert(fv && "we JUST put this in there!");
        rc = s2_ctor_method_set( se, proto,
                                 cwal_value_get_function(fv) );
      }
    }

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

#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));
  }else{
      *rv = cwal_value_undefined();
  }
  /* 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;
}

int s2_cb_print_helper( cwal_callback_args const * args,
                        cwal_value **rv,
                        uint16_t skipArgCount,
                        cwal_flags32_t flags ){
  uint16_t i, n;
  int rc = 0;
  char const * sep = " ";
  cwal_engine * e = args->engine;
  cwal_size_t const sepLen = cwal_strlen(sep);
  char const addSpace = (S2_PRINT_OPT_SPACE & flags) ? 1 : 0;
  /* dump_val(args->self, "'this' for print()"); */
  /* MARKER(("s2_cb_print() called with %"PRIu16" arg(s).\n", args->argc)); */
  if(S2_PRINT_OPT_RETURN_THIS & flags
     && S2_PRINT_OPT_RETURN_CALLEE & flags){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "s2_cp_print_helper() cannot handle both the "
                       "S2_PRINT_OPT_RETURN_THIS and "
                       "S2_PRINT_OPT_RETURN_CALLEE flags. "
                       "Pick one or the other.");
  }
  for(i = skipArgCount, n = 0; !rc && (i < args->argc); ++i ){
    cwal_value * v = (S2_PRINT_OPT_UNWRAP & flags)
      ? s2_value_unwrap(args->argv[i])
      : args->argv[i];
    if(addSpace && n++){
      rc = cwal_output(e, sep, sepLen);
      if(rc) break;
    }
    /* s2_dump_val(v,"arg"); */
    switch(cwal_value_type_id(v)){
      case CWAL_TYPE_ARRAY:
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_EXCEPTION:
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_NULL:
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_TUPLE:
        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:
      case CWAL_TYPE_UNIQUE:
        rc = cwal_outputf(e, "%s@0x%p", cwal_value_type_name2(v, 0),
                          (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 && (S2_PRINT_OPT_NEWLINE & flags)){
    cwal_output(args->engine, "\n", 1);
  }
  if(S2_PRINT_OPT_RETURN_CALLEE & flags){
    *rv = cwal_function_value(args->callee);
  }else if(S2_PRINT_OPT_RETURN_THIS & flags){
    *rv = args->self;
  }else{
    *rv = cwal_value_undefined();
  }
  cwal_output_flush(args->engine);
  return rc;
}


int s2_cb_print( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_print_helper(args, rv, 0,
                            S2_PRINT_OPT_SPACE
                            | S2_PRINT_OPT_NEWLINE
                            | S2_PRINT_OPT_RETURN_CALLEE );
}

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

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);
  cwal_value * xrv = 0;
  assert(se);
  if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT
                                | S2_DISABLE_FS_READ)) ) return rc;
#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.

    _Seems_ to work okay for the basic case, though, as long as one
    doesn't call this function from cwal_function_call_in_scope(),
    using a scope which already has a 'this'. In the "normal" case
    (when this function is called from s2 scripts) 'this' is the LHS
    of the s2.import() call (or is this function if the import()
    reference is used standalone), and we're overwriting that one (if
    it's set) in its call-local scope.

    Not yet sure i want to export our "this" this way, but it would be
    potentially useful in a few cases.
  */
  rc = s2_var_set( se, 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{
      cwal_value_unref(xrv);
      xrv = 0;
      rc = s2_eval_filename(se, 1, fn, nLen, &xrv);
      cwal_value_ref(xrv);
    }
  }
  switch(rc){
    case 0:
      if(xrv){
        cwal_value_unhand(xrv);
        *rv = xrv;
        assert((cwal_value_scope(*rv) || cwal_value_is_builtin(*rv))
               && "Seems like *rv was cleaned up too early.");
      }else{
        *rv = cwal_value_undefined();
      }
      break;
    case CWAL_RC_RETURN:
      *rv = cwal_propagating_take(se->e);
      assert(*rv && "Misuse of CWAL_RC_RETURN!");
      assert(xrv != *rv && "But... how???");
      cwal_value_unref(xrv);
      s2_engine_err_reset(se);
      rc = 0;
      break;
    default:
      cwal_value_unref(xrv);
      break;
  }
  return rc;
}

int s2_install_io( s2_engine * se, cwal_value * tgt,
                   char const * name ){
  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;
    cwal_value_ref(sub);
    rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub);
    cwal_value_unref(sub);
    if(rc) return rc;
  }else{
    sub = tgt;
  }
  cwal_value_ref(sub);
  {
    s2_func_def const funcs[] = {
      S2_FUNC2("flush", s2_cb_flush),
      S2_FUNC2("output", s2_cb_write),
      S2_FUNC2("print", s2_cb_print),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, sub, funcs, 0);
    if(rc) goto end;
    /**
       Add output.'operator<<' as a proxy for output(). Why?  Because
       (A) we end up doing something equivalent in a surprising amount
       of client code and (B) to provide a default operator<< impl for
       s2.tmpl(). Because of how s2 tokenizes parens groups, x<<y is,
       for many common cases, faster than x(y) because the latter gets
       tokenized at least twice (once to slurp the (...) group and
       once to evaluate the function call arguments).

       While we're here, we'll also add it to print(), but note that
       this instance of print() is a different one than gets installed
       globally in s2sh!

       As of 20191210, we recycle s2out.operator<< for use with
       s2.io.output/print. We do not make s2.io.output an alias
       s2out because s2out's object may be sealed.
    */
    rc = s2_eval_cstr_with_var( se, "x", sub, "s2.io setup",
                                "x.output.'operator<<'="
                                "x.print.'operator<<'="
                                "s2out.'operator<<'",
                                -1, NULL );
  }
  end:
#undef SET
  cwal_value_unhand(sub) /* it's either ==tgt or a property of tgt */;
  return rc;
}

#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_DLOPEN && S2_HAVE_LTDLOPEN
#error Only one of S2_HAVE_DLOPEN and S2_HAVE_LTDLOPEN should be true.
#endif

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

#if S2_ENABLE_MODULES
static void s2_dl_init(){
  static char once  = 0;
  if(!once && ++once){
#  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 * s2_dl_sym( dl_handle_t soh, char const * mname ){
  void * 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 (...sigh...) to try to please
  valgrind.
*/
#if S2_ENABLE_MODULES
static void s2_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


#if S2_ENABLE_MODULES
/**
   Looks for a symbol in the given DLL handle. If symName is NULL or
   empty, the symbol "s2_module" is used, else the symbols
   ("s2_module_" + symName) is used. If it finds one, it casts it to
   s2_loadable_module and returns it. On error it may update se->err
   with the error information.
*/
static s2_loadable_module const *
s2_module_fish_out_entry_pt(s2_engine * se,
                            dl_handle_t soh,
                            char const * symName){
  enum { MaxLen = 128 };
  char buf[MaxLen] = {0};
  cwal_size_t const slen = symName ? cwal_strlen(symName) : 0;
  s2_loadable_module const * mod;
  if(slen > (MaxLen-20)){
    s2_engine_err_set(se, CWAL_RC_RANGE,
                      "DLL symbol name '%.*s' is too long.",
                      (int)slen, symName);
    return 0;
  }
  if(!slen){
    memcpy(buf, "s2_module", 9);
    buf[9] = 0;
  }else{
    sprintf(buf,"s2_module_%s", symName);
  }
  mod = (s2_loadable_module*)s2_dl_sym( soh, buf );
  /* MARKER(("s2_module_fish_out_entry_pt [%s] ==> %p\n",buf,
     (void const *)mod)); */
  return mod;
}
#endif/*S2_ENABLE_MODULES*/


#if S2_ENABLE_MODULES
/**
   Tries to dlsym() the given s2_loadable_module symbol from the given
   DLL handle. On success, 0 is returned and *mod is assigned to the
   memory. On error, non-0 is returned and se's error state may be
   updated.

   Ownership of the returned module ostensibly lies with se, but
   that's not entirely true. If S2_CLOSE_DLLS is true then a copy of
   the module's pointer is stored in se for later closing. The memory
   itself is owned by the module loader, and "should" stay valid
   until the DLL is closed.
*/
static int s2_module_get_sym( s2_engine * se,
                              dl_handle_t soh,
                              char const * symName,
                              s2_loadable_module const ** mod ){

  s2_loadable_module const * lm;
  int rc;
  s2_engine_err_reset(se);
  lm = s2_module_fish_out_entry_pt(se, soh, symName);
  rc = s2_engine_err_has(se);
  if(rc) return rc;
  else if(!lm){
    s2_dl_close(soh);
    return s2_engine_err_set( se, CWAL_RC_NOT_FOUND,
                              "Did not find module entry point symbol '%s'.",
                              symName);
  }
  *mod = lm;
  if(S2_CLOSE_DLLS){
    /* Stash soh for potential closing later on. */
    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){
        s2_dl_close(soh);
        lm = 0;
        /* This is an allocation error, so don't
           bother updating s2_engine::err.
        */
      }
    }
  }/*S2_CLOSE_DLLS*/
  return rc;
}
#endif/*S2_ENABLE_MODULES*/

#if !S2_ENABLE_MODULES
static int s2_module_no_modules( s2_engine * se ){
  return s2_engine_err_set(se, CWAL_RC_UNSUPPORTED,
                           "No dlopen() equivalent is installed "
                           "for this build configuration.");
}
#endif

int s2_module_init( s2_engine * se,
                    s2_loadable_module const * mod,
                    cwal_value ** rv){
  int rc;
  cwal_scope sc = cwal_scope_empty;
  if(!mod->init) return CWAL_RC_MISUSE;
  rc = cwal_scope_push2(se->e, &sc);
  if(!rc){
    cwal_value * xrv = 0;
    rc = mod->init( se, &xrv );
    cwal_scope_pop2(se->e, (rc||!rv) ? 0 : xrv);
    if(!rc && rv) *rv = xrv;
  }
  return rc;
}

int s2_module_extract( s2_engine * se,
                       char const * fname,
                       char const * symName,
                       s2_loadable_module const ** mod ){
#if !S2_ENABLE_MODULES
  if(fname || symName || mod){/*avoid unused param warning*/}
  return s2_module_no_modules(se);
#else
#if !S2_HAVE_LTDLOPEN && !S2_HAVE_DLOPEN
#   error "We have no dlopen() and friends impl for this configuration."
#endif
  if(!se || !fname || !*fname || !mod) return CWAL_RC_MISUSE;
  else {
    dl_handle_t soh;
    char const * errMsg = 0;
    s2_dl_init();
    soh = dl_open( fname, &errMsg );
    if(!soh){
      if(errMsg){
        return s2_engine_err_set(se, CWAL_RC_ERROR,
                                 "DLL open failed: %s",
                                 errMsg);
      }else{
        return CWAL_RC_ERROR;
      }
    }
    else {
      s2_loadable_module const * x = 0;
      int const rc = s2_module_get_sym( se, soh, symName, &x );
      if(!rc){
        assert(x);
        if(mod) *mod = x;
      }
      return rc;
    }
  }
#endif
}


int s2_module_load( s2_engine * se,
                    char const * fname,
                    char const * symName,
                    cwal_value ** rv ){
#if !S2_ENABLE_MODULES
  if(fname || symName || rv){/*avoid unused param warning*/}
  return s2_module_no_modules(se);
#else
#  if !S2_HAVE_LTDLOPEN && !S2_HAVE_DLOPEN
#    error "We have no dlopen() and friends impl for this configuration."
#  endif
  if(!se || !fname || !*fname || !rv) return CWAL_RC_MISUSE;
  else {
    s2_loadable_module const * mod = 0;
    int rc = s2_module_extract( se, fname, symName, &mod );
    if(!rc){
      assert(mod);
      rc = s2_module_init(se, mod, rv);
    }
    return rc;
  }
#endif
}

int s2_cb_module_load( cwal_callback_args const * args,
                       cwal_value **rv ){
  s2_engine * se;
  int rc = 0;
#if !S2_ENABLE_MODULES
  se = s2_engine_from_args(args);
  assert(se);
  rc = s2_module_no_modules(se);
  if(rv){/*avoid unused param warning*/}
  return CWAL_RC_UNSUPPORTED==rc
    ? s2_throw_err( se, 0, 0, 0, 0 )
    : rc /* presumably CWAL_RC_OOM */;
#else
  char const * fn;
  char const * sym = NULL;
  cwal_value * mod = 0;
  se = s2_engine_from_args(args);
  assert(se);
  rc = s2_disable_check_throw(se, S2_DISABLE_FS_READ);
  if(rc) return rc;
  fn = cwal_string_cstr(cwal_value_get_string(args->argv[0]));
  if(!fn){
    goto misuse;
  }
  if(args->argc>1){
    if(cwal_value_is_string(args->argv[1])){
      sym = cwal_value_get_cstr(args->argv[1], 0);
    }else{
      goto misuse;
    }
  }
  rc = s2_module_load(se, fn, sym, &mod);
  /* MARKER(("load_module(%s, %s) rc=%d\n", fn, sym, rc)); */
  switch(rc){
    case 0:
      assert(mod);
      *rv = mod;
      break;
    case CWAL_RC_EXCEPTION:
    case CWAL_RC_INTERRUPTED:
    case CWAL_RC_OOM:
      break;
    case CWAL_RC_BREAK:
    case CWAL_RC_RETURN:
      s2_propagating_set(se, 0);
      CWAL_SWITCH_FALL_THROUGH;
    default:{
      /* We are pre-empting various codes here, e.g. CWAL_RC_EXIT,
         CWAL_RC_FATAL, and CWAL_RC_ASSERT. We do not want plugin
         initialization to be able to quit the top-running script that
         way (e.g. a failed s2 assert in some init script code). */
      if(se->err.code){
        rc = s2_throw_err(se, 0, 0, 0, 0);
        s2_engine_err_reset(se);
      }
      else{
        rc = s2_cb_throw(args, rc,
                         "Loading module failed with code "
                         "%d (%s).", rc, cwal_rc_cstr(rc));
      }
      break;
    }
  }
  return rc;
  misuse:
  return s2_cb_throw(args, CWAL_RC_MISUSE,
                     "Module loader expects "
                     "(string [,string]]) 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);
                s2_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_engine_from_args(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_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;
}

int s2_cb_format_self_using_arg( cwal_callback_args const * args, cwal_value **rv ){
  cwal_engine * e = args->engine;
  cwal_size_t fmtLen = 0;
  char const * fmtStr;
  fmtStr = args->argc
    ? cwal_value_get_cstr(args->argv[0], &fmtLen)
    : 0;
#if 0
  if(fmtStr && fmtLen && '%'==*fmtStr){
    ++fmtStr;
    --fmtLen;
  }
#endif
  if(!fmtLen){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting a single cwal_buffer_format()-compatible "
                       "string argument, minus the '$1%%' prefix.");
  }else{
    int rc;
    cwal_buffer fmtBuf = cwal_buffer_empty;
    char * zFmt = 0;
    cwal_buffer_printf( e, &fmtBuf, "%%1$%.*s",
                        (int)fmtLen, fmtStr);
    zFmt = (char *)fmtBuf.mem;
    fmtBuf = cwal_buffer_empty;
    rc = cwal_buffer_format(e, &fmtBuf, zFmt, cwal_strlen(zFmt),
                            1, &args->self);
    if(rc){
      rc = cwal_exception_setf(e, rc,
                    "Formatted conversion from number "
                    "to string using format '%s' failed: %.*s",
                    zFmt,
                    (int)fmtBuf.used,
                    (char const *)fmtBuf.mem);
    }else{
      *rv = cwal_buffer_to_zstring_value(e, &fmtBuf);
      if(!*rv) rc = CWAL_RC_OOM;
    }
    cwal_free(e, zFmt);
    zFmt = 0;
    cwal_buffer_clear(e, &fmtBuf);
    return rc;
  }
}

static int s2_cb_num_to_string( cwal_callback_args const * args, cwal_value **rv ){
  cwal_engine * e = args->engine;
  int rc = 0;
  if(args->argc){
    rc = s2_cb_format_self_using_arg(args, rv);
  }else{
    enum { BufLen = 100 };
    char buf[BufLen] = {0};
    cwal_size_t bLen = (cwal_size_t)BufLen;
    if(cwal_value_is_double(args->self)){
      rc = cwal_double_to_cstr( cwal_value_get_double(args->self),
                                buf, &bLen );
    }else{
      rc = cwal_int_to_cstr( cwal_value_get_integer(args->self),
                             buf, &bLen );
    }
    if(rc){
      return cwal_exception_setf(e, rc,
                      "Conversion from number to string failed.");
    }
    *rv = cwal_new_string_value(args->engine, buf, bLen);
    if(!*rv) rc = CWAL_RC_OOM;
  }
  return rc;
}

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){
#if 1
    *rv = cwal_value_undefined();
    return 0 /* arguably we should throw */;
#else
    return s2_throw(se, CWAL_RC_RANGE,
                    "Integer value %"CWAL_INT_T_PFMT" is not "
                    "a valid UTF8 character.", (cwal_int_t)self);
#endif
  }
  *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 ){
  cwal_int_t const 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;
}

int s2_cb_srand_int( cwal_callback_args const * args, cwal_value **rv ){
  if( 1 != args->argc ){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting exactly 1 integer value.");
  }else{
    srand( (unsigned int)cwal_value_get_integer(args->argv[0]) );
    *rv = cwal_value_undefined();
    return 0;
  }
}

static int s2_cb_nth_prime( cwal_callback_args const * args, cwal_value **rv ){
  static const int max = 1000;
  int const ndx = args->argc
    ? (int)cwal_value_get_integer(args->argv[0])
    : -1;
  if(ndx < 1 || ndx>max){
    return s2_cb_throw(args, args->argc ? CWAL_RC_RANGE : CWAL_RC_MISUSE,
                       "Expecting an integer value between 1 and %d.",
                       max);
  }
  *rv = cwal_new_integer(args->engine,
                         (cwal_int_t)cwal_first_1000_primes()[ndx-1]);
  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;
  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){
    const s2_func_def funcs[] = {
      S2_FUNC2("compare", s2_cb_value_compare),
      S2_FUNC2("nthPrime", s2_cb_nth_prime),
      S2_FUNC2("parseDouble", s2_cb_parse_double),
      S2_FUNC2("parseInt", s2_cb_parse_int),
      S2_FUNC2("parseNumber", s2_cb_parse_number),
      S2_FUNC2("toDouble", s2_cb_int_to_dbl),
      S2_FUNC2("toInt", s2_cb_dbl_to_int),
      S2_FUNC2("toJSONString", s2_cb_this_to_json_token),
      S2_FUNC2("toString", s2_cb_num_to_string),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
  }
  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 SETV(NAME) \
  if(!v){ rc = CWAL_RC_OOM; goto end; }                       \
  cwal_value_ref(v);                                          \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );   \
  cwal_value_unref(v);                                          \
  v = 0;                                                        \
  if(rc) goto end

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

  {
    const s2_func_def funcs[] = {
      S2_FUNC2("toChar", s2_cb_int_to_utf8char),
      S2_FUNC2("rand", s2_cb_rand_int),
      S2_FUNC2("srand", s2_cb_srand_int),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
  }

#undef SETV
  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;
  }else if(self<=0){
    *rv = cwal_new_integer(args->engine, (cwal_int_t)(self));
  }else{
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)(((cwal_double_t)(cwal_int_t)self)==self
                                        ? self : self+1));
  }
#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 * 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;

  {
    const s2_func_def funcs[] = {
      S2_FUNC2("ceil", s2_cb_dbl_ceil),
      S2_FUNC2("floor", s2_cb_dbl_floor),
      S2_FUNC2("format", s2_cb_dbl_format),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
  }

#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 0
#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 const int S2ObBuffer_typeid = 0;

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_free2(e, m, sizeof(S2ObBuffer));
}

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+n+1) * 3/2)
      : n*2;
    cwal_size_t const oflow = nSize;
    if(oflow > nSize) goto overflow;
    else if(!nSize) nSize = 50/*arbitrary*/;
    /* 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 to avoid yet another reallocation in
         cwal_buffer_append(). */
      ++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_malloc2(se->e, sizeof(cwal_outputer));
    if(co){
      rc = cwal_list_append( se->e, &se->ob, co );
    }else{
      rc = CWAL_RC_OOM;
    }
    if(rc){
      if(co) cwal_free2(se->e, co, sizeof(cwal_outputer));
    }else{
      *co = se->e->vtab->outputer /* Store the old one */;
      se->e->vtab->outputer = *out /* Replace it with the new one. */;
    }
    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 s2_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;
}

static S2ObBuffer * s2_ob_state_ptr( cwal_outputer const * from ){
  S2ObBuffer * rc = from->state.typeID == &S2ObBuffer_typeid
    ? (S2ObBuffer*)from->state.data
    : NULL;
  assert(rc && "Seems someone has mucked with a cwal_outputer instance");
  return rc;
}

#define s2__ob_current_buffer(SE) s2_ob_state_ptr(&(SE)->e->vtab->outputer)

int s2_ob_push( s2_engine * se ){
  int rc;
  cwal_outputer out = cwal_outputer_empty;
  S2ObBuffer * ob;
  assert(se);
  ob = (S2ObBuffer *)cwal_malloc2(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.state.typeID = &S2ObBuffer_typeid;
  out.output = s2_S2ObBuffer_output_f;
  /* out.flush = s2_S2ObBuffer_flush; */
  rc = s2_ob_push_outputer( se, &out );
  if(rc){
    cwal_free2( se->e, ob, sizeof(S2ObBuffer) );
  }
  return rc;   
}

int s2_ob_reserve( s2_engine * se, cwal_size_t reserveBufSize ){
  if(!se) return CWAL_RC_MISUSE;
  else if(!se->ob.count) return CWAL_RC_RANGE;
  else{
    S2ObBuffer * const ob = s2__ob_current_buffer(se);
    return cwal_buffer_reserve(se->e, &ob->buf, reserveBufSize);
  }
}

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_free2(se->e, ob, sizeof(cwal_outputer));
    --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 );
    if(!rc) *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)
     && (CWAL_RC_INTERRUPTED!=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 s2_cb_throw(args, rc, "s2_ob_push() failed.");
  }else{
    int rc = 0;
    if(args->argc
       && cwal_value_is_integer(args->argv[0])){
      cwal_int_t const sz = cwal_value_get_integer(args->argv[0]);
      if(sz<0){
        rc = s2_cb_throw(args, CWAL_RC_RANGE,
                         "Expecting an integer argument >= 0.");
      }else{
        rc = s2_ob_reserve(se, (cwal_size_t)sz);
        if(rc){
          assert(CWAL_RC_OOM == rc);
          s2_ob_pop(se);
        }
      }
    }
    if(!rc) *rv = args->self;
    return rc;
  }
}    

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{
    S2ObBuffer * const ob = s2__ob_current_buffer(se);
    *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{
    S2ObBuffer * const ob = s2__ob_current_buffer(se);
    if(tgt != &ob->buf){
      void * self = tgt->self;
      *tgt = ob->buf;
      tgt->self = self;
    }
    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{
    S2ObBuffer * const ob = s2__ob_current_buffer(se);
    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];
    S2ObBuffer * const ob = s2__ob_current_buffer(se);
    assert(to != &se->e->vtab->outputer);
    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;
}    

#define OB_THROW_IF_NO_OB if(!se->ob.count){                            \
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,            \
                               "Output buffering is not in effect.");   \
  }(void)0

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);
  OB_THROW_IF_NO_OB;
  rc = s2_ob_get(se, &buf);
  if(rc) return rc;
  else{
    assert(buf);
  }
  *rv = cwal_new_string_value(args->engine,
                              (char const *)buf->mem,
                              buf->used);
  return *rv ? 0 : CWAL_RC_OOM;
}    


/***
int s2_cb_ob_reserve( cwal_callback_args const * args, cwal_value **rv ){
  cwal_int_t const sz = args->argc ? cwal_value_get_integer(args->argv[0]) : -1;
  if(sz<0) return s2_cb_throw(args, CWAL_RC_RANGE,
                              "Expecting an integer value >= 0.");
  else{
    s2_engine * se = s2_engine_from_args(args);
    int const rc = s2_ob_reserve( se, (cwal_size_t)sz );
    assert(se);
    return rc ? s2_cb_throw(args, rc, "s2_ob_reserve() failed "
                            "with code %d (%s).", rc, s2_rc_cstr(rc))
      : 0;
  }
}    
***/

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);
  OB_THROW_IF_NO_OB;
  rc = s2_ob_take(se, &buf);
  if(rc) return rc;
#if 0
  *rv = cwal_new_string_value(args->engine,
                               (char const *)buf.mem,
                               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);
  OB_THROW_IF_NO_OB;
  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);
  OB_THROW_IF_NO_OB;
  rc = s2_ob_clear( se, 0 );
  if(rc) rc = cwal_exception_setf(args->engine, rc,
                                  "s2_ob_clear() failed.");
  else *rv = args->self;
  return rc;
}

int s2_cb_ob_capture( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  cwal_value * arg;
  cwal_function * fCallback;
  char const * sCallback;
  cwal_size_t nCallback = 0;
  cwal_size_t const oldLevel = s2_ob_level(se);
  cwal_int_t mode = -1;
  cwal_value * xrv = 0;
  assert(se);
  if(0==args->argc || args->argc>2){
    misuse:
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting (string|function [,int mode]) arguments.");
  }
  arg = args->argv[0];
  sCallback = cwal_value_is_string(arg)
    ? cwal_value_get_cstr(arg, &nCallback)
    : 0 /* disallow buffer input b/c of potential for Undefined Behaviour
           if it's modified during the buffering process */;
  if(!sCallback){
    fCallback = cwal_value_get_function(arg);
    if(!fCallback){
      goto misuse;
    }
  }
  if(args->argc>1){
    arg = args->argv[1];
    if(cwal_value_is_buffer(arg)){
      mode = 1;
    }else{
      mode = cwal_value_get_integer(arg);
    }
  }
  rc = s2_ob_push(se);
  if(rc) return rc;

  if(sCallback){
    rc = s2_eval_cstr(se, 0, "OB capture() callback",
                      sCallback, (int)nCallback, 0);
  }else{
    assert(fCallback);
    rc = cwal_function_call(fCallback, args->self, 0, 0, 0);
  }
  if(!rc){
    if(se->ob.count <= oldLevel){
      return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                 "Too many OB levels were popped via "
                                 "the callback.");
    }
  }
  while(se->ob.count > oldLevel+1){
    if(mode){
      int const rc2 = rc ? rc : s2_ob_flush(se);
      if(rc2 && !rc){
        rc = rc2;
        /* Don't break - keep popping so that our API guarantees
           hold. */
      }
    }
    s2_ob_pop(se)/*cannot fail*/;
  }
  if(!rc){
    assert(oldLevel + 1 == se->ob.count
           && "Should have been caught above.");
    if(0==mode){
      xrv = cwal_value_undefined();
    }else if(0 > mode){
      rc = s2_cb_ob_take_string(args,&xrv);
    }else{
      assert(args->argc>0);
      arg = args->argv[1];
      if(cwal_value_is_buffer(arg)){
        cwal_buffer * const tgt = cwal_value_get_buffer(arg);
        S2ObBuffer * const ob = s2__ob_current_buffer(se);
        /* Potential TODO/optimization: this lazy/easy approach
           captures (via the above s2_ob_flush()/pop combination) all
           captured OB levels into the current OB buffer layer before
           copying the memory to tgt. We "could" instead walk UP the
           OB stack, before popping it, and append each layer's memory
           to tgt (for the first level, if tgt->mem is NULL, we could
           transfer ownership of the OB buffer's memory to tgt). We
           would need to walk up, not down, the stack so that the
           order of the buffers is properly retained.
        */
        if(tgt->mem){
          rc = cwal_buffer_append(args->engine, tgt, ob->buf.mem,
                                  ob->buf.used);
        }else{
          /* Optimization: simply change ownership of the memory... */
          s2_buffer_swap(tgt, &ob->buf);
          assert(!ob->buf.mem);
        }
        if(!rc) xrv = arg;
      }else{
        rc = s2_cb_ob_take_buffer(args,&xrv);
      }
    }
  }
  s2_ob_pop(se) /* ignore result - cannot fail */;
  if(rc){
    assert(!xrv);
  }else{
    assert(xrv);
    *rv = xrv;
  }
  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);
  OB_THROW_IF_NO_OB;
  rc = s2_ob_flush( se );
  if(rc) rc = cwal_exception_setf(args->engine, rc,
                                  "s2_ob_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{
    s2_func_def const funcs[] = {
      S2_FUNC2("takeString", s2_cb_ob_take_string),
      S2_FUNC2("takeBuffer", s2_cb_ob_take_buffer),
      /* S2_FUNC2("reserve", s2_cb_ob_reserve), */
      S2_FUNC2("push", s2_cb_ob_push),
      S2_FUNC2("pop", s2_cb_ob_pop),
      S2_FUNC2("level", s2_cb_ob_level),
      S2_FUNC2("getString", s2_cb_ob_get),
      S2_FUNC2("flush", s2_cb_ob_flush),
      S2_FUNC2("clear", s2_cb_ob_clear),
      S2_FUNC2("capture", s2_cb_ob_capture),
      s2_func_def_empty_m
    };
    return s2_install_functions(se, tgt, funcs, 0);
  }
}

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 OB_THROW_IF_NO_OB
#undef s2__ob_current_buffer
#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 );


/**
   Operator for the dot and hash operators.

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

/**
   Operator for the dot-length operator (X.#).

   Required stack (top to bottom): LHS
*/
static int s2_op_f_dot_length( 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 the X=Y family of operators.

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

/**
   Impl for the X.Y=Z family of operators.

   Required stack (top to bottom): RHS, KEY, OBJECT
*/
static int s2_op_f_assign3( s2_op const * op, 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;
  const s2_op elvis;
  /*
    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 notInherits;
  const s2_op contains;
  const s2_op notContains;

  const s2_op assign;
  const s2_op assignConst;
  const s2_op assignMember;
  const s2_op assignMemberConst;
  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 dotDot;
  const s2_op dotLength;
  const s2_op dotHash;
  const s2_op arrow;
  const s2_op arrow2;
  const s2_op colon2;
  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, derivedFromOp } */
  { "+X",    S2_T_OpPlusUnary,  1,  1, S2_PR_PlusUnary,  -1,  s2_op_f_addsub, 0, 0, 0 },
  { "X+Y",   S2_T_OpPlus,       2, -1, S2_PR_Plus,        0,  s2_op_f_addsub, 0, 0, 0 },

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

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

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

  { "!X",    S2_T_OpNot,        1,  1, S2_PR_LogicalNot, -1, s2_op_f_not, 0, 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, 0 },
  { "X||Y",  S2_T_OpOr,           2, -1, S2_PR_LogicalOr,   0, s2_op_f_andor, 0, 0, 0 },
  { "X|||Y", S2_T_OpOr3,          2, -1, S2_PR_LogicalOr3,  0, s2_op_f_andor, 0, 0, 0 },
  { "X?:Y",  S2_T_OpElvis,        2, -1, S2_PR_LogicalOrElvis, 0, s2_op_f_andor, 0, 0, 0 },

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

  { "X=Y",     S2_T_OpAssign,       2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0, 0 },
  { "X:=Y",    S2_T_OpColonEqual,   2,  1, S2_PR_OpAssign,    0, 0/*handled by parser*/, 0, 0, 0 },
  { "X.Y=Z",   S2_T_OpAssign3,      3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0, 0 },
  { "X.Y:=Z",  S2_T_OpAssignConst3, 3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0, 0 },
  { "X+=Y",    S2_T_OpPlusAssign,   2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpPlus },
  { "X.Y+=Z",  S2_T_OpPlusAssign3,  3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpPlus },
  { "X-=Y",    S2_T_OpMinusAssign,  2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpMinus },
  { "X.Y-=Z",  S2_T_OpMinusAssign3,  3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpMinus },
  { "X*=Y",    S2_T_OpMultiplyAssign,  2, 1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpMultiply },
  { "X.Y*=Z",  S2_T_OpMultiplyAssign3, 3, 1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpMultiply },
  { "X/=Y",    S2_T_OpDivideAssign,    2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpDivide },
  { "X.Y/=Z",  S2_T_OpDivideAssign3,   3, 1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpDivide },
  { "X%=Y",    S2_T_OpModuloAssign,    2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpModulo },
  { "X.Y%=Z",  S2_T_OpModuloAssign3,   3,  1, S2_PR_OpAssign,   0, s2_op_f_assign3, 0, 0,
    S2_T_OpModulo },
  { "X<<=Y",   S2_T_OpShiftLeftAssign, 2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpShiftLeft },
  { "X.Y<<=Z", S2_T_OpShiftLeftAssign3, 3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpShiftLeft },
  { "X>>=Y",   S2_T_OpShiftRightAssign, 2, 1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpShiftRight },
  { "X.Y>>=Z", S2_T_OpShiftRightAssign3, 3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpShiftRight },
  { "X^=Y",    S2_T_OpXOrAssign,    2, 1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpXOr },
  { "X.Y^=Z",  S2_T_OpXOrAssign3,   3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpXOr },
  { "X&=Y",    S2_T_OpAndAssign,    2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpAnd },
  { "X.Y&=Z",  S2_T_OpAndAssign3,   3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpAnd },
  { "X|=Y",    S2_T_OpOrAssign,     2,  1, S2_PR_OpAssign,    0, s2_op_f_assign2, 0, 0,
    S2_T_OpOr },
  { "X.Y|=Z",  S2_T_OpOrAssign3,    3,  1, S2_PR_OpAssign,    0, s2_op_f_assign3, 0, 0,
    S2_T_OpOr },

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

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

  { "X,Y",   S2_T_Comma,          2, -1, S2_PR_Comma,       0, s2_op_f_comma, 0, 0, 0 },
  { "X.Y",   S2_T_OpDot,          2, -1, S2_PR_DotDeref,    0, s2_op_f_dot, 0, 0, 0 },
  { "X..",   S2_T_OpDotDot,       2, -1, S2_PR_DotDot,      0, 0, 0, 0, 0 },
  { "X.#",   S2_T_OpDotLength,    1, -1, S2_PR_DotDeref,    1, s2_op_f_dot_length, 0, 0, 0 },
  { "X#Y",   S2_T_OpHash,         2, -1, S2_PR_DotDeref,    0, s2_op_f_dot, 0, 0, 0 },
  { "X->Y",  S2_T_OpArrow,        2, -1, S2_PR_OpArrow,     0, s2_op_f_oload_arrow, 0, 0, 0 },
  /* S2_T_OpArrow2 is an internal-use pseudo-operator used to tell s2_eval_expr_impl() that
     it should stop processing. It is ONLY syntactically valid when a caller of s2_eval_expr_impl()
     passes this op as the "fromLhsOp" parameter.
     
     201608: Is that still true? i'd like to make => overrideable, but that would break
     foreach(x=>...). We could rename that to foreach(x as ...)?
   */
  { "X=>Y",  S2_T_OpArrow2,       0, -1,  S2_PR_OpArrow2,    0, 0, 0, 0, 0 },
  { "X::Y",  S2_T_OpColon2,       2, -1,  S2_PR_OpColon2,    0, s2_op_f_oload_arrow, 0, 0, 0 },

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

  { "X?Y:Z",  S2_T_Question,      3,  1, S2_PR_TernaryIf,   0, 0/*handled at parser level */, 0, 0, 0 },

  /* RHSEval was initially an experiment in handling precedence during
     recursive evaling, but it's no longer used. */
  { "...RHS", S2_T_RHSEval,       1,  1, S2_PR_RHSEval,     0, 0/*handled at parser level */ , 0, 0, 0 },

  /* FOO is/was only used for internal by-hand tests of the stack
     machine. */
  { "FOO...", S2_T_Foo,          -1,  1, S2_PR_Keyword,     0, s2_op_f_foo, 0, 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_OpElvis,elvis);

    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_OpNotInherits,notInherits);
    OP(S2_T_OpContains,contains);
    OP(S2_T_OpNotContains,notContains);

    OP(S2_T_OpAssign,assign);
    OP(S2_T_OpColonEqual,assignConst);
    OP(S2_T_OpAssign3,assignMember);
    OP(S2_T_OpAssignConst3,assignMemberConst);
    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);
#if 0
    OP(S2_T_OpDotDot,dotDot);
#else
    case S2_T_OpDotDot: return 0;
#endif
    OP(S2_T_OpDotLength,dotLength);
    OP(S2_T_OpHash,dotHash);
    OP(S2_T_OpArrow,arrow);
    OP(S2_T_OpArrow2,arrow2);
    OP(S2_T_OpColon2,colon2);
    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);
    /*CASE("operator..", S2_T_OpDotDot);*/
    CASE("operator::", S2_T_OpColon2);
#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:
    case CWAL_TYPE_UNIQUE:
      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);

    CASE("--operator", S2_T_OpDecrPre);
    CASE("operator--", S2_T_OpDecrPost);

    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 1 is returned, an overload was truly found. If 2 is
   returned, a synthesized overload was found, in which case the
   caller should possibly force "return this" semantics on its result
   before applying it.

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

   It may return >0 and set *theRc to non-0, meaning that the error
   indicated by *theRc was triggered by/via an overload.

   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 than 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->artity==3, neither lhs nor rhs may be NULL. The op is
   assumed to be an object property assignment form of a binary
   operation. e.g. X+=Z vs X.Y+=Z. The overload is called with lhs as
   the 'this' and (rhs) argument.

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

   If op->arity==1: 

   - One of lhs or rhs must be 0.

   - If op->placement<0 (prefix op) or op->placement>0 (postfix op)
   then the overload is called without parameters.
*/
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;
        }
        CWAL_SWITCH_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 0
    /*
      Enable this block to automatically derive combo assignment ops
      from their non-assignment variants.

      20160214: this actually seems to work, but has an interesting
      problem/property:

      var a = [1,2,3];
      a.'operator+' = proc(self,arg){return [@self,arg]}; // return !this
      a += 4;
      // ^^^^ [1,2,3,4], but a different instance, so a.operator+
      // is now gone!

      Is seems the only sane behaviour in overloaded ops is to always
      return "this". But maybe there are other valid uses? Can't think
      of any. Should we force 'this' as the result for such overloads?
    */
    if(op->derivedFromOp){
      char cc;
      assert(s2_ttype_is_assignment_combo(op->id));
#  if 1
      MARKER(("Checking derived op %s\n", s2_ttype_op(op->derivedFromOp)->sym ));
#  endif
      assert(s2_ttype_op(op->derivedFromOp));
      cc = s2_op_check_overload( s2_ttype_op(op->derivedFromOp),
                                 se, requireIt, lhs, rhs,
                                 rv, theRc );
      /*if(cc && !*theRc){
        s2_dump_val(lhs,"Forcing 'this' result...");
        *rv = theThis;
        }*/
      return cc ? cc+1 : cc;
    }
#endif
    if(!requireIt) *theRc = 0;
    else *theRc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                                    "LHS (type %s) has no '%s' "
                                    "(%s operator overload).",
                                    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 0
        if(op->placement<0){
          /*prefix op*/
        }else{/*postfix*/
#if 0
          /* Historical behaviour. Removed 20190804 when the prefix
             ++/-- ops were renamed so that they no longer collide
             with (postfix ++/--). With that change, this behaviour
             is no longer needed. */
          assert(op->placement!=0);
          argc = 1;
          argv[0] = theThis;
#endif
        }
#endif
        break;
      case 2:
        argc = 1;
        /* argv[0] = lhs; */
        argv[0] = rhs;
        break;
      default:
        /* X.Y OP Z:

          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 = 1;
        /* argv[0] = lhs; */
        argv[0] = 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 = 0;
  cwal_value * lhs = 0;
  char isAdd = 0;
  switch(op->id){
    case S2_T_OpPlus:
      isAdd = 1;
      CWAL_SWITCH_FALL_THROUGH;
    case S2_T_OpMinus:
      assert( 2 == argc );
      rhs = s2__pop_val( se );
      lhs = s2__pop_val( se );
      break;
    case S2_T_OpPlusUnary:
      isAdd = 1;
      CWAL_SWITCH_FALL_THROUGH;
    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!");
      if(argc){/*avoid unused param warning*/}
      return CWAL_RC_ASSERT;
  }
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    int rc = 0;
    cwal_value_ref(lhs);
    cwal_value_ref(rhs);
    if(!s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc)
       && !rc){
      rc = s2_values_addsub( se, isAdd, lhs, rhs, rv);
    }
    if(!rc) cwal_value_ref(*rv);
    cwal_value_unref(lhs);
    cwal_value_unref(rhs);
    if(!rc) cwal_value_unhand(*rv);
    return rc;
  }
}

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!");
      if(argc){/*avoid unused param warning*/}
      return CWAL_RC_ASSERT;
  }
  cwal_value_ref(lhs);
  cwal_value_ref(rhs);
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    cwal_value_unref(rhs);
    cwal_value_unref(lhs);
    return 0;
  }else{
    int rc = 0;
    if(!s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc)
       && !rc){
      rc = s2_values_multdivmod( se, mode, lhs, rhs, rv);
    }
    if(!rc) cwal_value_ref(*rv);
    cwal_value_unref(rhs);
    cwal_value_unref(lhs);
    if(!rc) cwal_value_unhand(*rv);
    return rc;
  }
}

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);
    cwal_value_ref(vlhs);
    if(argc){/*avoid unused param warning*/}
  }
  cwal_value_ref(vrhs);
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    assert(cwal_value_undefined()==vrhs);
    assert(!vlhs || (cwal_value_undefined()==vlhs));
    /*cwal_refunref(vrhs);
      if(vlhs && vlhs != rhs) cwal_refunref(vlhs);*/
    return 0;
  }else{
    int rc = 0;
    if(!s2_op_check_overload(op, se, 1, vlhs, vrhs, rv, &rc)
       && !rc){
      rc = s2_values_bitwiseshift( se, op->id, vlhs, vrhs, rv );
    }
    if(!rc) cwal_value_ref(*rv);
    cwal_value_unref(vlhs);
    cwal_value_unref(vrhs);
    if(!rc) cwal_value_unhand(*rv);
    return rc;
  }
}

int s2_op_f_not( s2_op const * op, s2_engine * se, int argc,
                    cwal_value **rv ){
  cwal_value * rhs;
  if(op || argc){/*avoid unused param warning*/}
  assert(1==argc);
  rhs = s2__pop_val(se);
  cwal_value_ref(rhs);
  *rv = (se->skipLevel>0)
    ? cwal_value_undefined()
    : cwal_new_bool( !cwal_value_get_bool(rhs) );
  assert(cwal_value_is_builtin(*rv));
  cwal_value_unref(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);
  cwal_value_ref(rhs);
  cwal_value_ref(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:
      /**
         Reminder to self: if i'm not mistaken, the short-circuiting
         ops (and/or/or3 and ternary if) are the only ones which can
         have a non-undefined value as an operand in short-circuit
         mode. i.e. we (theoretically) don't need to cwal_refunref()
         in any skip-mode blocks in other operators.
      */
      *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_OpElvis:
      *rv = cwal_value_undefined()==lhs ? rhs : lhs;
      break;
    case S2_T_OpOr3:
      *rv = cwal_value_get_bool(lhs) ? lhs : rhs;
      break;
    default:
      if(argc){/*avoid unused param warning*/}
      assert(!"Invalid op mapping!");
  }
  cwal_value_ref(*rv);
  cwal_value_unref(rhs);
  cwal_value_unref(lhs);
  cwal_value_unhand(*rv);
  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);
  cwal_value_ref(rhs);
  cwal_value_ref(lhs);
  if(se->skipLevel>0){
    cwal_value_unref(rhs);
    cwal_value_unref(lhs);
    *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_OpNotInherits:
      rc = !cwal_value_derives_from( se->e, lhs, rhs );
      break;
    case S2_T_OpInherits:
      rc = cwal_value_derives_from( se->e, lhs, rhs );
      break;
    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 1
      char const requiresOverload = 1;
      /* strange: it wasn't until 20160121 that i realized that ({} ==
         {}) throws an exception. Maybe it shouldn't? If it doesn't,
         though, we've also got to allow default impls of other
         comparison ops. Is that wrong? */
#else
      char requiresOverload = 1;
      switch(op->id){
        case S2_T_CmpEq:
        case S2_T_CmpNotEq:
          requiresOverload = 0;
          break;
        default:
          requiresOverload = 1;
          break;
      }
#endif
      if(s2_op_check_overload(op, se, requiresOverload,
                              lhs, rhs, rv, &rc)
         || rc){
        cwal_value_unref(rhs);
        cwal_value_unref(lhs);
        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");
          if(argc){/*avoid unused param warning*/}
          cwal_value_unref(rhs);
          cwal_value_unref(lhs);
          return CWAL_RC_CANNOT_HAPPEN;
      }
    }
  }
  *rv = rc ? cwal_value_true() : cwal_value_false();
  cwal_value_unref(rhs);
  cwal_value_unref(lhs);
  assert(cwal_value_is_builtin(*rv));
  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.
    */
    if(se || argc){/*avoid unused param warning*/}
    *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 */
    cwal_value * v1, * v2;
    v1 = s2__pop_val(se);
    v2 = s2__pop_val(se);
    *rv = cwal_value_undefined();
    assert(v1 && v2);
    /* v1/v2 are likely, but not guaranteed, to
       be cwal_value_undefined(), but in case
       they're not... */
    if(v2!=v1) cwal_refunref(v2);
    cwal_refunref(v1);
    if(op || argc){/*avoid unused param warning*/}
  }else{
    cwal_value * v;
    *rv = s2__pop_val(se) /* RHS */;
    v = s2__pop_val(se) /* discard LHS, but... */;
    assert(v);
    cwal_value_ref(*rv);
    /*
      Ref/unref cleans up any temps on the lhs (e.g. x++, y++).
      This allows this for loop to avoid temporarily leaking
      998 integers into a higher scope:

      var x = 0;
      for( var i = 0; x < 1000; x++, ++i );

      Without this hack, the x++ result becomes a temporary in its
      owning scope (x's) until the loop finishes and can clean up.
    */
    cwal_refunref(v);
    cwal_value_unhand(*rv);
  }
  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 = 0, * lhs, *lhsProto = 0;
  char isUniqueDotValue = 0;
  assert(S2_T_OpDot==op->id || S2_T_OpHash==op->id);
  assert(2==argc);
  rhs = s2__pop_val(se);
  lhs = s2__pop_val(se);
  cwal_value_ref(lhs);
  cwal_value_ref(rhs);
  /* s2_dump_val(rhs,"se->dotOp.key"); */
  /* s2_dump_val(lhs,"se->dotOp.lhs"); */
  if(!se->skipLevel && cwal_value_is_unique(lhs)){
    /* assume enum entry, allow only 'value' and 'prototype'
       pseudo-properties */
    if(!s2_value_is_value_string(se, rhs)
       && !s2_value_is_prototype_string(se, rhs)){
      rc = s2_engine_err_set(se, CWAL_RC_TYPE,
                             "Invalid key for '%s' operator on LHS of type '%s'.",
                             op->sym,
                             cwal_value_type_name(lhs)
                             /* ^^^ careful: lifetime! */);
      cwal_value_unref(lhs);
      cwal_value_unref(rhs);
      if(argc){/*avoid unused param warning*/}
      return rc;
    }
    isUniqueDotValue = 1;
  }
  if(!se->skipLevel
     && !isUniqueDotValue
     && ((!(lhsProto=cwal_value_prototype_get(se->e, lhs))
          && !cwal_props_can(lhs))
#if 1
         || (S2_T_OpHash==op->id && s2_value_is_enum(lhs))
      /* we disable the '#' on enums solely to force that
         clients do not use it with them, as enums
         might use Object storage instead of hashes. */
      /* As of 2020-02-21, enums are always hashes, so this
         limitation is no longer necessary. */
      /* However... if we allow this op then it behaves, on enums,
         exaclty as the -> op, except that the one requies an overload
         lookup. Having 2 ops with identical results makes me queasy,
         and i have client-side script code which makes use of ->, so
         we'll keep that one.*/
#endif
         )
     ){
    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){
      s2_dotop_state( se, lhs, lhs, rhs );
      assert( se->dotOp.self == se->dotOp.lhs );
      assert( se->dotOp.self == lhs );
      assert( se->dotOp.key == rhs );
    }else{
      s2_dotop_state( se, 0, cwal_value_undefined(),
                      cwal_value_undefined() );
      assert(!se->dotOp.self);
      assert(cwal_value_undefined() == se->dotOp.lhs);
      assert(cwal_value_undefined() == se->dotOp.key);
    }
    *rv = cwal_value_undefined();
    rc = 0;
  }
  else{ /* X.Y and X#Y... */
    cwal_value * xrv = 0;
    /* s2_dotop_state( se, 0, 0, 0 ); */
    /* s2_dump_val(lhs,"dot op lhs"); */
    if(S2_T_OpDot == op->id){
      const int noThis =
        (cwal_value_is_integer(rhs)
         && (cwal_value_is_string(lhs)
             || cwal_value_is_tuple(lhs)
             || cwal_value_array_part(se->e,lhs)))
        /* Workaround to keep array/tuple/string[int]'s LHS from
           becoming 'this' if it resolves to a function which gets
           called. i.e. myArray[1](...) must not bind myArray as
           'this'. */
        ;
      rc = s2_get_v(se, lhs, rhs, &xrv);
      if(!rc){
        s2_dotop_state( se, noThis ? 0 : lhs, lhs, rhs );
      }
    }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) s2_dotop_state(se, 0, lhs, rhs);
    }
    cwal_value_ref(xrv);
    cwal_value_unref(lhs);
    cwal_value_unref(rhs);
    if(rc){
      cwal_value_unref(xrv);
    }else{
      *rv = xrv ? xrv : cwal_value_undefined();
      cwal_value_unhand(xrv);
    }
  }
  return rc;
}

int s2_op_f_dot_length( s2_op const * op, s2_engine * se,
                        int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * lhs;
  assert(S2_T_OpDotLength==op->id);
  assert(1==argc);
  lhs = s2__pop_val(se);
  cwal_value_ref(lhs);
  if(se->skipLevel){
    /*s2_dotop_state( se, 0, cwal_value_undefined(),
      cwal_value_undefined() );*/
    *rv = cwal_value_undefined();
    assert(cwal_value_undefined()==lhs);
    cwal_value_unref(lhs);
    rc = 0;
  }
  else{
    /* Translate X.# as X.length() if X if of type (array, tuple,
       string, buffer), but without the cwal Function call()
       overhead. */
    cwal_size_t n = 0;
    cwal_value * xrv = 0;
    switch(cwal_value_type_id(lhs)){
      case CWAL_TYPE_ARRAY:
        n = cwal_array_length_get(cwal_value_get_array(lhs));
        break;
      case CWAL_TYPE_TUPLE:
        n = cwal_tuple_length(cwal_value_get_tuple(lhs));
        break;
      case CWAL_TYPE_STRING:
        n = cwal_string_length_utf8(cwal_value_get_string(lhs));
        break;
      case CWAL_TYPE_BUFFER:
        n = cwal_value_get_buffer(lhs)->used;
        break;
      case CWAL_TYPE_HASH:
        n = cwal_hash_entry_count(cwal_value_get_hash(lhs));
        if(s2_value_is_enum(lhs)) n = n/2 /* do not count reverse mappings */;
        break;
      default:
        if(cwal_props_can(lhs)){
          n = cwal_props_count(lhs);
        }else{
          if(argc){/*avoid unused param warning*/}
          rc = s2_engine_err_set(se, CWAL_RC_TYPE,
                                 "Invalid LHS (type %s) for '%s' operator.",
                                 cwal_value_type_name(lhs),
                                 op->sym);
        }
        break;
    }
    if(!rc){
      xrv = cwal_new_integer(se->e, (cwal_int_t)n);
      if(!xrv) rc = CWAL_RC_OOM;
    }
    cwal_value_ref(xrv);
    cwal_value_unref(lhs);
    if(rc){
      assert(!xrv);
      cwal_value_unref(xrv)/*just for symmetry's sake*/;
    }else{
      assert(xrv);
      cwal_value_unhand(xrv);
      *rv = xrv;
    }
  }
  if(!rc){
    assert(*rv);
  }
  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, * xrv = 0;
  assert(2==argc);
  rhs = s2__pop_val(se);
  lhs = s2__pop_val(se);
  *rv = 0;
  cwal_value_ref(lhs);
  cwal_value_ref(rhs);
  if(se->skipLevel){
    /*cwal_refunref(lhs);
    cwal_refunref(rhs);*/
    cwal_value_unref(lhs);
    cwal_value_unref(rhs);
    *rv = cwal_value_undefined();
  }else{
    assert(s2_overload_name(op->id, lhs, 0));
    if(!s2_op_check_overload(op, se, 1, lhs, rhs, &xrv, &rc)){
      assert(rc);
    }else{
      cwal_value_ref(xrv);
    }
#if 0
    /*??? can of worms ??? */
    if(!rc && S2_T_OpColon2==op->id){
      /* 20160819: this is causing a cwal-level cleanup assertion downstream. */
      /* Has to be done afterwards b/c overload can overwrite these,
         triggering an assertion in fcall() */
      s2_dotop_state( se, lhs, lhs, rhs );
      s2_dump_val(rhs,"se->dotOp.key");
      s2_dump_val(lhs,"se->dotOp.lhs");
    }
#endif
    cwal_value_unref(lhs);
    cwal_value_unref(rhs);
    if(rc){
      assert(!xrv);
      if(argc){/*avoid unused param warning*/}
    }else{
      cwal_value_unhand(xrv);
      *rv = xrv ? xrv : cwal_value_undefined();
    }
  }
  return rc;
}

int s2_handle_set_result( s2_engine * se, s2_ptoker const * pr, int rc ){
  if(rc && (pr || se->currentScript)){
    switch(rc){
      case CWAL_RC_OOM:
      case CWAL_RC_EXCEPTION:
        break;
      case CWAL_RC_NOT_FOUND:
        rc = 0;
        break;
      default:
        rc = s2_throw_err_ptoker(se, pr ? pr : se->currentScript);
        assert(rc);
        break;
    }
  }
  return rc;
}

int s2_handle_get_result( s2_engine * se, s2_ptoker const * pr, int rc ){
  if(rc && (pr || se->currentScript)){
    switch(rc){
      case CWAL_RC_OOM:
      case CWAL_RC_EXCEPTION:
        break;
      case CWAL_RC_NOT_FOUND:
        rc = 0;
        break;
      default:
        rc = s2_throw_err_ptoker(se, pr ? pr : se->currentScript);
        break;
    }
  }
  return rc;
}

/**
   Maintenance reminder: s2_op_f_assign2() and s2_op_f_assign3() share
   most of their logic, but having them separate improves their
   readability considerably. It might be useful to separate parts of
   the two implementations (e.g. the main operator dispatch switch)
   into a shared function at some point.
*/
int s2_op_f_assign3( s2_op const * op, s2_engine * se,
                     int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * rhs, * lhs, * self /* Assignment op: self[lhs]=rhs */;
  s2_stoken * vTok;
  int opReturnedLhs = -1
    /* optimization. If an operator returns the (X.Y) part of (X.Y OP
       Z) then we can skip the assignment back to X.Y.  A value of <0
       means we don't yet know. */;
  cwal_flags16_t const fSetFlags = (S2_T_OpAssignConst3 == op->id)
    ? CWAL_VAR_F_CONST : 0;
  S2_UNUSED_ARG argc;
  assert( 3==op->arity );
  rhs = s2__pop_val(se);
  vTok = s2__pop_tok(se, 1);
  lhs = vTok->value;
  self = s2__pop_val(se);
  assert(self);
  cwal_value_ref(lhs);
  cwal_value_ref(rhs);
  cwal_value_ref(self);
  s2_stoken_free(se, vTok, 1);
  vTok = 0;
  if(se->skipLevel>0){
    cwal_value_unref(lhs);
    cwal_value_unref(rhs);
    cwal_value_unref(self);
    *rv = cwal_value_undefined();
    return 0;
  }
  if(op->id != S2_T_OpAssign3 && op->id != S2_T_OpAssignConst3
     /* combo assignment, i.e. not: X.Y=Z or X.Y:=Z*/){
    /* For combo ops (all but 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 those 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.
    */
    char gotOverload = 0;
    cwal_value * lhsResolved = 0;
    cwal_value * xrv = 0;
    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->dotOp.lhs and
           friends _might_ (depending on stack conditions) have been
           cleared by now.
        */){
      assert(!lhsResolved);
      cwal_value_unref(lhs);
      cwal_value_unref(rhs);
      cwal_value_unref(self);
      return rc;
    }else if(!lhsResolved){
      /* unknown object property */
      lhsResolved = cwal_value_undefined();
      opReturnedLhs = 0 /* special case: we want to assign back over
                           this even if the op returns the undefined
                           value. */;
    }
    cwal_value_ref(lhsResolved);
    if(!(gotOverload=s2_op_check_overload(op, se, 1, lhsResolved, rhs, &xrv, &rc))
       && !rc){
      assert(!xrv);
      switch(op->id){
        case S2_T_OpPlusAssign3:
          rc = s2_values_addsub( se, 1, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpMinusAssign3:
          rc = s2_values_addsub( se, 0, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpMultiplyAssign3:
          rc = s2_values_multdivmod( se, 1, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpDivideAssign3:
          rc = s2_values_multdivmod( se, -1, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpModuloAssign3:
          rc = s2_values_multdivmod( se, 0, lhsResolved, rhs, &xrv);
          break;
        default:
          rc = s2_values_bitwiseshift( se, op->id, lhsResolved, rhs, &xrv );
          break;
      }/* the combo-assignment operators */
      if(!rc){
        assert(xrv);
        if(opReturnedLhs<0){
          opReturnedLhs = xrv==lhsResolved ? 1 : 0;
        }
        cwal_value_ref(xrv);
        cwal_value_unref(rhs);
        rhs = xrv;
      }
    }else if(gotOverload){
      if(rc){
        assert(!xrv);
      }else{
        assert(xrv);
        if(opReturnedLhs<0){
          opReturnedLhs = xrv==lhsResolved ? 1 : 0;
        }
        cwal_value_ref(xrv);
        cwal_value_unref(rhs);
        rhs = xrv;
        xrv = 0;
      }
    }
    cwal_value_unref(lhsResolved);
    lhsResolved = 0;
  }/* combo assignments */
  if(!rc){
    assert(lhs);
    assert(rhs);
    /* Initial thought was that we do not want to 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.
    */
#if 0
    /* an attempt at forcing "return this" semantics for
       combo assignment ops. Together with op->derivedFromOp
       handling, it seems to do what i want.

       Except that it breaks string+=string. :/

       ACH - i think we need to force those semantics on the
       _derived_ call, not this one.
    */
    if(gotOverload>1/*synthesized assignment op overload*/
       && s2_ttype_is_assignment_combo(op->id)){
      cwal_value_ref(lhsResolved);
      cwal_value_unref(rhs);
      rhs = lhsResolved;
      MARKER(("Forcing 'return this' semantics for overload...\n"));
      s2_dump_val(lhsResolved,"this is this");
    }
#endif
#if 0
    if(opReturnedLhs>0){
      MARKER(("%s opReturnedLhs=%d, so skipping assignment.\n", op->sym,
              opReturnedLhs));
    }
#endif
    opReturnedLhs = 0
      /*
        It turns out that this optimization, while it works, breaks
        const expectations. Assume X.Y is an array property which has
        been set const via C code and that array.operator+= behaves
        like array.push() but returns, as required for operator+=,
        'this'). In that case, (X.Y += 3) would (with this
        optimization) append to array X.Y while *appearing* to bypass
        the constness. The constness is not "actually" violated, but
        it would, to an observer who doesn't understand this
        optimization, appear to.
         
        Whether or not "appearances" is a reason to disallow this
        optimization is up for reconsideration.
      */;
    rc = opReturnedLhs>0
      ? 0
      : s2_handle_set_result(se, 0, s2_set_with_flags_v(se, self, lhs,
                                                        rhs, fSetFlags))
      /* Reminder: for full X#Y support this needs to be
         different. */;
  }
  if(!rc){
    *rv = rhs;
    cwal_value_unhand(rhs);
  }else{
    cwal_value_unref(rhs);
  }
  cwal_value_unref(lhs);
  cwal_value_unref(self);
  return rc;  
}

/**
   Maintenance reminder:

   s2_op_f_assign3() shares a good deal of logic with this routine,
   but they were separated to improve readability. See that function
   for other notes.
*/
int s2_op_f_assign2( s2_op const * op, s2_engine * se,
                    int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * rhs, * lhs /* Asignment op: lhs=rhs */;
  s2_stoken * vTok;
  int ttype;
  int opReturnedLhs = -1
    /* optimization. If an operator returns the X part of (X OP Y)
       then we can skip the assignment back to X. A value of <0 means
       we don't yet know. */;
  char gotOverload = 0;
  S2_UNUSED_ARG argc;
  rhs = s2__pop_val(se);
  vTok = s2__pop_tok(se, 1);
  lhs = vTok->value;
  assert( 2==op->arity );
  cwal_value_ref(rhs);
  cwal_value_ref(lhs);
  ttype = vTok->ttype;
  s2_stoken_free(se, vTok, 1);
  vTok = 0;
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    cwal_value_unref(rhs);
    cwal_value_unref(lhs);
    return 0;
  }
  if(S2_T_Identifier != ttype){
    cwal_value_unref(rhs);
    cwal_value_unref(lhs);
    return s2_engine_err_set(se, CWAL_SCR_SYNTAX,
                             "Invalid LHS (%s) for '%s' op.",
                             s2_ttype_cstr(ttype), op->sym);
  }

  if(op->id != S2_T_OpAssign /* a combo assignment, not plain X=Y */){
    /* For combo ops (all but X=Y) 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 those can skip all this.
    */
    cwal_value * lhsResolved = 0;
    cwal_value * xrv = 0;
    if( (rc = s2_get_v( se, NULL, 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->dotOp.lhs and
         friends _might_ (depending on stack conditions) have been
         cleared by now.
      */
      assert(!lhsResolved);
      cwal_value_unref(lhs);
      cwal_value_unref(rhs);
      return rc;
    }else if(!lhsResolved){
      cwal_size_t idLen = 0;
      char const * sym = cwal_value_get_cstr(lhs, &idLen);
      rc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                             "'%s' operator cannot resolve "
                             "identifier '%.*s'",
                             op->sym, (int)idLen, sym );
      cwal_value_unref(rhs);
      cwal_value_unref(lhs);
      return rc;
    }
    cwal_value_ref(lhsResolved);
    if(!(gotOverload=s2_op_check_overload(op, se, 1, lhsResolved, rhs, &xrv, &rc))
       && !rc){
      assert(!xrv);
      switch(op->id){
        case S2_T_OpPlusAssign:
          rc = s2_values_addsub( se, 1, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpMinusAssign:
          rc = s2_values_addsub( se, 0, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpMultiplyAssign:
          rc = s2_values_multdivmod( se, 1, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpDivideAssign:
          rc = s2_values_multdivmod( se, -1, lhsResolved, rhs, &xrv);
          break;
        case S2_T_OpModuloAssign:
          rc = s2_values_multdivmod( se, 0, lhsResolved, rhs, &xrv);
          break;
        default:
          rc = s2_values_bitwiseshift( se, op->id, lhsResolved, rhs, &xrv );
          break;
      }/* the combo-assignment operators */
      if(!rc){
        assert(xrv);
        if(opReturnedLhs<0){
          opReturnedLhs = lhsResolved==xrv ? 1 : 0;
        }
        cwal_value_ref(xrv);
        cwal_value_unref(rhs);
        rhs = xrv;
      }
    }else if(gotOverload){
      if(rc){
        assert(!xrv);
      }else{
        assert(xrv);
        if(opReturnedLhs<0){
          opReturnedLhs = lhsResolved==xrv ? 1 : 0;
        }
        cwal_value_ref(xrv);
        cwal_value_unref(rhs);
        rhs = xrv;
        xrv = 0;
      }
    }
    cwal_value_unref(lhsResolved);
    lhsResolved = 0;
  }/* combo assignments */
  if(!rc){
    assert(lhs);
    assert(rhs);
    /* Initial thought was that we do not want to 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 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.
    */
#if 0
    if(opReturnedLhs>0){
      MARKER(("%s opReturnedLhs==%d, so skipping assignment.\n", op->sym,
              opReturnedLhs));
    }
#endif
    opReturnedLhs = 0
      /*
        Though this optimization works, we arguably can't use it
        because it allows constructs which, on the surface, *appear*
        to violate costness (for an observer who doesn't understand
        this optimization):

        const a = 5;
        // a += 1; // disallowed: const violation.
        a += 0; // allowed by this optimization.

        That op returns 5, which is (or may be) a built-in constant,
        thus triggering opReturnedLhs. That construct must, however,
        for appearance's/consistency's sake, trigger a const violation
        error.

        Similarly, assume that array.operator+= works like
        array.push() but returns, as is required by operator+=,
        'this':

        const a = [1,2,3];
        a += 4; // allowed by this optimization

        That "should", at least for appearances sake, trigger a const
        violation error. With this optimization, however, it would be
        allowed.

        Whether or not "appearances" is a reason to disallow this
        optimization is up for reconsideration.
       */;
    rc = opReturnedLhs>0
      ? 0
      : s2_handle_set_result(se, 0, s2_set_v(se, NULL, lhs, rhs))
      /* Reminder: for full X#Y support this needs to be
         different. */
      ;
  }
  if(rc) cwal_value_unref(rhs);
  else{
    cwal_value_unhand(rhs);
    *rv = rhs;
  }
  cwal_value_unref(lhs);
  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 /* target of X.Y incr/decr*/,
    * key /* the identifier or self[propertyKey] of X resp X.Y */;
  int identType /* token type of the operator ID, for sanity
                   checking. */;
  int direction = 0 /* -1 for prefix incr/decr, +1 for postfix */;
  cwal_value * vResolved = 0 /* Pre-op resolved value of X.Y */
    , * vResult = 0 /* Post-op result */;
  cwal_int_t addVal = 0 /* value to incr/decr by: -1 or +1 */;
  char gotOverLoad = 0
    /* true if we found an overload. Only used for sanity checking. */;
  char resolvedIsContainer = 0
    /* true if vResolved is a container type.
       Used only for sanity checks. */;
  s2_stoken * vTok = 0;
  assert(1==op->arity);
  vTok = s2__pop_tok(se, 1);
  self = se->dotOp.lhs /* LHS of X.Y */;
  key = self
    ? se->dotOp.key /* RHS (property key) of X.Y */
    : vTok->value /* identifier X */;
  identType = vTok->ttype;
  cwal_value_ref(key);
  cwal_value_ref(self);
  if(key != vTok->value){
    /* vTok->value was (IIRC!) the result of an X.Y lookup. We don't
       need that result, only the property key. */
    cwal_refunref(vTok->value);
    vTok->value = 0;
  }
  s2_stoken_free(se, vTok, 1);
  /* s2_dump_val(key, "key"); */
  /* s2_dump_val(self, "self"); */
  s2_dotop_state( se, 0, 0, 0 );
  if(se->skipLevel>0){
    cwal_value_unref(key);
    cwal_value_unref(self);
    *rv = cwal_value_undefined();
    return 0;
  }

  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:
      if(argc){/*avoid unused param warning*/}
      assert(!"invalid op mapping");
      s2_fatal(CWAL_RC_FATAL,"Invalid op mapping for incr/decr: %s",
               op->sym) /* doesn't return */;
  }
  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 %s (%s) for '%s' op.%s",
                             direction>0 ? "LHS" : "RHS",
                             s2_ttype_cstr(identType),
                             op->sym,
                             direction>0 ? "" :
                             " Expecting identifier or object "
                             "property access.");
  }
  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();
    }
  }
  cwal_value_ref(vResolved);
  /*
    Reminder: refs are most definitely 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.
  */
  resolvedIsContainer = cwal_props_can(vResolved);
  if(!resolvedIsContainer){
    /* Bypass overload checks for non-container types. */
    rc = s2_values_addsub( se, addVal>0, vResolved,
                           cwal_new_integer(se->e, 1)
                           /* does not allocate */,
                           &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);
    cwal_value_ref(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){
      /* don't assign back over _overridden_ ++/-- because the results
         are unintuitive and normally quite useless.
                            
         Turns out that breaks other stuff, so we always assign back
         over the original.
      */
      rc = s2_handle_set_result(se, 0, s2_set_v(se, self, key, vResult));
    }
    if(!rc){
      switch(op->id){
        case S2_T_OpIncrPre:
        case S2_T_OpDecrPre:
          /* Prefix result = the after-evaluation value. */
          *rv = vResult ? vResult : cwal_value_undefined();
          break;
        case S2_T_OpIncrPost:
        case S2_T_OpDecrPost:
          /* Postfix result = the before-evaluation value. */
          *rv = vResolved ? vResolved : cwal_value_undefined();
          break;
      }
    }
    break;
  }
  /* Let go of our references... */
  /* 
     This snippet helps free up the old value of ++X immediately, but
     it's not yet clear if what we're doing here is really kosher
     vis-a-vis lifetimes. Seems to be.

     The difference can be seen by starting s2sh with the -W flag
     and running a simple loop:

     for(var i = 0; i < 100; ++i );
     
     With this block enabled, that doesn't sweep/vacuum at all because
     the unref() here cleans up quickly, whereas the unhand() delays
     it until the next sweep (necessary for getting X++ results back
     properly).

     We have a related sweeping problem, possibly not solvable
     at this level:

     var a = [];
     for(a.0 = 100; a.0 > 0; a.0-- );

     that doesn't sweep any of the reset values until after the loop
     because they're owned by the array's scope. Run that through
     (s2sh -W). This problem does not show up with the prefix ops
     because those unref() instead of unhand() in that case. We
     resolve that in the for() loop handling using a simple ref/unref
     trick which can nuke values living in older scopes.

     The for/while/do-while loops take care of that themselves (now
     they do, anyway), but we'll leave this in here for the sake of
     for-each callback functions which behave similarly, modifying
     lower-scope values which we can potentially clean up.

     Nonetheless, we still have a problem in loop bodies:

     var a = [100];
     do a.0--; while(a.0)

     Does not free up a.0's previous values until a's owning scope is
     swept because (a.0--) returns a newly-made-temporary value (the
     old one) which is owned by a's scope. After the loop:

     MARKER: s2.c:264:s2_engine_sweep():	Swept up 99 value(s) in sweep mode

     Conversely:

     s2> a.0 = 100; do ; while(a.0--)

     Extra handling in for/do/while() can get rid of the temp created
     in the condition, cleaning up those temps as it goes, but only if
     they are on they are the result in the condition part. If they
     are on the LHS of the condition part, they are still orphaned in
     the parent scope until it returns.

     If we handled refcounts at the stack machine (or eval_impl()
     loop) level this might not be so much of a problem.
  */
#if 0
  s2_dump_val(self,"self");
  s2_dump_val(key,"key");
  s2_dump_val(vResolved,"vResolved");
  s2_dump_val(vResult,"vResult");
#endif
  if(!rc){
    cwal_value_ref(*rv);
    assert(*rv == vResolved || *rv == vResult);
  }
  cwal_value_unref(vResolved);
  cwal_value_unref(vResult);
  cwal_value_unref(key);
  cwal_value_unref(self);
  if(!rc) cwal_value_unhand(*rv);
  /*
    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.

    The for() loop, comma operator, and a few other places account for
    that by doing extra cleanup.
  */
  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;
  cwal_array * ar;
  s2_stoken * vTok;
  assert(2==argc);
  rhs = s2__pop_val(se);
  vTok = s2__pop_tok(se, 1);
  lhs = vTok->value;
  cwal_value_ref(rhs);
  cwal_value_ref(lhs);
  if(se->skipLevel>0){
    cwal_value_unref(rhs);
    cwal_value_unref(lhs);
    *rv = cwal_value_undefined();
  }else if(!(ar = cwal_value_array_part(se->e, lhs))){
    /* s2_dump_val(rhs,"rhs"); */
    /* s2_dump_val(lhs,"lhs"); */
    rc = s2_engine_err_set(se, CWAL_SCR_SYNTAX,
                           "Invalid (non-array) LHS (token type '%s' "
                           "with value type '%s') for '%s' op.",
                           s2_ttype_cstr(vTok->ttype),
                           cwal_value_type_name(lhs),
                           op->sym);
    if(argc){/*avoid unused param warning*/}
  }else{
    assert(lhs);
    assert(ar);
    /* s2_dump_val(rhs,"rhs"); */
    /* s2_dump_val(lhs,"lhs"); */
    rc = cwal_array_append(ar, rhs);
    if(!rc){
      *rv = rhs;
      cwal_value_ref(*rv);
    }
    cwal_value_unref(rhs);
    cwal_value_unref(lhs);
    if(!rc) cwal_value_unhand(*rv);
  }
  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(("%s operator: argc=%d%s\n", op->sym,
          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(!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 math 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 potentially hold larger
      numbers.
    */
    /* Reminder: ("1"+n) is caught via string op overload. */
    if(!cwal_value_is_double(lhs)){
      cwal_uint_t const iL = (cwal_uint_t)cwal_value_get_integer(lhs);
      cwal_uint_t const iR = (cwal_uint_t)cwal_value_get_integer(rhs);
      *rv = cwal_new_integer(se->e, doAdd ? (cwal_int_t)(iL+iR) : (cwal_int_t)(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;
}

/**
   proxy for s2_op_f_assign2/3() for *, /, % operators.

   mode: -1 = division, 0 = modulo, 1 = multiplication
*/
int s2_values_multdivmod( s2_engine * se, int mode,
                          cwal_value * lhs, cwal_value * rhs,
                          cwal_value **rv ){
  switch(mode){
    case -1: {
      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);
        }
      }
      break;
    }
    case 0: {
      /* 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));
      }
      break;
    }
    case 1:
      /* Multiplication */
      if(!cwal_value_is_double(lhs)){
        /* Integer math */
        cwal_uint_t const iL = (cwal_uint_t)cwal_value_get_integer(lhs);
        if(!cwal_value_is_double(rhs)){
          cwal_uint_t const iR = (cwal_uint_t)cwal_value_get_integer( rhs );
          *rv = cwal_new_integer(se->e, (cwal_int_t)(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);
        }
      }
      break;
    default:{
      const char * msg =
        "Internal misuse: s2_values_multdivmod() expecting mode of -1, 0, or 1";
      assert(!msg);
      s2_fatal(CWAL_RC_ASSERT, msg) /* does not return */;
    }
  }
  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 = (cwal_int_t)((cwal_uint_t)lhs << (cwal_uint_t)rhs); break;
    case S2_T_OpShiftRight:
    case S2_T_OpShiftRightAssign:
    case S2_T_OpShiftRightAssign3:
      res = (cwal_int_t)((cwal_uint_t)lhs >> (cwal_uint_t)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)

/**
   The "prefix" and "suffix" PathFinder properties are arrays of
   strings holding the search paths (prefixes) and extensions
   (suffixes). The names were, in hindsight, an unfortunate choice:
   "path" and "extensions" (or "ext") would have been more intuitive.
   Lots of my scripts use these names, though :/.
*/
#define PF_PREFIX "prefix"
#define PF_PREFIX_N ((cwal_size_t)sizeof(PF_PREFIX)-1)
#define PF_SUFFIX "suffix"
#define PF_SUFFIX_N ((cwal_size_t)sizeof(PF_SUFFIX)-1)

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

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,PF_SUFFIX,PF_SUFFIX_N));
    assert(cwal_prop_get(vSelf,PF_PREFIX,PF_PREFIX_N));
#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;
}

static cwal_array * s2_pf_member_array(s2_pf *pf, char const * pKey, cwal_size_t pKeyLen ){
  cwal_value * ar = cwal_prop_get(pf->self,pKey, pKeyLen);
  if(!ar || !cwal_value_is_array(ar)){
    ar = cwal_new_array_value(pf->se->e);
    if(ar){
      int rc = 0;
      cwal_value_ref(ar);
      rc = cwal_prop_set(pf->self, pKey, pKeyLen, ar);
      cwal_value_unref(ar);
      if(rc!=0) ar = NULL;
    }
  }
  return cwal_value_get_array(ar);
}

cwal_array * s2_pf_exts(s2_pf *pf){
  return s2_pf_member_array(pf, PF_SUFFIX, PF_SUFFIX_N);
}

cwal_array * s2_pf_dirs(s2_pf *pf){
  return s2_pf_member_array(pf, PF_PREFIX, PF_PREFIX_N);
}

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, PF_PREFIX, PF_PREFIX_N,
                         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, PF_SUFFIX, PF_SUFFIX_N,
                         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, dir, dirLen);
    if(!v) rc = CWAL_RC_OOM;
    else {
      cwal_value_ref(v);
      rc = s2_pf_dir_add_v( pf, v);
      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, dir, dirLen);
    if(!v) rc = CWAL_RC_OOM;
    else {
      cwal_value_ref(v);
      rc = s2_pf_ext_add_v( pf, v);
      cwal_value_unref(v);
    }
    return rc;
  }
}

s2_pf * s2_value_pf_part(cwal_value const *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;
}

s2_pf * s2_value_pf(cwal_value const * v){
  cwal_native const * n = v ? cwal_value_get_native(v) : 0;
  return n
    ? (s2_pf *)cwal_native_get(n, &s2_pf_empty)
    : 0;
}

cwal_value * s2_pf_value(s2_pf const * pf){
  return pf->self;
}

#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{
      rc = S2_DIRECTORY_SEPARATOR;
      *sepLen = sizeof(S2_DIRECTORY_SEPARATOR)-
        sizeof(S2_DIRECTORY_SEPARATOR[0]);
    }
  }
  *sep = rc;
}


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_is_dir(NAME,0)     \
                            : (directoryPolicy > 0 \
                               ? 1 \
                               : !s2_is_dir(NAME,0)))))
  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 && (nD ? d < nD : 1); ){
    cwal_value * vD = nD
      ? cwal_array_get(ad, d)
      : 0;
    buf->used = 0;
    if(nD && 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;
      }
    }
    if(++d >= nD) break;
  }
#undef CHECK_FILE
  return NULL;
  gotone:
  if(rcLen) *rcLen = buf->used;
  return (char const *)buf->mem;
}

/**
   Script signature:

   string|undefined search(string baseName [, bool|int dirPolicy=0])

   dirPolicy: bool true or integer>0 mean match files and dirs, false or
   integer 0 mean only files, integer<0 means only match dirs. If this value
   is of the Unique type, its wrapped value is used in its place.

   Returns the first matching file/dir entry, or the undefined value
   if no entry is found.
*/
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;
  {
    int const rc = s2_cb_disable_check(args, S2_DISABLE_FS_STAT);
    if( rc ) return rc;
  }

  if(!args->argc) goto misuse;
  base = cwal_value_get_cstr(args->argv[0], &baseLen);
  if(!base || !baseLen) goto misuse;
  else if(args->argc>1){
    /* dirPolicy: bool|integer directoryPolicy */
    cwal_value const * a1 = s2_value_unwrap(args->argv[1]);
    if(cwal_value_is_bool(a1)){
      directoryPolicy = cwal_value_get_bool(a1)
        ? S2_PF_SEARCH_FILES_DIRS
        : S2_PF_SEARCH_FILES;
    }else{
      cwal_int_t const n = cwal_value_get_integer(a1);
      directoryPolicy = n==0
        ? S2_PF_SEARCH_FILES
        : (n<0
           ? S2_PF_SEARCH_DIRS
           : S2_PF_SEARCH_FILES_DIRS);
    }
  }
  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){
    /* Set or reserve the PF_PREFIX/PF_SUFFIX properties... */
    uint16_t i;
    typedef int (*setter_f)(s2_pf *, cwal_array *);
    typedef cwal_array * (*getter_f)(s2_pf *);
    for(i = 0; i<args->argc && i<2; ++i){
      cwal_value * const arg = args->argv[i];
      setter_f setter = i ? s2_pf_exts_set : s2_pf_dirs_set;
      getter_f getter = i ? s2_pf_exts : s2_pf_dirs;
      cwal_array * ar;
      if(cwal_value_undefined()==arg || cwal_value_null()==arg){
        /* Special case: skip these without an error to simplify
           certain usage patterns, e.g.: new
           s2.PathFinder(s2.getenv("blah")). */
        continue;
      }
      ar = cwal_value_array_part(args->engine, arg);
      if(ar){
        /* Array argument: use it as-is */
        rc = setter(pf, ar);
      }else{
        if(cwal_value_is_string(arg)){
          /* Parse string as a PATH. */
          cwal_size_t plen;
          char const * pstr = cwal_value_get_cstr(arg, &plen);
          cwal_array * tgt = getter(pf);
          if(!tgt){
            rc = CWAL_RC_OOM;
          }else{
            assert(pstr);
            rc = s2_tokenize_path_to_array(args->engine, &tgt,
                                           pstr, (cwal_int_t)plen)
              /* The only plausible error case here is an OOM, so we
                 don't translate the result to an exception. */;
          }
        }else{
          /* Lightly slap the user's fingers. */
          rc = s2_cb_throw(args, CWAL_RC_TYPE,
                           "Expecting 0, 1, or 2 string or array arguments.");
        }
      }
      if(rc) break;
    }
  }
  if(!rc) *rv = pf->self;
  else cwal_value_unref(pf->self) /* takes pf with it */;
  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;                                                      \
  cwal_value_ref(v);                                           \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );    \
  cwal_value_unref(v);                                         \
  v = 0;                                                       \
  if(rc) goto end

  v = cwal_new_string_value(se->e, "PathFinder", 10);
  CHECKV;
  cwal_value_ref(v);
  rc = cwal_prop_set_with_flags_v( proto, se->cache.keyTypename,
                                   v, CWAL_VAR_F_HIDDEN );
  cwal_value_unref(v);
  v = 0;
  if(rc) goto end;

  v = cwal_new_xstring_value(se->e,
#ifdef _WIN32
                             "\\",
#else
                             "/",
#endif
                             1);
  SET("separator");

  {
    s2_func_def const funcs[] = {
      S2_FUNC2("addDir", s2_cb_pf_dir_add),
      S2_FUNC2("addExt", s2_cb_pf_ext_add),
      S2_FUNC2("search", s2_cb_pf_search),
      S2_FUNC2("fileIsAccessible", s2_cb_file_accessible),
      S2_FUNC2("new", s2_cb_pf_new),
      S2_FUNC2("tokenizePath", s2_cb_tokenize_path),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
    if(rc) goto end;
    else {
      cwal_value * fv = 0;
      s2_get(se, proto, "new", 3, &fv);
      assert(fv && "we JUST put this in there!");
      rc = s2_ctor_method_set( se, proto,
                               cwal_value_get_function(fv) );
    }
  }

#undef SET
#undef CHECKV
    end:
    return rc
      ? NULL /* remember: proto is stashed at this point, so no leak. */
      : proto;
}

#undef ARGS_SE
#undef THIS_PF
#undef MARKER

#undef PF_PREFIX
#undef PF_PREFIX_N
#undef PF_SUFFIX
#undef PF_SUFFIX_N
/* 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

cwal_value * s2_prototype_stashed( s2_engine * se, char const * typeName ){
  enum { BufLen = 128 };
  char buf[BufLen];
  S2_UNUSED_VAR 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];
  S2_UNUSED_VAR 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_engine_from_args(args); \
    assert(se)

static int s2_cb_object_ctor( cwal_callback_args const * args, cwal_value **rv ){
  if(args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                      "Expecting no arguments.");
  }else{
    *rv = cwal_new_object_value( args->engine );
    return *rv ? 0 : CWAL_RC_OOM;
  }
}


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,
                                        args->argv[0],
                                        0));
    return 0;
  }
}


static int s2_cb_prop_clear( cwal_callback_args const * args, cwal_value **rv ){
    int rc = s2_immutable_container_check_cb(args, args->self);
    if( !rc && (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 = 0;
  s2_engine * se;
  if( (rc = s2_immutable_container_check_cb(args, args->self)) ) return rc;
  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;
    }
    else if(rc) rc = s2_handle_set_result(se, 0, rc);
    else ++b;
  }
  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 ){
  int rc;
  ARGS_SE;
  if( (rc = s2_immutable_container_check_cb(args, args->self)) ) return rc;
  else 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. OTOH, this makes it impossible to
       get at Object-side props of a Hash which is running
       in object-like mode.
    */
    rc =
      s2_handle_get_result(se, 0, 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)

   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 = args->self;
      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.");
}

#if 0
/**
   Just an experiment.
*/
static int s2_cb_container_experiment( cwal_callback_args const * args,
                                        cwal_value **rv ){
  cwal_value * arg = args->argc ? args->argv[0] : args->self;
  s2_container_config( arg, 1, 1, 1 );
  *rv = arg;
  return 0;
}
#endif

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)
          /* 2020-02-20: consider exposing
             s2_eval.c:cwal_kvp_visitor_props_copy_s2() for re-use
             here, primarily to improve the error reporting for const
             violations. */;
        if(!rc) *rv = arg;
        /*
          TODO: when i'm on a machine where i can change the docs to
          match: change *rv to args->self, for consistency. OTOH,
          being able to:

          var x = foo.copyPropertiesTo({});

          is convenient.
        */
      }
    }
    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;
}

/**
   Internal cwal_kvp_visitor_f() implementation which requires state
   to be a fully-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] = state->valueArgFirst
    ? cwal_kvp_value(kvp)
    : cwal_kvp_key(kvp);
  av[1] = state->valueArgFirst
    ? cwal_kvp_key(kvp)
    : cwal_kvp_value(kvp);
  assert(av[0] && av[1]);
  assert(state->e);
  assert(state->callback);
  assert(state->self);
  assert(cwal_props_can(state->self));
#if 0
  /* missing access to s2_engine object: s2_engine_sweep(state->s2); */
  cwal_engine_sweep(state->e);
#endif
#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 );
  switch(rc){
    case 0:
      if(rv==cwal_value_false()/*literal false, not another falsy*/){
        rc = S2_RC_END_EACH_ITERATION;
      }
      break;
    case CWAL_RC_IS_VISITING:
    case CWAL_RC_IS_VISITING_LIST /* for hashtable entries */:
      rc = cwal_exception_setf(state->e, rc,
                               "Illegal iteration attempt detected.");
      break;
  }
  cwal_refunref(rv);
  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 = s2_kvp_each_state_empty;
    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;
  }
}

static int s2_cb_with_this( 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,
                       "'withThis' expects a Function argument, "
                       "but got a %s.",
                       args->argc
                       ? cwal_value_type_name(args->argv[0])
                       : "<NULL>");
  }else{
#if 0
    return cwal_function_call(f, args->self, rv, 0, 0);
#elif 1
    /*
      In practice, the single most common script-side mistake when
      calling this method is forgetting to return 'this'. Also (in
      practice) callbacks must return 'this' for all current use cases
      (all others are hypothetical). So... if the function does not
      return anything (or returns undefined, since that's what script
      function calls will do rather than returning NULL), we'll return
      'this' instead...
    */
    int rc;
    *rv = 0;
    if(!(rc = cwal_function_call(f, args->self, rv, 0, 0))){
      if(!*rv || cwal_value_undefined()==*rv){
        *rv = args->self;
      }
    }
    return rc;
#else
    s2_func_state const * fst = s2_func_state_for_func(f);
    int const rc = cwal_function_call(f, args->self, rv, 0, 0);
    /* if(!rc) *rv = args->self; no, returning the callback
       result is more flexible.*/
    if(fst && !fst->lastCallReturned){
      /* if a SCRIPT func does not explicitly return then
         return 'this' instead of undefined. */
      *rv = args->self;
    }
    return rc;
#endif
  }
}

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;
    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).
     */
#define PROTO(F) if(!(v = F)) { \
      rc = se->err.code ? se->err.code : CWAL_RC_OOM; goto end; \
    } (void)0
    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));
    /* PROTO(s2_prototype_unique(se)); */
    /* PROTO(s2_prototype_tuple(se)); */
#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_v( proto, se->cache.keyTypename, v );
    RC;
#endif

    {
      s2_func_def const funcs[] = {
      /* S2_FUNC2("experiment", s2_cb_container_experiment), */
      S2_FUNC2("clearProperties", s2_cb_prop_clear),
      S2_FUNC2("copyPropertiesTo", s2_cb_props_copy_to),
      S2_FUNC2("compare", s2_cb_value_compare),
      S2_FUNC2("eachProperty", s2_cb_prop_each),
      S2_FUNC2("get", s2_cb_prop_get),
      S2_FUNC2("hasOwnProperty", s2_cb_prop_has_own),
      S2_FUNC2("mayIterate", s2_cb_value_may_iterate),
      S2_FUNC2("isEmpty", s2_cb_container_is_empty),
      S2_FUNC2("propertyKeys", s2_cb_prop_keys),
      S2_FUNC2("propertyCount", s2_cb_props_count),
      S2_FUNC2("set", s2_cb_prop_set),
      S2_FUNC2("toJSONString", s2_cb_this_to_json_token),
      S2_FUNC2("toString", s2_cb_value_to_string),
      S2_FUNC2("unset", s2_cb_prop_unset),
      S2_FUNC2("withThis", s2_cb_with_this),
      s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0);
      if(!rc) rc = s2_ctor_callback_set(se, proto,
                                        s2_cb_object_ctor);
    }

#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 = args->argc ? args->argv[0] : cwal_prop_get(args->self, "code", 4);
  if(v){
    s2_engine * se = s2_engine_from_args(args);
    cwal_value *hv = s2_stash_get(se, "RcHash")
      /* Optimization: if the stashed RcHash (set up in s2.c) is
         available, check it first. This avoids having to allocate
         x-strings which we know are already in that hash. It also
         incidentally supports a reverse mapping, such that passing in
         the string 'CWAL_RC_OOM' will return its integer value.
      */;
    *rv = 0;
    if(hv){
      cwal_hash * h = cwal_value_get_hash(hv);
      assert(h);
      *rv = cwal_hash_search_v(h, v);
    }
    if(!*rv){
      cwal_size_t vstrLen = 0;
      char const * const vstr = cwal_value_get_cstr(v, &vstrLen);
      if(vstr){ /* If passed a string, try (the hard way) to
                   find the integer code. */
        int code = 0;
        if(s2_cstr_to_rc(vstr, (cwal_int_t)vstrLen, &code)){
          if(! (*rv = cwal_new_integer(args->engine, (cwal_int_t)code)) ){
            rc = CWAL_RC_OOM;
          }
        }
        if(!rc && !*rv) *rv = cwal_value_undefined();
      }else{
        /* Assume the code is an integer and get its string
           form. */
        cwal_int_t const code = cwal_value_get_integer(v);
        char const * str = s2_rc_cstr2((int)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{
    /* No arg or "code" property. */
    *rv = cwal_value_undefined();
  }
  return rc;
}

cwal_value * s2_prototype_exception( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    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;

    {
      s2_func_def const funcs[] = {
      S2_FUNC2("codeString", s2_cb_exception_code_string),
      s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0);
    }


#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 !defined(S2_ENABLE_ZLIB)
#  define S2_ENABLE_ZLIB 0
#endif

#if !defined(S2_INTERNAL_MINIZ)
#  if S2_ENABLE_ZLIB
#    define S2_INTERNAL_MINIZ 0
#  else
#    define S2_INTERNAL_MINIZ 1
#  endif 
#endif

#if S2_INTERNAL_MINIZ
#  if S2_ENABLE_ZLIB
#    error Cannot enable both S2_INTERNAL_MINIZ and S2_ENABLE_ZLIB.
#  endif
#  define MINIZ_NO_STDIO /* necessary for linux, at least */
#  define MINIZ_NO_ARCHIVE_APIS /* we aren't using these */
#elif S2_ENABLE_ZLIB
#  include <zlib.h>
#endif

#define S2_ENABLE_ZLIKE (S2_ENABLE_ZLIB || S2_INTERNAL_MINIZ)

#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

char s2_is_compressed(unsigned char const * mem, cwal_size_t len){
  if(!mem || (len<6)) return 0;
  else{
    /**
       Adapted from:
       
       http://blog.2of1.org/2011/03/03/decompressing-zlib-images/

       Remember that s2-compressed data has a 4-byte big-endian header
       holding the uncompressed size of the data, so we skip those
       first 4 bytes.

       See also:

       http://tools.ietf.org/html/rfc6713

       search for "magic number".
    */
    int16_t const head = (((int16_t)mem[4]) << 8) | mem[5];
    /* MARKER(("isCompressed header=%04x\n", head)); */
    switch(head){
      case 0x083c: case 0x087a: case 0x08b8: case 0x08f6:
      case 0x1838: case 0x1876: case 0x18b4: case 0x1872:
      case 0x2834: case 0x2872: case 0x28b0: case 0x28ee:
      case 0x3830: case 0x386e: case 0x38ac: case 0x38ea:
      case 0x482c: case 0x486a: case 0x48a8: case 0x48e6:
      case 0x5828: case 0x5866: case 0x58a4: case 0x58e2:
      case 0x6824: case 0x6862: case 0x68bf: case 0x68fd:
      case 0x7801: case 0x785e: case 0x789c: case 0x78da:
        return 1;
      default:
        return 0;
    }
  }
}

char s2_buffer_is_compressed(cwal_buffer const *buf){
  return buf
    ? s2_is_compressed( buf->mem, buf->used )
    : 0;
}

uint32_t s2_uncompressed_size(unsigned char const *mem,
                              cwal_size_t len){
  return s2_is_compressed(mem,len)
    ? (uint32_t)((mem[0]<<24) + (mem[1]<<16) + (mem[2]<<8) + mem[3])
    : (uint32_t)-1;
}

uint32_t s2_buffer_uncompressed_size(cwal_buffer const * b){
  return b
    ? s2_uncompressed_size(b->mem, b->used)
    : (uint32_t)-1;
}

void s2_buffer_swap( cwal_buffer * left, cwal_buffer * right ){
  void * const self1 = left->self, * const self2 = right->self
    /* cwal_value parts of script-side buffers */;
  cwal_buffer const tmp = *left;
  assert(left && right);
  assert(left != right);
  *left = *right;
  *right = tmp;
  left->self = self1;
  right->self = self2;
}

#if S2_ENABLE_ZLIKE
/**
   calls s2_buffer_swap(left, right), then...

   clearWhich == 0 means clear neither, <0 means clear the left, >0
   means clear the right.
*/
static void s2_buffer_swap_clear( cwal_engine * e,
                                  cwal_buffer * left,
                                  cwal_buffer * right,
                                  int clearWhich ){
  s2_buffer_swap(left, right);
  if(0 != clearWhich) cwal_buffer_clear(e,
                                        (clearWhich<0) ? left : right);
}
#endif
/* ^^^ S2_ENABLE_ZLIKE */


int s2_buffer_compress(cwal_engine * e, cwal_buffer const *pIn,
                       cwal_buffer *pOut){
#if !S2_ENABLE_ZLIKE
  if(e || pIn || pOut){/*avoid unused param warning*/}
  return CWAL_RC_UNSUPPORTED;
#else
#define N_OUT (13 + nIn + (nIn+999)/1000)
  unsigned int nIn = (unsigned int)pIn->used;
  unsigned int nOut = N_OUT;
  cwal_buffer temp = cwal_buffer_empty;
  int rc;
#if CWAL_SIZE_T_BITS > 32
  if( (pIn->used != (cwal_size_t)nIn)
      || ((cwal_size_t)nOut != (cwal_size_t)N_OUT)){
    /* Trying to defend against a cwal_size_t too big
       for unsigned int. */
    return CWAL_RC_RANGE;
  }else
#endif
  if(s2_buffer_is_compressed(pIn)){
    if(pIn == pOut) return 0;
    else{
      cwal_buffer_reset(pOut);
      return cwal_buffer_append(e, pOut, pIn->mem, pIn->used);
    }
  }
#undef N_OUT
  rc = cwal_buffer_resize(e, &temp, nOut+4);
  if(rc) return rc;
  else{
    unsigned long int nOut2;
    unsigned char *outBuf;
    unsigned long int outSize;
    outBuf = temp.mem;
    outBuf[0] = nIn>>24 & 0xff;
    outBuf[1] = nIn>>16 & 0xff;
    outBuf[2] = nIn>>8 & 0xff;
    outBuf[3] = nIn & 0xff;
    nOut2 = (long int)nOut;
    rc = compress(&outBuf[4], &nOut2,
                  pIn->mem, pIn->used);
    if(rc){
      cwal_buffer_clear(e, &temp);
      return CWAL_RC_ERROR;
    }
    outSize = nOut2+4;
    rc = cwal_buffer_resize(e, &temp, outSize);
    if(rc){
      cwal_buffer_clear(e, &temp);
    }else{
      s2_buffer_swap_clear(e, &temp, pOut, -1);
      assert(0==temp.used);
      assert((cwal_size_t)outSize==pOut->used);
    }
    return rc;
  }
#endif
/* S2_ENABLE_ZLIKE */
}

int s2_buffer_uncompress(cwal_engine * e, cwal_buffer const *pIn,
                         cwal_buffer *pOut){
#if !S2_ENABLE_ZLIKE
  if(e || pIn || pOut){/*avoid unused param warning*/}
  return CWAL_RC_UNSUPPORTED;
#else
  unsigned int nOut;
  unsigned char *inBuf;
  unsigned int nIn = (unsigned int)pIn->used;
  cwal_buffer temp = cwal_buffer_empty;
  int rc;
  unsigned long int nOut2;
  if( nIn<=4 ){
    return CWAL_RC_RANGE;
  }else if(pIn == pOut && !s2_buffer_is_compressed(pIn)){
    return 0;
  }
  inBuf = pIn->mem;
  nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
  /* MARKER(("decompress size: %u\n", nOut)); */
  rc = cwal_buffer_reserve(e, &temp, nOut+1);
  if(rc) return rc;
  nOut2 = (long int)nOut;
  rc = uncompress(temp.mem, &nOut2,
                  &inBuf[4], nIn - 4)
    /* valgrind says there's an uninitialized memory access
       somewhere under uncompress(), _presumably_ for one of
       these arguments, but i can't find it. fsl_buffer_reserve()
       always memsets() new bytes to 0.

       Turns out it's a known problem:

       http://www.zlib.net/zlib_faq.html#faq36
    */;
  if( rc ){
    cwal_buffer_clear(e, &temp);
    return CWAL_RC_ERROR;
  }
  rc = cwal_buffer_resize(e, &temp, nOut2);
  if(!rc){
    void * const self = pOut->self
      /* cwal_value part of script-side buffers */;
    temp.used = (cwal_size_t)nOut2;
    if( pOut==pIn ){
      cwal_buffer_clear(e, pOut);
    }
    assert(!pOut->mem);
    *pOut = temp;
    pOut->self = self;
  }else{
    cwal_buffer_clear(e, &temp);
  }
  return rc;
#endif
/* S2_ENABLE_ZLIKE */
}


/*
  Various helper macros for callbacks...
*/

#define ARGS_SE s2_engine * se = s2_engine_from_args(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_CSTRING(USE_EMPTY_FOR_BUFFER)                              \
  char const * self; cwal_size_t selfLen; ARGS_SE;                      \
  if(!se) return CWAL_RC_MISUSE;                                        \
  else if(!(self=cwal_value_get_cstr(args->self, &selfLen))){          \
    if(USE_EMPTY_FOR_BUFFER && cwal_value_get_buffer(args->self)){        \
      self = ""; selfLen = 0;                                             \
    }else {                                                               \
      return cwal_exception_setf( args->engine, CWAL_RC_TYPE,             \
                                  "Expecting a string or buffer 'this' argument." ); \
    }\
  } (void)0


#define THIS_BUFFER                                             \
  cwal_buffer * self; ARGS_SE;                                  \
  if(!se) return CWAL_RC_MISUSE;                                \
  else if(!(self=cwal_value_buffer_part(args->engine, 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_engine_from_args(args);
    cwal_string * str = cwal_value_get_string(args->self);
    assert(se);
    if(1==args->argc && str
       && 0==cwal_string_length_bytes(str)
       && cwal_value_is_string(args->argv[0])){
      /* optimization: "".concat(anyString) simply returns argv[0] */
      *rv = args->argv[0];
      return 0;
    }
    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 operator+ or operator+= call form with the operand at args->argv[0].
*/
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(1==args->argc);
    rc = s2_value_to_buffer( args->engine, &buf, args->self );
    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_isascii( cwal_callback_args const * args, cwal_value **rv ){
  THIS_STRING;
  *rv = cwal_new_bool( cwal_string_is_ascii(self) );
  return 0;
}

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);
    int const asInt = (args->argc>1) && cwal_value_get_bool(args->argv[1]);
    if(pos >= (cwal_int_t)slen){
      *rv = cwal_value_undefined();
    }else{
      unsigned int cpoint = (unsigned int)-1;
      if( cwal_string_is_ascii(self) ){
        cpoint = (unsigned int)cstr[pos];
      }else{
        cwal_utf8_char_at( cstr, cstr + slen, pos, &cpoint);
      }
      if((unsigned int)-1 == cpoint){
        *rv = cwal_value_undefined();
      }else{
        if(asInt){
          *rv = cwal_new_integer(args->engine, (cwal_int_t)cpoint);
        }
        else {
          unsigned char buf[6] = {0,0,0,0,0,0};
          int const clen = cwal_utf8_char_to_cstr(cpoint, buf,
                                                  sizeof(buf));
          assert(clen<(int)sizeof(buf));
          assert(0 != clen);
          if(clen<1) *rv = cwal_value_undefined();
          else *rv = cwal_new_string_value(se->e, (char const *)buf,
                                           (cwal_size_t)clen);
        }
      }
    }
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

static int s2_cb_str_toupperlower( cwal_callback_args const * args,
                                   cwal_value **rv,
                                   char doUpper ){
  THIS_STRING;
  return cwal_string_case_fold( args->engine, self, rv, doUpper );
}

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


/**
   Internal helper for s2_cb_str_split(), which splits the given
   string into an array of all of its individual characters.
*/
static int s2_cb_str_split_chars(cwal_callback_args const * args,
                                 cwal_value **rv,
                                 unsigned char const * str,
                                 cwal_size_t slen,
                                 cwal_int_t limit ){
  int rc = 0;
  cwal_array * ar;
  unsigned char const * pos = str;
  unsigned char const * eof = str + slen;
  cwal_value * v;
  cwal_int_t count = 0;
  assert(str);
  ar = cwal_new_array(args->engine);
  if(!ar) return CWAL_RC_OOM;
#if 1
  rc = cwal_array_reserve(ar, slen /*in bytes, but it's close enough */);
#endif
  for( ; !rc && pos < eof; ){
    unsigned char const * cend = pos;
    cwal_utf8_read_char(pos, eof, &cend);
    if(!(cend-pos)) break /* ??? */;
    v = cwal_new_string_value(args->engine, (char const *)pos,
                              (cwal_size_t)(cend-pos));
    if(!v){
      rc = CWAL_RC_OOM;
      break;
    }
    cwal_value_ref(v);
    rc = cwal_array_append(ar, v);
    cwal_value_unref(v);
    pos = cend;
    if(limit > 0 && ++count==limit) break;
  }
  if(rc){
    cwal_array_unref(ar);
  }else{
    *rv = cwal_array_value(ar);
  }
  return rc;
  
}

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 non-empty string argument.");
  }else if(!sepLen){
    /* Split into individual characters */
    cwal_size_t slen = 0;
    unsigned char const * pos =
      (unsigned char const *)cwal_string_cstr2(self, &slen);
    cwal_int_t limit;
    assert(pos);
    limit = (args->argc>1)
      ? cwal_value_get_integer(args->argv[1])
      : 0;
    return s2_cb_str_split_chars(args, rv, pos, slen, limit);
  }else{
    int rc = 0;
    cwal_int_t count = 0;
    cwal_int_t limit;
    cwal_array * ar = NULL;
    cwal_size_t slen = 0;
    unsigned char const * pos =
      (unsigned char const *)cwal_string_cstr2(self, &slen);
    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;
    cwal_value_ref(cwal_array_value(ar));
    if(!slen || (sepLen>slen)){
      rc = cwal_array_append(ar, args->self);
    }
    else while( 1 ){
        if( (pos>=eof)
            || (0==memcmp(sep, pos, sepLen))
            ){
          cwal_size_t sz;
          char last = (pos>=eof);
          if(pos>eof) pos=eof;

          sz = pos-start;
          v = cwal_new_string_value(args->engine,
                                    (char const *)start,
                                    sz);
          cwal_value_ref(v);
          if(!v && sz){
            rc = CWAL_RC_OOM;
            goto end;
          }
          else rc = cwal_array_append(ar, v);
          cwal_value_unref(v);
          v = 0;
          if(rc) goto end;
          if(limit>0 && ++count==limit){
            pos = eof;
            last = 1;
          }

          if(last) goto end;
          /* ++count; */
          pos += sepLen;
          start = pos;
        }else{
          cwal_utf8_read_char( pos, eof, &pos );
        }
      }
    end:
    if(rc && ar){
      cwal_value_unref(cwal_array_value(ar));
    }else if(!rc){
      *rv = cwal_array_value(ar);
      cwal_value_unhand(*rv);
      if(!*rv) rc = CWAL_RC_OOM;
    }
    return rc;
  }
}

/**
   Script usage:

   var newString = aString.replace(needle, replacement [, limit = 0])

   TODO: a variant for Buffers, except that we need different return
   semantics (no returning self, unless we edit the buffer inline).
*/
static int s2_cb_str_replace( cwal_callback_args const * args,
                              cwal_value **rv ){
  cwal_size_t needleLen = 0;
  unsigned char const * needle;
  cwal_size_t replLen = 0;
  unsigned char const * repl;
  cwal_string * self;
  ARGS_SE;
  assert(se);
  self = cwal_value_string_part(args->engine, args->self);
  if(!self){
    return s2_cb_throw( args, CWAL_RC_TYPE,
                       "Expecting a string 'this'." );
  }
  needle = args->argc
    ? (unsigned char const *)cwal_value_get_cstr(args->argv[0], &needleLen)
    : NULL;
  repl = args->argc>1
    ? (unsigned char const *)cwal_value_get_cstr(args->argv[1], &replLen)
    : NULL;
  if(!needle || !repl){
    return s2_cb_throw( args, CWAL_RC_MISUSE,
                       "Expecting a non-empty string argument.");
  }else if(!needleLen){
    *rv = args->self;
    return 0;
  }else{
    int rc = 0;
    cwal_int_t matchCount = 0;
    cwal_size_t slen = 0;
    unsigned char const * pos =
      (unsigned char const *)cwal_string_cstr2(self, &slen);
    unsigned char const * eof = pos + slen;
    unsigned char const * start = pos;
    cwal_buffer * buf = &se->buffer;
    cwal_size_t const oldUsed = buf->used;
    cwal_int_t const limit = args->argc>2
      ? cwal_value_get_integer(args->argv[2])
      : 0;
    if(!slen || (needleLen>slen)){
      *rv = args->self;
      return 0;
    }
    while( 1 ){
      if( (pos>=eof)
          || (0==memcmp(needle, pos, needleLen))
          ){
        cwal_size_t sz;
        char last = (pos>=eof) ? 1 : 0;
        if(!last && matchCount++==limit && limit>0){
          pos = eof;
          last = 1;
        }
        else if(pos>eof) pos=eof;
        sz = pos-start;
        if(sz){
          /* Append pending unmatched part... */
          rc = cwal_buffer_append(args->engine, buf,
                                  start, sz);
        }
        if(!rc && pos<eof && replLen){
          /* Append replacement... */
          rc = cwal_buffer_append(args->engine, buf,
                                  repl, replLen);
        }
        if(rc || last) break;
        pos += needleLen;
        start = pos;
      }else{
        cwal_utf8_read_char( pos, eof, &pos );
      }
    }
    if(!rc){
      if(!matchCount){
        *rv = args->self;
      }else if(buf->used == oldUsed){
        /* replaced the whole string with nothing */
        *rv = cwal_new_string_value(args->engine, "", 0);
      }else{
        assert(buf->used > oldUsed);
        *rv = cwal_new_string_value(args->engine, (char const *)buf->mem,
                                    buf->used - oldUsed);
        if(!*rv) rc = CWAL_RC_OOM;
      }
    }
    buf->used = oldUsed;
    assert( buf->capacity > oldUsed );
    buf->mem[oldUsed] = 0;
    return rc;
  }
}

/**
   Script usage:

   var x = "haystack".indexOf("needle" [, startOffset=0])

   if(x<0) { ...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{
      cwal_size_t sLen = 0;
      char const * myStr = cwal_string_cstr2(self, &sLen);
      cwal_int_t i = 0;
      cwal_int_t offset;
      if(aLen>sLen){
        *rv = cwal_new_integer(args->engine, -1);
        return 0;
      }
      offset = (args->argc>1)
        ? cwal_value_get_integer(args->argv[1])
        : 0;
      if(!offset && aLen==sLen){
        int const rc = memcmp(myStr, arg, (size_t)sLen);
        *rv = rc
          ? cwal_new_integer(args->engine, -1)
          : cwal_new_integer(args->engine, 0);
        return 0;
      }else{
        i = cwal_utf8_indexof( myStr, sLen, offset, 
                               arg, aLen, 0 );
        *rv = cwal_new_integer(args->engine, i>=0 ? i : -1);
        return *rv ? 0 : CWAL_RC_OOM;
      }
    }
  }
}

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;
    THIS_CSTRING(1);
    offset = cwal_value_get_integer(args->argv[0]);
    len = (args->argc>1)
      ? cwal_value_get_integer(args->argv[1])
      : -1;
    if(len<0){
      len = (cwal_int_t)selfLen;
    }
    if(offset<0){
      offset = (cwal_int_t)selfLen + offset;
      if(offset < 0) offset = 0;
    }
    if(offset>=(cwal_int_t)selfLen){
      *rv = cwal_new_string_value(args->engine, NULL, 0);
      return 0;
    }
    else if((offset+len) > (cwal_int_t)selfLen){
      len = (cwal_int_t)selfLen - offset;
      /* assert(len < sLen); */
      if(len > (cwal_int_t)selfLen) len = (cwal_int_t)selfLen;
    }
    {
      /* Calculate the range/length based on the UTF8
         length. */
      unsigned char const * at = (unsigned char const *)self;
      unsigned char const * eof = at + selfLen;
      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>=0){
        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,
                                   (char const *)start, 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_engine_from_args(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 with rc=%s. "
                      "Probably(?) due to invalid UTF8 or an unknown "
                      "\\Uxxxxxxxx sequence.",
                      s2_rc_cstr(rc));
    }
    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,
                                   ((char const *)buf->mem+oldUsed),
                                   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,
                                 ((char const *)buf->mem+oldUsed), 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 = 0;
  cwal_size_t nameLen = 0, slen = 0;
  char const catchReturn = 1;
  char const * src;
  s2_engine * se = s2_engine_from_args(args);
  cwal_buffer bufSwap = cwal_buffer_empty;
  cwal_buffer * bufSelf = cwal_value_get_buffer(args->self);
  cwal_value * vars = 0;
  /*
    BUG (and fix) REMINDER:

    When this callback is applied to a Buffer, if that buffer's
    contents are modified while this call is active, results are
    undefined. As a partial solution, we move the buffer's contents
    out of the way before evaluation, swapping it back in before
    returning.  That disallows the (corner case) possibility of
    recursion, but it gives us predictable/safe results.
  */
  assert(se);
  src = cwal_value_get_cstr(args->self, &slen)
    /* Corner case reminder: that won't work if args->self
       _derives_ from a Buffer */;
  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;
  }

  if(2==args->argc){
    /*
      Accept (string,object) or (object,string)
    */
    if(cwal_props_can(args->argv[0])){
      vars = args->argv[0];
      fname = cwal_value_get_cstr(args->argv[1], &nameLen);
    }else{
      fname = cwal_value_get_cstr(args->argv[0], &nameLen);
      if(cwal_props_can(args->argv[1])) vars = args->argv[1];
    }
  }else if(args->argc){
    /* Accept (string) or (object) */
    fname = cwal_value_get_cstr(args->argv[0], &nameLen);
    if(!fname && cwal_props_can(args->argv[0])){
      vars = args->argv[0];
    }
  }
  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

  if(vars){
    rc = cwal_scope_import_properties( args->scope, vars );
    if(rc){
      rc = s2_cb_throw(args, rc, "Import of vars for evaluation failed.");
      goto end;
    }
  }
  
  if(bufSelf){
    /* Move bufSelf->mem out of the way in case this eval modifies
       bufSelf, because we'd otherwise have undefined behaviour.
       If bufSelf is modified by the eval, any modifications to it
       are discarded after the eval. That's probably the safest option
       for how to handle that corner case.
    */
    s2_buffer_swap(bufSelf, &bufSwap);
  }
  *rv = cwal_value_undefined()
    /* make sure it's populated to avoid an assertion failure below */;
  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.
    */;
  end:
  if(bufSelf && bufSwap.mem){
    /* restore bufSelf's memory */
    s2_buffer_swap(bufSelf, &bufSwap);
    cwal_buffer_clear(args->engine, &bufSwap);
  }
  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 if(!rc){
    assert((*rv
           ? ((cwal_value_is_builtin(*rv) || cwal_value_scope(*rv)))
            : 1)
           || "lifetime check failed! cwal memory mismanagement!");
    if(!*rv) *rv = cwal_value_undefined();
  }
  return rc;
}


cwal_value * s2_prototype_string( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    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")); */

    {
      cwal_value * v;
      const s2_func_def funcs[] = {
      S2_FUNC2("applyFormat", s2_cb_str_apply_format),
      S2_FUNC2("byteAt", s2_cb_str_byte_at),
      S2_FUNC2("charAt", s2_cb_str_char_at),
      S2_FUNC2("compare", s2_cb_value_compare),
      S2_FUNC2("concat", s2_cb_str_concat),
      S2_FUNC2("evalContents", s2_cb_strish_eval_contents),
      S2_FUNC2("indexOf", s2_cb_str_indexof),
      S2_FUNC2("isAscii", s2_cb_str_isascii),
      S2_FUNC2("length", s2_cb_str_length_utf8),
      S2_FUNC2("lengthBytes", s2_cb_str_length),
      S2_FUNC2("lengthUtf8", s2_cb_str_length_utf8),
      S2_FUNC2("replace", s2_cb_str_replace),
      S2_FUNC2("split", s2_cb_str_split),
      S2_FUNC2("substr", s2_cb_str_substr),
      S2_FUNC2("toLower", s2_cb_str_tolower),
      S2_FUNC2("toJSONString", s2_cb_this_to_json_token),
      S2_FUNC2("toString", s2_cb_str_to_string),
      S2_FUNC2("toUpper", s2_cb_str_toupper),
      S2_FUNC2("trim", s2_cb_str_trim_both),
      S2_FUNC2("trimLeft", s2_cb_str_trim_left),
      S2_FUNC2("trimRight", s2_cb_str_trim_right),
      S2_FUNC2("unescape", s2_cb_str_unescape_c),
      s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0 );
      if(rc) goto end;
      /* Create a Function instance for s2_cb_str_op_concat and
         install it manually, to avoid duplicating 2 cwal_function
         wrappers for it via s2_install_functions(). */
      v = cwal_new_function_value(se->e, s2_cb_str_op_concat, 0, 0, 0);
      if(!v){ rc = CWAL_RC_OOM; goto end; }
      cwal_value_ref(v);
      rc = cwal_prop_set_with_flags(proto, "operator+", 9, v,
                                    CWAL_VAR_F_CONST);
      if(!rc){
        rc = cwal_prop_set_with_flags(proto, "operator+=", 10, v,
                                      CWAL_VAR_F_CONST);
      }
      cwal_value_unref(v);
      if(rc) goto end;
    }

    end:
    return rc ? NULL : proto;
}

/**
   Buffer slice([offset=0 [,count=0]])

   where count==0 means "to the end".
*/
static int s2_cb_buffer_slice( cwal_callback_args const * args, cwal_value **rv ){
  cwal_int_t offset = 0, count = -1;
  cwal_buffer * bcp = NULL;
  cwal_value * bv = NULL;
  int rc;
  THIS_BUFFER;
  offset = args->argc
    ? cwal_value_get_integer(args->argv[0])
    : 0;
  count = args->argc>1
    ? cwal_value_get_integer(args->argv[1])
    : -1;
  if(offset<0){
    return s2_throw(se, CWAL_RC_RANGE,
                    "Slice offset must be 0 or higher.");
  }
  if(offset > (cwal_int_t)self->used){
      offset = (cwal_int_t)self->used;
  }
  if(count < 0) count = self->used;
  if((cwal_size_t)(offset + count) >= self->used){
    count = self->used - offset;
  }
  bcp = cwal_new_buffer(args->engine, count ? count+1 : 0 );
  if(!bcp) return CWAL_RC_OOM;
  bv = cwal_buffer_value(bcp);
  cwal_value_ref(bv);
  rc = cwal_buffer_append(args->engine, bcp,
                          self->mem + offset, (cwal_size_t)count);
  if(rc) cwal_value_unref(bv);
  else {
    cwal_value_unhand(bv);
    *rv = bv;
  }
  return rc;
}

/**
   Script usage:

   assert aBuffer === aBuffer.replace(needle, replacement [, limit = 0])

   (needle, replacement) must be (string, string) or (byte, byte),
   where byte means integer in the range 0..255. Potential TODO:
   change 'byte' to mean "UTF codepoint". That's currently easier to do
   in script code: b.replace("...", 0x00a9.toChar())

   Potential TODO: replace(buffer needle,buffer replacement), which
   replaces arbitrary byte-range matches (as opposed to UTF8 chars).
*/
static int s2_cb_buffer_replace( cwal_callback_args const * args,
                                 cwal_value **rv ){
  cwal_size_t needleLen = 0;
  unsigned char const * needle;
  cwal_size_t replLen = 0;
  unsigned char const * repl;
  cwal_buffer * self;
  static const char * usageError =
    "Expecting (string,string) or (int, int) arguments.";
  ARGS_SE;
  assert(se);
  self = cwal_value_buffer_part(args->engine, args->self);
  if(!self){
    if(se){/*avoid unused param warning*/}
    return s2_cb_throw( args, CWAL_RC_TYPE,
                       "Expecting a Buffer 'this'." );
  }
  needle = args->argc
    ? (unsigned char const *)cwal_value_get_cstr(args->argv[0],
                                                 &needleLen)
    : NULL;
  repl = args->argc>1
    ? (unsigned char const *)cwal_value_get_cstr(args->argv[1],
                                                 &replLen)
    : NULL;
  if(!needle && args->argc && cwal_value_is_integer(args->argv[0])){
    /* replace(byte, byte) */
    cwal_int_t const needleByte =
      cwal_value_get_integer(args->argv[0]);
    cwal_int_t const replByte =
      (args->argc>1 && cwal_value_is_integer(args->argv[1]))
      ? (cwal_value_get_integer(args->argv[1]))
      : -1;
    if(replByte<0){
      return s2_cb_throw( args, CWAL_RC_MISUSE, "%s", usageError);
    }else if((needleByte & ~0xFF) || (replByte & ~0xFF)) {
      return s2_cb_throw( args, CWAL_RC_RANGE,
                         "Byte values must be in the range [0,255]." );
    }else{
      cwal_int_t const limit = args->argc>2
        ? cwal_value_get_integer(args->argv[2])
        : 0;
      unsigned char const bN = (unsigned char)(needleByte & 0xFF);
      unsigned char const bR = (unsigned char)(replByte & 0xFF);
      int const rc =
        cwal_buffer_replace_byte(args->engine, self, bN, bR,
                                 (cwal_size_t)(limit>=0 ? (cwal_size_t)limit : 0U),
                                 NULL);
      assert(!rc && "There are no error cases here.");
      if(rc){/*avoid unused param warning*/}
      *rv = args->self;
      return 0;
    }    
  }else if(!needle || !repl){
    return s2_cb_throw( args, CWAL_RC_MISUSE, "%s", usageError);
  }else if(!needleLen){
    return s2_cb_throw( args, CWAL_RC_RANGE,
                        "Needle length must be >0.");
  }else if(!self->used || (needleLen>self->used)){
    /* Nothing to do. */
    *rv = args->self;
    return 0;
  }else{
    int rc = 0;
    /* replace(needle, replacement) ... */
    cwal_int_t const limit = args->argc>2
      ? cwal_value_get_integer(args->argv[2])
      : 0;
    if(!needle || !repl){
      rc = s2_cb_throw( args, CWAL_RC_MISUSE, "%s", usageError);
    }else{
      rc = cwal_buffer_replace_str(args->engine, self,
                                   needle, needleLen,
                                   repl, replLen,
                                   (limit>=0 ? (cwal_size_t)limit : 0U),
                                   NULL);
      if(rc){
        rc = s2_cb_throw(args, rc, "cwal_buffer_replace_str() failed "
                         "with code %d (%s).", rc, s2_rc_cstr(rc));
      }else{
        *rv = args->self;
      }
    }
    return rc;
  }
}



/**
   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,
                                 "%s argument must be "
                                 "non-negative integer.",
                                 isCapacity ? "capacity" : "length");
    }
    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));
      }
    }
    if(!isCapacity) 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_is_empty( cwal_callback_args const * args, cwal_value **rv ){
  THIS_BUFFER;
  *rv = cwal_new_bool(self->used>0 ? 0 : 1);
  return 0;
}

static int s2_cb_buffer_length_utf8( cwal_callback_args const * args, cwal_value **rv ){
  cwal_size_t ulen;
  THIS_BUFFER;
  ulen = cwal_strlen_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;
  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.");
  }
  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 operator call form.
*/
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 = 0, argc = args->argc;
    assert(1==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, begin,
                               (cwal_size_t)(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.");
    }else 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_buffer_part(args->engine, args->self); 
  ARGS_SE;
  assert(se);
  if( (rc = s2_cb_disable_check(args, S2_DISABLE_FS_READ)) ) return rc;
  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;
  rc = s2_cb_disable_check(args, S2_DISABLE_FS_WRITE);
  if(rc) return rc;
  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, s2_errno_to_cwal_rc(errno, 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, s2_errno_to_cwal_rc(errno, CWAL_RC_IO),
                      "Could not open file [%.*s] "
                      "in %s mode.",
                      (int)nameLen, fname,
                      append ? "append" : "truncate/write");
      }
    }else{
      if(self->used &&
         1 != fwrite(self->mem, self->used, 1, f)){
        rc = s2_throw(se, errno
                      ? s2_errno_to_cwal_rc(errno, CWAL_RC_IO)
                      : 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 = args->self;
    return 0;
  }else{
    cwal_int_t offset;
    cwal_int_t len;
    cwal_size_t const limit = 1 ? self->used : self->capacity;
    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)limit <= offset)
        ? 0
        : ((cwal_int_t)limit - offset);
    }
    if( (len>0) && ((cwal_size_t)offset < limit) ) {
      if((cwal_size_t)(offset+len) > limit){
        len = (cwal_int_t)(limit - (cwal_size_t)offset);
      }
      /*MARKER(("Filling %d byte(s) at offset %d to 0x%02x\n", (int)len, (int)offset, (int)byte));*/
      if(len>0){
        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
  }
}

/*
** Combined impl for Buffer.compress() and Buffer.uncompress().  If
** doCompress is true, it is the former, else the latter.
*/
static int s2_cb_buffer_press( cwal_callback_args const * args,
                               cwal_value **rv, char doCompress ){
#if !S2_ENABLE_ZLIKE
  if(rv || doCompress){/*avoid unused param warning*/}
  return s2_cb_throw(args, CWAL_RC_UNSUPPORTED,
                    "Compression support is not enabled in this build.");
#else
  int rc;
  cwal_value * arg = args->argc ? args->argv[0] : args->self;
  cwal_buffer * buf = cwal_value_buffer_part(args->engine, arg);
  char const isPressed = buf ? s2_buffer_is_compressed(buf) : 0;
  if(!buf){
    return s2_cb_throw(args, CWAL_RC_TYPE,
                      "%s is-not-a Buffer.",
                      args->argc ? "Argument" : "'this'");
  }
  if(doCompress && isPressed){
    rc = 0;
  }else if(!doCompress && !isPressed){
    rc = 0;
  }else{
    rc = doCompress
      ? s2_buffer_compress( args->engine, buf, buf )
      : s2_buffer_uncompress( args->engine, buf, buf );
  }
  if(rc && CWAL_RC_OOM!=rc){
    rc = s2_cb_throw(args, rc,
                    "Buffer %s failed with code %d (%s).",
                    doCompress ? "compression" : "decompression",
                    rc, s2_rc_cstr(rc));
  }
  if(!rc) *rv = arg;
  return rc;
#endif
}

/**
   Script usage:

   bufferInstance.compress()

   returns 'this'.

   compress(buffer)

   returns the given buffer.

   Throws if the arg/this is not/does not derive a cwal_buffer.
*/
static int s2_cb_buffer_compress( cwal_callback_args const * args,
                                  cwal_value **rv ){
  return s2_cb_buffer_press(args, rv, 1);
}

/**
   Script usage:

   bufferInstance.uncompress()

   returns 'this'.

   compress(buffer)

   returns the given buffer.

   Throws if the arg/this is not/does not derive a cwal_buffer.
*/
static int s2_cb_buffer_uncompress( cwal_callback_args const * args,
                                    cwal_value **rv ){
  return s2_cb_buffer_press(args, rv, 0);
}

/**
   Script signature:

   boolean bufferInstance.isCompressed([buffer])

   If passed an argument, it evaluates that argument,
   else it evaluates args->self.
*/
static int s2_cb_buffer_is_compressed( cwal_callback_args const * args,
                                       cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine,
                                             args->argc
                                             ? args->argv[0]
                                             : args->self);
  if(buf){
    *rv = cwal_new_bool( s2_buffer_is_compressed(buf) );
    return 0;
  }else{
    return s2_cb_throw(args, CWAL_RC_TYPE,
                      "%s is-not-a Buffer.",
                      args->argc ? "Argument" : "'this'");
  }
}

static int s2_cb_buffer_uncompressed_size( cwal_callback_args const * args,
                                           cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine,
                                             args->argc
                                             ? args->argv[0]
                                             : args->self);
  if(!buf){
    return s2_cb_throw(args, CWAL_RC_TYPE,
                      "%s is-not-a Buffer.",
                      args->argc ? "Argument" : "'this'");
  }else{
    uint32_t const sz = s2_buffer_uncompressed_size(buf);
    if((uint32_t)-1 == sz){
      *rv = cwal_value_undefined();
      return 0;
    }else{
      *rv = cwal_new_integer(args->engine, (cwal_int_t)sz);
      return *rv ? 0 : CWAL_RC_OOM;
    }
  }
}



cwal_value * s2_prototype_buffer( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_BUFFER );
    if(proto
       || !s2_prototype_object(se) /* timing hack */
       ) return proto;
#define DERIVE_FROM_HASH 0
#if DERIVE_FROM_HASH
    /* the unit tests actually pass with this (after one
       bug was fixed in the s2_get() family of funcs),
       but the implications on the prototype chain currently
       disturb me. */
    proto = cwal_new_hash_value(se->e, cwal_first_1000_primes()[13]
                                /* a bit higher than the property
                                   count we will install */ );
#else
    proto = cwal_new_object_value(se->e);
#endif
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
#if DERIVE_FROM_HASH
    s2_hash_dot_like_object( proto, 1 );
#endif
    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")); */

    
    {
      cwal_value * v = 0;
#if S2_ENABLE_ZLIKE
      if(S2_INTERNAL_MINIZ){
        v = cwal_new_string_value(se->e, "miniz", 5);
      }else if(S2_ENABLE_ZLIB){
        v = cwal_new_string_value(se->e, "zlib", 4);
      }else{
        assert(!"Not possible unless the internal #ifs are wrong.");
        v = cwal_value_undefined();
      }
      if(!v) return NULL /* proto is already stashed
                            at the cwal level, not leaked. */;
#endif
      cwal_value_ref(v);
      rc = s2_set_with_flags(se, proto, "compression", 11,
                              v ? v : cwal_value_false(),
                              CWAL_VAR_F_CONST);
      cwal_value_unref(v);
      v = 0;
      if(rc) goto end;
#if DERIVE_FROM_HASH
      /* __typename doesn't resolve via hash entries b/c the callback
         for it doesn't currently provide enough state for us to do
         that. So we have to make sure it's in an Object-level
         property...
      */
      v = cwal_new_string_value(se->e, "buffer", 6);
      if(!v){
        rc = CWAL_RC_OOM;
        goto end;
      }
      cwal_value_ref(v);
      rc = cwal_prop_set_v(proto, se->cache.keyTypename, v );
      cwal_value_unref(v);
      if(rc) goto end;
#endif
    }
#undef DERIVE_FROM_HASH
    
    {
      const s2_func_def funcs[] = {
        S2_FUNC2("append", s2_cb_buffer_append),
        S2_FUNC2("appendJSON", s2_cb_this_to_json_token),
        S2_FUNC2("appendf", s2_cb_buffer_appendf),
        S2_FUNC2("byteAt", s2_cb_buffer_byte_at),
        S2_FUNC2("capacity", s2_cb_buffer_length_c),
        S2_FUNC2("evalContents", s2_cb_strish_eval_contents),
        S2_FUNC2("fill", s2_cb_buffer_fill),
        S2_FUNC2("isEmpty", s2_cb_buffer_is_empty),
        S2_FUNC2("length", s2_cb_buffer_length_u),
        S2_FUNC2("lengthUtf8", s2_cb_buffer_length_utf8),
        S2_FUNC2("operator<<", s2_cb_buffer_op_append),
        S2_FUNC2("readFile", s2_cb_buffer_file_read),
        S2_FUNC2("replace", s2_cb_buffer_replace),
        S2_FUNC2("reserve", s2_cb_buffer_length_c) /* deprecated name for capacity(N) */,
        S2_FUNC2("reset", s2_cb_buffer_reset),
        S2_FUNC2("resize", s2_cb_buffer_resize),
        S2_FUNC2("slice", s2_cb_buffer_slice),
        S2_FUNC2("substr", s2_cb_str_substr),
        S2_FUNC2("takeString", s2_cb_buffer_take_string),
        S2_FUNC2("toString", s2_cb_buffer_to_string),
        S2_FUNC2("writeFile", s2_cb_buffer_file_write),
        S2_FUNC2("new", s2_cb_construct_buffer),
        S2_FUNC2("isCompressed", s2_cb_buffer_is_compressed),
        S2_FUNC2("compress", s2_cb_buffer_compress),
        S2_FUNC2("uncompress", s2_cb_buffer_uncompress),
        S2_FUNC2("uncompressedSize", s2_cb_buffer_uncompressed_size),
        s2_func_def_empty_m
      };
      rc = s2_install_functions(se, proto, funcs, 0);
      if(rc) goto end;
      else {
        cwal_value * fv = 0;
        s2_get(se, proto, "new", 3, &fv);
        assert(fv && "we JUST put this in there!");
        rc = s2_ctor_method_set( se, proto,
                                 cwal_value_get_function(fv) );
      }
    }

    end:
    return rc
      ? NULL /* remember: proto is stashed at this point, so no leak. */
      : proto;
}


#undef MARKER
#undef ARGS_SE
#undef THIS_STRING
#undef THIS_BUFFER
#undef THIS_CSTRING
#undef S2_ENABLE_ZLIKE
/* 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 !defined(S2_T10N_COMPILE)
/* 202008: EXPERIMENTAL and not at all functional - just bits and
   pieces are in place so far. */
#  define S2_T10N_COMPILE 0
#endif
#if S2_T10N_COMPILE
#  define COMPILED_MEMBER(S2_PTOKER) ((s2_cptoker*)(S2_PTOKER)->compiled)
#  define COMPILED_MEMBER_C(S2_PTOKER) ((s2_cptoker const*)(S2_PTOKER)->compiled)
#  define COMPILED_DECL(S2_PTOKER) s2_cptoker * compiled = COMPILED_MEMBER(S2_PTOKER)
#  define COMPILED_DECL_C(S2_PTOKER) s2_cptoker const * compiled = COMPILED_MEMBER_C(S2_PTOKER)
#endif

#if 1
#include <stdio.h>
#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:\t",__FILE__,__LINE__); 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;
const s2_path_toker s2_path_toker_empty = s2_path_toker_empty_m;

/**
   S2_T10N_LCCACHE: set to 0 to disable the line-counting cache. It
   appears to work, saving more than half of the CPU cycles in the s2
   unit tests, but is not yet battle-hardened (or even optimized).

   Sidebar: those tests are essentially a pathological case, as they
   intentionally throw many exceptions in their testing duties,
   exceptions being a major culprit in the line-counting rabbit hole,
   along with function definitions.
*/
#define S2_T10N_LCCACHE 1
#if S2_T10N_LCCACHE
#  define S2_T10N_LCCACHE_SLOT 0
/*
  S2_T10N_LCCACHE_SLOT == whether or not to use a predictable cache
  slot placement algorithm which always places the same pos in the
  same slot and orders slots by position.

  Some test metrics (20191229)...

  "Ir" values as reported by callgrind (in debug builds):

  cache size 20: 1=328M w/ 457 hits, 0=325M w/ 458 hits
  cache size 13: 1=330M w/ 457 hits, 0=324.9M w/ 458 hits
  cache size 10: 1=331M w/ 457 hits, 0=324.8M w/ 458 hits
  cache size 9:  1=331M w/ 457 hits, 0=324.7M w/ 458 hits

  (Sidebar: those Ir values drop by about a 3rd in non-debug builds:
  ~330M --> ~195M.)

  Summary: this approach doesn't perform quite as well, presumably
  because closely-located entries take the same slot, and thus can't
  benefit from potentially closer matches, especially on a large
  tokenizer (where each slot spans a larger space).

  Maybe if we spread out the entries, instead of ordering them
  strictly by position (use modulo instead of division to place
  them)...

  cache size 20: 1=345M w/ 447 hits
  cache size 13: 1=345M w/ 451 hits
  cache size 10: 1=340M w/ 446 hits

  (Noting that the fallback handler when a hit is not found on the
  first try is/was not explicitly optimized for this case: it would
  essentially need to degrade to the same loop the circular queue
  does.)

  Summary: simply using a small circular cache, discarding older
  entries as we loop around, performs better in every regard.
*/
static const int s2_ptoker_lccache_size = sizeof(s2_ptoker_empty._lcCache.lines)
  /sizeof(s2_ptoker_empty._lcCache.lines[0]);
#else
#  define S2_T10N_LCCACHE_SLOT 0
#endif

#if 0 && S2_T10N_LCCACHE
#  define LCMARKER MARKER
#else
#  define LCMARKER(PFEXP) (void)0
#endif

#if S2_T10N_LCCACHE && S2_T10N_LCCACHE_SLOT
#  if 1
/* Only a miniscule performance improvement. */
#    define s2_ptoker_lc_slot(PT,POS) (PT)->_lcCache.slotSize ? (int)(POS - (PT)->begin) / (PT)->_lcCache.slotSize : 0
#  else
/*inline (not C89)*/ static int s2_ptoker_lc_slot(s2_ptoker const * pt, char const * pos){
  assert(pos >= pt->begin);
  assert(pos < pt->end);
#    if 1
  return pt->_lcCache.slotSize ? (int)(pos - pt->begin) / pt->_lcCache.slotSize : 0;
#    else
  /* Performs worse, but the fallback lookup is not optimized for this case. */
  return pt->_lcCache.slotSize ? (int)(pos - pt->begin) % s2_ptoker_lccache_size : 0;
#    endif
}
#  endif
#endif

#if S2_T10N_LCCACHE
static s2_ptoker_lccache_entry const *
s2_ptoker_lc_search( s2_ptoker const * pt, char const * pos ){
  if(pos>=pt->begin && pos<pt->end){
    s2_ptoker_lccache const * const lcc = &pt->_lcCache;
#if S2_T10N_LCCACHE_SLOT
    int slot = s2_ptoker_lc_slot(pt, pos);
    assert(slot < s2_ptoker_lccache_size);
    while( slot >= 0 ){
      s2_ptoker_lccache_entry const * const rc
        = (s2_ptoker_lccache_entry const *)&lcc->lines[slot];
      if(rc->pos && rc->pos <= pos){
        LCMARKER(("%p Search slot %d, possible hit for %d, %d\n",
                  (void const *)pt, slot, rc->line, rc->col));
        return rc;
      }
      --slot;
    }
    return 0;
#else
    s2_ptoker_lccache_entry const * rc = 0;
    int i;
    for( i = 0; i < s2_ptoker_lccache_size; ++i ){
      s2_ptoker_lccache_entry const * const m = (s2_ptoker_lccache_entry const *)&lcc->lines[i];
      if(!m->pos) break;
      assert(m->pos>=pt->begin && m->pos<pt->end);
      if(m->pos<=pos){
        if(!rc || rc->pos<m->pos){
          rc = m;
          LCMARKER(("%p Search slot %d, possible hit for %d, %d\n",
                    (void const *)pt, i, rc->line, rc->col));
          if(m->pos==pos) break;
        }
      }
    }
    return rc;
#endif
  }else{
    return 0;
  }
}
#else
#  define s2_ptoker_lc_search(X,Y) 0
#endif

#if S2_T10N_LCCACHE
static void s2_ptoker_lc_cache( s2_ptoker const * pt, char const * pos, int line, int col ){
  if(pos>=pt->begin && pos<pt->end){
    s2_ptoker_lccache * lcc = (s2_ptoker_lccache *)&pt->_lcCache;
    static int once = 0;
    s2_ptoker_lccache_entry * m;
#if S2_T10N_LCCACHE_SLOT
    int const slot = s2_ptoker_lc_slot(pt, pos);
    assert(slot < s2_ptoker_lccache_size);
    m = (s2_ptoker_lccache_entry *)&lcc->lines[slot];
    /*if(m->pos && pos>=m->pos) return; wow, this adds 37M instructions
      in these tests!*/
#else
    if(lcc->cursor==s2_ptoker_lccache_size){
      lcc->cursor = 0;
      /*
        For now, rather than do something clever, we'll just wrap
        around, under the thinking that we're far more likely to hit
        the counter for more recently-visited positions than older
        ones.

        With a large enough cache size (60 entries), when tested against
        the current (20191228) s2 amalgamated unit tests, this literally,...

        1) Cuts the wall-clock time execution of the tests by *HALF*.

        2) Reduces line-counting from the single most CPU-intensive
        operation (~56% of CPU instructions) to something like 1.8% of
        the instructions.

        3) Cuts the overall number of CPU instructions by more than
        half: from ~730-ish million to ~330M (values reported by
        callgrind).

        What i'd *like* to do is divvy the cache up into slots and
        pack cache entries into slots based on their position within
        the tokenizer, so that we can quickly (O(1)), based on the
        search pos, figure out which slot to use, but my brain
        apparently isn't yet big enough to get that math right.
        Update: tried that but it provides far fewer cache hits and
        (curiously) more total CPU instructions (382M vs 324M in the
        same test run. The problem seems to be that the grouping of
        cache slots can easily lead to nearby neighbors not being
        considered as near hits.
      */
    }
    m = (s2_ptoker_lccache_entry *)&lcc->lines[lcc->cursor];
#endif
    if(!once++){
      LCMARKER(("sizeof(s2_ptoker_lccache)=%d\n",(int)sizeof(s2_ptoker_lccache)));
    }
    m->line = line;
    m->col = col;
    m->pos = pos;
#if S2_T10N_LCCACHE_SLOT
    LCMARKER(("%p Cached slot %d @ %d, %d\n", (void const *)pt, slot, m->line, m->col));
#else
    LCMARKER(("%p Cached slot %d @ %d, %d\n", (void const *)pt, lcc->cursor, m->line, m->col));
    ++lcc->cursor;
#endif
  }
}
#endif

int s2_ptoker_init_v2( cwal_engine * e, s2_ptoker * t, char const * src, cwal_int_t len,
                       uint32_t flags ){
  if(!t||!src) return CWAL_RC_MISUSE;
  else{
    *t = s2_ptoker_empty;
    t->e = e;
    t->flags = flags;
    /*memset(&t->_lcCache, 0, sizeof(t->_lcCache));*/
    if(len<0) len = cwal_strlen(src);
    t->begin = src;
    t->end = src+len;
    memset(&t->_lcCache, 0, sizeof(t->_lcCache));
    s2_ptoker_reset(t);
#if S2_T10N_LCCACHE_SLOT
    {
      int ptl = (int)len;
      int const cs = s2_ptoker_lccache_size;
      assert(0==t->_lcCache.lines[cs-1].pos);
      t->_lcCache.slotSize = (ptl + (ptl / cs)) / cs + 1;
      LCMARKER(("%p slot size = %d\n", (void const *)t, t->_lcCache.slotSize));
    }
#endif
    return 0;
  }
}

int s2_ptoker_init( s2_ptoker * t, char const * src, cwal_int_t len ){
  return s2_ptoker_init_v2( NULL, t, src, len, 0 );
}

void s2_ptoker_reset( s2_ptoker * t ){
  t->_pbToken = t->_errToken = t->token = s2_ptoken_empty;
  s2_ptoken_begin_set(&t->token, s2_ptoker_begin(t))
    /* begin w/o an end set is our internal signal that we're
       just starting off tokenization */;
  t->currentLine = 1;
  t->currentCol = 0;
}

#if S2_T10N_COMPILE
/** Returns true if pt seems to own pt->compiled. */
static int s2_ptoker_owns_compiled(s2_ptoker const * const pt){
  if(!pt->parent || !pt->parent->compiled) return 1;
  else return pt->parent->compiled != pt->compiled;
}
#endif

void s2_ptoker_finalize( s2_ptoker * pt ){
#if S2_T10N_COMPILE
  COMPILED_DECL(pt);
  assert(pt->begin ? !!pt->e : 1);
  if(compiled && s2_ptoker_owns_compiled(pt)){
    s2_cptoker_free(compiled);
  }
#endif
  *pt = s2_ptoker_empty;
}

int s2_ptoker_sub_from_token( s2_ptoker const * parent,
                              s2_ptoken const * tok,
                              s2_ptoker * dest ){
  char const * begin;
  char const * end;
  int rc;
#if 0
  /* 20200107: this breaks stuff */
  if(!s2_ttype_is_group(tok->ttype)){
    return CWAL_RC_TYPE;
  }
#endif
  if(s2_ptoken_adjbegin(tok)){
    begin = s2_ptoken_adjbegin(tok);
    end = s2_ptoken_adjend(tok);
  }else{
    begin = s2_ptoken_begin(tok);
    end = s2_ptoken_end(tok);
  }
  if(!begin || (begin>end)) return CWAL_RC_RANGE;
  rc = s2_ptoker_init_v2( parent->e, dest, begin,
                          (cwal_int_t)(end - begin), 0 );
  if(!rc){
    dest->parent = parent;
    assert(parent->e == dest->e);
  }
  return rc;
}

int s2_ptoker_sub_from_toker( s2_ptoker const * parent, s2_ptoker * sub ){
  int const rc = s2_ptoker_sub_from_token( parent, &parent->token, sub );
  if(!rc){
    assert(sub->parent == parent);
    assert(sub->e == parent->e);
    /* There are multiple TODOs here if/when we add compiled token
       chains. */
  }
  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 = cwal_strlen(t->name);
  return t ? t->name : 0;
}

char const * s2_ptoker_err_pos_first( s2_ptoker const * t,
                                      s2_ptoker const ** foundIn){
  while(t && !s2_ptoken_begin(&t->_errToken)){
    t = t->parent;
  }
  if(t && foundIn) *foundIn = t;
  return t ? s2_ptoken_begin(&t->_errToken) : 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:
    case S2_T_SquigglyBlock:
      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: A //-style comment implies a newline, so
       we might consider treating it as one (and consuming a trailing
       newline, if any, as part of the token).
    */
      return ttype;
    default:
      return 0;
  }
}

int s2_ttype_is_eox( int ttype ){
  switch(ttype){
    case S2_T_EOF:
    case S2_T_EOX:
    case S2_T_Semicolon:
      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_object_keyable( int ttype ){
  switch(ttype){
    case S2_T_LiteralIntBin:
    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_ttype_is_int( int ttype ){
  switch(ttype){
    case S2_T_LiteralIntBin:
    case S2_T_LiteralIntDec:
    case S2_T_LiteralIntOct:
    case S2_T_LiteralIntHex:
      return ttype;
    default:
      return 0;
  }
}

int s2_ttype_is_number( int ttype ){
  switch(ttype){
    case S2_T_LiteralIntBin:
    case S2_T_LiteralIntDec:
    case S2_T_LiteralIntOct:
    case S2_T_LiteralIntHex:
    case S2_T_LiteralDouble:
      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_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_OpArrow2: */
    case S2_T_OpOr:
    case S2_T_OpOr3:
    case S2_T_OpElvis:
    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_OpNegateBitwise:
    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);
    }
}

void s2_ptoker_putback_set( s2_ptoker * const st,
                            s2_ptoken const * const tok ){
  st->_pbToken = *tok;
}

s2_ptoken const * s2_ptoker_putback_get( s2_ptoker const * const st ){
  return &st->_pbToken;
}

char s2_ptoker_putback( s2_ptoker * st ){
  char const rc = s2_ptoken_begin(&st->_pbToken) ? 1 : 0;
  if(rc){
    st->token = st->_pbToken;
    st->_pbToken = s2_ptoken_empty;
    /*s2_ptoker_token_set(st, &st->_pbToken);*/
  }
  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 const rc = s2_ptoker_next_token( st );
  *tgt = st->token;
  st->token = oldT;
  st->_nextToken = *tgt;
  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)){
  }
  st->_nextToken = s2_ptoken_empty;
  *tgt = st->token;
  s2_ptoker_token_set(st, &oldT);
  /*if(!rc) st->_nextToken = *tgt;*/
  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 * const st, s2_ptoken const * 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;
  st->_nextToken = s2_ptoken_empty;
}

#if 0
s2_ptoken const * s2_ptoker_token_get( s2_ptoker const * st ){
  return &st->token;
}
int s2_ptoker_ttype( s2_ptoker const * st ){
  return st->token.ttype;
}
#endif

void s2_ptoker_next_token_set( s2_ptoker * pt, s2_ptoken const * tk ){
  pt->_nextToken = *tk;
}

int s2_ptoker_next_token( s2_ptoker * t ){
  int rc = 0;
  s2_ptoken * pt = &t->token;
  char const * curpos = s2_ptoken_end(&t->token)
    ? s2_ptoken_end(&t->token) /* for the 2nd and subsequent calls */
    : s2_ptoken_begin(&t->token) /* for the first run through this function */;
  /**
    TODO, but it'd be a lot of work: rewrite this to use a uint32_t
    current character, rather than (char const *), to handle
    UTF better from here.
  */
  assert(curpos);
  assert(curpos >= s2_ptoker_begin(t));
  assert(curpos <= s2_ptoker_end(t));
  t->_pbToken = t->token;
  s2_ptoken_adjbegin_set(&t->token, 0);
  s2_ptoken_adjend_set(&t->token, 0);
  t->errMsg = 0;
  t->_errToken = s2_ptoken_empty;
  if(s2_ptoken_begin(&t->_nextToken)){
    /* Reminder to self: certain s2 logic now depends on _nextToken in
       order to function, so we cannot simply rip it out. */
#if 1
    /*
      Optimization: use _nextToken if it is set. Some lookahead
      ops set this when they end up not consuming the looked-ahead
      (lookaheaded?) token.
    */
#if 0
    {
      /* This actually saves right at 11.6k tokens in the ~3.3k code
         lines of the s2 unit test suite (as of 202001). */
      static int counter = 0;
      MARKER(("Using t->_nextToken for token type %s (%d)\n",
              s2_ttype_cstr(t->_nextToken.ttype), ++counter));
    }
#endif
    t->token = t->_nextToken;
    t->_nextToken = s2_ptoken_empty;
    return 0;
#else
    /* Just to compare results in cachegrind... */
    curpos = s2_ptoken_begin(&t->_nextToken);
#endif
  }

  if(!t->currentLine) t->currentLine = 1;
  pt->line = t->currentLine;
  pt->column = t->currentCol;

#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)/*only correct when skipping ASCII!*/
#define RETURN_ERR(RC,MSG) pt->ttype = S2_T_TokErr; t->errMsg = MSG; rc=RC; goto end
#define NEXT_LINE  ++t->currentLine; t->currentCol = 0
  
  if( curpos >= s2_ptoker_end(t) ) {
    pt->ttype = S2_T_EOF;
    s2_ptoken_begin_set(&t->token, s2_ptoker_end(t));
    s2_ptoken_end_set(&t->token, s2_ptoker_end(t));
    return 0;
  }

  if(!t->parent && curpos == s2_ptoker_begin(t)){
    /* 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. */
      for( ; ++curpos < s2_ptoker_end(t)
             && *curpos
             && ('\n' != *curpos); ++t->currentCol){}
      ++curpos /* skip NL */;
      if( curpos >= s2_ptoker_end(t) ) {
        pt->ttype = S2_T_EOF;
        s2_ptoken_begin_set(&t->token, s2_ptoker_end(t));
        s2_ptoken_end_set(&t->token, s2_ptoker_end(t));
        return 0;
      }
      s2_ptoken_end_set(&t->token, curpos);
      t->token.ttype = S2_T_Shebang;
      ++t->currentLine;
      t->currentCol = 0;
      return 0;
    }else{
      /* Check for a UTF-8 BOM. */
      unsigned char const * ccp = (unsigned char const*)curpos;
      if(0xEF==*ccp && 0xBB==ccp[1] && 0xBF==ccp[2]){
        curpos += 3;
        s2_ptoken_end_set(&t->token, curpos);
        t->token.ttype = S2_T_UTFBOM;
        t->currentCol += 3;
        return 0;
      }
    }
  }

  pt->ttype = S2_T_INVALID;
  s2_ptoken_begin_set(&t->token, 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;
      }else{
        RETURN_ERR(CWAL_SCR_SYNTAX,"Unexpected backslash.");
      }
      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_ptoker_end(t)
             && *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 if( '=' == *(curpos+1) ){
        pt->ttype = S2_T_OpColonEqual;
        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);
      switch(*curpos){
        case (int)'=':
          pt->ttype = S2_T_OpDivideAssign;
          BUMP(1);
          break;
        case (int)'*':/* C-style comment block */
          pt->ttype = S2_T_CommentC;
          BUMP(1);
          do{
            while( curpos<s2_ptoker_end(t)
                   && *curpos && ('*' != *curpos) ){
              if('\n'==*curpos){
                NEXT_LINE;
              }
              BUMP(1);
            }
            if(s2_ptoker_end(t)==curpos){
              RETURN_ERR(CWAL_SCR_SYNTAX,
                         "Reached (virtual) EOF while looking for "
                         "C-style comment closer.");
            }
            else if( s2_ptoker_end(t)<=curpos || !*curpos ) break;
            BUMP(1); /* skip '*' */
          } while( curpos < s2_ptoker_end(t)
                   && *curpos && ('/' != *curpos));
          if( curpos < s2_ptoker_end(t) && *curpos != '/' ){
            RETURN_ERR(CWAL_SCR_SYNTAX,"End of C-style comment not found."); 
          }else{
            BUMP(1); /* get that last slash */
          }
          break;
        case (int)'/':/* C++-style comment line */
          BUMP(1);
          pt->ttype = S2_T_CommentCpp;
          while( curpos<t->end && *curpos && ('\n' != *curpos) ){
            BUMP(1);
          }
#if 0
          if(curpos<t->end && *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.

              This would probably (untested) break those few
              constructs which optionally treat EOL as an EOX unless
              we reported this comment as an EOL token.
            */
            BUMP(1);
          }
#endif
          break;
      } /* end post-/ checks */
      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<s2_ptoker_end(t) && *curpos && (*curpos != quote)){
        /*
          BUG: our counting of t->currentCol (via BUMP())
          is off for non-ASCII characters. Need to loop over
          this as UTF.
        */
        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(s2_ptoker_end(t)<=curpos || *curpos != quote){
        RETURN_ERR(CWAL_SCR_SYNTAX,
                   "Unexpected end of string literal.");
      }else{
        BUMP(1)/*trailing quote*/;
      }
      break;
    } /* end literal string */
    case '&': /* & or && or &= */
      pt->ttype = S2_T_OpAndBitwise;
      BUMP(1);
      switch(*curpos){
        case '&':
          pt->ttype = S2_T_OpAnd;
          BUMP(1);
          break;
        case '=':
          pt->ttype = S2_T_OpAndAssign;
          BUMP(1);
          break;
      }
      break;
    case '|': /* | or || or |= or ||| */
      pt->ttype = S2_T_OpOrBitwise;
      BUMP(1);
      switch(*curpos){
        case '|':
          pt->ttype = S2_T_OpOr;
          BUMP(1);
          if( '|' == *curpos ){
            pt->ttype = S2_T_OpOr3;
            BUMP(1);
          }
          break;
        case '=':
          pt->ttype = S2_T_OpOrAssign;
          BUMP(1);
          break;
      }
      break;
    case '?': /* ? or ?: */
      pt->ttype = S2_T_Question;
      BUMP(1);
      if( ':' == *curpos ){
        pt->ttype = S2_T_OpElvis;
        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 '*': /* * or *= */
      pt->ttype = S2_T_OpMultiply;
      BUMP(1);
      switch(*curpos){
        case '=':
          pt->ttype = S2_T_OpMultiplyAssign;
          BUMP(1);
          break;
        case '/':
          pt->ttype = S2_T_INVALID;
          RETURN_ERR(CWAL_SCR_SYNTAX,
                     "Comment closer (*/) not inside a comment.");
          break;
      }
      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 >= or >>= */{
      pt->ttype = S2_T_CmpGT;
      BUMP(1);
      switch(*curpos){
        case '>': /* >> */
          pt->ttype = S2_T_OpShiftRight;
          BUMP(1);
          if( '=' == *curpos ){/* >>= */
            pt->ttype = S2_T_OpShiftRightAssign;
            BUMP(1);
          }
          break;
        case '=': /* >= */
          pt->ttype = S2_T_CmpGE;
          BUMP(1);
          break;
        default: break;
      }
      break;
    }
    case '=': /* = or == or === or =~ or => */
      pt->ttype = S2_T_OpAssign;
      BUMP(1);
      switch( *curpos ){
        case '~': /* -- */
          pt->ttype = S2_T_OpContains;
          BUMP(1);
          break;
        case '=':
          pt->ttype = S2_T_CmpEq;
          BUMP(1);
          if( '=' == *curpos ){
            pt->ttype = S2_T_CmpEqStrict;
            BUMP(1);
          }
          break;
        case '>':
          pt->ttype = S2_T_OpArrow2;
          BUMP(1);
          break;
        default: break;
      }
      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_OpDot;
      BUMP(1);
      if( '.' == *curpos ){
        pt->ttype = S2_T_OpDotDot;
        BUMP(1);
      }
      break;
    case '{': /* { */
      pt->ttype = S2_T_SquigglyOpen;
      BUMP(1);
      break;
    case '}': /* } */
      pt->ttype = S2_T_SquigglyClose;
      BUMP(1);
      break;
    case '0': /* 0 or hex or octal or binary literals */
      BUMP(1);
      if(s2_ptoker_end(t) <= curpos){
        /* special case: 0 at the end of input. */
        pt->ttype = S2_T_LiteralIntDec;
        break;
      }
      switch (*curpos)/* try hex or octal or binary */{
        case 'x':
        case 'X':{/** hex digit. */
          int digitCount = 0;
          BUMP(1);
          while(curpos<s2_ptoker_end(t) && *curpos){
            if('_'==*curpos){
              BUMP(1);
              continue /* separator */;
            }else if(s2_is_xdigit(*curpos)){
              ++digitCount;
              BUMP(1);
              continue;
            }
            break;
          }
          /*
            20200828: bug: the following syntax errors are not
            reporting error positions. e.g.

            catch { 0x }
            catch { 0x3_ }

            But only *sometimes*!?!?

            i'm imagining this, certainly.
          */
          if(!digitCount){
            RETURN_ERR(CWAL_SCR_SYNTAX,
                       "No digits in hexidecimal int literal.");
          }else{
            pt->ttype = S2_T_LiteralIntHex;
            if('_' == curpos[-1] /* last char was a separator */
               || s2_is_alnum(*curpos)
               ){
              BUMP(1); /* make sure it shows up in the error string. */
              RETURN_ERR(CWAL_SCR_SYNTAX,
                         "Malformed hexidecimal int literal.");
            }
          }
          break;
        }
        case 'o':{/* try octal... */
          int digitCount = 0;
          BUMP(1);
          while(curpos<s2_ptoker_end(t) && *curpos){
            if('_'==*curpos){
              BUMP(1);
              continue /* separator */;
            }else if(s2_is_octaldigit(*curpos)){
              ++digitCount;
              BUMP(1);
              continue;
            }
            break;
          }
          if(!digitCount){
            RETURN_ERR(CWAL_SCR_SYNTAX,
                       "No digits in octal int literal.");
          }else{
            pt->ttype = S2_T_LiteralIntOct;
            if('_' == curpos[-1] /* last char was a separator */
               || s2_is_alnum(*curpos)
               ){
              BUMP(1); /* make sure it shows up in the error string. */
              RETURN_ERR(CWAL_SCR_SYNTAX,
                         "Malformed octal int literal.");
            }
          }
          break;
        }
        case 'b':{
          /* try binary... */
          int digitCount = 0;
          BUMP(1);
          while(*curpos){
            if(*curpos=='0' || *curpos=='1'){
              ++digitCount;
              BUMP(1);
              continue;
            }else if('_'==*curpos){
              BUMP(1);
              continue /* separator */;
            }
            break;
          }
          if(!digitCount){
            RETURN_ERR(CWAL_SCR_SYNTAX,
                       "No digits in binary int literal.");
          }
          /*else if(digitCount > CWAL_INT_T_BITS){
            RETURN_ERR(CWAL_SCR_SYNTAX,
            "Binary value is too large for this build.");
            }*/
          else{
            pt->ttype = S2_T_LiteralIntBin;
            if('_' == curpos[-1] /* last char was a separator */
               || s2_is_alnum(*curpos)
               ){
              BUMP(1); /* make sure it shows up in the error string. */
              RETURN_ERR(CWAL_SCR_SYNTAX,
                         "Malformed binary int literal.");
            }
          }
          break;
        }/*binary*/
        default:
          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 nor binary.");
          }
          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)){
              RETURN_ERR(CWAL_SCR_SYNTAX,
                         "Mis-terminated floating point value.");
            }
            pt->ttype = S2_T_LiteralDouble;
          }
          else {
            pt->ttype = S2_T_LiteralIntDec;
            /* A literal 0. This is okay. */
          }
          break;
      }
      break;
    case '1': case '2': case '3':
    case '4': case '5': case '6':
    case '7': case '8': case '9': /* integer or double literal. */{
      int gotSep = 0;
      /*
        Reminder to self: The 0x, 0o, and 0b formats all support '_'
        characters as "visual separators" in their numeric literals.
        Decimals values "should" do the same but...  our downstream
        code for parsing doubles does not handle those characters,
        thus we prohibit the '_' separators in floating-point values
        here. At _this_ point in the tokenization we don't yet know if
        we're reading an integer or double, so we have to remember
        whether we hit a '_' and error out if it turns out we're
        parsing a double. Not elegant, but that's okay.
      */
      BUMP(1);
      while(curpos<s2_ptoker_end(t) && *curpos){
        /* integer or first part of a double. */
        if('_'==*curpos){
          ++gotSep;
          BUMP(1);
          continue;
        }else if(s2_is_digit(*curpos)){
          BUMP(1);
          continue;
        }
        break;
      }
      if( curpos<s2_ptoker_end(t)
          && ('.' == *curpos) && s2_is_digit(*(curpos+1)) ){
        /* double number */
        if(gotSep){
          RETURN_ERR(CWAL_SCR_SYNTAX,
                     "'_' separators are not legal in floating-point literals.");
        }
        pt->ttype = S2_T_LiteralDouble;
        BUMP(1);
        while(curpos<s2_ptoker_end(t) && *curpos && s2_is_digit(*curpos)){
          BUMP(1);
        }
      }
      else {
        pt->ttype = S2_T_LiteralIntDec;
      }
      if( (curpos[-1] == '_'
           /* disallow trailing separator for symmetry
              with 0x/0o/0b literals. */)
          || (curpos<s2_ptoker_end(t)
              && *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;
    }
    case '@':
      pt->ttype = S2_T_At;
      BUMP(1);
      break;
  }/* end of switch(*curpos) */

  if(0==rc
     && (curpos == s2_ptoken_begin(&t->token))
     && (S2_T_EOF != pt->ttype) ){
    /* keep trying... */
    int const idChars = s2_read_identifier2( curpos, s2_ptoker_end(t),
                                             &curpos, t->flags );
    if( idChars ) /* identifier string */{
      t->currentCol += idChars;
      pt->ttype = S2_T_Identifier;
    }
#if 1
    /* i don't like this, but removing it causes errors i'm
       not willing to chase down right now: a falsely-reported
       CWAL_RC_OOM triggered via the eval loop. OTOH, if
       we don't do this then we need to explicitly catch and tag all
       1-char operators. */
    else if( (*((unsigned char const *)curpos) > 0)
             && (*((unsigned char const *)curpos) <= 127) ) {
      pt->ttype = *curpos;
      BUMP(1);
    }
#endif
    else {
      assert(curpos == s2_ptoken_begin(&t->token));
      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! */{
      int const chars = s2_read_identifier2( curpos, s2_ptoker_end(t),
                                             &curpos, t->flags );
      if(chars){
        t->currentCol += chars;
        pt->ttype = S2_T_Identifier;
      }else{
        t->errMsg = "Don't know how to tokenize this.";
        rc = CWAL_SCR_SYNTAX;
      }
    }else{
      pt->ttype = S2_T_TokErr;
      /* MARKER(("byte=%02x\n", (unsigned char)*curpos)); */
      t->errMsg = "Don't know how to tokenize this.";
      rc = CWAL_SCR_SYNTAX;
    }
  }
  s2_ptoken_end_set(&t->token, (curpos > s2_ptoker_end(t))
                    ? s2_ptoker_end(t) : curpos);
#undef CHECKEND
#undef BUMP
#undef RETURN_ERR
#undef NEXT_LINE
  end:
  if(rc){
    assert(t->errMsg);
    t->_errToken = t->token;
  }
  return rc;
}

int s2_ptoker_next_token_skip_junk( s2_ptoker * st ){
  int rc = 0;
  do{
    rc = s2_ptoker_next_token(st);
  }while(!rc && s2_ttype_is_junk(st->token.ttype));
  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_octaldigit( int ch ){
  return ('0'<=ch && '7'>=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);
}

int s2_read_identifier2( char const * zPos,
                         char const * zEnd,
                         char const ** zIdEnd,
                         uint32_t flags ){
  unsigned char const * start = (unsigned char const *) zPos;
  unsigned char const * pos = start;
  unsigned char const * end = (unsigned char const *) zEnd;
  unsigned char const * endChar = pos;
  int ch;
  int rc = 0;
  int const allowDash = (S2_T10N_F_IDENTIFIER_DASHES & flags);
  assert(zEnd>zPos);
  for( ; pos < end; ){
    ch = cwal_utf8_read_char( pos, end, &endChar );
    if(endChar == pos) break;
    else if(!s2_is_id_char(ch, (pos==start) ? 1 : 0)){
      if(!('-'==ch && allowDash)){
        break;
      }
    }
    ++rc;
    pos = endChar;
  }
  *zIdEnd = zPos + (pos - start);
  return rc;
}

int s2_read_identifier( char const * zPos,
                        char const * zEnd,
                        char const ** zIdEnd ){
  return s2_read_identifier2( zPos, zEnd, zIdEnd, 0 );
}

#if 0
/* For later study. It's not clear whether this will help us
   count lines more quickly but may have use in the core cwal
   UTF8 APIs.
*/
/**
 Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
 See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
*/
#define UTF8_ACCEPT 0
#define UTF8_REJECT 1
static const uint8_t utf8d[] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 00..1f */
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 20..3f */
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 40..5f */
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 60..7f */
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, /* 80..9f */
  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* a0..bf */
  8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* c0..df */
  0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, /* e0..ef */
  0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, /* f0..ff */
  0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, /* s0..s0 */
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, /* s1..s2 */
  1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, /* s3..s4 */
  1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, /* s5..s6 */
  1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1  /* s7..s8 */
};
static uint32_t inline
bh_decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
  uint32_t type = utf8d[byte];

  *codep = (*state != UTF8_ACCEPT) ?
    (byte & 0x3fu) | (*codep << 6) :
    (0xff >> type) & (byte);

  *state = utf8d[256 + *state*16 + type];
  return *state;
}
#endif

int s2_count_lines( char const * src, char const * end_,
                    char const * pos_,
                    s2_linecol_t *line, s2_linecol_t *col ){
  s2_linecol_t ln = 1, c = 0;
  unsigned char const * x = (unsigned char const *)src;
  unsigned char const * const pos = (unsigned char const *)pos_;
  unsigned char const * const end = (unsigned char const *)end_;
  if((pos<x) || (pos>=end)) {
    return CWAL_RC_RANGE;
  }
    /* 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(). (That failed because of how we
       continually swap tokens in and out.)

       Misc. interesting UTF-decoding links with free code:

       https://gist.github.com/gorb314/7888804
       https://bjoern.hoehrmann.de/utf-8/decoder/dfa/

    */
#if 1
  /**
     Vaguely derived from: https://nullprogram.com/blog/2017/10/06/

     A notably faster than the daemonology.net impl in the s2
     amalgamated unit tests. :-D i'm not sure which values in
     callgrind are the significant ones, but in the test data i'm
     working from, where this routine is the framework's hotspot, this
     algo cuts total instructions(?) by about 25% compared to that
     one.
  */
  else{
    static const unsigned char lengths[32] = {
    /* ^^^ changing this to int takes more CPU instructions. */
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0
    };
    while( x < pos  ){
      if(*x > 127U) x += lengths[*x>>3];
      /* ^^^^ oddly, this loop is slower if we invert
         this if/else into if(*x<128U)... */
      else{
        if('\n'==*x){
          ++ln;
          c = -1;
        }
        ++x;
        /* Reminder to future self: we spent more than an hour
           trying out various combinations to reduce the instruction
           count and tiny changes have huge effects. e.g., combining
           the increment of x into:

           if('\n'==*x++){...}

           Increased the overall instruction count on the unit tests
           by a whopping 33M.
         */
      }
      ++c;
    }
  }
#elif 1
  /**
     Vaguely derived from: https://nullprogram.com/blog/2017/10/06/

     We unfortunately can't use that algo as-is because it requires
     that the input be padded to an increment of 4 bytes.

     After notable tinkering, a tiny tick faster than the
     daemonology.net impl.
  */
  else{
    static const char lengths[32] = {
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0
    };
    char i;
    while(x<pos && (i = lengths[x[0]>>3])){
      if('\n'==x[0]){
        ++ln;
        c = -1;
      }
      x+=i;
      ++c;
    }
  }
#elif 0
  /* 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
  /* EXTREMELY slow impl... */
  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,
                           s2_linecol_t * line, s2_linecol_t * col ){
  s2_ptoker const * top;
  s2_ptoker const * prev;
  int rc = 0;
  s2_linecol_t li = 0, c = 0;
  s2_linecol_t offLine = 0, offCol = 0;
  s2_ptoker_lccache_entry const * cachedPos;
  /* look for an already-calculated starting point... */
  for( prev = top = pt; top; prev = top, top = top->parent){
    if(top->lineOffset){
      offLine = top->lineOffset;
      offCol = top->colOffset;
      break;
    }
  }
  if(!top) top = prev;
  assert(top);
  assert(s2_ptoker_begin(pt) >= s2_ptoker_begin(top)
         && s2_ptoker_end(pt) <= s2_ptoker_end(top));
  assert(pos<s2_ptoker_end(pt) && pos >= s2_ptoker_begin(top));
  if(S2_T10N_F_LINEAR_TOKER & pt->flags){
    s2_ptoken const * et = s2_ptoken_begin(&pt->_errToken)
      ? &pt->_errToken
      : &pt->token;
    li = et->line;
    c = et->column;
    /*MARKER(("Using S2_T10N_F_LINEAR_TOKER line %d, col %d\n",
      li, c));*/
  }
  if(!li && (cachedPos = s2_ptoker_lc_search(top, pos))){
    LCMARKER(("%p Got a hit: (%d, %d) distance from target pos=%d, from start=%d\n",
              (void const *)top, cachedPos->line, cachedPos->col,
              (int)(pos - cachedPos->pos), (int)(pos - s2_ptoker_begin(top))
              ));
    if(cachedPos->pos==pos){
      li = cachedPos->line;
      c = cachedPos->col;
    }else{
      offLine = cachedPos->line;
      offCol = cachedPos->col;
      rc = s2_count_lines( cachedPos->pos, s2_ptoker_end(pt), pos,
                           &li, &c );
      assert(!rc);
    }
  }else if(!li){
    rc = s2_count_lines( s2_ptoker_begin(top), s2_ptoker_end(pt),
                         pos, &li, &c );
    assert(!rc);
  }
  if(!rc && (line || col)){
    if(offLine){
      /*MARKER(("top->lineOffset=%d, top->colOffset=%d\n",
        top->lineOffset, top->colOffset));*/
      c += (1==li) ? offCol : 0;
      li += offLine - 1;
    }
    if(line) *line = li;
    if(col) *col = c;
#if S2_T10N_LCCACHE
      /*LCMARKER(("%p Attempting to cache entry (%d, %d)\n", (void const *)top, li, c));*/
    s2_ptoker_lc_cache(top, pos, li, c);
#endif
  }
  return rc;
}

int s2_ptoker_count_lines2( s2_ptoker const * pt,
                            s2_ptoken const * tok,
                            s2_linecol_t * line, s2_linecol_t * col ){
  s2_linecol_t li = 0, c = 0;
  s2_ptoker const * top;
  int rc = 0;
  if(!tok) tok = &pt->token;
  for( top = pt; top; top = top->parent){
    if(top->lineOffset) break;
  }
  if(!top) top = s2_ptoker_top_parent(pt);
  if(tok->line){
    MARKER(("Using token-embedded line %d, col %d [%.*s] parent?=%d\n",
            tok->line, tok->column,
            (int)s2_ptoken_len(tok), s2_ptoken_begin(tok),
            !!pt->parent));
    li = tok->line;
    c = tok->column;
  }else{
    rc = s2_count_lines( s2_ptoker_begin(top), s2_ptoker_end(pt),
                         s2_ptoken_begin(tok), &li, &c );
  }
  if(!rc && (line || col)){
    /* Adjust the line/column for the line/colOffset
       members of the parent tokenizer... */
    int oL = 0, oC = 0;
    assert(top->lineOffset || !top->parent);
    if(top->lineOffset){
      oL += top->lineOffset /* - 1 */;
      if(1==li) oC += top->colOffset;
      else oC = 0;
      MARKER(("top->lineOffset=%d, top->colOffset=%d\n",
              top->lineOffset, top->colOffset));
      MARKER(("adjusted line=%d=>%d, col=%d=>%d\n",
              li, li+oL, c, c+oC));
      li += oL;
      c += oC;
    }
    if(line) *line = li;
    if(col) *col = c;
  }
  return rc;
}

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){
    ch1 = *zPos ? s2_hexbyte(*zPos++) : -1;
    ch2 = (ch1>=0) ? s2_hexbyte(*zPos++) : -1;
    if(ch2<0) return -1;
    rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2);
    n += 2;
  }
  *rv = rc;
  return (int)n;
}

/**
   TODO?: return a positive value (the length of the unescaped string)
   on success and the negated byte offset of an error?
*/
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;
  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 '\\':
        if(end==p+1){
          /* stray slash at the end of the string. */
          *(out++) = '\\';
          break;
        }
        switch(*++p){
#if 0
          case 0:
            *(out++) = '\0';
            break;
#endif
          case '0': *(out++) = 0; break;
          case 'b': *(out++) = '\b'; break;
          case 't': *(out++) = '\t'; break;
          case 'n': *(out++) = '\n'; break;
          case 'r': *(out++) = '\r'; break;
          case 'f': *(out++) = '\f'; break;
          case 'v': *(out++) = '\v'; break;
          case 'u':
          case 'U':{
            /* \uXXXX and \UXXXXXXXX */
            unsigned char uCount = 0;
            unsigned int uChar = 0;
            const unsigned ulen = 'u'==*p ? 4U : 8U;
            uCount = s2_read_hex_bytes(p+1, ulen, &uChar);
            if(uCount != ulen) return CWAL_RC_RANGE;
            assert(0 == (uCount % 2));
            check = cwal_utf8_char_to_cstr(uChar, out, uCount);
            if(check<0){
              /*MARKER(("Invalid UTF: \\%c seq: uCount=%d, uChar=0x%08x\n",
               *p, (int)uCount, uChar));*/
              return CWAL_RC_RANGE
                /* Invalid UTF */;
            }
            out += check /* length of the char, in bytes */;
            p += uCount;
            break;
          }
#if 0
          default:
           /* strip the backslash */
            *(out++) = *p;
            break;
#else
          case '\\':
          case '"':
          case '\'':
            *(out++) = *p;
            break;
          default:
           /* retain the backslash. The main benefit of this is that
            libraries which take escaped strings don't have to be
            double-escaped in many cases. This "problem" was
            discovered in the POSIX regex extension, where we had to
            double-escape parens and '+' before this change. */
            *(out++) = '\\';
            *(out++) = *p;
            break;
#endif
        }
        break;
      default:
        *(out++) = *p;
        break;
    }
  }
  *out = 0;
  dest->used = out - dest->mem;
  assert(dest->used <= dest->capacity);
  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/*==prefix strlen("S2_T_")*/
    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_At);
    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_OpDotDot);
    CASE(S2_T_OpDotLength);
    CASE(S2_T_OpInherits);
    CASE(S2_T_OpNotInherits);
    CASE(S2_T_OpContains);
    CASE(S2_T_OpNotContains);
    CASE(S2_T_OpArrow);
    CASE(S2_T_OpArrow2);
    CASE(S2_T_OpDivide);
    CASE(S2_T_OpDivideAssign);
    CASE(S2_T_OpDivideAssign3);
    CASE(S2_T_Colon);
    CASE(S2_T_Colon2);
    CASE(S2_T_OpColonEqual);
    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_Heredoc);
    CASE(S2_T_OpAssign);
    CASE(S2_T_OpAssign3);
    CASE(S2_T_OpAssignConst3);
    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_QDot);
    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_SquigglyBlock);
    CASE(S2_T_OpOrBitwise);
    CASE(S2_T_OpOr);
    CASE(S2_T_OpOr3);
    CASE(S2_T_OpElvis);
    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_LiteralIntBin);
    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_KeywordClass);
    CASE(S2_T_KeywordConst);
    CASE(S2_T_KeywordContinue);
    CASE(S2_T_KeywordDefine);
    CASE(S2_T_KeywordDefined);
    CASE(S2_T_KeywordDelete);
    CASE(S2_T_KeywordDo);
    CASE(S2_T_KeywordEcho);
    CASE(S2_T_KeywordEnum);
    CASE(S2_T_KeywordEval);
    CASE(S2_T_KeywordException);
    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_KeywordForEach);
    CASE(S2_T_KeywordFunction);
    CASE(S2_T_KeywordIf);
    CASE(S2_T_KeywordImport);
    CASE(S2_T_KeywordInclude);
    CASE(S2_T_KeywordInterface);
    CASE(S2_T_KeywordIs);
    CASE(S2_T_KeywordIsA);
    CASE(S2_T_KeywordLINE);
    CASE(S2_T_KeywordNameof);
    CASE(S2_T_KeywordNew);
    CASE(S2_T_KeywordNull);
    CASE(S2_T_KeywordPragma);
    CASE(S2_T_KeywordPrivate);
    CASE(S2_T_KeywordProc);
    CASE(S2_T_KeywordProtected);
    CASE(S2_T_KeywordPublic);
    CASE(S2_T_KeywordReturn);
    CASE(S2_T_KeywordRefcount);
    CASE(S2_T_KeywordS2Out);
    CASE(S2_T_KeywordSRCPOS);
    CASE(S2_T_KeywordScope);
    CASE(S2_T_KeywordStatic);
    CASE(S2_T_KeywordThrow);
    CASE(S2_T_KeywordTrue);
    CASE(S2_T_KeywordTry);
    CASE(S2_T_KeywordTypeinfo);
    CASE(S2_T_KeywordTypename);
    CASE(S2_T_KeywordUndefined);
    CASE(S2_T_KeywordUnset);
    CASE(S2_T_KeywordUKWD);
    CASE(S2_T_KeywordUsing);
    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
  }
#if 0
  /* Make ttypes matching characters in the ASCII range (even
     unprintable ones) their own strings. Exception: the value 0 is
     reserved for S2_T_INVALID, so we don't have a string for the
     token \0. We don't really need these in scripts, though, and NOT
     having these allows code to call this function to determine
     whether or not it's a known-to-s2 token type.
  */
  if(ttype>0 && ttype<=127){
    static char singleChars[127*2] =
      {0} /* all characters in (1..127), each followed by a NUL */;
    int ntype = ttype-1;
    if(0 == singleChars[0]){
      int i = 0, n = 1;
      for(; n <=127; ++n, ++i ){
        singleChars[i*2] = (char)n;
        singleChars[i*2+1] = 0;
      }
    }
    return &singleChars[ntype*2];
  }
#endif
  return 0;
}

int s2_ptoken_has_content( s2_ptoken const * tok ){
  char const * begin;
  char const * end;
  if(s2_ptoken_adjbegin(tok)){
    begin = s2_ptoken_adjbegin(tok);
    end = s2_ptoken_adjend(tok);
  }else{
    begin = s2_ptoken_begin(tok);
    end = s2_ptoken_end(tok);
  }
  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;
  }
}

/**
   Expects digits to point to digLen bytes with the ASCII values '0'
   or '1'. It parses them as a binary value. On success, if out is not
   NULL then the final parsed result is written there, and it returns
   0. On error (non-binary-digit encountered) then CWAL_RC_RANGE is
   returned and out is not modified.

   The digits may contain any number of '_' characters, which are
   treated as "visual separators" (i.e. simply skipped).

   Minor achtung: this routine accepts, for simplicity, '_' as a
   leading or trailing character but the core tokenizer does not. It
   expects to be fed only inputs which have already been vetted by the
   tokenizer.
*/
static int s2_parse_binary_digits( char const * digits,
                                   cwal_size_t digLen,
                                   cwal_int_t * out ){
  cwal_uint_t i = 0
    /* use unsigned during parsing for guaranteed overflow behaviour */;
  char const * end = digits + digLen;
  assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t));
  if(end <= digits) return CWAL_RC_RANGE;
  for( ; digits < end; ++digits ){
    switch(*digits){
      case '0':
        i <<= 1;
        continue;
      case '1':
        i = (i << 1) | 1;
        continue;
      case '_':
        continue;
      default:
        return CWAL_RC_RANGE;
    }
  }
  if(out) *out = (cwal_int_t)i;
  return 0;
}

/**
   The octal-digit counterpart of s2_parse_binary_digits(), and works
   identically except that it requires octal-digit input.
*/
static int s2_parse_octal_digits( char const * digits,
                                  cwal_size_t digLen,
                                  cwal_int_t * out ){
  cwal_uint_t i = 0
    /* use unsigned during parsing for guaranteed overflow behaviour */;
  char const * end = digits + digLen;
  char digit;
  assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t));
  if(end <= digits) return CWAL_RC_RANGE;
  for( ; digits < end; ++digits ){
    digit = *digits;
    switch(digit){
      case '0': case '1': case '2':
      case '3': case '4': case '5':
      case '6': case '7':
        i = (i << 3) | (cwal_uint_t)(digit-'0');
        break;
      case '_':
        continue;
      default:
        return CWAL_RC_RANGE;
    }
  }
  if(out) *out = (cwal_int_t)i;
  return 0;
}

/**
   The decimal-digit counterpart of s2_parse_binary_digits(), and
   works identically except that it requires decimal-digit input.
*/
static int s2_parse_decimal_digits( char const * digits,
                                    cwal_size_t digLen,
                                    cwal_int_t * out ){
  cwal_uint_t i = 0
    /* use unsigned during parsing for guaranteed overflow behaviour */;
  char const * end = digits + digLen;
  char digit;
  assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t));
  if(end <= digits) return CWAL_RC_RANGE;
  for( ; digits < end; ++digits ){
    digit = *digits;
    switch(digit){
      case '0': case '1': case '2':
      case '3': case '4': case '5':
      case '6': case '7': case '8':
      case '9':
        i = (i * 10) + (cwal_uint_t)(digit-'0');
        break;
      case '_':
        continue;
      default:
        return CWAL_RC_RANGE;
    }
  }
  if(out) *out = (cwal_int_t)i;
  return 0;
}

/**
   The hex-digit counterpart of s2_parse_binary_digits(), and works
   identically except that it requires hex-digit input.
*/
static int s2_parse_hex_digits( char const * digits,
                                cwal_size_t digLen,
                                cwal_int_t * out ){
  cwal_uint_t i = 0
    /* use unsigned during parsing for guaranteed overflow behaviour */;
  char const * end = digits + digLen;
  char digit;
  assert(sizeof(cwal_size_t) >= sizeof(cwal_int_t));
  if(end <= digits) return CWAL_RC_RANGE;
  for( ; digits < end; ++digits ){
    digit = *digits;
    switch(digit){
      case '0':
      case '1': case '2': case '3': case '4': case '5':
      case '6': case '7': case '8': case '9':
        i = (i << 4) | (cwal_uint_t)(digit-'0');
        break;
      case 'a': case 'b': case 'c':
      case 'd': case 'e': case 'f':
        i = (i << 4) | (cwal_uint_t)(10 + digit -'a');
        break;
      case 'A': case 'B': case 'C':
      case 'D': case 'E': case 'F':
        i = (i << 4) | (cwal_uint_t)(10 + digit - 'A');
        break;
      case '_':
        continue;
      default:
        return CWAL_RC_RANGE;
    }
  }
  if(out) *out = (cwal_int_t)i;
  return 0;
}

char s2_ptoken_parse_int( s2_ptoken const * t, cwal_int_t * rc ){
  switch(t->ttype){
    case S2_T_LiteralIntDec:
      assert(s2_ptoken_end(t) > s2_ptoken_begin(t));
      return s2_parse_decimal_digits(s2_ptoken_begin(t),
                                     s2_ptoken_len(t),
                                     rc)
        ? 0 : 1;
    case S2_T_LiteralIntOct:
      assert( s2_ptoken_len(t) > 2 /*"0o" prefix */);
      return s2_parse_octal_digits( s2_ptoken_begin(t)+2,
                                    s2_ptoken_len(t)-2,
                                    rc )
        ? 0
        : 1;
    case S2_T_LiteralIntHex:
      assert( s2_ptoken_len(t) > 2 /*"0x" prefix */);
      return s2_parse_hex_digits( s2_ptoken_begin(t)+2,
                                  s2_ptoken_len(t)-2,
                                  rc )
        ? 0
        : 1;
    case S2_T_LiteralIntBin:
      assert( s2_ptoken_len(t) > 2/*"0b" prefix*/);
      return s2_parse_binary_digits( s2_ptoken_begin(t)+2,
                                     s2_ptoken_len(t)-2,
                                     rc )
        ? 0
        : 1;
    default:
      return 0;
  }
}

char s2_ptoken_parse_double( s2_ptoken const * t, cwal_double_t * dest ){
  if(t->ttype == S2_T_LiteralDouble){
    if(dest){
      return cwal_cstr_to_double( s2_ptoken_begin(t),
                                  s2_ptoken_len(t), dest)
        ? 0 : 1;
    }
    return 1;
  }else{
    return 0;
  }
}

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;
  cwal_int_t rv = 0;
  if(slen<0) slen = (cwal_int_t)cwal_strlen(str);
  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, &rv )
     ){
    return 0;
  }else{
    s2_ptoken_begin_set(&pr.token, s2_ptoken_end(&pr.token));
    s2_ptoken_end_set(&pr.token, s2_ptoker_end(&pr));
    if(s2_ptoken_has_content(&pr.token)){
      /* trailing junk */
      return 0;
    }else{
      if(result) *result = sign < 0 ? -rv : rv;
      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;
  cwal_double_t rv = 0;
  if(slen<0) slen = (cwal_int_t)cwal_strlen(str);
  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, &rv)){
    return 0;
  }else{
    s2_ptoken_begin_set(&pr.token, s2_ptoken_end(&pr.token));
    s2_ptoken_end_set(&pr.token, s2_ptoker_end(&pr));
    if(s2_ptoken_has_content(&pr.token)){
      /* trailing junk */
      return 0;
    }else{
      if(result) *result = sign < 0 ? -rv : rv;
      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;
  /**
     TODO 20200828: we're doing this the hard way. We can simply start
     at the end and backtrack for '/' or '\\'.
  */
  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;
}

#if 0
cwal_size_t s2_ptoken_len( s2_ptoken const * token ){
  return (s2_ptoken_begin(token)
          && s2_ptoken_end(token) > s2_ptoken_begin(token))
    ? (cwal_size_t)(s2_ptoken_end(token) - s2_ptoken_begin(token))
    : 0;
}
cwal_size_t s2_ptoken_len2( s2_ptoken const * token ){
  if(token->adjBegin && token->adjEnd && token->adjBegin<=token->adjEnd){
    return (cwal_size_t)(token->adjEnd - token->adjBegin);
  }
  return (token->begin && token->end > token->begin)
    ? (cwal_size_t)(token->end - token->begin)
    : 0;
}
#endif

char const * s2_ptoken_cstr( s2_ptoken const * tok,
                             cwal_size_t * len ){
  if(len) *len = s2_ptoken_len(tok);
  return s2_ptoken_begin(tok);
}

char const * s2_ptoken_cstr2( s2_ptoken const * tok,
                              cwal_size_t * len ){
  if(len) *len = s2_ptoken_len2(tok);
  return s2_ptoken_adjbegin(tok)
    ? s2_ptoken_adjbegin(tok)
    : s2_ptoken_begin(tok);
}

cwal_value * s2_ptoken_is_tfnu( s2_ptoken const * tok ){
  cwal_value * rv = 0;
  if(S2_T_Identifier == tok->ttype){
    cwal_size_t const tlen = s2_ptoken_len(tok);
    switch(tlen){
      case 4:
        if(0==cwal_compare_cstr("true", 4,
                                s2_ptoken_begin(tok), tlen)){
          rv = cwal_value_true();
        }else if(0==cwal_compare_cstr("null", 4,
                                      s2_ptoken_begin(tok), tlen)){
          rv = cwal_value_null();
        }
        break;
      case 5:
        if(0==cwal_compare_cstr("false", 5,
                                s2_ptoken_begin(tok), tlen)){
          rv = cwal_value_false();
        }
        break;
      case 9:
        if(0==cwal_compare_cstr("undefined", 9,
                                s2_ptoken_begin(tok), tlen)){
          rv = cwal_value_undefined();
        }
        break;
      default:
        break;
    }
  }
  return rv;
}

char const * s2_ptoker_err_pos( s2_ptoker const * pt ){
  char const * rc = s2_ptoker_err_pos_first(pt, 0);
  if(!rc){
    if(s2_ptoken_begin(&pt->token) < s2_ptoken_end(&pt->token)){
      rc = s2_ptoken_begin(&pt->token);
    }
    if(!rc){
      if(s2_ptoken_begin(&pt->_pbToken) < s2_ptoken_end(&pt->_pbToken)){
        rc = s2_ptoken_begin(&pt->_pbToken);
      }
      if(!rc) rc = s2_ptoker_begin(pt);
    }
#if 0
    if(rc>=s2_ptoker_end(pt) && s2_ptoker_end(pt)>s2_ptoker_begin(pt)) rc = s2_ptoker_end(pt)-1;
#endif
  }
  return rc;
}


void s2_ptoker_errtoken_set( s2_ptoker * const st, s2_ptoken const * const tok ){
  st->_errToken = tok ? *tok : s2_ptoken_empty;
}

s2_ptoken const * s2_ptoker_errtoken_get( s2_ptoker const * st ){
  return &st->_errToken;
}

char s2_ptoker_errtoken_has( s2_ptoker const * st ){
  return s2_ptoken_begin(&st->_errToken)
    && s2_ptoken_begin(&st->_errToken) >= s2_ptoker_begin(st)
    && s2_ptoken_begin(&st->_errToken) < s2_ptoker_end(st)
    ? 1 : 0;
}

char const * s2_ptoker_capture_cstr( s2_ptoker const * st,
                                     cwal_size_t * len ){
  char const * rc = 0;
  char const * begin = s2_ptoken_begin(&st->capture.begin);
  char const * end = s2_ptoken_begin(&st->capture.end);
  if(begin && begin>=s2_ptoker_begin(st)
     && end>=begin && end<=s2_ptoker_end(st)){
    rc = begin;
    if(len) *len = (cwal_size_t)(end - begin);
  }
  return rc;
}

void s2_path_toker_init( s2_path_toker * pt, char const * path, cwal_int_t len ){
  *pt = s2_path_toker_empty;
  pt->pos = pt->begin = path;
  pt->end = pt->begin + ((len>=0) ? (cwal_size_t)len : cwal_strlen(path));
}

int s2_path_toker_next( s2_path_toker * pt, char const ** token,
                        cwal_size_t * len ){
  if(!pt->pos || pt->pos>=s2_ptoker_end(pt)) return CWAL_RC_RANGE;
  else if(!pt->separators || !*pt->separators) return CWAL_RC_MISUSE;
  else{
    char const * pos = pt->pos;
    char const * t;
    char const * sep;
    for( sep = pt->separators; *sep; ++sep){
      if(*sep & 0x80) return CWAL_RC_MISUSE;
      /* non-ASCII */
    }
    for( ; pos<s2_ptoker_end(pt); ){
      /*skip leading separators*/
      for( sep = pt->separators;
           *sep && *pos!=*sep; ++sep ){
      }
      if(*pos == *sep) ++pos;
      else break;
    }
    t = pos;
    for( ; pos<s2_ptoker_end(pt); ){
      /*skip until the next separator*/
      for( sep = pt->separators;
           *sep && *pos!=*sep; ++sep ){
      }
      if(*pos == *sep) break;
      else ++pos;
    }
    pt->pos = pos;
    if(pos>t){
      *token = t;
      *len = (cwal_size_t)(pos - t);
      return 0;
    }
    return CWAL_RC_NOT_FOUND;
  }
}

#if S2_T10N_LCCACHE
#  undef s2_ptoker_lc_slot
#else
#  undef s2_ptoker_lc_search
#endif
#undef MARKER
#undef S2_T10N_LCCACHE
#undef S2_T10N_LCCACHE_SLOT
#undef LCMARKER
#undef S2_T10N_COMPILE
#undef COMPILED_DECL
#undef COMPILED_DECL_C
#undef COMPILED_MEMBER
#undef COMPILED_MEMBER_C
/* 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;
  char const * outAlias = "ⓞⓤⓣ" /*"➤➤"*/ /* "ᐊᐊ" */ /*"ᗛᗛ"*/ /* local var alias for OUT */;
  char const * hDocPrefix;
  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 && *opt->outputSymbolInternal){
      outAlias = opt->outputSymbolInternal;
    }
    if(opt->outputSymbolPublic && *opt->outputSymbolPublic){
      outsymLong = opt->outputSymbolPublic;
    }
    if(opt->heredocId && *opt->heredocId) hDocId = opt->heredocId;
    if(opt->tagCodeOpen && opt->tagCodeClose){
      tagOpenScript = opt->tagCodeOpen;
      nOpenScript = (int)cwal_strlen(tagOpenScript);
      tagCloseScript = opt->tagCodeClose;
      nCloseScript = (int)cwal_strlen(tagCloseScript);
    }
    if(opt->tagValueOpen && opt->tagValueClose){
      tagOpenValue = opt->tagValueOpen;
      nOpenValue = (int)cwal_strlen(tagOpenValue);
      tagCloseValue = opt->tagValueClose;
      nCloseValue = (int)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;
  if(!rc) rc = cwal_buffer_reserve(e, dest, src->capacity)
            /* very possibly not enough, but we cannot guess
               the approximate final size. */;
  while(!rc){/* not a loop - we only use while() so we can break out
                of this block */
    /**
       Set up some bootstrapping stuff, e.g. make sure we have
       outsymLong in place.
    */
    rc = cwal_buffer_append( e, dest, "(", 1);
    if(!rc
       && (!opt->outputSymbolPublic || !*opt->outputSymbolPublic)
       && !(opt->flags & S2_TMPL_ELIDE_TMPLOUT)){
      /* Set up local vars, if not done already */
      rc = cwal_buffer_printf( e, dest,
                               "(typeinfo(isdeclared %s) || var %s),\n"
                               "((undefined === %s) "
                               "&& (%s=s2out)),\n",
                               outsymLong,
                               outsymLong,
                               outsymLong, outsymLong);
    }
    if(rc) break;
    else if(!opt->outputSymbolInternal || !*opt->outputSymbolInternal){
      rc = cwal_buffer_printf( e, dest,
                               "(typeinfo(islocal %s) || (var %s=%s))",
                               /* ^^^ can't be const b/c of operator<< rewrite below */
                               outAlias, outAlias, outsymLong);
    }
    if(!rc) rc = cwal_buffer_append( e, dest, ");\n", 3);
    if(rc) break;
    /*
       If TMPLOUT does not have operator<<, re-map outAlias to be a
       local wrapper object which proxies operator<< to TMPLOUT.
    */
    rc = cwal_buffer_printf( e, dest,
                             "(typeinfo(isfunction %s.'operator<<')"
                             " || (%s={prototype:null,"
                             "'operator<<':"
                             "proc(a)using{$:%s}{return $(a),this}"
                             "}));\n",
                             outAlias, outAlias, outAlias)
      /* reminder to self: we use an intermediary proc() here, instead
         of directly using TMPLOUT as operator<< impl, to avoid any
         problems wrt expectations of the value of 'this' in TMPLOUT's
         body. */;
    /*
       The problem with operator<< is that it interferes with (has
       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 %>.

       20181118: we solve that by wrapping the output of value blocks
       (but not code blocks) with an eval{...}. That minor
       tokenization overhead is orders of magnitude smaller than what
       we save by using operator<<, rather than a normal function
       call, for the code block output.
    */
    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)){
      /**
         20181118: we wrap VALUE blocks in eval{...} so that compound
         expressions won't interfere with the operator<<. e.g.: <%
         1,2,3 %> would not behave inutitively without this. In
         practice, <% %> blocks are generally small, so we don't have
         an arbitrarily large re-tokenization performance hit here
         (like we would if we did the same with <? ... ?> blocks).
      */
      openStart = pos;
      pos += nOpenValue;
      if(hIsOpen){
        rc = cwal_buffer_printf(e, dest, " %s;\n%s<< eval{ ",
                                hDocId, outAlias);
        hIsOpen = 0;
      }else if(openStart == origin
               /*workaround for <% at starting pos. */){
        rc = cwal_buffer_printf(e, dest, "%s<< eval {", outAlias);
        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,
                                  " }"/*<-- end eval block*/";\n%s%s ",
                                  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;", hDocId);
    }
    if(!rc) rc = cwal_buffer_printf(e, dest,
                                    "\ntrue /*result value*/;\n");
  }
  cwal_buffer_reserve(e, &hTag, 0);
  return rc;
  syntax_err:
  {
    s2_linecol_t line = 1, 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,
                               "tmpl syntax 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>1
    ? 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, cwal_strlen(SPROP))) \
           && (vKey=cwal_value_get_cstr(v,0)))                      \
          topt.CPROP = vKey
        CHECK("codeOpen", tagCodeOpen);
        CHECK("codeClose", tagCodeClose);
        CHECK("valueOpen", tagValueOpen);
        CHECK("valueClose", tagValueClose);
        CHECK("outputSymbol", outputSymbolPublic);
#undef CHECK
  }

  if(0){ /* just testing, but this makes for a much shorter header! */
    topt.outputSymbolPublic = "s2out";
    /* topt.outputSymbolInternal = "print"; */
    /* topt.heredocId = "'~_~'"; */
  }
#if 0
  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;
  }
#endif

  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{
      s2_buffer_swap(rb, &xbuf)
        /* transfer ownership of memory, but be sure to restore
           rb->self!!! */;
      assert(!xbuf.self);
      assert(!xbuf.mem);
      assert(rb->self);
      *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>
#endif
#if defined(S2_OS_UNIX)
#  include <unistd.h> /* usleep(), possibly _POSIX_TIMERS */
#endif

#include <stdlib.h>

#if defined(_POSIX_TIMERS) && _POSIX_TIMERS>0 && _POSIX_C_SOURCE>=199309L
#  include <time.h>
#  include <errno.h>
#  define S2_ENABLE_CLOCK_GETTIME 1
#else
#  define S2_ENABLE_CLOCK_GETTIME 0
#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


/**
   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,
                                 s2_errno_to_cwal_rc(errno, 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_UNSUPPORTED,
                             "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_mstime( cwal_callback_args const * args, cwal_value **rv ){
#if !S2_ENABLE_CLOCK_GETTIME
  return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED,
                             "mstime() is not implemented in this build.");
#else
  /*
    Per https://upvoid.com/devblog/2014/05/linux-timers/
    CLOCK_REALTIME_COARSE has a resolution of within 1ms, which is
    fine for what we're doing.
  */
  struct timespec spec;
  int rc;
  rc = clock_gettime(CLOCK_REALTIME_COARSE, &spec);
  if(rc){
    int const errNo = errno;
    if(EINVAL==errNo){
      rc = CWAL_RC_UNSUPPORTED;
    }else{
      rc = s2_errno_to_cwal_rc( errNo, CWAL_RC_ERROR );
    }
    return cwal_exception_setf(args->engine, rc,
                               "clock_gettime() failed with errno %d (%s).",
                               errNo, strerror(errNo));
  }else{
    cwal_int_t const it = (cwal_int_t)spec.tv_sec * 1000;
    if(spec.tv_sec != (time_t)(it/1000)){
      /* too large for our int type. Let's hope it will fit in a
         double... */
      *rv = cwal_new_double(args->engine, (double)it
                            + spec.tv_nsec/1000000);
    }else{
      *rv = cwal_new_integer(args->engine, (cwal_int_t)it
                             + spec.tv_nsec/1000000);
    }
    return *rv ? 0 : CWAL_RC_OOM;
  }
#endif
}

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){
#if CWAL_INT_T_BITS > 32
    timt = (time_t)cwal_value_get_integer(args->argv[1]);
#else
    /* Let's hope 48 bits is sufficient! */
    timt = (time_t)cwal_value_get_double(args->argv[1]);
#endif
  }
  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 * sub;
  int rc;
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE;
  else if(name && *name){
    sub = cwal_new_object_value(se->e);
    if(!sub) return CWAL_RC_OOM;
    cwal_value_ref(sub);
    rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub);
    cwal_value_unref(sub);
    if(rc) return rc;
  }else{
    sub = tgt;
  }

  {
    s2_func_def const funcs[] = {
      S2_FUNC2("sleep", s2_cb_sleep),
      S2_FUNC2("time", s2_cb_time),
      S2_FUNC2("mssleep", s2_cb_mssleep),
      S2_FUNC2("mstime", s2_cb_mstime),
      S2_FUNC2("strftime", s2_cb_strftime),
      s2_func_def_empty_m
    };
    return s2_install_functions(se, sub, funcs, 0);
  }
}


#undef MARKER
/* end of file s2_time.c */
/* start of file s2_unix.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_UNIX)
#include <unistd.h> /* fork() and friends */
#include <sys/types.h> /* pid_t, if not picked up via unistd.h */
#include <errno.h> /* errno! */
#include <string.h> /* strerror() */
#endif

int s2_cb_fork( cwal_callback_args const * args, cwal_value **rv ){
#if !defined(S2_OS_UNIX)
  return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED,
                             "fork(2) is not available in this configuration.");
#else
  pid_t pid;
  char doReturn = 0;
  uint16_t funcIndex = 0;
  cwal_function * f = 0;

  if(args->argc>1){
    doReturn = cwal_value_get_bool(args->argv[0]);
    funcIndex = 1;
  }
  if(!args->argc ||
     !(f = cwal_value_function_part(args->engine,
                                    args->argv[funcIndex]))){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting a Function argument.");
  }
  pid = fork();
  if(-1==pid){
    return (ENOMEM==errno)
      ? CWAL_RC_OOM
      : cwal_exception_setf(args->engine,
                            s2_errno_to_cwal_rc(errno, CWAL_RC_ERROR),
                            "Fork failed with errno %d (%s).",
                            errno, strerror(errno));
  }
  else if(pid){
    /* Parent */
    *rv = cwal_new_integer(args->engine, (cwal_int_t)pid);
    return *rv ? 0 : CWAL_RC_OOM;
  }else{
    /* Child */
    cwal_value * frv = NULL;
    int frc = cwal_function_call(f, args->self, &frv, 0, NULL);
    /* MARKER(("frc=%d/%s, doReturn=%d\n", frc,
       s2_rc_cstr(frc), doReturn)); */
    switch(frc){
      case CWAL_RC_OOM:
      case CWAL_RC_EXIT: /* assume exit value was already set. */
      case CWAL_RC_FATAL:
        break;
      case 0:
        if(doReturn){
          if(!frc) *rv = frv ? frv : cwal_value_undefined();
        }else{
          *rv = 0;
          cwal_propagating_set(args->engine,
                               frv ? frv : cwal_value_undefined()
                               /* assertions in s2 require a value
                                  to be propagated for RETURN/EXIT.*/);
        }
        break;
      default:
        break;
    }
    /* MARKER(("frc=%d/%s, doReturn=%d\n", frc,
       s2_rc_cstr(frc), doReturn)); */
    return (doReturn || frc) ? frc : CWAL_RC_EXIT;
  }
#endif
}

/* end of file s2_unix.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 */

/* minimum --- return minimum of two numbers */
#define minimum(A,B) ((A)<(B) ? (A) : (B))
/* maximum --- return maximum of two numbers */
#define maximum(A,B) ((A)>(B) ? (A) : (B))

#define range(low, item, hi)    maximum(low, minimum(item, hi))

/* 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) {
            char * newtz = (char *) realloc(savetz, i);
            if(newtz){
                savetz = newtz;
                savetzlen = i;
                strcpy(savetz, tz);
            }else{
                /* ... just keep the old one! */
            }
        } 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;
        }
    }
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 range
#undef maximum
#undef minimum
#undef HAVE_GET_TZ_NAME
#undef GAWK
/* end of file strftime.c */
