cwal

whcl.h
Login

whcl.h

File include/wh/cwal/whcl/whcl.h from the latest check-in


/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  License: same as cwal. See cwal.h resp. libcwal.h for details.
*/
#ifndef NET_WANDERINGHORSE_CWAL_WHCL_H_INCLUDED_
#define NET_WANDERINGHORSE_CWAL_WHCL_H_INCLUDED_
/** @file */ /* for doxygen */

#include "whcl_config.h" /* must come before libcwal.h */
#include "libcwal.h"
#include <stdio.h> /* FILE * */
#include <time.h>

/** @internal
   
    An internal helper macro to help localize OOM error reports (which
    are most often side effects of other issues, rather than being
    real OOM cases).
*/
#define WHCL__WARN_OOM \
  fprintf(stderr,"OOM @ %s:%d\n", __FILE__, __LINE__)

/** @page page_whcl whcl scripting language

   Whcl is the WanderingHorse.net Command Language for the cwal
   scripting engine.

   It is an incomplete experiment.

   Primary properties of the language:

   - "Command-centric," heavily influenced by TCL, but...

   - Uses cwal data types instead of TCL's EIAS. One implication being
     that...

   - "Bare" string tokens are not a thing. Strings are quoted.

   - Intended primarily for unit testing and basic scripting of
     3rd-party libraries. Not intended to be a language for developing
     full-features apps.

   API notes:

   - Symbols starting with `whcl__` (with TWO underscores) are
     internal. Such types are exposed in the public headers primarily
     so that they can facilitate stack allocation of other structs
     which embed them. _Functions_ with such names are exposed only to
     facilitate access from various project files.
*/

#ifdef __cplusplus
extern "C" {
#endif
typedef struct whcl_engine whcl_engine;
typedef struct whcl_scope whcl_scope;
typedef struct whcl_script whcl_script;
typedef struct whcl__stoken whcl__stoken;
typedef struct whcl__stoken_stack whcl__stoken_stack;
typedef struct whcl__estack whcl__estack;
typedef struct whcl__sweep_guard whcl__sweep_guard;
typedef struct whcl__func whcl__func;
typedef struct whcl__strace_entry whcl__strace_entry;

/**
   Numeric type used for counting script line and column numbers, as
   well as maximum token lengths. Note that we aim for a 16-bit type
   to shave a few bytes from oft-used token types. As of this writing
   (20200105), the single largest s2 script (its amalgamated unit
   tests) is right at 5400 lines (size=160kb), so any dreams of
   scripts with more than 64k lines would seem to be... ah... somewhat
   ambitious.
*/
typedef uint16_t whcl_linecol_t;

/**
   "Extended" result codes for functions whose results might pass
   through the cwal APIs. By and large this project uses cwal_rc_e
   result codes.
*/
enum whcl_rc_e {
WHCL_RC_placeholder = CWAL_RC_CLIENT_BEGIN1,
/**
   Used internally by routines which visit lists of keys/values
   to provide a mechanism for aborting traversal early without
   triggering an error.
*/
WHCL_RC_END_EACH_ITERATION,
/**
   Internal code which indicates that the a (sub)script has no more
   commands.
 */
WHCL_RC_NO_COMMANDS,

/** Sentinel. */
WHCL_RC_end,
/**
   Client-thrown exceptions which use their own error codes "should
   not" use codes below this value. Put differently, result codes
   starting at this value are guaranteed not to collide with
   CWAL_RC_xxx and WHCL_RC_xxx codes. Keep in mind that numerous
   CWAL_RC_xxx and WHCL_RC_xxx codes have very specific meanings in
   various contexts, so returning, from cwal/s2-bound client-side
   code, values which just happen to collide with those may confuse s2
   and/or cwal into Doing The Wrong Thing. (Examples: CWAL_RC_OOM is
   used solely to propagate out-of-memory (OOM) conditions, and
   handling of CWAL_RC_RETURN is context-specific.)
*/
WHCL_RC_CLIENT_BEGIN = CWAL_RC_CLIENT_BEGIN2
};

/**
   This type is reserved for future use in passing init options to
   whcl_engine_init.
*/
struct whcl_init_opt {
  int dummy;
};

/** Convenience typedef. */
typedef struct whcl_init_opt whcl_init_opt;

/** Initialized-with-defaults whcl_init_opt structure, intended for
    const-copy initialization. */
#define whcl_init_opt_empty_m {0}

/** Initialized-with-defaults whcl_init_opt structure, intended for
    non-const copy initialization. */
extern const whcl_init_opt whcl_init_opt_empty;

/** @internal

    An internal helper type for swapping an whcl_engine's sweep-guard
    state in and out at certain points.

    The docs for this struct assume only one context: that this is
    used embedded in an whcl_engine struct.
*/
struct whcl__sweep_guard {
  /**
     If greater than 0, whcl_engine_sweep() will neither sweep nor
     vacuum.
  */
  short sweep;
  /**
     If greater than 0, whcl_engine_sweep() will not vacuum, but will
     fall back to sweep mode (if not disabled) instead. This HAS to
     be >0 if the client makes use of any non-script-visible values
     which are not otherwise vacuum-proofed and may be needed at a
     time when script code may trigger whcl_engine_sweep() (see
     cwal_value_make_vacuum_proof()).
  */
  short vacuum;
  /**
     Helps keeps track of when to sweep and vacuum - incremented once
     per call to whcl_engine_sweep().
  */
  short sweepTick;
};

/** @internal

    Empty-initialized whcl__sweep_guard state, intended for const
    initialization.
*/
#define whcl__sweep_guard_empty_m {0,0,0}

/**
   An abstraction layer over cwal_scope so that we can store more
   per-scope metadata. This type's definition is only in the public
   API in order to facilitate testing of low-level internals from
   non-core code. This type must be considered 100% opaque by
   client-side code (and it may one day actually be made so).

   This type is not _directly_ responsible for cwal_value lifetime
   management: that's what cwal_scope does. This type is primarily for
   holding scope-level variables (which are, themselves, owned by a
   cwal_scope, but are not referenced as variable by that scope).

   One convenient way of thinking of the difference between cwal_scope
   and whcl_scope is that the former acts (in whcl) purely as a "GC
   root" whereas the latter is responsible for managing named
   variables. whcl_scope relies very much on cwal_scope, but the
   opposite is not true. Historically, cwal client code has always
   combined the two concepts into one via cwal_scope's var-tracking
   capabilities. whcl _not_ doing so is a bit of a risk factor for the
   engine but it's an option which has been on my mind for several
   years but there was no expedient way to test it until whcl came
   along. whcl takes the approach of using as few GC roots as it can,
   in order to make best use of the vacuum algorithm and do a better
   job of keeping certain pathological cases of non-script-reachable
   cyclic data structures from leaking for any significant length of
   time. (That said, such cases have never shown up organically in
   cwal client code, but they are easy to intentionally construct,
   creating cases where memory growth is continuous and unbounded.)

   It _might_ seem intuitive, to anyone remotely familiar with how
   cwal works (who, me?), that whcl_scope instances need to be pushed
   and popped within the confines of a single cwal_scope
   push/pop. Though "code cleanliness" implies as much, that's not
   actually the case. whcl_scope instances _are_ managed in a stack,
   and must be popped in reverse order of their pushing, but they
   manage their cwal_value-level memory via the top-most
   (a.k.a. "global") cwal_scope instance. That is a stark deparature
   from all cwal client code conventions which predate this change
   (2022-03-20(ish)).
*/
struct whcl_scope {
  /* This scope's partner cwal_scope. */
  cwal_scope cs;

  /**
     Storage for scope-local variables. This member is made
     vacuum-proof so long as this scope owns it, but that flag is
     unset when the owning scope pops, so it is safe for higher-level
     code to do things such as propagate this value through
     cwal_scopes without danger of it staying vacuum-proof longer than
     it should.

     This member is rescoped to the top-most cwal_scope as soon as
     it's created.
  */
  cwal_value * props;

  /**
     A "safe zone" to store values which are currently undergoing
     evaluation and need a reference held to them. NEVER upscope this
     array to a different scope: it is internal-only, owned by this
     scope.
  */
  cwal_array * evalHolder;

  /**
     State for propagating property access metadata through the
     internal machinery.

     A 3-entry tuple for the so-called "dot-op" state. For a property
     access in the form X[Y], index 0 is X, index 1 is Y, and index
     2 _might_ be set to a prototype of X (the object in which Y
     was actually found).

     Routines which do dot-op-like property access set [0] to the
     LHS container. Extreme care must be taken to ensure that this
     does not point to a stale value.

     [1] is set by the property-access operations to the RHS (key)
     part of the operation. Used by assignment and/or the unset op to
     get access to the property name, as opposed to the resolved
     property value which the dot operator evaluates to.
    
     We probably don't need [2] for whcl - it was grabbed as-is from
     s2. Here's what it does in s2, though:

     Used to differentiate between prop access wrt to types which
     should bind as 'this' in calls to props resolved as
     functions. Namely:

     ```
     obj.FUNC() // binds 'this'
     array.NUMBER // does not
     hash # KEY // does not
     ```

     All three set [0] and [1]. Only "this-respecting" ops (namely,
     the DOT op) sets [2].
  */
  cwal_tuple * dotHolder;  

  /**
     Internal flags.
  */
  uint16_t flags;

  /**
     whcl_scope_push() depth level, starting at 1. This is
     semi-independent of the cwal_scope level. It will always be <=
     the cwal scope depth level.
  */
  uint16_t level;
};

/** @internal

   Holds a stack of whcl__stokens.
*/
struct whcl__stoken_stack {
  /**
     The top of the stack.

     Maintained via whcl__stoken_stack_push(),
     whcl__stoken_stack_pop(), and friends.
  */
  whcl__stoken * top;
  /**
     Number of items in the stack.
  */
  int size;
};

/** @internal

   Empty-initialized whcl__stoken_stack structure, intended for
   const-copy initialization.
*/
#define whcl__stoken_stack_empty_m {0,0}

/** @internal
   Internal representation of a stack trace entry.
*/
struct whcl__strace_entry {
  /**
     The next entry "up" (older) in the stack.
  */
  whcl__strace_entry * up;
  /**
     The next entry "down" (newer) in the stack.
  */
  whcl__strace_entry * down;
  /**
     The tokenizer active when this entry is created.
   */
  whcl_script * ct;
  /**
     Active token when this entry is created.
  */
  struct whcl_stoken const * pos;
};

/** @internal

   Empty-initilized whcl__strace_entry object.
*/
#define whcl__strace_entry_empty_m {                \
  NULL/*up*/, NULL/*down*/, NULL/*ct*/, NULL/*pos*/  \
}

/** @internal

   An "evaluation stack," a thin wrapper over two whcl__stoken_stacks,
   intended to simplify swapping the stacks in and out of an s2_engine
   while parsing subscripts. whcl internally uses a distinct stack per
   sub-eval, rather than one massive stack, as it's simply easier to
   manage. When the main eval loop is (re)entered, a new eval stack is
   put on the C stack (a local var of the eval function) and whcl_engine
   is pointed to it so we know which stack is current.
*/
struct whcl__estack{
  /** The value stack. */
  whcl__stoken_stack vals;
  /** The operator stack. */
  whcl__stoken_stack ops;
};

/** @internal
   Empty-initialized whcl__estack structure, intended for const-copy
   initialization.
*/
#define whcl__estack_empty_m {whcl__stoken_stack_empty_m, whcl__stoken_stack_empty_m}

/** @internal
   Empty-initialized whcl__estack structure, intended for copy
   initialization.
*/
extern const whcl__estack whcl__estack_empty;

/**
   The primary state for an whcl interpreter. Its contents are to be
   considered private and for internal use only, but it is not hidden
   from client-side code so that it may be stack allocated.
*/
struct whcl_engine {
  /** The underlying cwal engine. */
  cwal_engine * ec;

  /**
     Current evaluation chain. This gets temporarily swapped out when
     processing subscripts (like script-side functions).
  */
  whcl_script * ct;

  /**
     State for propagating property access metadata through the
     internal machinery.

     A 3-entry tuple for the so-called "dot-op" state. For a property
     access in the form X[Y], index 0 is X, index 1 is Y, and index
     2 _might_ be set to a prototype of X (the object in which Y
     was actually found).

     Routines which do dot-op-like property access set [0] to the
     LHS container. Extreme care must be taken to ensure that this
     does not point to a stale value.

     [1] is set by the property-access operations to the RHS (key)
     part of the operation. Used by assignment and/or the unset op to
     get access to the property name, as opposed to the resolved
     property value which the dot operator evaluates to.
    
     We probably don't need [2] for whcl - it was grabbed as-is from
     s2. Here's what it does in s2, though:

     Used to differentiate between prop access wrt to types which
     should bind as 'this' in calls to props resolved as
     functions. Namely:

     ```
     obj.FUNC() // binds 'this'
     array.NUMBER // does not
     hash # KEY // does not
     ```

     All three set [0] and [1]. Only "this-respecting" ops (namely,
     the DOT op) sets [2].
  */
  cwal_tuple * dotHolder;  

  /**
     If greater than 0, "skip-mode" must be honored by all evaluation
     code. Skip-mode basically means "consume as much as you normally
     would, but have (if possible) no side-effects while doing so."
     That allows us to consume tokens with or without actually
     evaluating the results as we go (the engine pretends to evaluate,
     but uses the 'undefined' value for everything, so it doesn't
     actually allocate any values). This is the basis of short-circuit
     evaluation.
  */
  short skipLevel;
  
  /**
     Internal state related to managing whcl_scope instances.

     Potential TODO: instead of allocating scopes in a single list
     which may require reallocation, allocate them in fixed-sized
     chunks of, say, 10 or 20 scopes per chunk. The management
     overhead would be minimal and would eliminate the "reallocation
     invalidation" problem but would still have the property that a
     popped scope's memory will later be reused for a pushed scope.
  */
  struct {
    /**
       Manages an array of whcl_scope in response to cwal_scope
       push/pop events.

       This array holds sub-arrays, each with whcl__scope_block_size
       entries. Though this list may get reallocated (invalidating its
       pointer), the sub-arrays are never reallocated, the express intent
       being that any given whcl_scope pointer remain valid even if
       the number of scopes changes.

       The engine will grow this list as necessary, but won't shrink
       it until the engine is finalized, at which point the memory is
       (of course) freed.
    */
    whcl_scope ** blocks;

    /**
       Points to memory in this->list for the current whcl_scope. Note
       that it is generally NOT SAFE to keep a pointer to an whcl_scope
       from this->list because any resize of the list can invalidate
       it, but this member is only modified in the routines which
       manage the list's size.
    */
    whcl_scope * current;

    /**
       Top-most scope of the cwal_engine.
    */
    cwal_scope * topCwal;

    /**
       The number of entries reserved in this->blocks.
    */
    uint16_t blockCount;

    /**
       The current whcl_scope depth level.
    */
    uint16_t level;

    /**
       A flag used for passing scope flags through certain internals.
    */
    uint16_t nextFlags;

    /**
       The highest scope depth reached.
    */
    uint16_t maxLevel;
  } scopes;

  /**
     Internal buffer for (un)escaping stuff and variable expansion. It
     is important that this never be used from routines which invoke
     script code, as that would risk recursion modifying this buffer
     while higher-up code is using it.
  */
  cwal_buffer escBuf;

  /**
     Current expression evaluation stack.
  */
  whcl__estack estack;

  /**
     Various commonly-used values which the engine stashes away for
     its own use. Their lifetimes are (mostly) managed via
     this->stash.
  */
  struct {
    /**
       Some sort of container used by whcl_stash_get() and
       whcl_stash_set(). This is where we keep internally-allocated
       Values which must not be garbage-collected. This value is made
       vacuum-proof.
    */
    cwal_hash * stash;
    /** Stores function script names. */
    cwal_value * scriptNames;
    /** The word "argv". */
    cwal_value * keyArgv;
    cwal_value * keyColumn;
    /** The word "__command" */
    cwal_value * keyCommand;
    cwal_value * keyLine;
    /** The word "prototype". */
    cwal_value * keyPrototype;
    cwal_value * keyScript;
    cwal_value * keyStackTrace;
    cwal_value * keyThis;
    cwal_value * keyTypename;
    cwal_value * keyUsing;
    cwal_value * keyNewCtor;
    /** The function installed by whcl__install_command_cb(). */
    cwal_value * commandCb;
    /** "whcl" builtin value */
    cwal_value * vWhcl;
    /** whcl[prototypes] object */
    cwal_value * vProtos;
    /** Bitmask of APIs which have been installed via
        whcl_install_XX()-style functions. */
    uint32_t installAPI;
  } cache;

  /**
     Stack-trace state.
  */
  struct {
    /**
       Stored as an optimization for sizing the target
       array when collecting stack traces.
    */
    uint16_t count;
    /**
       whcl will bail out if this call stack limit is reached,
       under the assumption that infinite recursion is going on. Set
       to 0 to disable the limt.

       Notes:

       - Only script-called functions count for this purpose, not
       calls to Functions (be they script functions or not) called via
       native code (cwal_function_call() and friends).
    */
    uint16_t max;
    /**
       Head of the current stack trace.
    */
    whcl__strace_entry * head;
    /**
       tail of the current stack trace.
    */
    whcl__strace_entry * tail;
  } strace;
  
  struct {
    /**
       Every this-many calls to whcl_engine_sweep() should either sweep or
       vacuum. When 0, sweeping is disabled (generally not a good
       idea).
    */
    short sweepInterval;

    /**
       Every this-many sweep attempts will be replaced by a vaccuum
       instead if this->guard.vacuum is 0.
    */
    short vacuumInterval;

    /**
       Total number of whcl_engine_sweep() calls which led to a sweep
       or a vacuum. Thus the total number of sweeps is this number
       minus this->vacuumTotal. (The totals are combined here because
       it simplifies an internal calculation of when to trigger a
       vacuum.)
    */
    unsigned int sweepTotal;
    /**
       Total number of whcl_engine_sweep() calls which led to a vacuum.
    */
    unsigned int vacuumTotal;
    /**
       An internal counter to disable sweep/vacuum for a bit. Very
       possibly not needed (and not currently used (it's a porting
       artifact)), subject for removal.

       2022-04-09: we might want to move this back into whcl_scope
       now that whcl now uses a "more deeply-scoped" approach to
       lifetimes.
    */
    whcl__sweep_guard guard;
  } sweeper;

  struct {
    /**
       A special-case error reporting flag. Set by whcl_interrupt()
       and recognized by the evaluation engine as meaning it should
       stop evaluation.
    */
    int interrupted;
    /**
       0 = disable exception stacktraces, <0 = unlimited,
       >0 = limit stacktraces to that many entries.
     */
    short stacktraceLimit;
    /**
       If on, all script-side assert calls are emmited to the
       configured output channel.
    */
    bool traceAssert;
    /**
       If on, all script-side affirm calls are emmited to the
       configured output channel.
    */
    bool traceAffirm;
    /**
       If on then script-side `__debug {...}` blocks are retained,
       else they are stripped.
    */
    bool enableDebugBlock;
  } flags;

  /**
     State for various internal recycling bins.
  */
  struct {
    /**
       Recycle bin for stokens.
    */
    whcl__stoken_stack stok;

    /**
       The max number of items to keep in the stok recycler stack.
    */
    int maxSTokens;

    /**
       Recycle bin for script-function state.
    */
    struct {
      /** Head of the recyling list. */
      whcl__func * head;
      /** Current number of entries in the list. */
      uint16_t count;
      /** Max allowed entry count before we stop accepting new
          items into the list, and free them instead. */
      uint16_t max;
      /** Internal metrics: number of times whcl__func_alloc()
          was able to fetch from the recycler. */
      uint32_t hits;
      /** Internal metrics: number of times whcl__func_alloc()
          was not able to fetch from the recycler. */
      uint32_t misses;
    } scriptFuncs;
  } recycler;
  /**
     Holds cwal_outputer instances managed by the whcl_ob_push() family
     of functions.
  */
  cwal_list ob;
  /**
     Holds DLL/module handles so that the interpreter can close them
     when it cleans up.
  */
  cwal_list modules;

  /**
     State related to internals needed in the context of making
     a script-bound function call.
  */
  struct {
    /**
       Used for propagating the currently-being-called script
       func through certain depths of the internals.
    */
    whcl__func * currentScriptFunc;

    /**
       Available for reuse.
    */
    cwal_value * _reuse;

    /**
       Gets set by function calls so that the `using` builtin value
       can resolve.
    */
    cwal_value * currentUsing;
  } fcall;

  /**
     State for use with (as it were) `with` blocks.
   */
  struct {
    /**
       Holds a list of (cwal_value*) corresponding to the `with` block
       objects. This is a cwal_list, instead of an array, because it
       needs to be global and needs to _not_ impact value
       lifetimes. This list owns nothing but its own memory and
       manages no value lifetimes.
    */
    cwal_list holder;
    /**
       The current `with` object. It's held in, and potentially owned
       by, its `with`-scope's evalHolder.
    */
    cwal_value * current;
  } with;
};

/** @def WHCL_DEFAULT_SWEEP_INTERVAL

    The default interval (measured in number of commands) for between
    sweep-ups. This must be set very low in dev builds because it
    uncovers value lifetime misuses early. Low values are dog slow, though,
    so we default to a much higher value in non-dev builds.
*/
#if !defined(WHCL_DEFAULT_SWEEP_INTERVAL)
#  if defined(WHCL_AMALGAMATION_BUILD)
#    define WHCL_DEFAULT_SWEEP_INTERVAL 20
#    define WHCL_DEFAULT_VACUUM_INTERVAL 10
#  else
#    define WHCL_DEFAULT_SWEEP_INTERVAL 1
#    define WHCL_DEFAULT_VACUUM_INTERVAL 20
#  endif
#endif

#define whcl_engine_empty_m { \
  NULL/*ec*/,NULL/*ct*/, \
  NULL/*dotHolder*/,0/*skipLevel*/,    \
  {/*scopes*/ \
    NULL/*blocks*/,NULL/*current*/,NULL/*topCwal*/,\
    0/*blockCount*/,0/*level*/, 0/*nextFlags*/, \
    0/*maxLevel*/                             \
  },                                      \
  /*escBuf*/cwal_buffer_empty_m, \
  whcl__estack_empty_m/*estack*/,                       \
  {/*cache*/\
    NULL/*stash*/, NULL/*scriptNames*/, \
    NULL/*keyArgv*/, NULL/*keyColumn*/,         \
    NULL/*keyCommand*/, NULL/*keyLine*/,          \
    NULL/*keyPrototype*/, NULL/*keyScript*/, \
    NULL/*keyStackTrace*/, \
    NULL/*keyThis*/,NULL/*keyTypename*/, NULL/*keyUsing*/,    \
    NULL/*keyNewCtor*/, \
    NULL/*commandCb*/,NULL/*vWhcl*/,NULL/*vProtos*/,        \
    0/*installAPI*/\
  },      \
  {/*strace*/ \
    0/*count*/,50/*max*/,NULL/*head*/,NULL/*tail*/\
  },                                            \
  {/*sweeper*/\
    WHCL_DEFAULT_SWEEP_INTERVAL/*sweepInterval*/, \
    WHCL_DEFAULT_VACUUM_INTERVAL/*vacuumInterval*/,\
    0/*sweepTotal*/,0/*vacuumTotal*/,               \
    whcl__sweep_guard_empty_m/*guard*/\
  },                                                    \
  {/*flags*/ 0/*interrupted*/,-1/*stacktraceLimit*/,\
    false/*traceAssert*/, false/*traceAffirm*/, \
    false/*enableDebugBlock*/                           \
  },                                                        \
  {/*recycler*/                                               \
    whcl__stoken_stack_empty_m/*stok*/,                       \
    50 /*maxSTokens*/,                                      \
    {/*scriptFuncs*/ 0/*head*/, 0/*count*/, 20 /*max*/,\
      0/*hits*/, 0/*misses*/}                               \
  },                                                          \
  cwal_list_empty_m/*ob*/,cwal_list_empty_m/*modules*/,     \
  {/*fcall*/\
    NULL/*currentScriptFunc*/, NULL/*_reuse*/,\
    NULL/*currentUsing*/\
  },                \
  {/*with*/cwal_list_empty_m/*holder*/,NULL/*current*/} \
}

extern const whcl_engine whcl_engine_empty;


/** @internal

   The number of whcl_scope objects to allocate in each block of
   whcl_engine::scopes::blocks. Scopes managed via whcl_scope_push()
   and whcl_scope_pop() are allocated in blocks with this many scopes
   per block.

   This value is solely internal but is exposed in the public API so
   that whclsh can use it when producing memory metrics output.
   (TODO: move that output into a public member function.)
*/
#define whcl__scope_block_size ((uint16_t)20)


/**
   Initializes an whcl_engine instances and transfers ownership of the
   given cwal_engine to it.

   The final argument is optional - it may be NULL. As of this writing
   the options object is reserved for future use.

   Returns 0 on success, non-0 on failure. Regardless of success or
   failure, the caller is obligated to eventually pass el to
   whcl_engine_finalize() to clean up any resources it owns.

   Achtung: any scopes created by the given cwal_engine up to this
   point will be destroyed by this routine. Thus if clients have set
   any scope-level variables, they will be lost. This is necessary in
   order to get and keep the cwal/whcl scope stacks in sync. (Reminder
   to self: that may not be the case. We can hypothetically just tie
   new whcl scopes to existing cwal scopes in this routine.)
*/
int whcl_engine_init(whcl_engine * const el, cwal_engine * const ec,
                     whcl_init_opt const * opt);

/**
   Shuts down the given WHCL interpreter and cleans up all resources
   owned by it and its associated cwal_engine. It is safe to call this
   whether or not whcl_engine_init() has been called, or to call it
   multiple times (the 2nd and subsequent being no-ops) but el may not
   be NULL.
*/
void whcl_engine_finalize(whcl_engine * const el);

/**
   Returns the cwal_engine associated with the given whcl_engine.
   Ownership of the pointer is not modified.
*/
cwal_engine * whcl_engine_cwal(whcl_engine * const el);

/**
   Evaluates the first len bytes of the given whcl script code, using
   cwal_strlen() if len is negative. If pushScope is true, a new cwal
   scope is pushed before the script is evaluated and popped when it
   is finished. scriptName may be NULL but that's not terribly
   helpful: it "should" be the name of the script, either a filename
   or a virtual name (e.g. "REPL input"). That name is used in any
   error reporting regarding the script. If rv is not NULL then on
   success any result value from the script is propagated back to the
   caller via `*rv` (in the conventional cwal manner, with all
   responsibilities and caveats regarding its lifetime, ownership,
   etc.). If rv is NULL then any result is discarded. On error `*rv`
   is not modified.

   Returns 0 on success and any number of different non-zero codes on
   error.

   @see whcl_eval_buffer()
   @see whcl_eval_buffer_take()
   @see whcl_eval_file()
*/
int whcl_eval_cstr( whcl_engine * const el,
                    bool pushScope,
                    char const * scriptName,
                    char const * src,
                    cwal_int_t len,
                    cwal_value ** rv );

/**
   Proxy for whcl_eval_cstr() which simply passes on the given
   buffer's memory to that function.

   ACHTUNG: it is up to the client to ensure that the buffer is not
   itself using memory which might be indirectly modified via the
   script code being executed. If buf is a script-bound buffer
   instance then it is not generically possible to ensure that but
   there are at least two defensive strategies:

   1) Before evaluating the buffer, _move_ its memory to a temporary
      buffer using cwal_buffer_swap_mem(), and eval _that_
      buffer. After evaluation, use cwal_buffer_swap_mem() to swap the
      memory back and cwal_buffer_clear() the temporary buffer.  The
      end effect is that if the buffer passed in to this routine is
      modified by the script code, any changes made to its buffer
      contents (as opposed to its object-level properties) are
      reverted after the script runs. In case it's not clear: it is
      not safe to evaluate a script if its source code is modified or
      reallocated (possibly to a different address) while evaluation
      is underway.

   2) Make a call-local bitwise copy of the buffer's memory and
      evaluate that copy.

   @see whcl_eval_buffer_take()
   @see whcl_eval_cstr()
*/
int whcl_eval_buffer( whcl_engine * const el,
                      bool pushScope,
                      char const * scriptName,
                      cwal_buffer const * const buf,
                      cwal_value **rv );

/**
   This variant of whcl_eval_buffer() differs only in that if the
   compilation phase succeeds, it takes over src's memory, as
   documented for whcl_compile_buffer_take(). If the compilation phase
   fails, src's memory is kept intact. Either way, the caller may pass
   src to cwal_buffer_clear() after calling this to be sure that the
   memory (if it's still there) is freed.

   @see whcl_eval_buffer()
   @see whcl_eval_file()
   @see whcl_eval_cstr()
 */
int whcl_eval_buffer_take( whcl_engine * const el,
                           bool pushScope,
                           char const * scriptName,
                           cwal_buffer * const src,
                           cwal_value ** rv );


/**
   A proxy for whcl_eval_cstr() which evaluates the give file with one
   additional behavior: if the script propagates a `return` result,
   this function assigns the propagating returned result to `*rv` and
   returns 0.
*/
int whcl_eval_file( whcl_engine * const el,
                    bool pushScope,
                    char const * scriptName,
                    cwal_value ** rv );

/**
   Creates a new script scope, declares the given var name with the
   given value, evals that script and, if `rv` is not NULL, assigns
   `*rv` to the result of that script (which may validly be `NULL`).

   The scriptName, src, and srcLen arguments are interpreted as for
   whcl_eval_cstr().

   Returns 0 on success. On error `*rv` may have been assigned to by
   deeper-level code but will have been invalidated by the error
   handling.

   This can be used to perform script-side initialization of native
   objects.
*/
int whcl_eval_cstr_with_var( whcl_engine * const el,
                             char const * varName,
                             cwal_value * const varValue,
                             char const * scriptName,
                             char const * src, cwal_int_t srcLen,
                             cwal_value **rv );

/**
   Evaluates the given script code in a new scope and stores the
   result of that script in the given container, using the given
   property name (which must be a NUL-terminated string). This is
   intended to simplify installation of small/embedded
   script-implemented functions from C code.

   If srcLen is negative, cwal_strlen() is used to calculate
   src's length.

   If the script triggers a 'return' then this routine captures
   that return'd value as the result.

   Returns 0 on success. On error any given number of CWAL_RC_xxx
   codes could be returned.
*/
int whcl_set_from_script( whcl_engine * const el, char const * src,
                          cwal_int_t srcLen, cwal_value * const addResultTo,
                          char const * const propName );

/**
   Works identically to whcl_set_from_script() except that it takes
   its property name as a cwal_value.
*/
int whcl_set_from_script_v( whcl_engine * const el, char const * src,
                            cwal_int_t srcLen, cwal_value * const addResultTo,
                            cwal_value * const propName );


/**
   Intended to be passed a result code from whcl_eval_cstr() or
   whcl_eval_buffer() (or functionally similar calls). If that result
   code is CWAL_RC_RETURN (indicating that a `return` is propagating)
   then this function assigns the propagating return value to `*rv`,
   assigns `*rc` to 0, and returns true, else it returns false and
   does not modify `*rc` or `*rv`. If it returns true, the propagating
   value is removed from the engine's scope-pop propagation. Its
   ownership is effectively passed to the caller, with the usual
   caveat that it may be a value shared in other places. If the caller
   intends to keep it around, they must apply the usual proper care
   and feeding of cwal_values, e.g. give it a reference point and, if
   necessary, make it "vacuum-proof".

   If this function returns true and the result `*rv` is NULL, this
   indicates a violation of the framework's use of the CWAL_RC_RETURN
   result code: functions returning that value are required to store
   the propagating result in cwal_propagating_set().

   Example usage:

   ```
   cwal_value * rv = NULL;
   int rc = whcl_eval_cstr(el, ..., &rv);
   whcl_check_for_return(el, &rc, &rv);
   ...
   ```
*/
bool whcl_check_for_return(whcl_engine * const el,
                           int * const rc,
                           cwal_value **rv);

/**
   Returns the whcl_engine associated with the given cwal_engine, if
   any. Returns NULL if ec is not managed by an whcl_engine.
   (whcl_engine_init() binds its whcl_engine to that state slot.)
*/
whcl_engine * whcl_engine_from_state( cwal_engine * const ec );

/**
   Returns the whcl_engine associated with args->engine, or NULL
   if the engine in question is not managed by an whcl_engine.
*/
whcl_engine * whcl_engine_from_args( cwal_callback_args const * const args );


/** @internal

    (Mostly) internal debugging tool which dumps out info about v (may
    be NULL), with an optional descriptive message. Expects file, func and
    line to be the __FILE__, __func__ resp.  __LINE__ macros. Use the
    whcl__dump_val() macro to simplify that.

    Reminder: v cannot be const b/c some types go through JSON output,
    which requires non-const so that it can catch cycles.
*/
void whcl__dump_value( cwal_value * const v, char const * msg, char const * file,
                      char const * func, int line );

/**
   Equivalent to whcl__dump_value(V, MSG, __func__, __LINE__).
*/
#define whcl__dump_val(V, MSG) whcl__dump_value((V), (MSG), __FILE__, __func__, __LINE__)

/**
   Sets the error state of el, as for cwal_error_set(), and
   returns that call's result value.

   Returns the error code, not 0, on success! Returns some other
   non-0 code on error.

   This routine takes pains not to allocate any memory, which also
   means not generating an error message, if the given error code is
   CWAL_RC_OOM.
*/
int whcl_err_set(whcl_engine * const el, int code, char const * fmt, ... );

/**
   Resets el's error and exception state to the non-error state,
   including clearing the was-interrupted flag.
*/
void whcl_err_reset(whcl_engine * const el);

/**
   If el or its cwal_engine have any current non-exception error
   state, the code for the error is returned, else 0 is returned.  If
   alsoCheckException is true, the engine has no non-exception error,
   and an exception is currently propagating then this function
   returns CWAL_RC_EXCEPTION.
*/
int whcl_err_has(whcl_engine const * const el, bool alsoCheckException);

/**
   If el's cwal_engine has non-exception error state set, its code is
   returned and, if not NULL, the message string is assigned to `*msg`
   and its length is assigned to `*msgLen` (if not NULL). The message
   bytes are owned by the engine and may be invalidated on any further
   API calls. If no error state is set, returns 0 and the other output
   arguments are not modified.
*/
int whcl_err_get(whcl_engine const * const el, char const ** msg,
                 cwal_size_t * msgLen );

/**
   Triggers an exception in el's cwal_engine.

   If el has an active script then this function uses that script to
   add error location information to the exception.

   One exception (as it were) is if the code is CWAL_RC_OOM, in which
   case the error state is set but no exception is thrown and no
   formatted message is generated because doing would presumably
   require allocating memory.

   If the 2nd argument is 0, it uses a code of CWAL_RC_EXCEPTION
   instead.

   @see whcl_toker_throw()
   @see whcl_toker_err()
*/
int whcl_err_throw(whcl_engine * const el, int code, char const * fmt, ... );

/**
   Adds a persistent value to the interpreter. These are stored, for
   lifetime purposes, under the top-most scope with one reference to
   it, and they are not visible to script code. They will be made
   vacuum-proof so long as they are in the stash.

   This is where clients might store, e.g. references to their custom
   native-side prototype objects (optionally (and preferably), they
   may set them as normal variables, but that is not always feasible).

   key must be NUL-terminated.

   Returns 0 on success.
*/
int whcl_stash_set( whcl_engine * const el, char const * const key, cwal_value * const v );

/**
   Equivalent to whcl_stash_set() but takes its key in the form of a
   Value instance.
*/
int whcl_stash_set_v( whcl_engine * const el, cwal_value * const key, cwal_value * const v );

/**
   Fetches a value set with whcl_stash_set(). Returns NULL if not found,
   if !el, or if (!key || !*key). key must be NUL-terminated.
*/
cwal_value * whcl_stash_get( whcl_engine * const el, char const * const key );

/**
   Identical to whcl_stash_get() but accepts the length of the key, in bytes.
*/
cwal_value * whcl_stash_get2( whcl_engine * const el, char const * const key,
                              cwal_size_t keyLen );

/**
   Identical to whcl_stash_get2() but returns the matching cwal_kvp on a
   match (else NULL).
*/
cwal_kvp * whcl_stash_get2_kvp( whcl_engine * const el, char const * const key,
                                cwal_size_t keyLen);

/**
   Identical to whcl_stash_get() but takes its key in the form of a Value
   instance.
*/
cwal_value * whcl_stash_get_v( whcl_engine * const el, cwal_value const * const key );

/**
   If cwal_value_is_unique() is true for v then this returns the value
   of passing v to cwal_unique_wrapped_get(), else it returns v.

*/
cwal_value * whcl_value_unwrap( cwal_value * const v );

/**
   Const-friendly brother of whcl_value_unwrap().
*/
cwal_value const * whcl_value_unwrap_c( cwal_value const * const v );

/**
   Sends v to the output channel configured for el's cwal_engine. The
   handling is type-dependent and not configurable. Returns 0 on
   success or any of several non-0 codes on error, e.g. propagated
   from JSON conversion routines or the I/O layer.
*/
int whcl_value_output( whcl_engine * const el, cwal_value * const v );

/**
   Tells el to _possibly_ sweep or vacuum, depending on various
   internal state.
*/
void whcl_engine_sweep( whcl_engine * const el );

/**
   Looks for a property in self (if not NULL) or the current scope
   lookup chain. This takes into account numerous special cases,
   including:

   - If self is not NULL and the key is "__prototype" then self's
     prototype value is returned.

   - If self is an array or string and key is an integer, the
     array/char index at that position is returned, or the undefined
     value if the number is out of range.

   If no property is found;

   - If failIfNotFound is true then CWAL_RC_NOT_FOUND is returned and
     an exception is thrown.

   - If failIfNotFound is false then `*rv` (if rv is not NULL) is set
     to NULL and 0 is returned.

   If one is found, `*rv` (if rv is not NULL) is assigned to it and 0
   is returned.
*/
int whcl_lookup_vsym_v(whcl_engine * const el,
                       cwal_value * self,
                       cwal_value const * const key,
                       bool failIfNotFound,
                       cwal_value **rv);

/**
   C-string counterpart of whcl_lookup_vsym_v().
*/
int whcl_lookup_vsym(whcl_engine * const el, cwal_value * const self,
                     char const * key, cwal_int_t keyLen,
                     bool failIfNotFound, cwal_value **rv);

/**
   Returns el's current scope. It _must not_ be modified by client
   code. The only legal operations client code can perform with this
   is to pass it to a public API function which takes a whcl_scope
   argument.

   ***ACHTUNG***: _never_ hold a copy of the returned pointer because
   the pointer may become invalidated by any evaluation of script
   code. Operations like whcl_set_v() and friends do not invalidate
   it, but anything which evaluates script code, invokes a
   script-bound function, or calls whcl_scope_push() may, If a scope
   may need to be referenced across calls to script code, grab the
   scope, extract its level using whcl_scope_level(), run the script
   code, then fetch the scope at that level again using
   whcl_scope_for_level(). More often than not, the end result will be
   the same pointer, but that is not _always_ the case, so _never_
   rely on it.

   @see whcl_scope_parent()
   @see whcl_scope_level()
   @see whcl_scope_for_level()
   @see whcl_scope_cwal()
*/
whcl_scope * whcl_scope_current( whcl_engine const * const el );

/**
   Returns the cwal scope for the given whcl_scope. (There is a
   1-to-1 relationship.) See whcl_scope_current() for important
   pointer lifetime details.
*/
cwal_scope * whcl_scope_cwal(whcl_scope * const s);


/**
   Returns the parent scope of the given scope or the parent of el's
   current scope if s is NULL. If the current scope is the top-most
   scope, the function will return NULL. See whcl_scope_current() for
   an important warning about _not_ holding this pointer for any
   longer than necessary.

   @see whcl_scope_current()
   @see whcl_scope_level()
   @see whcl_scope_for_level()
 */
whcl_scope * whcl_scope_parent( whcl_engine const * const el,
                                 whcl_scope * const s );

/**
   Returns s's scope level, with 1 being the level of the top-most
   ("global") scope.

   @see whcl_scope_current()
   @see whcl_scope_parent()
   @see whcl_scope_for_level()
 */
uint16_t whcl_scope_level(whcl_scope const * const s);

/**
   Returns the scope for the 1-based level number (1 being the
   top-most/global scope) or NULL if level is out of range.

   @see whcl_scope_current()
   @see whcl_scope_parent()
   @see whcl_scope_level()
*/
whcl_scope * whcl_scope_for_level( whcl_engine const * const el,
                                    uint16_t level );

/**
   Searches for the given variable, starting at the given scope (or
   el's current scope if sc is NULL), returning it if found. If not
   found and `searchParents` is true then the search continues as
   follows:

   Each parent scope is checked until a function call scope is hit
   (and searched). If that call scope does not contain the var then it
   skips to the global scope and continues searching there.

   In other words: it will search within the current function call's
   scope and subscopes but will not cross a call scope boundary except
   to skip to the top-most (global) scope.

   If `foundIn` is not NULL and this function returns non-NULL then
   `*foundIn` will be set to the scope in which the var was found. If
   it returns NULL then no match was found and `*foundIn` is not
   modified.
*/
cwal_value * whcl_var_search_v(whcl_engine * const el,
                               whcl_scope * sc,
                               bool searchParents,
                               cwal_value const * const key,
                               whcl_scope ** foundIn );

/**
   C-string counterpart of whcl_var_search_v(). If keyLen is negative
   then cwal_strlen() is used to calculate its length. This function
   must allocate a cwal_value string to perform the search, and simply
   returns NULL if that allocation fails, so it is not possible to
   distinguish a failed search from an OOM condition with this
   function.
*/
cwal_value * whcl_var_search(whcl_engine * const el,
                             whcl_scope * sc,
                             bool searchParents,
                             char const * key,
                             cwal_int_t keyLen,
                             whcl_scope ** foundIn );

/**
   This sets a property on self (if not NULL) or a scope (the one
   containing the given key, or the current scope if not already
   set). Returns 0 on success. If a scope is the target then this
   function behaves like whcl_scope_set_with_flags_v() (namely,
   in that it does not require that the given key be declared
   as a scope variable).
   
   The propertyFlags argument may be any of the CWAL_VAR_F_xxx
   values. In practice it is either 0 or CWAL_VAR_F_CONST.

   This routine takes into account the following special cases:

   - If self is an array and key is an integer>=0 then this sets
     the given array index.

   - If key is "__prototype" and self is capable of having its own
     prototype then the prototype is set.

   - A `v` of NULL unsets the property except in the case of
     `array[integer]` access, in which case it assigns the given index
     to a C NULL. Note that script code cannot see NULL values and
     care must be taken to translate them to cwal_value_undefined()
     for purposes of passing them to script code. If the property does
     not exist, unsetting it is a harmless no-op. If a scope is the
     target, `v` is NULL, and the given var is not found, no error is
     triggered.

   Non-OOM errors are reported via whcl_err_throw(), as opposed to
   whcl_err_set(), as they tend to be constness or type-related
   violations which should arguably not outright kill a script.

   @see whcl_get()
   @see whcl_get_v()
*/
int whcl_set_with_flags_v(whcl_engine * const el,
                          cwal_value * self,
                          cwal_value * const key,
                          cwal_value * const v,
                          uint16_t propertyFlags);

/**
   The C-string variant of whcl_set_with_flags_v(), differing only in
   that it takes its key from the frist keyLen bytes of the given key
   string. If keyLen is negative then cwal_strlen() is used to
   calculate it.
*/
int whcl_set_with_flags(whcl_engine * const el,
                        cwal_value * self,
                        char const * key,
                        cwal_int_t keyLen, 
                        cwal_value * const v,
                        uint16_t propertyFlags);


/**
   Equivalent to calling whcl_set_with_flags_v() with
   a final argument of 0.

   @see whcl_set()
   @see whcl_get()
   @see whcl_get_v()
*/
int whcl_set_v(whcl_engine * const el,
               cwal_value * self,
               cwal_value * const key,
               cwal_value * const v);

/**
   C-string variant of whcl_set_v().

   If keyLen is negative, cwal_strlen() is used
   to calculate the key's length.

   @see whcl_set_v()
   @see whcl_get()
   @see whcl_get_v()
*/
int whcl_set(whcl_engine * const el, cwal_value * self,
             char const * key,
             cwal_int_t keyLen, cwal_value * const v);

/**
   This is equivalent to passing the first 3 arguments to
   `whcl_lookup_vsym_v(..., false, X)` and returning that X.
   Does not set any error state if no entry is found.
   
   @see whcl_lookup_vsym_v()
   @see whcl_get()
*/
cwal_value * whcl_get_v(whcl_engine * const el,
                        cwal_value * self,
                        cwal_value const * const key);

/**
   C-string counterpart of whcl_get_v().
*/
cwal_value * whcl_get(whcl_engine * const el,
                      cwal_value * self,
                      char const * const key,
                      cwal_int_t keyLen);

/**
   Sets the given key/value pain in a scope in the current scope
   chain. If scope is non-NULL, it becomes the target, else el's
   current scope is used. If searchParents is true then any prior
   scopes in the scope chain are checked for that var first and the
   one containing it is where the var is set. If searchParents is
   false, or no prior scope in the chain has the given key, the value
   is set in the given scope.

   The propertyFlags argument may be any of the CWAL_VAR_F_xxx
   values. In practice it is either 0 or CWAL_VAR_F_CONST.

   If v is NULL then the property, if found, gets unset.

   Returns 0 on success. On error an exception is thrown and non-zero
   is returned. It can return any of the numerous codes which are
   documented for cwal_prop_set_with_flags_v() except for
   CWAL_RC_NOT_FOUND (when v is NULL), which this function treats as a
   non-error.

   This function does NOT enforce that the given key has already been
   declared as a variable. If a given context requires that to be the
   case then it needs to check for the var first using
   whcl_var_search() or whcl_var_search_v(). It does, however,
   necessarily search for the given key so that it can (if found) be
   re-set in the the scope which already holds it.

   Potential TODO: switch from an exception to a non-exception error.
*/
int whcl_scope_set_with_flags_v(whcl_engine * const el,
                                whcl_scope * scope,
                                bool searchParents,
                                cwal_value * const key,
                                cwal_value * v,
                                uint16_t propertyFlags);

/**
   Equivalent to calling whcl_scope_set_with_flags_v() with a final
   argument of 0.
*/
int whcl_scope_set_v(whcl_engine * const el,
                     whcl_scope * scope,
                     bool searchParents,
                     cwal_value * const key,
                     cwal_value * const v);

/**
   "Declares" a variable in the given scope, or el's current scope if
   the given scope is NULL. If the given variable is already in the
   scope. this function returns non-0 and updates el's error state
   with an informative message. If isConst is true then the variable
   is creates with the "const" flag. If v is NULL then
   cwal_value_undefined() is used, but v may not be NULL if isConst is
   true.
*/
int whcl_var_decl_v(whcl_engine * const el, whcl_scope * const scope,
                    bool isConst, cwal_value * const key,
                    cwal_value * const v);

/**
   C-string variant of whcl_var_decl_v(), differing only in that it
   takes its property key as a C string. keyLen is the length of the
   key string. If keyLen is negative, cwal_strlen() is used to
   calculate the key's length.
*/
int whcl_var_decl(whcl_engine * const el, whcl_scope * const scope,
                  bool isConst, char const * key, cwal_int_t keyLen,
                  cwal_value * const v);

/**
   Pushes a new whcl scope (as opposed to cwal scope) onto the stack.
   This only returns NULL if allocation fails. Calling this obligates
   the caller call whcl_scope_pop() one time within the same general
   call context. Results are undefined if this rule is not observed.

   Ideally, all whcl client code "should" use whcl_scope_push(),
   rather than cwal_scope_push(), for scope management, but many APIs
   internally use cwal_scope for local lifetime management. When
   the two APIs are used together, they MUST be pushed/popped
   in matching pairs:

   @code
   cwal_scope_push(...);
   whcl_scope_push(...);
   whcl_scope_pop(...);
   cwal_scope_pop(...);
   @endcode

   Or the other way around, with the cwal scope inside the whcl scope,
   but their lifetimes MUST NOT cross:

   @code
   // DON'T do this..
   cwal_scope_push(...);
   whcl_scope_push(...);
   cwal_scope_pop(...);
   whcl_scope_pop(...);
   @endcode

   The returned pointer remains valid until a matching call to
   whcl_scope_pop() is made.

   Variables set via whcl_var_decl_v() and whcl_scope_set_v() and the
   like are set in the context of a whcl_scope, and all variables
   owned by the scope which are not propagated out by the time the
   scope is popped will be cleaned up by the scope (with the caveat
   that its underlying property storage object (see
   whcl_scope_props()) may change ownership/management before the
   scope is popped). cwal-level scopes are the final arbiter of value
   lifetimes: it is possible that a value owned by a whcl scope cannot
   be destroyed due to other references being held to it, but
   destruction (popping) of the cwal-level scope which owns it will
   destroy it.

   @see whcl_scope_pop()
*/
whcl_scope * whcl_scope_push(whcl_engine * const el);

/**
   Pops the given scope from el. The 2nd argument may be NULL, in
   which case the current scope is popped, but passing the scope
   returned from whcl_scope_push() causes this function to assert()
   that the scope push/pop level expectations match, so that usage is
   recommended.

   If the 3rd argument is non-NULL then it ensures that if propagate
   is owned by the current scope then it survives the popping process
   (its final refcount will be the same as it was before this call and
   it is not reparented to a new scope if it's already managed by an
   older scope). For example, if the value passed as the 3rd argument
   is currently stored as a variable in the current scope and that is
   the only reference to it, it will survive the cleanup of those
   variables and be left with no reference count point (but it will
   not be immediately destroyed by the virtue of having been passed as
   the second argument to this function). Such behavior is the basis
   of implementing propagate-on-return semantics.

   This function semantically invalidates the given (or current) scope
   pointer. The engine is free to keep that memory around for re-use,
   but whether or not it does so is not part of the API.  Client code
   _must_ treat that scope object as if this function frees it, even
   if this function does not really do so.

   The final argument may be NULL and _must_ be NULL if the final
   scope is being popped (else an assert() may be triggered). (That
   said, it is illegal to pop the final scope from client-level code,
   as that one is pushed by the engine during its initialization.)

   @see whcl_scope_push()
*/
void whcl_scope_pop(whcl_engine * const el, whcl_scope * const sc,
                    cwal_value * const propagate);

/**
   Returns the given scope's properties object, creating it if needed.
   Returns NULL only on OOM. If sc is NULL, el's current scope is
   used. The returned value is where scope-level variables are
   stored.

   The returned value is ostensibly owned by the given scope, insofar
   as any ref-counted value is owned by anyone.

   Sidebar: when a properties object is created via this API, it is
   made "vacuum-proof" (see cwal_value_make_vacuum_proof()) and that
   flag is removed when the scope is popped (whcl_scope_pop()). Thus
   if client code takes a reference to this object (including refcount
   point) and that reference outlives the scope, the properties object
   loses any inherent vacuum safety the moment the scope pops. This is
   almost always the desired behavior, but particularly unusual client
   code may need to be aware of that any behave appropriately, either
   by making the properties object explicitly vacuum-proof or by
   adding it to a container which is itself vacuum-proof (either
   directly or by virtue of being contained in (perhaps indirectly) a
   vacuum-proof container).

   @see whcl_scope_push()
   @see whcl_scope_pop()
*/
cwal_value * whcl_scope_props(whcl_engine * const el,
                              whcl_scope * const sc);

/**
   A (somewhat) convenience form of whcl_set() which passes
   the (callback, state, stateDtor, stateTypeID) flags to
   cwal_new_function() and installs the resulting function int the tgt
   container value (or the current scope if tgt is NULL).

   If nameLen is <0 then cwal_strlen() is used to calculate its
   length. propertyFlags may be 0 or a mask of CWAL_VAR_F_xxx flags.

   Returns 0 on success or a CWAL_RC_xxx code on error.
 */
int whcl_install_callback( whcl_engine * const el, cwal_value * const tgt,
                           cwal_callback_f callback,
                           char const * const name,
                           cwal_int_t nameLen, 
                           uint16_t propertyFlags,
                           void * const state,
                           cwal_finalizer_f stateDtor,
                           void const * const stateTypeID );


/**
   Flags for use with whcl_dump_tokens().
*/
enum whcl_dump_tokens_e {
/**
   Outputs additional details, e.g. the content of each token.
*/
WHCL_DUMP_TOKENS_VERBOSE = 0x01,
/**
   Do not rewind tokenizer before dumping.
*/
WHCL_DUMP_TOKENS_NO_REWIND = 0x02,
/**
   Only output to the next EOX (end-of-expression) at the current
   token's level, but still output recursively.
*/
WHCL_DUMP_TOKENS_TO_EOX = 0x04,
/**
   Include virtual EOF tokens in the output. Most scripts have many of
   these. They clutter up the output but also provide a more complete
   picture of how this framework sees the world.
*/
WHCL_DUMP_TOKENS_EOFS = 0x10,
/**
   Emits some metrics after the dump.
*/
WHCL_DUMP_TOKENS_METRICS = 0x20
};
/**
   Dumps out all tokens in the given script. By default
   this rewinds ct but it always resets the token
   position when it's done.
*/
void whcl_dump_tokens(whcl_engine * const el,
                      whcl_script * const ct,
                      uint32_t flags);


/**
   Appends the given value to the given buffer in string form.
   Returns 0 on success.

   Objects and Arrays/Tuples are buffered in JSON form, and this
   function will fail if traversing them discovers cycles.
*/
int whcl_value_to_buffer( whcl_engine * const el, cwal_buffer * const buf,
                          cwal_value * const arg );

/**
   A cwal_callback_f() impl which uses whcl_value_to_buffer() to convert
   args->self to a string. The other arguments are ignored.
*/
int whcl_cb_value_to_string( cwal_callback_args const * args, cwal_value **rv );

/**
   A cwal_callback_f() impl which passes args->self through
   cwal_json_output() to produce JSON output. Assigns the resulting
   string value to *rv. If args->argc is not 0 then args->argv[0] is
   used to specify the indentation, as per cwal_json_output_opt.

   Script usage depends on whether or not args->self is-a (or inherits)
   Buffer. If not, then the function's usage abstractly looks like:

   ```
   string t = self.toJSONToken([indentation=0 [, cyclesAsStrings=false]])
   ```

   and returns the JSON-ified from of self.
   
   If self is-a Buffer, it looks like:

   ```
   self.toJSONToken(Value v [, indentation=0 [, cyclesAsStrings=false]])
   ```

   It appends the JSON form of v to self and returns self.

   If cyclesAsStrings is true, recursion/cycles are rendered in some
   useless (debugging only) string form, otherwise cycles cause an
   exception to be thrown.
*/
int whcl_cb_this_to_json_token( cwal_callback_args const * args, cwal_value **rv );

/**
   A cwal_callback_f() which passes its first argument through
   cwal_json_output() to produce JSON output. Assigns the resulting
   string value to *rv. If args->argc is greater than 1 then the
   second argument specifies the indentation: a positive number for
   that many spaces per level and a negative number for that many hard
   tabs per level.

   ```
   string t = toJSONToken(value, [indentation=0 [, cyclesAsStrings=false]])
   ```

   and returns the JSON-ified from of the value.

   If cyclesAsStrings is true, recursion/cycles are rendered in some
   useless (debugging only) string form, otherwise cycles cause an
   exception to be thrown.
*/
int whcl_cb_arg_to_json_token( cwal_callback_args const * args, cwal_value **rv );

/**
   A cwal_callback_f() implementation which parses JSON string input.

   Script usage:

   ```
   var json = '{"a":"hi!"}';
   var obj = thisFunction(json)
   ```
*/
int whcl_cb_json_parse_string( cwal_callback_args const * args, cwal_value **rv );

/**
   The file-based counterpart of whcl_cb_json_parse_string(). It works
   identically except that it takes a filename as input instead of a
   JSON string.
*/
int whcl_cb_json_parse_file( cwal_callback_args const * args, cwal_value **rv );

/**
   Tries to parse a given C-string as an integer or double value.

   srcLen must be the string length of str or a negative value
   (in which case cwal_strlen() is used to count the length).

   If the string can be parsed to an integer or double value, *rv is
   set to the newly-created value and 0 is returned.  If no conversion
   can be made, *rv is set to NULL and 0 is returned. On OOM error,
   CWAL_RC_OOM is returned.
*/
int whcl_cstr_parse_number( cwal_engine * const e, char const * src,
                            cwal_int_t slen, cwal_value ** rv );


/**
   Flags for use with with cwal_container_client_flags_set() and
   friends.
*/
enum whcl_container_flags_e {
/**
   EXPERIMENTAL. SUBJECT TO CHANGE OR REMOVAL.

   Tells the whcl eval engine that this function should not be treated
   as a boundary for symbol resolution purposes.
*/
WHCL_CONTAINER_F_XSYM = 0x01
};

/**
   Utility type for use with whcl_install_functions(), allowing simple
   installation of a series of callbacks in one go.
*/
struct whcl_func_def {
  /** Name to install the function as. */
  char const * name;
  /** Callback function. */
  cwal_callback_f callback;
  /** State for use with cwal_function_state_get(). */
  void * state;
  /** State destructor for cleaning up this->state when this function
      is finalized. In practice it is very rare for a function to have
      state which requires a finalizer. */
  cwal_finalizer_f stateDtor;
  /** Type ID for use with cwal_function_state_get(). */
  void const * stateTypeID;
  /** Flags from the whcl_container_flags_e enum. */
  cwal_flags16_t cwalContainerFlags;
};
#define WHCL_FUNC6(NAME,CALLBACK,STATE,StateDtor,StateTypeId,CwalContainerFlags)  \
  {NAME,CALLBACK,STATE,StateDtor,StateTypeId, CwalContainerFlags}
/**
   Convenience macro for initializing an whcl_func_def entry.
*/
#define WHCL_FUNC5(NAME,CALLBACK,STATE,StateDtor,StateTypeId) \
  WHCL_FUNC6(NAME,CALLBACK,STATE,StateDtor,StateTypeId, 0)
/**
   Convenience macro for initializing an whcl_func_def entry.
*/
#define WHCL_FUNC2(NAME,CALLBACK) WHCL_FUNC5(NAME,CALLBACK,0,0,0)
/**
   Convenience macro which is requivalent to WHCL_FUNC2 but also sets
   the WHCL_CONTAINER_F_XSYM flag on the function.
*/
#define WHCL_FUNC2_XSYM(NAME,CALLBACK) \
  WHCL_FUNC6(NAME,CALLBACK,0,0,0,WHCL_CONTAINER_F_XSYM)

/**
   Empty-initialized const whcl_func_def struct.
*/
#define whcl_func_def_empty_m {0,0,0,0,0,0}
/** Convenience typedef. */
typedef struct whcl_func_def whcl_func_def;

/**
   Installs a list of cwal callback functions into the given target
   container value. defs must be an array of whcl_func_def objects
   terminated by an entry with a NULL name field (most simply, use
   whcl_func_def_empty_m to intialize the final element). All member
   pointers in each entry must be valid, and 0 is (generally speaking)
   valid for all but the name and callback fields.

   If propertyFlags is not 0 then each property gets set with those
   flags, as per cwal_prop_set_with_flags().

   If tgt is NULL then the functions are installed into the _current_
   scope. Note that outside of initialization of the engine, the
   current scope is very likely not the top-most, and may well
   disappear soon (e.g. call this function with a NULL tgt from within
   a cwal_callback_f() implementation will only install these for the
   duration of the current function call!).

   Returns 0 on success, a non-0 CWAL_RC_xxx code on error.

   Example:

   ```
   const whcl_func_def funcs[] = {
     WHCL_FUNC2("myFunc1", my_callback_1),
     WHCL_FUNC2("myFunc2", my_callback_2),
     whcl_func_def_empty_m // IMPORTANT that the list end with this!
   };
   int rc = whcl_install_functions(se, myObj, funcs, CWAL_VAR_F_CONST);
   ```
*/
int whcl_install_functions( whcl_engine * const el, cwal_value * const tgt,
                            whcl_func_def const * defs,
                            uint16_t propertyFlags );

/**
   If ctrl-c handling is enabled at compile time, this function sets
   el to be the one listening for ctrl-C events, which it will report
   as an error. If not, this is a no-op.

   It goes without saying that using this in a multi-threaded app is
   fraught with peril.
*/
void whcl_set_interrupt_handlable( whcl_engine * const el );

/**
   This sets el's error state to CWAL_RC_INTERRUPTED, a flag it checks
   for at various points during evaluation and which causes the
   interpretter to behave essentially as if 'exit' had been used (but
   without a result value). Calling this is not a guaranty that the
   engine will stop processing its current script - there are corner
   cases where the flag can get "lost" during evaluation.

   On success, returns CWAL_RC_INTERRUPTED. On error (an allocation
   error generating a message string), it will return another non-0
   code (likely CWAL_RC_OOM).

   This is not strictly thread safe, but "should" be okay to call from
   a separate thread (e.g. a UI) in most cases, though (depending on
   timing) it might not have an effect. Known potential race
   conditions include, but are not necessarily limited to:

   - el is clearing its error state (which will clear the
     is-interrupted flag). It resets its error state internally for
     "non-error errors" which propagate a ways, like "return" and
     "throw", but those keywords "should" catch and propagate this
     condition, trumping their own. The lowest-level eval handler
     checks at every sensible opportunity.

   - el is cleaning up (inside s2_engine_finalize()), in which case
     accessing it might (depending on the timing)lead to an illegal
     memory access (if dynamically allocated) or a useless but
     harmless[1] access if it's stack-allocated. [1]=so long as the
     memory itself is still legal to access (e.g. app-level/static).

   - Third-party bindings may clear el's error state (which includes
     this flag) indescriminately, without being aware of this
     condition.


   There are likely others.
*/
int whcl_interrupt( whcl_engine * const el );

/**
   The reverse of cwal_rc_cstr(), this function tries to find a
   CWAL_RC_xxx error code for a string form of an enum entry's name,
   e.g. "CWAL_RC_OOM". On success it returns the code value via
   `*code` and returns true, else it does not modify `*code` and
   returns false.

   The first argument may be the full form of a CWAL_RC_... value or
   the same without the CWAL_RC_ prefix. CWAL_SCR_ values may be
   passed in without the CWAL_ prefix but require the SCR_ part. e.g.
   "MISUSE" may be used in place of "CWAL_RC_MISUSE" and "SCR_EOF" may
   be used in place of "CWAL_SCR_EOF"

   The 2nd argument is the length of the first one. If it is negative,
   cwal_strlen() is used to calculate it.

   This is an O(1) operation, performing one hash calculation and (at
   most) one string comparison.
*/
bool whcl_cstr_to_rc(char const *str, cwal_int_t len, int * code);

/**
   Returns the core object prototype, or NULL on allocation
   error. This object can be extended to add features to
   all values which derive from the Object class.
*/
cwal_value * whcl_prototype_object(whcl_engine * const el);
/** 
   Returns the core function prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_function(whcl_engine * const el);
/** 
   Returns the core array prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_array(whcl_engine * const el);
/** 
   Returns the core tuple prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_tuple(whcl_engine * const el);
/** 
   Returns the core buffer prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_buffer(whcl_engine * const el);
/** 
   Returns the core string prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_string(whcl_engine * const el);
/** 
   Returns the core integer prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_integer(whcl_engine * const el);
/** 
   Returns the core double prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_double(whcl_engine * const el);
/** 
    On success, returns 0 and assigns the PathFinder (pf) prototype to
    `*rv`. On error, returns non-0 and does not modify `*rv`. If rv
    is NULL this function returns 0 if it initializes the prototype
    or if it has already been initialized.

    Trivia: this function's signature is different from the other
    whcl_prototype_xxx() functions because this prototype has error
    modes other than an OOM condition and needs to be able to
    propagated them without it being mistaken downstream for an OOM.
*/
int whcl_prototype_pf(whcl_engine * const el, cwal_value ** rv);
/** 
   Returns the Exception prototype, or NULL on allocation error.
*/
cwal_value * whcl_prototype_exception(whcl_engine * const el);
  
/**
   cwal_callback_f() implementation which acts as a proxy for
   cwal_value_compare().

   If passed two values it passes those to cwal_value_compare(), else
   it passes args->self and args->argv[0] to it (in that order).

   If both the lhs/rhs values are the same pointer, they of course
   compare as equivalent (without calling cwal_value_compare()).

   If passed 3 values and the 3rd is truthy, it checks to see if the
   values have the same type. If they do not, it returns an arbitrary
   (but stable) non-0 value. If they do have the same type, or if the
   3rd parameter is falsy, it behaves as if passed 2 parameters.

   Throws on usage error, else returns the result of the comparison
   via *rv.

   Script signatures:

   ```
   integer compare(value rhs); // requires a 'this'
   integer compare(value lhs, value rhs[, boolean typeStrict = false]);
   ```

   The latter form works independently of the current 'this'.
*/
int whcl_cb_value_compare( cwal_callback_args const * args, cwal_value **rv );

/**
   Returns true if the given value is a string and matches the first
   fLen bytes of f. If fLen is negative, cwal_strlen() is used to
   calculate it.
 */
bool whcl_val_is_flag( cwal_value const * const v, char const * const f,
                       cwal_int_t fLen );

/**
   A helper for for cwal_callback_f() flag-style argument handling. If
   `*argNdx<args->argc` and `args->argv[*argNdx]` is a string which
   starts with `-` and has a length of 2 or more, this function
   increments `*argNdx`, sets `*flag` to the string value of that
   argument, and `*len` to its length. Otherwise it returns false.

   Either of `flag` and `len` may be NULL. `argNdx` may not be NULL.

   Note that this routine cannot tell the difference between a
   string-format negative number and a flag.
*/
bool whcl_arg_has_flag( cwal_callback_args const * args,
                        uint16_t * argNdx, char const **flag,
                        cwal_size_t * len);

/**
   This may optionally be called by clients, soon after
   whcl_engine_init(), to install the core-most data types and APIs
   into el. Without this, the language itself will function but it
   will be missing many type-level and utility APIs which this
   installs. Returns 0 on success. The "only conceivable" error case
   is OOM, in which case it returns CWAL_RC_OOM. If called more than
   once, this is a no-op.

   This also installs the script-side function `whcl[install-api]`
   which works as documented for whcl_install_api().
*/
int whcl_install_core_apis(whcl_engine * const el);

/**
   Works just like the C-standard fopen() except that if zName is "-"
   then it returns either stdin or stdout, depending on whether zMode
   contains a "r" (meaning stdin) or one of "w", "a", or "+" (meaning
   stdout). Neither argument may be NULL.

   Returns a newly-opened file handle on success, else NULL. The
   handle should eventually be passed to whcl_fclose() to close it. It
   may optinoally be passed to fclose() instead, but that routine will
   unconditionally close the handle, whereas this one is a no-op for
   the standard streams.

   @see whcl_fclose()
*/
FILE *whcl_fopen(const char *zName, const char *zMode);

/**
   If the given FILE handle is one of (stdin, stdout, stderr), this is
   a no-op, else it passes the file handle to fclose().

   @see whcl_fopen()
*/
void whcl_fclose(FILE *);

/**
   The "Path Finder" class is a utility for searching the filesystem
   for files matching a set of common prefixes and/or suffixes
   (i.e. directories and file extensions).

   @see whcl_new_pf()
   @see whcl_pf_value()
   @see whcl_value_pf()
   @see whcl_value_pf_part()
   @see whcl_pf_dir_add()
   @see whcl_pf_dir_add_v()
   @see whcl_pf_dirs()
   @see whcl_pf_dirs_set()
   @see whcl_pf_ext_add()
   @see whcl_pf_ext_add_v()
   @see whcl_pf_exts_set()
   @see whcl_pf_exts()
   @see whcl_pf_search()
*/
typedef struct whcl_pf whcl_pf;


/**
   Creates a new PathFinder instance. PathFinders are bound to cwal as
   cwal_native instances and are initially owned by the currently
   active scope. Returns NULL on allocation error.
*/
whcl_pf * whcl_pf_new(whcl_engine * const el);

/**
   Returns the underlying cwal_value which acts as pf's "this".

   pf may not be NULL.

   @see whcl_value_pf()
   @see whcl_value_pf_part()
*/
cwal_value * whcl_pf_value(whcl_pf const * const pf);

/**
   If v is-a PathFinder or derives from it, this function returns the
   whcl_pf part of v or one of its prototypes.

   It is legal for v to be NULL.

   @see whcl_value_pf()
   @see whcl_pf_value()
*/
whcl_pf * whcl_value_pf_part(cwal_value const * v);

/**
   If v was created via whcl_pf_new() then this function returns
   its whcl_pf counterpart, else it returns NULL.

   It is legal for v to be NULL.

   @see whcl_pf_value()
   @see whcl_value_pf_part()
*/
whcl_pf * whcl_value_pf(cwal_value const * const v);


/**
   Adds a directory to pf's search path. dir must be at least dirLen bytes
   and may be an empty but may not be NULL. If dirLen is negative,
   cwal_strlen() is used to calculate it.

   Returns 0 on success.

   @see whcl_pf_dir_add_v()
*/
int whcl_pf_dir_add( whcl_pf * const pf, char const * dir, cwal_int_t dirLen);

/**
   Adds a file suffix (extension) to pf's search path. ext must be at
   least extLen bytes and may be an empty but may not be NULL.  If
   extLen is negative, cwal_strlen() is used to calculate it.

   Returns 0 on success.

   @see whcl_pf_ext_add_v()
*/
int whcl_pf_ext_add( whcl_pf * const pf, char const * ext, cwal_int_t extLen);

/**
   Variant of whcl_pf_dir_add() which takes its directory part in the
   form of a cwal_value.

   Returns 0 on success, CWAL_RC_MISUSE if !v, CWAL_RC_OOM on OOM.
*/
int whcl_pf_dir_add_v( whcl_pf * const pf, cwal_value * const v );

/**
   Variant of whcl_pf_ext_add() which takes its directory part in the
   form of a cwal_value.

   Returns 0 on success, CWAL_RC_MISUSE if !v, CWAL_RC_OOM on OOM.
*/
int whcl_pf_ext_add_v( whcl_pf * const pf, cwal_value * const v );

/**
   Replaces pf's directory list with the given one.

   Returns 0 on success, CWAL_RC_MISUSE if !ar, or some other code if
   actually setting the member fails ("probably" CWAL_RC_OOM or
   CWAL_RC_CONST_VIOLATION).
*/
int whcl_pf_dirs_set( whcl_pf * const pf, cwal_array * const ar );

/**
   Replaces pf's extension/suffix list with the given one.

   Returns 0 on success, CWAL_RC_MISUSE if !ar, or some other code if
   actually setting the member fails ("probably" CWAL_RC_OOM or
   CWAL_RC_CONST_VIOLATION).
*/
int whcl_pf_exts_set( whcl_pf * const pf, cwal_array * const ar );

/**
   Symbolic values for use with whcl_pf_search()'s final
   parameter.
*/
enum whcl_pf_search_e {
/**
   Indicates that ONLY directory names will be considered as matches.
*/
WHCL_PF_SEARCH_DIRS = -1,
/**
   Indicates that ONLY file (not directory) names will be considered
   as matches.
*/
WHCL_PF_SEARCH_FILES = 0,
/**
   Indicates that both file and directory names will be considered as
   matches.
*/
WHCL_PF_SEARCH_FILES_DIRS = 1
};

/**
   Searches for a file whose name can be constructed by some
   combination of pf's directory/suffix list and the given base name.
   baseLen is the length of the base name - if it is negative,
   cwal_strlen() is used to calculate it.

   The 5th argument specificies whether searching is allowed to match
   directory names or not. A value of 0 means only files (not
   directories) will be considered for matching purposes. A value
   greater than zero means both files and directories may be
   considered for matching purposes. A value less than zero means only
   directories (not files) may be considered a match.  (See the
   whcl_pf_search_e enum for symbolic names for this policy.)

   BUG: the directory policy does not currently work on non-Unix
   platforms because we don't have the code to check if a file name is
   a directory for such platforms (patches are welcomed!).

   Returns NULL if !pf, !base, !*base, !baseLen, or on allocation
   error (it uses/recycles a buffer to hold its path combinations).

   On success it returns a pointer to the (NUL-terminaed) path under
   which it found the item and rcLen (if not NULL) will be set to the
   length of the returned string. The bytes of the returned string are
   only valid until the next operation on pf, so copy them if you need
   them.

   If no match is found, rcLen is not modified.

   By default the host platform's customary path separator is used to
   separate directory/file parts ('\\' on Windows and '/' everywhere
   else). To change this, set the "separator" property of pf to a
   string value (even an empty one, in which case the directory paths
   added to pf should have the trailing separator added to them in
   order for searching to work).

   Pedantic sidebar: if the search path is empty, a match can still be
   found if the base name by itself, or in combination with one of the
   configured extensions, matches an allowed type of filesystem entry
   (as designated via the final argument).

   @see whcl_pf_search_e
*/
char const * whcl_pf_search( whcl_pf * const pf, char const * base,
                             cwal_int_t baseLen, cwal_size_t * const rcLen,
                             int directoryPolicy);


/**
   Returns pf's list of directories, creating it if needed. Only
   returns NULL if !pf or on allocation error. If this function
   creates the list, it is initially owned by pf.

   In script space this value is available via the "path" property.
*/
cwal_array * whcl_pf_dirs(whcl_pf * const pf);

/**
   Returns pf's list of extensions/suffixes, creating it if
   needed. Only returns NULL if !pf or on allocation error. If this
   function creates the list, it is initially owned by pf.

   In script space this value is available via the "ext" property.
*/
cwal_array * whcl_pf_exts(whcl_pf * const pf);

/**
   Expects to be passed all arguments from main() which appear _after_
   a `--` flag. argc is the argument count and argv is the list. It is
   legal for argv to be 0, in which case this function sets up the
   script-accessible arguments list (see below) as an empty
   list. (Much practice has shown that having an empty list is
   preferable to having to distinguish, client-side, whether the list
   was initialized or not.)

   By long-standing cwal client app convention, all arguments after
   `--` are for script-side consumption. This function processes them
   using cwal_parse_argv_flags() and, on success, installs them as
   `whcl[ARGV]`. Returns 0 on success.  On error it does not install
   them and returns non-0 (with CWAL_RC_OOM being the only "likely"
   error result code).

   This function must only be called once in the lifetime of an app.
   It installs the `whcl[ARGV]` object as const, so any further calls
   to this function will trigger a CWAL_RC_CONST_VIOLATION.
*/
int whcl_install_argv(whcl_engine * const el, int argc,
                      char const * const * argv);

/**
   A helper type for tokenizing conventional PATH-style strings.
   Initialize them with whcl_path_toker_init() and iterate over them
   with whcl_path_toker_next().
*/
struct whcl_path_toker {
  /** Begining of the input range. */
  char const * begin;
  /** One-after-the-end of the input range. */
  char const * end;
  /** Position for the next token lookup. */
  char const * pos;
  /** List of token separator characters (ASCII only). */
  char const * separators;
};
typedef struct whcl_path_toker whcl_path_toker;
/**
   Default-initialized whcl_path_toker instance, intended for const-copy
   initialization. On Windows builds its separators member is set to
   ";" and on other platforms it's set to ":;".
*/
#if defined(WHCL_OS_WINDOWS)
#  define whcl_path_toker_empty_m {NULL,NULL,NULL,";"}
#else
#  define whcl_path_toker_empty_m {NULL,NULL,NULL,":;"}
#endif

/**
   Default-initialized whcl_path_toker instance, intended for
   copy initialization.

   @see whcl_path_toker_empty_m
*/
extern const whcl_path_toker whcl_path_toker_empty;

/**
   Wipes out pt's current state by copying whcl_path_toker_empty over it
   and initializes pt to use the given path as its input. If len is 0
   or more then it must be the length of the string, in bytes. If len
   is less than 0, cwal_strlen() is used to determine the path's
   length.  (When dealing with inputs which are not NUL-terminated,
   it's critical that the user pass the correct non-negative length.)

   If the client wants to modify pt->separators, it must be done so
   *after* calling this.

   Use whcl_path_toker_next() to iterate over the path entries.
*/
void whcl_path_toker_init( whcl_path_toker * const pt, char const * path,
                           cwal_int_t len );

/**
   Given a whcl_path_toker which was formerly initialized using
   whcl_path_toker_init(), this iterates over the next-available path
   component in the input, skipping over empty entries (consecutive
   separator characters). 

   The separator characters are specified by pt->separators, which must
   be a NUL-terminated string of 1 or more characters.

   If a non-empty entry is found then:

   - *token is set to the first byte of the entry.

   - *len is assigned to the byte length of the entry.

   If no entry is found then:

   - *token, and *len are not modified.

   - CWAL_RC_NOT_FOUND is returned if the end of the path was found
   while tokenizing.

   - CWAL_RC_MISUSE is returned if pt->separators is NULL or empty or
   contains any non-ASCII characters.

   - CWAL_RC_RANGE is returned if called after the previous case, or
   if the input object's path has a length of 0.

   In any non-0-return case, it's not a fatal error, it's simply
   information about why tokenization cannot continue, and can
   normally be ignored. After non-0 is returned, the tokenizer must be
   re-initialized if it is to be used again.

   Example:

   @code
   char const * t = 0;
   cwal_size_t tLen = 0;
   whcl_path_toker pt = whcl_path_toker_empty;
   whcl_path_toker_init(&pt, path, pathLen);
   while(0==whcl_path_toker_next(&pt, &t, &tLen)){
      // The next element is the tLen bytes of memory starting at t:
      printf("Path element: %.*s\n", (int)tLen, t);
   }
   @endcode
*/
int whcl_path_toker_next( whcl_path_toker * const pt, char const ** token,
                          cwal_size_t * const len );


/**
   Behaves more or less like the access(2) C function (_access() on
   Windows builds).

   Returns true if the given filename is readable (writeable if
   checkForWriteAccess is true), else false.
*/
bool whcl_file_is_accessible( char const * fn, bool checkForWriteAccess );

/**
   Checks for the existence of a directory with the given NUL-terminated
   name. If passed a true 2nd argument then it also checks whether the
   directory is writeable.

   Returns true (non-0) if the directory exists and (if
   checkForWriteAccess is true) writeable. If checkForWriteAccess is
   false, it returns true if the directory can be stat()ed.

   Returns 0 if the given name cannot be stat()ed or if
   checkForWriteAccess is true and the directory is not writeable.

   Currently on works on Unix platforms. On others it always returns
   0.
*/
bool whcl_is_dir( char const * name, bool checkForWriteAccess );

/**
   cwal_callback_f() impl binding whcl_file_is_accessible() in scriptable form:

   fileIsAccessible(string filename [, bool checkWriteMode=false])

*/
int whcl_cb_file_accessible( cwal_callback_args const * args, cwal_value **rv );

/**
   The directory counterpart of whcl_cb_file_accessible().
*/
int whcl_cb_dir_accessible( cwal_callback_args const * args, cwal_value **rv );

/**
   A cwal_callback_f() implementation which tokenizes conventional
   PATH-style strings. Script-side usage:

   array f( string path [, array target] )

   Each element in the input path is appended to the target array (or
   a new array if no array is passed in) and that array is returned.

   It triggers an exception if passed any invalid arguments, noting
   that an empty input string is not an error but will cause an empty
   list to be returned (resp. no entries to be added to the target
   list).
*/
int whcl_cb_tokenize_path( cwal_callback_args const * args, cwal_value **rv );

/**
   A cwal_callback_f() implementation which tokenizes its input into
   an array of whcl tokens, but supporting only a subset of its core
   token types: numbers, quoted strings, and the built-in constants
   bool/null/undefined values. All identifiers, operators, and the
   like will be tokenized as strings. Heredocs and similar block
   constructs are not handled here.

   Its intended purpose is to tokenize single lines of "commands" for
   use in interactive command-based dispatching in scripts.

   Script usage:

   array tokenizeLine(string input)

   e.g.:

   tokenizeLine('1 2.3 "hi there" true null')

   would result in an array: [1, 2.3, 'hi there', true, null]

   This function does not parse higher-level constructs like objects,
   arrays, or functions. "Junk" tokens, such as whitespace and EOLs,
   are elided. It throws for any sort of tokenization error, e.g.  an
   unclosed quoted string.

   If the input string is empty, it resolves to the undefined value,
   not an empty array (though that decision is up for reconsideration,
   depending on what pending experience suggests).
*/
int whcl_cb_tokenize_line(cwal_callback_args const * args, cwal_value ** rv);


/**
   Tokenizes a conventional PATH-style string into an array.

   *tgt must either be NULL or point to an array owned by the
   caller. If it is NULL, this function creates a new array and (on
   success) assigns it to *tgt. Each entry in the path is added to the
   array.

   None of the pointer-type arguments, including tgt (as opposed to
   *tgt), may be NULL.

   The 4th argument must be the length, in bytes, of the given path
   string. If it is negative, the equivalent of strlen() is used to
   calculate its length. If the argument, or its resulting calculated
   value, is 0 then the result will be that the target array will be
   created, if needed, but will get no entries added to it.

   On success, 0 is returned and 0 or more entries will have been
   added to the target array. If *tgt was NULL when this function was
   called, *tgt will be assigned to a new array, with a refcount of 0,
   which is owned by the caller.

   Assuming all arguments are valid, the only plausible error this
   function will return is CWAL_RC_OOM, indicating that allocation of
   the target array, an entry for the array, or space within the array
   for an entry, failed. On error, if *tgt was NULL when this function
   was called, *tgt is not modified, otherwise *tgt may have been
   modified before the allocation error occurred.

   Example:

   ```
   cwal_array * ar = 0;
   int rc = whcl_tokenize_path_to_array(e, &ar, "foo;bar", -1);
   ```

   On success, ar will be non-NULL and owned by that code.
   Contrast with:

   ```
   int rc;
   cwal_array * ar = cwal_new_array(e);
   if(!ar) return CWAL_RC_OOM;
   rc = whcl_tokenize_path_to_array(e, &ar, "foo;bar", -1);
   ```

   In that case, any entries in the given path string will be appended
   to the array provided by the caller.

   @see whcl_path_toker
   @see whcl_path_toker_init()
 */
int whcl_tokenize_path_to_array( cwal_engine * e, cwal_array ** tgt,
                                 char const * path,
                                 cwal_int_t pathLen );


/**
   Returns the `whcl` "namespace" object. `whcl` is a built-in value
   in the interpreter, intended to be a named global place to store
   classes and such. Ownership of the returned pointer does not change
   and the caller need not add a reference to it: the value is kept
   around until el is finalized.
*/
cwal_value * whcl_namespace(whcl_engine * const el);

/**
   A proxy for getcwd(2) which appends the current working directory's
   name to the end of the given buffer. This function expands tgt
   by some relatively large amount to be able to handle long
   paths. Because of this, it's recommended that a shared/recycled
   buffer be used to handle calls to this function.

   On success, tgt gets appended to, updating tgt's members as
   appropriate, and 0 is returned. On error, non-0 is returned (the
   exact code may depend on the errno set by getcwd(2)).

   ACHTUNG: this function is only implemented for Unix systems. On
   Windows builds it returns CWAL_RC_UNSUPPORTED because i don't have
   a Windows system to implement this on.
*/
int whcl_getcwd( cwal_engine * const e, cwal_buffer * const tgt );

/**
   A cwal_callback_f() implementation wrapping whcl_getcwd(). On
   success, it returns (via *rv) the current working directory's name
   as a string. If script passed an argument from script code, it is
   interpreted as a boolean: if true, the directory separator is
   appended to the result, else it is not. The default is not to
   append the directory separator to the result.
*/
int whcl_cb_getcwd( cwal_callback_args const * args, cwal_value ** rv );

/**
   Works like mkdir(2). Returns 0 on success, else a CWAL_RC_xxx value
   approximating the underlying errno result. If errMsg is not NULL
   then on error, *errMsg will point to an error message string owned
   by the C library. Its contents may be modified by calls to
   strerror(3), so must be copied if it should be retained.

   LIMITATIONS:

   1) Returns CWAL_RC_UNSUPPORTED on builds which don't have mkdir(2).

   2) Does not create intermediate directories, so the parent dir
   of the new directory must already exist. See whcl_mkdir_p().

   3) The message string returned by strerror() may be modified by
   calls to that function from other threads, so the errMsg
   argument is only known to be useful for single-threaded clients.

   @see whcl_mkdir_p()
*/
int whcl_mkdir( char const * name, int mode, char const ** errMsg );

/**
   A convenience wrapper around whcl_mkdir() which creates parent
   directories, if needed, for the target directory name. e.g.  if
   passed "a/b/c" and "a" and/or "b" do not exist, they are created
   before creating "c". Fails if creation of any part of the path
   fails.

   It requires a well-formed Unix-style directory name (relative or
   absolute). Returns 0 on success, else non-0 and an error message,
   as documented for whcl_mkdir().

   @see whcl_mkdir()
*/
int whcl_mkdir_p( char const * name, int mode, char const ** errMsg );

/**
   A cwal_callback_f() implementation wrapping whcl_mkdir(). It mkdir() is
   not available in this built, throws an exception with code
   CWAL_RC_UNSUPPORTED.

   Script-side signatures:

   (string dirName [, bool makeParentDirs=false [, int mode = 0750]])
   (string dirName [, int mode = 0750])

   Noting that the access mode may be modified by the OS, e.g. to
   apply the umask.

   If a directory with the given name already exists, this function
   has no side-effects. Note that the access mode is ignored for
   purposes of that check.

   Throws on error. On success, returns the undefined value.
*/
int whcl_cb_mkdir( cwal_callback_args const * args, cwal_value ** rv );


/**
   Reads the given file (synchronously) until EOF and streams its
   contents (in chunks of an unspecified size) to cwal_output() using
   the given cwal_engine. Returns 0 on success, non-0 (likely
   CWAL_RC_IO) on error. Does not modify ownership of the passed-in
   pointers.
*/
int whcl_passthrough_FILE( cwal_engine * e, FILE * file );

/**
   A convenience wrapper around whcl_passthrough_FILE() which uses
   whcl_fopen() to open the given filename and (on success) stream that
   file's contents to cwal_output(). Returns CWAL_RC_IO if the fopen()
   fails, else returns as documented for whcl_passthrough_FILE().
*/
int whcl_passthrough_filename( cwal_engine * e, char const * filename );

/**
   File/directory types for use with whcl_fstat_t.
*/
enum whcl_fstat_types {
WHCL_FSTAT_TYPE_UNKNOWN = 0,
WHCL_FSTAT_TYPE_REGULAR = 0x01,
WHCL_FSTAT_TYPE_DIR = 0x02,
WHCL_FSTAT_TYPE_LINK = 0x04,
WHCL_FSTAT_TYPE_BLOCK = 0x08,
WHCL_FSTAT_TYPE_CHAR = 0x10,
WHCL_FSTAT_TYPE_FIFO = 0x20,
WHCL_FSTAT_TYPE_SOCKET = 0x40
};
/**
   Filesystem entry info for use with whcl_fstat().
*/
struct whcl_fstat_t {
  /**
     The type of a given entry.
  */
  enum whcl_fstat_types type;
  /**
     Change time (includes metadata changes, e.g. permissions and
     such).
  */
  uint64_t ctime;
  /**
     Modification time (does not account for metadata changes,
     e.g. permissions and such).
  */
  uint64_t mtime;
  /** Size, in bytes. */
  uint64_t size;
  /** Unix permissions. */
  int perm;
};
typedef struct whcl_fstat_t whcl_fstat_t;
/**
   Intended to be used as a copy-construction source for local whcl_fstat_t copies,
   to ensure a clean state.
 */
extern const whcl_fstat_t whcl_fstat_t_empty;
/**
   Cleanly-initialized whcl_fstat_t entry, intended for const copy
   initialization.
*/
#define whcl_fstat_t_empty_m {0,0,0,0,0}

/**
   Wrapper for stat(2) and lstat(2). The filename is taken from the
   first fnLen bytes of the given filename string. (We take the length
   primarily because cwal X-strings and Z-strings need not be
   NUL-terminated.) If fnLen is negative then cwal_strlen() is used to
   calculate it. If tgt is not NULL then the results of the stat are
   written there. If derefSymlinks is true then stat() is used, else
   lstat() is used, which fetches information about a symlink, rather
   than the file the symlink points to.

   On success, returns 0 and, if tgt is not NULL, populates tgt.  On
   error, tgt is not modified.

   If stat() is not available in this build, CWAL_RC_UNSUPPORTED
   is returned.

   If lstat() is not available in this build, it returns
   CWAL_RC_UNSUPPORTED if derefSymlinks is false.

   If tgt is NULL and 0 is returned, it means that stat(2) succeeded.

   If stat(2) or lstat(2) fail, non-0 is returned (a CWAL_RC_xxx value
   approximating the errno from the failed call).
*/
int whcl_fstat( char const * filename,
                cwal_int_t fnLen,
                whcl_fstat_t * const tgt,
                bool derefSymlinks );

/**
   A cwal_callback_f() implementation wrapping whcl_fstat().

   Script signatures:

   1) object stat( string filename, [derefSymlinks=true] )

   Returns an object representing the stat() info (see
   whcl_fstat_to_object() for the structure. Throws an exception if
   stat() fails.

   2) bool stat(string filename, undedfined [, derefSymlinks=true])

   If the argument count is greater than 1 and the the second argument
   has the undefined value then this function returns a boolean
   instead of an object. In that case, it returns false, instead of
   throwing, if stat() fails. That's admittedly a horribly awkward way
   to distinguish between the two return modes, and it may well be
   changed at some point.
*/
int whcl_cb_fstat( cwal_callback_args const * args, cwal_value ** rv );

/**
   Converts a populated whcl_fstat_t struct to an Object with properties describing
   the whcl_fstat_t values:

   ```
   {
     ctime: integer, // state change time (includes perms changes)
     mtime: integer, // modification time
     perm: integer, // Unix permissions bits
     size: integer, // size, in bytes
     type: string // "unknown", "file", "dir", "link", "block", "char", "fifo", "socket"
   }
   ```

   On success, assigns *rv to the newly-created object and returns 0. On error
   *rv is not modified and returns non-zero (likely CWAL_RC_OOM).

   Just FYI: this function caches the keys (via whcl_stash_set()) used
   for the returned object's properties, so it's not quite as
   expensive as it may initially seem.
*/
int whcl_fstat_to_object( whcl_engine * const el,
                          whcl_fstat_t const * const fst,
                          cwal_value ** rv );

/**
   A cwal_callback_f() which expects its first argument to be an
   integer. It tries to sleep for at least that many seconds and
   returns the number of seconds left to sleep if it is interrupted
   (as per sleep(3)).

   @see whcl_install_time()
 */
int whcl_cb_sleep(cwal_callback_args const * args, cwal_value ** rv);

/**
   A cwal_callback_f() which expects its first argument to be an
   integer. It sleeps for that many milliseconds. It throws an
   exception if usleep(3) fails or if the library is built without
   usleep(3) support. It returns the undefined value.

   @see whcl_install_time()
*/
int whcl_cb_mssleep(cwal_callback_args const * args, cwal_value ** rv);

/**
   A cwal_callback_f() impl binding the C-standard time(3).

   @see whcl_install_time()
*/
int whcl_cb_time( cwal_callback_args const * args, cwal_value **rv );

/**
   A cwal_callback_f() impl returning the current time in milliseconds
   since the start of the Unix epoch. This requires platform-specific
   calls and throws an exception, with code CWAL_RC_UNSUPPORTED, if
   built on a platform with the required API.

   @see whcl_install_time()
*/
int whcl_cb_mstime( cwal_callback_args const * args, cwal_value **rv );

/**
   A strftime() implementation.

   dest must be valid memory at least destLen bytes long. The result
   will be written there.

   fmt must contain the format string. See the file toys/strftime.s2
   (or strftime.c, if you're more into C) for the complete list of
   format specifiers and their descriptions.

   timeptr must be the time the caller wants to format.

   Returns 0 if any arguments are NULL.
   
   On success it returns the number of bytes written to dest, not
   counting the terminating NUL byte (which it also writes). It
   returns 0 on any error, and the client may need to distinguish
   between real errors and (destLen==0 or !*fmt), both of which could
   also look like errors.

   TODOs:

   - Refactor this to take a callback or a cwal_buffer, so that we can
   format arbitrarily long output.

   - Refactor it to return an integer error code.

   (i didn't write this implementation - it is derived from public domain
   sources dating back to the early 1990's.)

*/
cwal_midsize_t whcl_strftime(char *dest, cwal_midsize_t destLen,
                           const char *format, const struct tm *timeptr);

    
/**
   A cwal_callback_f() which wraps whcl_strftime().

   Script usage:

   ```
   var tm = time();
   var str = strftime("%Y-%m-%d %H:%M:%S", tm);
   ```

   The default time value, if no second argument is passed in or a
   negative value is passed in, is the current time.

   Note that this implementation has a limit on the length of the
   result string (because whcl_strftime() works that way), and
   throws if that length is violated. It's suitable for "usual"
   time strings but not for formatting whole sentences.

   This function takes an optional boolean 3rd argument: if truthy,
   local time is used, else GMT is used (the default).
*/
int whcl_cb_strftime( cwal_callback_args const * args, cwal_value **rv );

/**
   Pushes one level of output buffer into se's output buffer stack.
   Buffering works similarly to PHP's ob_start() (and friends) support.
   While a buffer is active, all output send to cwal_engine_output()
   and friends is redirected to a buffer. The various whcl_ob_xxx()
   functions can be used to:

   - fetch or discard the contents
   - push a new buffer onto the stack
   - pop the buffer from the stack (discarding its contents)

   When the interpreter is shut down it automatically removes any
   pushed buffers, but clients should call whcl_ob_pop() once
   for each time they call whcl_ob_push()

   Returns 0 on success, CWAL_RC_MISUSE if !se, CWAL_RC_RANGE if there
   has been no corresponding call to whcl_ob_push().

   Results of the whole whcl_ob_XXX() API are undefined if another API
   manipulates the contents of the underlying cwal_engine's output
   redirection bits (i.e. cwal_engine_vtab::outputer via
   cwal_engine::vtab).
   
   @see whcl_ob_pop()
   @see whcl_ob_get()
   @see whcl_ob_take()
   @see whcl_ob_clear()
   @see whcl_ob_level()
   @see whcl_ob_flush()
*/
int whcl_ob_push( whcl_engine * const el );

/**
   Attempts to reserve at least reserveBufSize bytes of memory for the
   current buffering level. This does not change the buffering level.

   Returns 0 on success, CWAL_RC_RANGE if whcl_ob_push() has not
   previously been called, and CWAL_RC_OOM if allocation of new memory
   fails.

   @see whcl_ob_push()
 */
int whcl_ob_reserve( whcl_engine * const el, cwal_size_t reserveBufSize );

/**
   Removes the current level of output buffer from ie.

   Returns 0 on success, CWAL_RC_RANGE if there
   has been no corresponding call to whcl_ob_push().
*/
int whcl_ob_pop( whcl_engine * const el );

/**
   Returns the current buffering level, or 0 if !ie or ie is
   not in buffering mode.

   @see whcl_ob_push()
   @see whcl_ob_pop()
*/
cwal_size_t whcl_ob_level( whcl_engine * const el );

/**
   Gets a pointer to the raw buffer owned by the current level of
   output buffer, assigning it to `*tgt`. The buffer is owned by the
   OB layer and its contents may be modified on any API routines which
   end up calling cwal_engine_output() or the other whcl_ob_xxx()
   APIs. The caller is intended to copy/use the buffer's contents
   immediately, and not hold on to it past the current operation.

   Returns 0 on success, CWAL_RC_RANGE if there has been no
   corresponding call to whcl_ob_push(). On error `*tgt` is
   not modified.
*/
int whcl_ob_get( whcl_engine * const el, cwal_buffer ** tgt );

/**
   Like whcl_ob_get(), but moves the contents of the current
   buffer layer into tgt, clearing the OB buffer but leaving
   it on the buffer stack for later use.

   Returns 0 on success, CWAL_RC_RANGE if there has been no
   corresponding call to whcl_ob_push().

   tgt must be empty-initialized or the caller must call
   cwal_buffer_reserve(..., tgt, 0) before calling this or memory may
   leak. On success ownership of the memory in tgt->mem is transfered
   to the caller. If tgt was created via cwal_new_buffer() or
   cwal_new_buffer_value() then tgt and tgt->mem are owned by se->e.
*/
int whcl_ob_take( whcl_engine * const el, cwal_buffer * const tgt );

/**
   Clears the contents of the current buffering layer. If
   releaseBufferMem is true (non-0) then the buffer memory is
   deallocated, otherwise it is just reset for later use by the OB
   layer. If it is deallocated, it will be re-allocated later if more
   output is buffered.

   Returns 0 on success, CWAL_RC_RANGE if there has been no
   corresponding call to whcl_ob_push().
*/
int whcl_ob_clear( whcl_engine * const el, bool releaseBufferMem );

/**
   Pushes the current contents of the output buffer layer to the next
   output destination in the stack and the current level is cleared of
   contents (but stays on the stack). If the next outputer is a buffer
   then the current buffer is appended to it, otherwise it is sent to
   the originally configured output destination.

   Returns 0 on success, CWAL_RC_RANGE
   if there has been no corresponding call to whcl_ob_push(),
   and potentially some other error if flushing to the lower-level
   implementation fails.

   @see whcl_ob_push()
   @see whcl_ob_pop()
*/
int whcl_ob_flush( whcl_engine * const el );

/**
   Returns a handle to the given interpreter's "whcl object".  This is
   the object to which the `whcl` builtin value resolves to and is, in
   practice, where most global-level functionality gets install. The
   returned value is owned by el and remains valid until el's top-most
   scope is popped (normally during finalization).
*/
cwal_value * whcl_whcl_object(whcl_engine * const el);


/**
   Installs a dispatcher function property named "__command"
   in tgt. The property is defined with the CONST/HIDDEN flags.
   Returns 0 on success.

   The dispatcher looks at the first arg to the function and searches
   for a function-type member property of the same name. If found, it
   forwards the call, minus the first arg, to that function, else it
   fails loudly.

   Reminder to self: do not install this on the Function type. That
   leads to a chicken/egg scenario.

   Note that the whcl-installed types, aside from Function, already
   have such a dispatcher installed. This function is provided for
   client-side use with custom types which do _not_ inherit from
   one of the built-in prototypes.
*/
int whcl_install_command_cb(whcl_engine * const el,
                            cwal_value * const tgt);

/**
   Works like whcl_install_command_cb() but takes a cwal_callback_f
   which gets installed as a command handler into the given target
   object. It gets added with the CONST/HIDDEN flags.

   Returns 0 on success.

   @see whcl_function_forward()
*/
int whcl_install_command_cb2(whcl_engine * const el,
                              cwal_callback_f cb,
                              cwal_value * const tgt);


/**
   A proxy for cwal_function_forward() which works identically except
   that it checks to see if f has the WHCL_CONTAINER_F_XSYM flag
   and, if so, it adjusts the current scope flags to take that into
   account. The primary (perhaps the only) use case for this in whcl
   client code is when clients implement their own `__command`
   dispatchers and need to ensure that the container flags of the
   being-dispatched-to function, rather than the `__command` function,
   are taken into account.

   @see whcl_install_command_cb2()
*/
int whcl_function_forward( whcl_engine * const el, cwal_function * const f,
                           uint16_t trimArgCount, cwal_callback_args const * args,
                           cwal_value ** rv );

/**
   Flags for use with the 4th argument of whcl_function_call(). See that
   function for their meanings.
*/
enum whcl_function_call_e {
/**
   Signals the pending call scope that it should not act as a symbol
   lookup barrier, permitting symbol lookup to continue up the scope
   stack from this point instead of skipping to the global scope.
 */
WHCL_FCALL_F_XSYM = WHCL_CONTAINER_F_XSYM,
/**
   Signals the pending script-function call to re-use the current
   scope, instead of pushing a new one.
*/
WHCL_FCALL_F_REUSE_WSCOPE = 0x02
};

/**
   A wrapper around cwal_function_call() which works _almost_ identically
   except that:

   1) It ensures that the scope pushed for the call acts as a boundary
      for symbol lookup purposes _unless_ f has the
      WHCL_CONTAINER_F_XSYM client container flag set on it OR the 3rd
      argument contains the WHCL_FCALL_F_XSYM flag.

   2) If the 3rd argument has the WHCL_FCALL_F_REUSE_WSCOPE bit set
      then the caller is assumed to have already pushed and populated
      a whcl_scope for the call (using whcl_scope_push() then
      whcl_scope_set_v() and friends). The proxied call will, _if f is
      a script function_, retain that whcl_scope for holding the usual
      call-local vars such as `argv` and `this`.

   The 3rd argument's flags have no effect on non-script functions.

   Results are undefined if f is NULL or invalid.
*/
int whcl_function_call( cwal_function * f,
                        cwal_value * self,
                        cwal_flags16_t callFlags,
                        cwal_value ** resultVal,
                        uint16_t argc,
                        cwal_value * const * argv );

/**
   Installs a property named `__typename` into the given
   properties-capable value, with the hidden/const attributes. Returns
   0 on success, sets el's error or exception state and returns non-0
   on error. It generally triggers an exception rather than a
   non-exception error, but will trigger a non-exception for
   especially serious problems (e.g. OOM).

   On success "someone" will take a new reference to the given name
   value. On error no reference is taken.
*/
int whcl_install_typename_v(whcl_engine * const el, cwal_value * const tgt,
                            cwal_value * const name);
/**
   A proxy for whcl_install_typename_v() which differs only in how it
   accepts its argument (a NUL-terminated string).
*/
int whcl_install_typename(whcl_engine * const el, cwal_value * const tgt,
                          char const * name);

/**
   Returns true if the given value is not NULL and is currently being
   constructed via whcl's `new` builtin command, else returns false.
*/
bool whcl_is_newing(cwal_value const * const v);

/**
   cwal_callback_f() wrapper for cwal_glob_matches_cstr().

   Script-side usages:

   bool glob( string glob, string haystack [, int globStyle=-1] )

   bool glob( string haystack [, int globStyle=-1] )

   Glob matching styles (3rd glob() param):

   <0 = (default) wildcard-style (case sensitive)

   0 = SQL LIKE style (case insensitive)

   >0 = SQL LIKE style (case sensitive)

   If args->self is-a string then this function behaves like the
   second form: it expects that args->self is a string to match glob
   pattern args->argv[0] against, using the optional policy specified
   by args->argv[1]. If args->self is not a string, it behaves like
   the first.
*/
int whcl_cb_glob_matches_str( cwal_callback_args const * args,
                              cwal_value ** rv );

/**
   This cwal_callback_f() impl functions identically to
   whcl_cb_glob_matches_str() except that the order of the glob and
   haystack arguments are swapped, with the haystack on the left.
*/
int whcl_cb_matches_glob_str( cwal_callback_args const * args,
                              cwal_value ** rv );

/**
   Callback signature for whcl module import routines.

   When called by whcl_module_load(), whcl_module_init(), or similar, this
   function type is passed the associated whcl_engine instance and the
   client-provided module result value address.

   Implementations "should" (by convention) return their module by
   assigning it to `*module`. Optionally, they may use the
   whcl_engine's facilities to store the functionality long-term (in
   terms of value lifetimes), e.g. using whcl_stash_set(), or even
   forcing them into the top-most scope. In any case, they should, on
   success, assign some result value to `*module`, even if it's
   NULL. Note, however, that NULL is not generally a useful result
   value. Most modules return a "namespace object" which contains the
   module's functionality.

   When assigning to `*module`, the API expects that this function
   will not hold any extraneous references to the returned
   value. i.e. if it's a new Value with no circular references, its
   refcount "should" be zero when `*module` is assigned to and 0 is
   returned.

   @see whcl_module_load()
*/
typedef int (*whcl_module_init_f)( whcl_engine * const el, cwal_value ** module );

/**
   Holds information for mapping a whcl_module_init_f to a name.
   Its purpose is to get installed by the WHCL_MODULE_xxx family of
   macros and referenced later via a module-loading mechanism.
*/
struct whcl_loadable_module{
  /**
     Symbolic name of the module.
  */
  char const * name;

  /**
     The initialization routine for the module.
  */
  whcl_module_init_f init;
};

/** Convenience typedef. */
typedef struct whcl_loadable_module whcl_loadable_module;

/**
   If compiled without WHCL_ENABLE_MODULES defined to a true value
   then this function always returns CWAL_RC_UNSUPPORTED and updates
   the error state of its first argument with information about that
   code.

   Its first argument is the controlling whcl_engine.

   Its second argument is the name of a DLL file.

   Its third argument is the name of a symbol in the given DLL which
   resolves to a whcl_loadable_module pointer. It may be NULL, in which
   case a default symbol name is used (which is only useful when
   plugins are built one per DLL).

   The final parameter is the target for the module's result value,
   and it may not be NULL (but the value it points to should initially
   be NULL, as it will be overwritten). It is passed directly to the
   module's whcl_loadable_module::init() function, which is responsible
   for (on success) assigning `*mod` to the value the module wants to
   return.

   This function tries to open a DLL named fname using the system's
   DLL loader. If none is found, CWAL_RC_NOT_FOUND is returned and the
   whcl_engine's error state is populated with info about the error. If
   one is found, it looks for a symbol in the DLL: if symName is not
   NULL and is not empty then the symbol "whcl_module_symName" is
   sought, else "whcl_module". (e.g. if symName is "foo" then it
   searches for a symbol names "whcl_module_foo".) If no such symbol is
   found then CWAL_RC_NOT_FOUND (again) is returned and the
   whcl_engine's error state is populated, else the symbol is assumed to
   be a (whcl_loadable_module*), its init() function is called, and its
   result is returned to the caller of this function.

   On error, this routine generally updates the whcl_engine's error
   state with more info (e.g. the name of the symbol on a symbol
   lookup failure). Not all errors update the engine's error state,
   only those with more information to convey than the result code. On
   error, the result Value (final parameter) is not modified.

   Returns 0 on success.

   Note that the API provides no mechanism for unloading DLLs because
   it is not generically possible to know if it is safe to do
   so. Closing a DLL whose resources (e.g. a native class definition
   for a client-bound type) are still in use leads, of course, to
   undefined results. The caveat, however, is that because dlopen()
   and friends allocate memory when we open DLLs, and we don't close
   them, valgrind reports this (rightfully) as a leak. It is not so
   much a leak as it is a required safety net. That said, the
   interpreter will close all DLLs it opened (or believes it opened)
   when it is finalized. That, however, opens up another potential
   problem: interpreters will close a DLL one time for each time they
   opened it. How the underlying (system-level) module API deals with
   that is up to that API. The dlopen()-based and lt_dlopen()-based
   implementations are safe in that regard (at least on Linux,
   according to their man pages and a peek at their sources).

   In practice this routine is not called by client C code, but is
   instead called indirectly via whcl_cb_module_load(), which is a
   script-side binding of this function.

   @see whcl_cb_module_load()
   @see WHCL_MODULE_DECL
   @see WHCL_MODULE_IMPL
   @see WHCL_MODULE_REGISTER
   @see WHCL_MODULE_REGISTER_
*/
int whcl_module_load( whcl_engine * const el, char const * fname,
                      char const * symName, cwal_value ** mod );

/**
   Behaves similarly to whcl_module_load(), and its first 3 parameters
   are used as documented for that function, but this variant does not
   invoke the init() method of the module before returning that module
   via `*mod`.

   On success `*mod` is set to the module object. Its ownship is kinda
   murky: it lives in memory made available via the module loader. It
   remains valid memory until the DLL is closed.

   Returns 0 on success. On error, el's error state may contain more
   information.

   After calling this, the next call would typically be
   whcl_module_init().

   @see whcl_module_load()
   @see whcl_module_init()
*/
int whcl_module_extract( whcl_engine * const el,
                         char const * dllFileName,
                         char const * symName,
                         whcl_loadable_module const ** mod );

/**
   This function pushes a new cwal scope, calls mod->init(), and
   propagates any result value from that routine back out of that new
   scope via `*rv`. On error `*rv` is not modified. If rv is NULL then the
   Value result of the module init is ignored (destroyed before this
   routine returns unless the module stores is somewhere long-lived),
   but any integer result code of the init routine is propagated back
   to the caller of this function and the init routine might update
   el's error state.

   @see whcl_module_load()
   @see whcl_module_extract()
*/
int whcl_module_init( whcl_engine * const el,
                      whcl_loadable_module const * mod,
                      cwal_value ** rv);

/** @def WHCL_MODULE_DECL

   Declares an extern (whcl_loadable_module*) symbol called
   whcl_module_#\#NAME.

   Use WHCL_MODULE_IMPL to create the matching implementation
   code.
   
   This macro should be used in the C or H file for a loadable module.
   It may be compined in a file with a single WHCL_MODULE_IMPL1()
   declaration with the same name, such that the module can be loaded
   both with and without the explicit symbol name.

   @see WHCL_MODULE_IMPL
*/
#define WHCL_MODULE_DECL(NAME)                            \
    extern const whcl_loadable_module * whcl_module_##NAME

/** @def WHCL_MODULE_IMPL
   
   Intended to be used to implement module declarations.  If a module
   has both C and H files, WHCL_MODULE_DECL(NAME) should be used in the
   H file and WHCL_MODULE_IMPL() should be used in the C file. If the
   DLL has only a C file (or no public H file), WHCL_MODULE_DECL is
   unnecessary.

   Implements a static whcl_loadable_module object named
   whcl_module_#\#NAME#\#_impl and a non-static (whcl_loadable_module*)
   named whcl_module_#\#NAME which points to
   whcl_module_#\#NAME#\#_impl. (The latter symbol may optionally be
   declared in a header file via WHCL_MODULE_DECL.)

   INIT_F must be a whcl_module_init_f() function pointer. That function
   is called when whcl_module_load() loads the module.

   This macro may be combined in a file with a single
   WHCL_MODULE_IMPL1() declaration using the same NAME value, such that
   the module can be loaded both with and without the explicit symbol
   name.

   Example usage, in a module's header file, if any:

   ```
   WHCL_MODULE_DECL(mymodule);
   ```

   (The declaration is not strictly necessary - it is more of a matter
   of documentation.)
   
   And in the C file:

   ```
   WHCL_MODULE_IMPL(mymodule,mymodule_module_init);
   ```

   If it will be the only module in the target DLL, one can also add
   this:
   
   ```
   WHCL_MODULE_IMPL1(mymodule,mymodule_install_to_interp);
   // _OR_ (every so slightly different):
   WHCL_MODULE_STANDALONE_IMPL(mymodule,mymodule_install_to_interp);
   ```

   Which simplifies client-side module loading by allowing them to
   leave out the module name when loading, but that approach only
   works if modules are compiled one per DLL (as opposed to being
   packaged together in one DLL).
   
   @see WHCL_MODULE_DECL
   @see WHCL_MODULE_IMPL1
*/
#define WHCL_MODULE_IMPL(NAME,INIT_F)                                     \
  static const whcl_loadable_module                                       \
  whcl_module_##NAME##_impl = { #NAME, INIT_F };                          \
  const whcl_loadable_module * whcl_module_##NAME = &whcl_module_##NAME##_impl
/** @def WHCL_MODULE_IMPL3

    A variant of WHCL_MODULE_IMPL for cases where a module name
    is not a legal C symbol name. The first argument is the C-friendly
    name, the 2nd is the human-friendly one, and the 3rd is the module
    init function.
*/
#define WHCL_MODULE_IMPL3(CNAME,NAME,INIT_F)                            \
  static const whcl_loadable_module                                       \
  whcl_module_##CNAME##_impl = { #NAME, INIT_F };                          \
  const whcl_loadable_module * whcl_module_##CNAME = &whcl_module_##CNAME##_impl


/** @def WHCL_MODULE_IMPL1

   Implements a static whcl_loadable_module symbol called
   whcl_module_impl and a non-static (whcl_loadable_module*) named
   whcl_module which points to whcl_module_impl

   INIT_F must be a whcl_module_init_f.
   
   This macro must only be used in the C file for a loadable module
   when that module is to be the only one in the resuling DLL. Do not
   use it when packaging multiple modules into one DLL: use
   WHCL_MODULE_IMPL for those cases (WHCL_MODULE_IMPL can also be used
   together with this macro).

   @see WHCL_MODULE_IMPL
   @see WHCL_MODULE_DECL
   @see WHCL_MODULE_STANDALONE_IMPL
*/
#define WHCL_MODULE_IMPL1(NAME,INIT_F)                                \
  static const whcl_loadable_module                                   \
  whcl_module_impl = { #NAME, INIT_F };                               \
  const whcl_loadable_module * whcl_module = &whcl_module_impl

/** @def WHCL_MODULE_STANDALONE_IMPL

    WHCL_MODULE_STANDALONE_IMPL() works like WHCL_MODULE_IMPL1() but
    is only fully expanded if the preprocessor variable
    WHCL_MODULE_STANDALONE is defined (to any value).  If
    WHCL_MODULE_STANDALONE is not defined, this macro expands to a
    dummy placeholder which does nothing (but has to expand to
    something to avoid leaving a trailing semicolon in the C code,
    which upsets the compiler (the other alternative would be to not
    require a semicolon after the macro call, but that upsets emacs'
    sense of indentation (and keeping emacs happy is more important
    than keeping compilers happy (all of these parens are _not_ a
    reference to emacs lisp, by the way)))).

    This macro may be used in the same source file as
    WHCL_MODULE_IMPL.

    The intention is that DLLs prefer this option over
    WHCL_MODULE_IMPL1, to allow that the DLLs can be built as
    standalone DLLs, multi-plugin DLLs, and compiled directly into a
    project (in which case the code linking it in needs to resolve and
    call the whcl_loadable_module entry for each built-in module).

   @see WHCL_MODULE_IMPL1
   @see WHCL_MODULE_REGISTER
*/
#if defined(WHCL_MODULE_STANDALONE)
#  define WHCL_MODULE_STANDALONE_IMPL(NAME,INIT_F) WHCL_MODULE_IMPL1(NAME,INIT_F)
#else
#  define WHCL_MODULE_STANDALONE_IMPL(NAME,INIT_F) \
  extern void whcl__module_dummy_does_not_exist_()
#endif

/** @def WHCL_MODULE_REGISTER

   Performs all the necessary setup for registering a loadable module,
   including declaration and definition. NAME is the name of the
   module. This is normally called immediately after defining the
   plugin's init func (which is passed as the 2nd argument to this
   macro).

   See WHCL_MODULE_IMPL() and WHCL_MODULE_STANDALONE_IMPL() for
   the fine details.
*/
#define WHCL_MODULE_REGISTER(NAME,INIT_F)  \
  WHCL_MODULE_IMPL(NAME,INIT_F);           \
  WHCL_MODULE_STANDALONE_IMPL(NAME,INIT_F)
/** @def WHCL_MODULE_REGISTER3

    A variant of WHCL_MODULE_REGISTER for cases where a module name
    is not a legal C symbol name. The first argument is the C-friendly
    name, the 2nd is the human-friendly one, and the 3rd is the module
    init function.
 */
#define WHCL_MODULE_REGISTER3(CNAME,NAME,INIT_F)                    \
  WHCL_MODULE_IMPL3(CNAME,NAME,INIT_F); \
  WHCL_MODULE_STANDALONE_IMPL(NAME,INIT_F)

/**
   Functionally equivalent to:
   WHCL_MODULE_REGISTER(NAME, whcl_module_init_#\#NAME).
*/
#define WHCL_MODULE_REGISTER_(NAME)                             \
  WHCL_MODULE_IMPL(NAME,whcl_module_init_##NAME);          \
  WHCL_MODULE_STANDALONE_IMPL(NAME,whcl_module_init_##NAME)


/**
   cwal_callback_f() impl which wraps whcl_module_load().

   Script-side usages:

   // For single-module DLLs:
   var module = loadModule("filename");
   // Or, for multi-module DLLs:
   var module = loadModule("filename", "symbolName");

   On success it returns the module's value (which can be anything),
   or the undefined value if the module returns nothing (which would be
   unusual).

   If passed a NULL symbol name, it assumes that the DLL is a
   single-module DLL and uses the symbol name "whcl_module". If passed
   a symbol name, it looks for "whcl_module_SYMBOL_NAME". If such a
   symbol is found it is assumed to be a (whcl_loadable_module const
   *) and its init() function is called.

   On success 0 is returned. On error it throws or returns a
   lower-level error code (e.g. CWAL_RC_OOM). This function will
   intercept any script-level `return` triggered by the module
   initialization and treat it as the result value.

   Achtung: there is no module caching going on here, and loading a
   module multiple times may be expensive or confusing (the returned
   objects from separate calls will, unless the module itself somehow
   caches results, be different instances).

   Achtung: this binding requires that whcl_engine_from_args() return
   non-0 (it will do so if args->engine is managed by an whcl_engine
   instance).
*/
int whcl_cb_module_load( cwal_callback_args const * args,
                         cwal_value **rv );

/**
   Loads, if needed, one of a hard-coded set of APIs into the
   script-side `whcl` object as `whcl[apiName]`. If the given name
   matches one of the built-in ones, it is installed/initialized and
   `*rv` (if rv is not NULL) is assigned to its value. If
   `whcl[apiName]` already exists when this is called, this function
   becomes a no-op and returns that value via `*rv` (if rv is not
   NULL). If the given name does not match an existing object or one
   of the built-in/installable APIs, an exception is triggered and
   non-0 is returned.

   The list of built-in API names can be fetched via
   whcl_install_api_list().

   whcl_install_core_apis() installs the script-visible function
   `whcl[install-api]` which is wrapper around this function. It
   accepts one or more string arguments naming an API. It installs, if
   needed, the API and its script-side result is the object associated
   with the final API it loads (the `wchl[X]` value for the final
   argument `X`). It throws if passed an unknown API name and the
   resulting message contains the list of all known APIs. If called
   with no arguments, it instead returns a space-delimited string
   of all modules known by that function.

   In addition to the built-in set of APIs, this library may be built
   with additional external modules which are exposed via this
   function. Thus it _might_ have more than those listed above.
*/
int whcl_install_api(whcl_engine * const el, char const * apiName,
                     cwal_value **rv);

/**
   Returns the built-in list of APIs which are usable with
   whcl_install_api(). If `oneString` is not NULL, `*oneString` is
   assigned a static string which lists all available API names in an
   unspecified order. If `strList` is not NULL then `*strList` is
   assigned a NULL-terminated array of those module names in an
   unspecified order.

   The names in the returned lists are suitable for use as the 2nd
   argument to whcl_install_api(). The first form is guaranteed to be
   formatted like "X Y Z", where X, Y, and Z are module names
   separated by a single space, with no leading or trailing space.

   The returned bytes are immutable and static, so have no particular
   lifetime-/ownership-related issues.
*/
void whcl_install_api_list(char const **oneString,
                           char const * const ** strList);


/**
   A convenience routine to bind a callback function as a constructor
   for use with whcl's "new" builtin command.
   
   Installs method as a hidden/const property named "__new" in the
   given container. The method parameter is assumed to conform to the
   "new" command's constructor conventions.

   This has the same effect as setting the property oneself except
   that this routine uses a cached copy of the key string and sets the
   property to hidden/const.

   Returns 0 on success, else:

   - CWAL_RC_MISUSE if any argument is NULL.

   - CWAL_RC_OOM on allocation error.

   - CWAL_RC_TYPE if !cwal_props_can(container).

   - CWAL_RC_CONST_VIOLATION if container already has a constructor
   property and it is const (otherwise it is overwritten).

   An example of customizing the constructor with state:

   ```
   int rc;
   cwal_function * f;
   cwal_value * fv;
   f = cwal_new_function( el->ec, my_callback_f, my_callback_state,
                          my_callback_state_finalizer_f, my_type_id );
   if(!f) return CWAL_RC_OOM;
   fv = cwal_function_value(fv);
   assert(f == cwal_value_get_function(fv)); // will always be the case if f is valid
   cwal_ref(fv);
   rc = whcl_ctor_method_set( el, myContainer, f );
   cwal_unref(fv);
   return rc;
   ```

   In such a setup, from inside the my_callback() implementation,
   cwal_args_state(args->engine, my_type_id) can be used to
   (type-safely) fetch the my_callback_state pointer. The whcl_engine
   instance associated with the call can be fetched via
   whcl_engine_from_args().

   @see whcl_ctor_callback_set()
*/
int whcl_ctor_method_set( whcl_engine * const el, cwal_value * const container,
                          cwal_function * const method );

/**
   A convenience form of whcl_ctor_method_set() which instantiates a
   cwal_function from the its 3rd argument using cwal_new_function().
   Returns CWAL_RC_MISUSE if any argument is NULL, else returns as for
   whcl_ctor_method_set().
*/
int whcl_ctor_callback_set( whcl_engine * const el,
                            cwal_value * const container,
                            cwal_callback_f method );

/**
   Invokes a "constructor" function in the same manner as the 'new'
   builtin command does. 

   If ctor is NULL then it looks in operand
   (which must be a container type) for a function-type property named
   `__new`. If that property is not found or is not a Function, el's
   error state is set and non-0 is returned.

   For purposes of construction, the default prototype used by the new
   object depends on how this function is called: if ctor is not NULL
   then its prototype is used. Else if a `__new` constructor method
   property is found in the operand, that constructor is used and, the
   operand argument is the prototype. If ctor is NULL and no
   constructor method is found in the operand, CWAL_RC_TYPE is
   returned and the engine's error state is updated.

   All arguments except args and ctor must be non-NULL.

   This routine abstractly does the following:

   - creates a new Object.

   - sets operand to be the new object's prototype.

   - calls the ctor, passing on the new object as the "this" and any
   arguments in the args array (which may be NULL or empty).

   - if that ctor returns successfully: if it returns a cwal_value
   other than cwal_value_undefined() then that value is assigned to
   *rv, otherwise the newly-created Object is set to *rv. Thus a
   "return this", "return undefined", "return" and an implicit return
   all cause the newly-created object to be returned.  The intention
   is to allow constructors to return a different object which should
   then be substituted in the original's place (this is what "new"
   does with it). That capability is required for returning, e.g.,
   dynamically-loaded cwal_native-bound values from a constructor.

   This all happens in an internally-pushed scope, but *rv will be
   rescoped to the caller's scope (for lifetime management purposes)
   if necessary.

   ACHTUNG: make sure you have a ref point on all parameters, else
   this function might end up cleaning any one of them up. If passed,
   e.g. an args array with no reference point, the passed-in array
   may be cleaned up by this function.

   On error, non-0 is returned and *rv will be assigned to 0.

   TODO: a variant of this which takes a C array of cwal_value
   pointers and the length of that array, as that's generally easier
   to work with in client code (but an array-based impl is what the
   "new" keyword needs).
*/
int whcl_ctor_apply( whcl_engine * const el, cwal_value * const operand,
                     cwal_function * ctor, cwal_array * args,
                     cwal_value **rv );

/**
   This odd function is intended to assist in certain types of
   bindings which want to store, e.g. a custom Prototype. This
   function creates a new key using cwal_new_unique() and uses it
   store 'what' in 'where' with the CWAL_VAR_F_HIDDEN and
   CWAL_VAR_F_CONST flags. This binding will ensure that 'what' gets
   referenced and rescoped as necessary to keep it alive along with
   'where'. One caveat: if cwal_props_clear(where) is called, it will
   delete that binding, possibly leading to unexpected behaviour in
   other places which are expecting 'what' to be alive.

   A concrete example: custom modules often need a custom prototype,
   but don't really have a good place to store it. One option is to
   attach it as state to the Function which acts as that type's
   constructor (because the prototype is typically only invoked, at
   the C level, from there). However, Function state is of type
   (void*), and thus is not "Value-aware" and cannot be rescoped as
   needed.  One workaround to keep that prototype alive is to store it
   as a Value in the Function. Because the key is unknown, it cannot
   be looked up, but it can (with a little extra work) be accessed via
   cwal_function_state_get() and friends. The binding created by
   _this_ function keeps that state alive so long as the property does
   not get removed.

   Reminder to self: it might be interesting to add a new
   CWAL_VAR_F_xxx flag which tells even cwal_props_clear() not to
   remove the property. That would be somewhat more invasive and
   special-case than i'm willing to hack right now, though. :/
*/
int whcl_stash_hidden_member( cwal_value * const where,
                              cwal_value * const what );

/**
   This is a simple wrapper around whcl_scope_set_v() which sets the
   scope-level variable `this` to the given value. The main benefit of
   this function is that it uses a cached copy of the `this` string so
   might not have to allocate anything.

   Returns 0 on success, else some error code propagated from
   whcl_scope_set_v().

   If `that` is NULL then the `this` var is removed from the current
   scope.

   Trivia: in client-side wrappers it's often useful to set a specific
   `this` for code blocks which callbacks are intended to eval.
*/
int whcl_set_this(whcl_engine * const el, cwal_value * const that);

/**
   Feature flags for use with whcl_feature_flag_set().
*/
enum whcl_engine_feature_e {
/**
   Tells the whcl_engine to enable execution of:

   `__debug {...}`

   Without the flag, such blocks are not executed.

   Note that `pragma __debug` can be used to change this setting from
   scripts.
*/
WHCL_FEATURE_F_DEBUG_BLOCK,
/**
   When enabled, all script-side `assert` calls are logged to the
   engine's output channel.

   From script code, `pragma trace-assert` can be used to enable this
   by passing it a value which is true when masked with 0x01.
*/
WHCL_FEATURE_F_TRACE_ASSERT,
/**
   When enabled, all script-side `affirm` calls are logged to the
   engine's output channel.

   From script code, `pragma trace-assert` can be used to enable this
   by passing it a value which is true when masked with 0x02.
*/
WHCL_FEATURE_F_TRACE_AFFIRM
};

/**
   Enables or disables, depending on the third argument, the feature
   of whcl_engine described by the second argument.
*/
void whcl_feature_flag_set( whcl_engine * const el,
                            enum whcl_engine_feature_e,
                            bool on );

/**
   "Compiles" the given script source code into a new whcl_script
   object, which can then (on success) be passed to whcl_script_eval()
   to run it.

   `*tgt` must, for proper behavior, initially point to NULL, as
   opposed to being uninitialized. Future potential changes to this
   function will break code which violates that.

   len must be the number of bytes in src to process. If it is
   negative then cwal_strlen() is used to calculate it. On success
   `*tgt` is assigned to the new script instance and ownership of it
   is transferred to the caller, who must eventually pass it to
   whcl_script_free() to free its memory. On error `*tgt` is not
   modified. Errors are reported by returning non-0 and setting the
   error state in the underlying cwal_engine instance.

   If scriptName is not NULL, it must be a NUL-terminated string and
   it gets copied for use as the script's name for error-reporting
   purposes.

   The resulting whcl_script object does not own any cwal_value-level
   state, so has no lifetime issues related to script-engine scoping
   and whatnot, but it _must not_ outlive the engine for which it was
   compiled. i.e. it must be freed before the engine is finalized.

   @see whcl_compile_buffer()
   @see whcl_compile_buffer_take()
   @see whcl_script_free()
   @see whcl_script_eval()
*/
int whcl_compile( whcl_engine * const el,
                  whcl_script ** tgt,
                  char const * scriptName,
                  char const * const src, cwal_int_t len );

/**
   This convenience wrapper for whcl_compile() differs only in that it
   takes its input source code in the form of a buffer. It otherwise
   functions identically to that function. If the caller will not need
   the memory in the buffer after the compilation process,
   whcl_compile_buffer_take() is preferred, as it takes over the
   buffer's memory rather than making a copy for the compiled script.

   @see whcl_compile()
   @see whcl_compile_buffer_take()
   @see whcl_script_free()
   @see whcl_script_eval()
*/
int whcl_compile_buffer( whcl_engine * const el, whcl_script ** tgt,
                         char const * scriptName,
                         cwal_buffer const * const buf );

/**
   This variant of whcl_compile() differs in that (1) it
   accepts its source code as a cwal_buffer object and (2) on success
   it takes over the memory of that buffer (and it must not be
   modified after that). Any non-buffer-memory state of the buffer is
   retained (e.g. if it's a buffer from cwal_new_buffer_value(), its
   Value state is retained). On error, ownership of src->mem is not
   changed.

   @see whcl_compile()
   @see whcl_compile_buffer()
   @see whcl_script_free()
   @see whcl_script_eval()
*/
int whcl_compile_buffer_take(whcl_engine * const el, whcl_script ** tgt,
                             char const * const name,
                             cwal_buffer * const src);

/**
   This is a convenience wrapper aroud whcl_compile() which reads its
   contents from the given filename. Argument and return semantics are
   as for whcl_compile(), these notes:

   - If `scriptName` is not NULL, it is copied for use as the script's
     name, else `filename` is used for that purpose.

   - It reading the file fails it may return any code documented for
     cwal_buffer_fill_from_filename().

   On error, el's error state is updated.
*/
int whcl_compile_file( whcl_engine * const el, whcl_script ** tgt,
                       char const * filename,
                       char const * scriptName);

/**
   If cp is not NULL, finalizes it to free up any resources it owns, then
   frees its own memory.
*/
void whcl_script_free( whcl_script * const cp );

/**
   If v, or one of its prototypes, is a cwal_native value created by
   the whcl framework to wrap a whcl_script object, this returns the
   matching whcl_script instance, else it returns NULL.
*/
whcl_script * whcl_value_get_script(cwal_value * const v);

/**
   Flag bits for use with whcl_script_eval().
*/
enum whcl_script_eval_e {
/**
   Indicates that whcl_script_eval() must push a new whcl scope (as
   opposed to cwal_scope) before processing.
*/
WHCL_SCRIPT_EVAL_SCOPE = 0x01
};

/**
   Runs the given script object and evaluates it in the context of
   of the whcl_engine which was used to whcl_compile() it.

   Returns 0 on success. If rv is not NULL then on success the final
   result of the eval is assigned to `*rv`. On error `*rv` is not
   modified.
*/
int whcl_script_eval(whcl_script * const sc, uint32_t flags,
                     cwal_value ** rv);

/**
   Frees any current name string owned by scr and and replaces it with
   a copy of the given name. If the length is negative, cwal_strlen()
   is used to calculate it. Returns 0 on success, CWAL_RC_OOM on
   error. On allocation error of a new name, the previous name is not
   modified.

   As a special case, if name is NULL then the script's name,
   if any, is freed and its name is set to NULL.
*/
int whcl_script_name_set(whcl_script * const scr,
                         char const * name,
                         cwal_int_t len);

/**
   Returns either t->name or the first name encountered while climbing
   up t's parent chain. Returns NULL if no name is found. If len is
   not 0 then if this function returns non-0, len is set to that
   name's length. The bytes are owned by t or one of its parents, so
   must be copied if that lifetime might be problematic.
*/
char const * whcl_script_name_get( whcl_script const * t,
                                   cwal_midsize_t * const len );

/**
   Fetches the line/column offset of the given script.  Returns true
   if src has an initial token (from which the position is extracted),
   else false. Either of the 2nd or 3rd arguments may be NULL. If they
   are not NULL and this function returns false, they are set to 0
   (which is an invalid line number).

   Line numbering starts with 1, column numbering with 0 (because
   that's how emacs does it).

   Note that top-level scripts will have a position of (1,0), but
   "inner" scripts retain their offset origin from their parent.

   @see whcl_script_renumber()
*/
bool whcl_script_linecol(whcl_script const * const src,
                         whcl_linecol_t * line,
                         whcl_linecol_t * col);

/**
   Renumbers the lines of a script starting at the given values.  The
   given column is only used for the offset of the script's first
   line, and can normally be left at 0. All lines after the first
   retain their column values. When a script is compiled from within
   another script it may (depending on how it was constructed) have a
   non-0 column offset for the first token.

   If a line value of 0 is passed in, it is treated as 1.

   @see whcl_script_linecol()
*/
void whcl_script_renumber(whcl_script * const src,
                          whcl_linecol_t line,
                          whcl_linecol_t col);

/**
   Dumps all sorts of cwal/whcl metrics to the given engine's
   current cwal-level output channel.
*/
void whcl_dump_metrics(whcl_engine * const el);

typedef struct whcl_tmpl_opt whcl_tmpl_opt;
/**
   Holds options for the whcl_tmpl_to_code() function.  Clients
   must initialize them by copying either whcl_tmpl_opt_empty or
   (for const contexts) whcl_tmpl_opt_empty_m.  */
struct whcl_tmpl_opt {
    /**
       This may be set to a command (optionally with flags) which will
       be passed each token of parsed output. It is passed a single
       string argument at a time and should collect or output it
       as-is, with no additional spacing or formatting.

       The command need not be resolvable during the code-generation
       step, but needs to be so when the resulting code is eval'd.

       If this is empty then `echo -n -s` is used.
    */
    char const * outputCommand;

    /**
       This specifies the name of the processed-template-internal
       heredoc delimiter. By default (if this is 0 or empty) some
       cryptic combination of non-ASCII UTF8 character is used.
    */
    char const * heredocId;

    /**
       The opening tag for "code" blocks. Default is `<?`.
       If set, then tagCodeClose must also be set. Must differ
       from all other tag open/close entries.
     */
    char const * tagCodeOpen;

    /**
       The opening tag for "code" blocks. Default is `?>`.
       If set, then tagCodeOpen must also be set. Must differ
       from all other tag open/close entries.
    */
    char const * tagCodeClose;

    /**
       The opening tag for "expression" blocks. Default is `<%`.
       If set, then tagExprClose must also be set. Must differ
       from all other tag open/close entries.
    */
    char const * tagExprOpen;

    /**
       The opening tag for "expression" blocks. Default is `%>`.
       If set, then tagExprOpen must also be set. Must differ
       from all other tag open/close entries.
    */
    char const * tagExprClose;
};

/**
   An initialized-with-defaults instance of whcl_tmpl_opt,
   intended for const-copy initialization.
*/
#define whcl_tmpl_opt_empty_m {0,0,0,0,0,0}

/**
   An initialized-with-defaults instance of whcl_tmpl_opt,
   intended for copy initialization.
*/
extern const whcl_tmpl_opt whcl_tmpl_opt_empty;

/**
   Implements a very basic text template processing mechanism for
   whcl.

   The el arg must be a valid whcl_engine instance.

   src must be the template source code to process. It is treated as
   nearly-opaque text input which may contain markup tags (described
   below) to embed either code blocks or values into the output.

   dest is where all output is appended (the buffer is not reset by
   this function).

   The opt parameter may be 0 (for default options) or an object which
   configures certain parts of the template processing, as described
   in the whcl_tmpl_opt docs.

   Returns 0 on success, non-0 on error. Error codes include:

   - CWAL_RC_MISUSE if !src or !dest.

   - CWAL_RC_RANGE if any of the open/close tags specified in the opt
   parameter are invalid (empty strings or validate rules described in
   the whcl_tmpl_opt docs).

   - CWAL_RC_OOM on allocation errors.

   - CWAL_RC_EXCEPTION if it is reporting an error via a cwal
   exception. On code generation errors it throws an exception
   containing information about the nature and location (in the
   original source) of the problem.

   That said, it may not catch many conceivable malformed content
   cases and in such cases may generate malformed (as in not
   eval'able) code.

   Template processing...

   (Note that while these docs use fixed tag names, the exact
   tags can be configured via the opt parameter.)

   All non-code parts of src are filtered to be output wrapped in
   individual HEREDOCs embedded in the output script. All code parts
   of src are handled as follows:

   `<?` starts a code block, running until and closing `?>` tag. This
   ends any current HEREDOC and passes through the code as-is to dest.
   It does not generate any output in the processed document unless
   the embedded code generates it by calling the output command.

   `<%` starts an "expression block," which is processed a little bit
   differently. The contents between that and the next `%>` tag are
   evaluated as a single expression and the result is passed to the
   configured output command (see below).

   An example input document should clear this up:

   ```
   Hi, world!
   <? decl x 1; decl y 2; ?>
   x = <%$x%>, y = <% $y %>, x+y=<% $x + $y %>
   ```

   The generated code is an whcl script which, when eval'd, outputs a
   processed document. All non-script parts get wrapped in HEREDOCs
   for output. When eval'd, the above script outputs:

   ```
   Hi, world!
   x = 1, y = 2, x+y=3
   ```

   The generated code "should" be evaluated in a scope of its own, but
   it can be run in the current scope if desired. The code relies on
   an output command being defined (resolvable in the evalution
   scope). The command must accept any number of parameters and output
   them "in its conventional string form" (whatever that is). It must
   not perform any formatting such as spaces between the entries or
   newlines afterwards. It may define formatting conventions for
   values passed to it (e.g. it may feel free to reformat doubles to a
   common representation).

   The generator outputs some weird/cryptic UTF8 symbols as heredoc
   markers. It's conceivable, though very unlikely, that these could
   collide with symbols in the document for heredoc processing
   purposes, in which case they can be redefined via the `heredocId`
   member of the options object.

   Whitespace handling:

   - If the script starts with `<?` or `<%`, any whitespace leading up
   to that are discarded, otherwise all leading whitespace is
   retained.

   - Replacement of `<% %>` and `<? ?>` blocks retains whitespace to the
   left of the openener and right of the closer, so `{abc<%$x%>def}` will
   form a single output token (provided 'x' evaluates to such), where
   `{abc <%$x%> def}` will generate three. Inside the `<? ?>` blocks, all
   whitespace is retained. Inside `<% %>` blocks, the contents are
   treated as if they were inside a normal HEREDOC, so their
   leading/trailing spaces are stripped BUT they are not significant -
   the _result_ of evaluating the `<% %>` content gets output when
   executed, not the content itself.

   TODOs:

   - a variant which takes a cwal_output_f() instead of a buffer.
*/
int whcl_tmpl_to_code( whcl_engine * const el, cwal_buffer const * const src,
                       cwal_buffer * const dest, whcl_tmpl_opt const * opt );

/**
   A cwal_callback_f() binding for whcl_tmpl_to_code(). It expects one
   string/buffer argument containing tmpl code and it returns a new
   script or buffer value containing the processed code. Throws on error.

   Script usage:

   ```
   decl compiled [thisFunction [-buffer] templateSource [, optObject]]
   ```

   By default it returns a new whcl_script instance (via cwal_native)
   but the -buffer flag tells it to return the tmpl-generated source
   code as a buffer object instead.

   If optObject is-a Object then the following properties of that
   object may influence template processing:

   - `expr-open` and `expr-close` specify the open/close tags for
     Expression Blocks.

   - `code-open` and `code-close` specify the open/close tags for Code
     Blocks.

   - `output-command` sets the name of the symbol the generated
     template code will (when eval'd) use for output. It may be any
     string which expands to the name of a script-visible command,
     including optional leading flags. The default is unspecified but
     functionally equivalent to `echo -n -s`. The command need not be
     resolvable during the code-generation step, but needs to be so
     when the resulting code is eval'd.
*/
int whcl_cb_tmpl_to_code( cwal_callback_args const * args, cwal_value ** rv );

/**
   A cwal_json_override_f() impl which handles JSONizing of
   wchl_script-type values. The only use for this function is
   assigning to the cwal_json_output_opt::override.f member.  Its
   state argument is ignored, so cwal_json_output_opt::override.state
   may be NULL.
*/
int cwal_json_override_f_whcl(cwal_engine * e, cwal_value * src,
                              cwal_value **rv, void * state);

/**
   Creates a new plain object which represents the JSON-able state of
   the given script. On success, returns 0 and assigns `*rv` the new
   value (an Object). On error, returns non-0 and does not modify
   `*rv`. The only known error, assuming all arguments are in order,
   is CWAL_RC_OOM.

   The returned object has the following properties representing
   the script's state:

   - "script": source code
   - "name": script name, if set, else this property is elided

*/
int whcl_script_to_jsonable( whcl_script * const scr,
                             cwal_value ** rv );

/**
   If str, ignoring any leading (but not trailing) spaces, is a
   well-formed integer, this function returns true and updated
   `*result` (if result is not NULL) to the parse value. Returns false
   and does not modify `*result` if parsing fails.

   slen is the length of the string, or cwal_strlen() is used
   if slen is negative.

   It permits any integer formats supported by whcl script code
   (decimal, octal with leading 0o, binary with leading 0b, and hex
   with leading 0x) with an optional sign prefix.
*/
bool whcl_parse_int(char const * str, cwal_int_t slen,
                    cwal_int_t * result );

/**
   This uses a combination of whcl_parse_int() and cwal_cstr_to_double()
   on the given string to try to determine if it's valid, then
   updates `*rv` with its result.

   If it cannot parse, it sets `*rv` to NULL and returns 0.

   If it can parse, it sets `*rv` to a new integer or double value
   and returns 0. Returns CWAL_RC_OOM if allocation of a value
   fails. The result value follows all of the normal cwal_value
   lifetime rules.
*/
int whcl_parse_number( cwal_engine * const e, char const * src,
                       cwal_int_t slen, cwal_value ** rv );

/**
   Returns a pointer to a static/const cwal_json_output_opt instance
   which provides the whcl-wide default values for JSON output. Most
   notably, the object's `override.f` member is set to a function
   which knows to how to look up and apply `to-jsonable` member
   functions.

   If passed true, the returned pointer is set up for "formatted"
   output, including indentation and whatnot. If passed false, it's
   geared more towards compact output.
*/
cwal_json_output_opt const * whcl_json_output_opt(bool formatted);

#ifdef __cplusplus
}/*extern "C"*/
#endif

#endif
/* include guard */