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