#if !defined(__STDC_FORMAT_MACROS) /* required for PRIi32 and friends.*/ # define __STDC_FORMAT_MACROS #endif #include "th1ish_amalgamation.h" /* start of file ../cwal_amalgamation.c */ #if !defined(__STDC_FORMAT_MACROS) /* required for PRIi32 and friends.*/ # define __STDC_FORMAT_MACROS #endif #include "string.h" /*memset()*/ #include "assert.h" /* start of file cwal_internal.h */ #if !defined(WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED) #define WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED 1 /** This file documents the internal/private API of libcwal. It's strictly internal parts are only in the main implementation file, but the parts in this file are thought to potentially be useful in future sub-systems which live somewhere between the private API and public API (not quite public, not quite core). */ #if defined(__cplusplus) extern "C" { #endif /** A callback type for use with cwal_ptr_table_visit(). The elem argument is the entry being visited over. The state argument is whatever state the caller passed to cwal_ptr_table_visit(). If a visitor function returns non-0, iteration ceases and that result code is returned to the caller of cwal_ptr_table_visit(). */ typedef int (*cwal_ptr_table_visitor_f)( cwal_value ** elem, void * state ); /** @internal Operation values for cwal_ptr_table_op(). */ enum cwal_ptr_table_ops { CWAL_PTR_TABLE_OP_REMOVE = -1, CWAL_PTR_TABLE_OP_SEARCH = 0, CWAL_PTR_TABLE_OP_INSERT = 1 }; typedef enum cwal_ptr_table_ops cwal_ptr_table_ops; /** @internal Initializes a new pointer table. *T may be NULL, in which case this function allocates a new cwal_ptr_table. If *T is not NULL then it is assumed to be a freshly-allocated, non-initialized pointer table and is initialized as-is. Returns 0 on error. On success *T is set to the ptr table (possibly freshly allocated) and ownership is transfered to the caller, who must eventually call cwal_ptr_table_destroy() to free its resources. */ int cwal_ptr_table_create( cwal_engine * e, cwal_ptr_table ** T, uint16_t hashSize, uint16_t step ); /** @internal Frees all resources owned by t. t must have been initialized by cwal_ptr_table_create(), and if that function allocated t, then t will also be free()'d, otherwise the caller must free t using the method which complements its allocation technique (e.g. do nothing for stack-allocated values). Returns 0 on success. */ int cwal_ptr_table_destroy( cwal_engine * e, cwal_ptr_table * t ); /** @internal Performs a search/remove/insert operation (specified by the op parameter) on the given ptr table. Returns 0 on success. The valid operations are: CWAL_PTR_TABLE_OP_REMOVE: remove the given key from the table. CWAL_PTR_TABLE_OP_SEARCH: return 0 if the table already has such an entry. CWAL_PTR_TABLE_OP_INSERT: insert the given key into the table. Returns CWAL_RC_ALREADY_EXISTS if key is already in the table. */ int cwal_ptr_table_op( cwal_engine * e, cwal_ptr_table * t, void * key, cwal_ptr_table_ops op ); /** @internal Equivalent to cwal_ptr_table_op(e,t,key,CWAL_PTR_TABLE_OP_SEARCH). */ int cwal_ptr_table_search( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ); /** @internal Equivalent to cwal_ptr_table_op(e,t,key,CWAL_PTR_TABLE_OP_REMOVE). */ int cwal_ptr_table_remove( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ); /** @internal Equivalent to cwal_ptr_table_op(e,t,key,CWAL_PTR_TABLE_OP_INSERT). */ int cwal_ptr_table_insert( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ); /** @internal Calculates the amount of memory allocated for use with the given table. The results are stored in *mallocs (the total number of memory allocation calls) and *memory (the total number of bytes allocated for use with t). Either of mallocs or memory may be NULL. This is an O(N) operation, where N is the number of pages currently managed by t. */ int cwal_ptr_table_mem_cost( cwal_ptr_table const * t, uint32_t * mallocs, uint32_t * memory ); /** @internal "Visits" each entry in it, calling the given callback for each one (NULL entries in the table are skipped - only "live" entries are iterated over). The callback is passed a pointer to a pointer to the original entry, and if that pointer is re-assigned, that change is written back to the table when the visitor returns. (Why? i'm not sure why it was done that way - it is a dangerous feature and should not be used.) Returns 0 on success. if the visitor returns non-0, that code is returned to the caller of this function. */ int cwal_ptr_table_visit( cwal_ptr_table * t, cwal_ptr_table_visitor_f f, void * state ); /** @internal Strings are allocated as an instances of this class with N+1 trailing bytes, where N is the length of the string being allocated. To convert a cwal_string to c-string we simply increment the cwal_string pointer. To do the opposite we use (cstr - sizeof(cwal_string)). Zero-length strings are a special case handled by a couple of the cwal_string functions. The top-most 2 bits of the length field are reserved for flags. This is how we mark "x-strings" (external strings, held in memory allocated elsewhere (e.g. static globals or client-interned strings). */ struct cwal_string { cwal_size_t length; }; /** @internal Internal state flags. Keep these at 16 bits or adjust various flags types accordingly! Note that some flags apply to only to particular types, and thus certain flags might have the same values to conserve bit-space (if we can reduce it to 8 bits, great, because that allows us to reasonably use the top 8 bits of the refcount for cwal_value::flags, removing the flags member to save a few bytes). */ enum CWAL_FLAGS { /** Sentinel value for empty flags. */ CWAL_F_NONE = 0, /** Used by cwal_value_vtab::flags to indicate that the type contains a cwal_obase as its first struct member and therefore qualifies as an "obase" (object base) sub-type. This is a casting-related optimization. "OBases" must meet these requirements: - It is allocated using the library-conventional single-malloc method of (cwal_value,concrete_type). See cwal_value_new() for the many examples. - Its concrete value type has a cwal_object struct as its first member. This, in combination with the first requirement, allows us (due to an interest C rule) to efficiently/safely cast directly between a cwal_value, its "object type," and a convenience base type (cwal_obase) which is used in many internal algorithms to consolidate code for container handling. And... we don't hold an extra pointer (or two) for this - it comes for free as a side-effect of these first 2 requirements. See cwal_object and cwal_array for examples of using the obase flag. Note that cwal_object and cwal_array actually have identical signatures and _could_ be packed into a single type, but by the time that became possible (in terms of API evolution) i already had the type-specific signatures in place (and now prefer them for the public API for type-safety reasons). */ CWAL_F_ISA_OBASE = 0x01, CWAL_F_IS_GC_QUEUED = 0x08, /** Is used as an extra safety mechanism to avoid a double-delete if the refcounts-with-cycles-counting mechanism breaks. This only means that we convert a double free() into a leak (or an assert() in debug builds). Currently only used for obase types. */ CWAL_F_IS_DESTRUCTING = 0x10, /** Is set on container values during the various visit() APIs to detect/break cycles during iteration over a container. Currently used for obase types. */ CWAL_F_IS_VISITING = 0x20, /** This cwal_obase::flags is set when running through the count-cycles phase. We use this to detect/count cycles during the traversal. Currently used for obase types. */ CWAL_F_IS_COUNTING_CYCLES = 0x40, CWAL_F_IS_RECYCLED = 0x80 }; /** @internal Convenience typedef. */ typedef struct cwal_obase cwal_obase; /** @internal "Base" type for types which contain other values (which means they are subject to cycles). An instance of this struct must be embedded at the FIRST MEMBER of any such class, and any type-specific members must come after that. See cwal_array and cwal_object for examples. Note that the interpretation of the list member is type-dependent. e.g. cwal_array stores (cwal_value*) whereas cwal_object uses (cwal_kvp*). */ struct cwal_obase { /** Linked list of key/value pairs held by this object. */ cwal_kvp * kvp; /** The (experimental) "prototype" (analog to a parent class). */ cwal_value * prototype; /** Internal flags. */ uint16_t flags; /** We might want/need to add a counter for recursion levels, to allow us to be able to recurse through an object while recursion is already in place. Currently we use the CWAL_F_IS_VISITING flag to mark objects being recursed over, but that only allows one level of recursion, and disallows constructs which need to enter looping on a given container more than once. Here's a non-sensical example which we cannot currently (directly) support: forEach( key in myObj ) { forEach( key2 in myObj ){ } } That will currently be reported as CWAL_RC_CYCLES_DETECTED. We might be able to solve this by adding an "allow recursion" flag which will allow _one_ level of recursion. When recursion is encountered, disable the flag. It could be set from, e.g., the forEach implementation before looping over myObj. Alternately, a counter, and in the container traversal code we check if that counter is equal to our current recursion depth (but we'd need to keep that value somewhere... where?). */ }; /** @internal Empty-initialized cwal_obase object. */ #define cwal_obase_empty_m { NULL/*kvp*/, NULL/*prototype*/, CWAL_F_NONE/*flags*/ } /** @internal Concrete value type for Arrays. */ struct cwal_array { /** base MUST be the first member for casting reasons. */ cwal_obase base; /** Holds (cwal_value*) and NULL entries ARE semantically legal. */ cwal_list list; }; #define cwal_array_empty_m { cwal_obase_empty_m, cwal_list_empty_m } extern const cwal_array cwal_array_empty; /** @internal The metadata for concrete Object values. */ struct cwal_object { /** base MUST be the first member for casting reasons. */ cwal_obase base; }; #define cwal_object_empty_m { cwal_obase_empty_m } extern const cwal_object cwal_object_empty; /** Information for binding a native function to the script engine in the form of a Function value. */ struct cwal_function { /** base MUST be the first member for casting reasons. */ cwal_obase base; cwal_state state; cwal_callback_f callback; }; /** Empty-initialized cwal_function object. */ #define cwal_function_empty_m { cwal_obase_empty_m, cwal_state_empty_m, NULL } /** Empty-initialized cwal_function object. */ extern const cwal_function cwal_function_empty; /** Concrete type for generic error/exception values. */ struct cwal_exception { /** base MUST be the first member for casting reasons. */ cwal_obase base; int code; }; #define cwal_exception_empty_m { cwal_obase_empty_m, -1 } extern const cwal_exception cwal_exception_empty; /** A key/value pair of cwal_values. While key is a cwal_value, the engine requires that it be-a CWAL_TYPE_STRING (cwal_string). Each of these objects owns its key/value pointers, and they are cleaned up by cwal_kvp_clean(). TODO: make this type a doubly-linked list for: a) so we can get rid of cwal_list in cwal_object and b) we can use it in place of a HashEntry in the planned cwal_hashtable type (pending hashing support in the cwal_value_vtab interface). */ struct cwal_kvp{ /** The key. Keys are compared using cwal_value_vtab::compare(). */ cwal_value * key; /** Arbitrary value. Objects do not have NULL values - a literal NULL means to "unset" a property. cwal_value_null() can be used as a value, of course. */ cwal_value * value; /** Right-hand member in a linked list. */ cwal_kvp * right; /** We need this for intepreter-level flags like "const" and "non-removable." */ uint16_t flags; }; #define cwal_kvp_empty_m {NULL,NULL,NULL,0U/*flags*/} extern const cwal_kvp cwal_kvp_empty; /** @internal Allocates a new cwal_kvp object, owned by e. Returned NULL on error. On success the returned value is empty-initialized. */ cwal_kvp * cwal_kvp_alloc(cwal_engine *e); /** @internal Unrefs kvp->key and kvp->value and sets them to NULL, but does not free kvp. If !kvp then this is a no-op. */ void cwal_kvp_clean( cwal_engine * e, cwal_scope * fromScope, cwal_kvp * kvp ); /** @internal Calls cwal_kvp_clean(e,fromScope,kvp) and then either adds it to the recycle bin or frees it, depending on the value of allowRecycle and the capacity/size of the associated recycler list. Callers must treat this call as if kvp is free()d by it, whether or not this function actually does so. fromScope should be the owning scope of the container on behalf of whom kvp is being freed. It may be 0, but this is semantically legal only in a few select cases. */ void cwal_kvp_free( cwal_engine * e, cwal_scope * fromScope, cwal_kvp * kvp, char allowRecycle ); /** Typedef for cwal_value hashing functions. Must return a hash value for the given value. */ typedef cwal_hash_t (*cwal_value_hash_f)( cwal_value const * v ); /** Returns v's hash value, as computed by v's vtab. Returns 0 if !v. */ cwal_hash_t cwal_value_hash( cwal_value const * v ); /** Typedef for cwal_value comparison functions. Hash memcmp() semantics. Ordering of mismatched types is type-dependent, possibly undefined. Implementations of this function are type-specific and require that the lhs (left-hand-side) argument be of their specific type (and are permitted to assert that that is so). When performing conversions, they should treat the LHS as the primary type for conversion/precision purposes. e.g. comparison of (int 42) and (double 42.24) might be different depending on which one is the LHS because of changes in precision. Beware that these comparisons are primarily intended for cwal-internal use (e.g. in the context of hashtables), and are not strictly required to follow the semantics of any given scripting environment or specificiation. Where (lhs,rhs) do not have any sort of natural ordering, implementations should return any value other than 0, implementing ECMAScript-like semantics if feasible. Implementations are encouraged to do cross-type comparisons where feasible (e.g. "123"==123 is true), and to delegate to the converse comparison (swapping lhs/rhs and the return value) when the logic for the comparison is non-trivial and already implemented for the other type. Strict-equality comparisons (e.g. "123"===123 is false) are handled at a higher level which compares type IDs and/or pointers before passing the values on to this function. */ typedef int (*cwal_value_compare_f)( cwal_value const * lhs, cwal_value const * rhs ); /** This type holds the "vtbl" for type-specific operations when working with cwal_value objects. All cwal_values of a given logical type share a pointer to a single library-internal instance of this class. */ struct cwal_value_vtab { /** The logical JavaScript/JSON type associated with this object. */ const cwal_type_id typeID; /** A descriptive type name intented primarily for debuggering, and not (necessarily) as a type's name as a client script language might see/name it. */ char const * typeName; /** Internal flags. */ uint16_t flags; /** Must free any memory associated with the second argument (which will be a cwal_value of the concrete type associated with this instance of this class), but not free self. The API shall never pass a NULL value to this function. */ cwal_finalizer_f cleanup; /** Must return a hash value for this value. Hashes are used only as a quick comparison, with the compare() method being used for a more detailed comparison. */ cwal_value_hash_f hash; /** Must perform a comparison on its values. The cwal engine guarantees that the left-hand argument will be of the type managed by the instance of the cwal_value_tab on which this is called, but the right-hand argument may be of an arbitrary type. This function checks for equivalence, not identity, and uses memcmp() semantics: less than 0 means that the left-hand argument is "less than" the right, 0 means they are equivalent, and 1 means the the lhs is "greater than" the right. It is understood, of course, that not all comparisons have meaningful results, and implementations should always return non-0 for meaningless comparisons. They are not required to return a specific value but should behave consistently (e.g. not swapping the order on every call or some such sillynesss). For many non-POD types, 0 can only be returned when both pointers have the same address (that said, the framework should short-circuit any such comparison itself). */ cwal_value_compare_f compare; /** Called by the framework when it determines that v needs to be "upscoped." The framework will downscope v but cannot generically know if v contains any other values which also need to be upscoped. So this function has the following responsibilities: For any child values, this function must call cwal_value_rescope( v->scope->e, v->scope, child ). That, in turn, will trigger the recursive rescoping of any children of that value. Note that the rescoping problem is not subject to "cyclic misbehaviours" - the worst that will happen is a cycle gets visited multiple times, but those after the first will be no-ops because no re-scoping is necessary. The framework guarantees the following: Before this is called on a value, the following preconditions exist: - v->scope has been set to the "proper" scope by e. This function will never be called when v->scope is 0. - v->scope->e points to the active engine. - This is only called when/if v is actually upscoped. Future re-scope requests which do not require a rescope will not trigger a call to this function. If v owns any "unmanaged" child values (e.g. not kept in the cwal_obase base class container or as part of a cwal_array) then those must be rescoped by this function (the framework does not know about them). Classes which do not contain other values may set this member to 0. It is only called if it is not NULL. Classes which do hold other values _must_ implement it (properly!). Must return 0 on success and "truly shouldn't fail" because all it does it pass each child on to cwal_value_rescope(), which cannot fail if all arguments are in order and the internal state of the values seems okay (the engine does a good deal of extra checking). However, if it fails it should return an appropriate value from the cwal_rc enum. */ int (*rescope_children)( cwal_value * v ); /** TODOs: // Using JS semantics for true/value char (*bool_value)( cwal_value const * self ); // The problem with adding an allocator is... int (*allocate)( cwal_engine *, ??? ); // If we split it into allocation and initialization, might that solve it? Or cause more problems? // cwal_value * (*allocate)( cwal_engine * e ); // int (*initialize)( cwal_engine * e, cwal_value * v, ... ) // Deep copy. int (*clone)( cwal_engine *e, cwal_value const * self, cwal_value ** tgt ); // Property interceptors: int (*get)( cwal_engine *e, cwal_value const * self, cwal_value const * key, cwal_value **rv ); int (*set)( cwal_engine *e, cwal_value * self, cwal_value * key, cwal_value *v ); But for convenience the get() op also needs a variant taking a c-string key (otherwise the client has to create values when searching more often than not) */ }; typedef struct cwal_value_vtab cwal_value_vtab; /** Empty-initialized cwal_value_vtab object. */ #define cwal_value_vtab_empty_m { \ CWAL_TYPE_UNDEF/*typeID*/, \ ""/*typeName*/, \ 0U/*flags*/, \ NULL/*cleanup()*/, \ NULL/*hash()*/, \ NULL/*compare()*/, \ NULL/*rescope_children()*/ \ } /** Empty-initialized cwal_value_vtab object. */ extern const cwal_value_vtab cwal_value_vtab_empty; /** cwal_value represents an opaque Value handle within the cwal framework. Values are all represented by this basic type but they have certain polymorphic behaviours and many concrete types have high-level handle representations. @see cwal_new_VALUE() */ struct cwal_value { /** The "vtbl" of type-specific operations. All instances of a given logical value type share a single api instance. Results are undefined if this value is NULL. */ cwal_value_vtab const * vtab; /** The "current owner" scope of this value. Its definition is as follows: When a value is initially allocated its owner is the currently-active scope. If a value is referenced by a higher-level (older) scope, it is migrated into that scope (recursively for containers) so that we can keep cleanup of cycles working (because all values in a given graph need to be in the same scope for cleanup to work). */ cwal_scope * scope; /** The left-hand-link for a linked list. Who exactly owns a value, and which values they own, are largely handled via this list. Each manager (e.g. scope, recycle bin, or gc queue) holds the head of a list and adds/removes the entries as needed. Note that we require two links because if we only have a single link then cleanup of a member in a list can lead traversal through that list into places it must not go (like into the recycler's memory). Design notes: in the original design each value-list manager had a separate list to manage its values. Switching to this form (where the values can act as a list) actually (perhaps non-intuitively) cuts both the number of overall allocations and memory cost, converts many of the previous operations from O(N) to O(1), and _also_ removes some unrecoverable error cases caused by OOM during manipulation of the owner's list of values. So it's an overall win despite the cost of having 2 pointers per value. Just remember that it's not strictly Value-specific overhead... it's overhead Scopes and other Value owners would have if this class didn't :/. */ cwal_value * left; /** Right-hand link of a linked list. */ cwal_value * right; /** We use this to allow us to store cwal_value instances in multiple containers or multiple times within a single container (provided no cycles are introduced). Notes about the rc implementation: - The refcount is for the cwal_value instance itself, not its value pointer. - Instances start out with a refcount of 0 (not 1). Adding them to a container will increase the refcount. Cleaning up the container will decrement the count. - cwal_value_unref() decrements the refcount (if it is not already 0) and cleans/frees the value only when the refcount is 0. - Some places in the internals add an "extra" reference to objects to avoid a premature deletion. Don't try this at home. */ cwal_size_t refcount; /* Potential TODO: if we _need_ flags in this class, we can use the high byte (or half-byte) of refcount and restrict refcount to the lower bytes (possibly only 3 resp. 3.5). We need to make sure refcount uses a 32-bit type in that case, as opposed to cwal_size_t (which can be changed to uint16_t, which only gives us a range of up to 4k if we reserve 4 bits!). Class-level flags are set in the associated cwal_value_vtab instance. Container classes have their own flags. Reminder to self: interpreters are going to need flags for marking special values like constants/non-removable. Oh, but that's going to not like built-in constants. So we'll need to tag these at the cwal_kvp level (which costs us less :). */ }; /** Empty-initialized cwal_value object. */ #define cwal_value_empty_m { \ &cwal_value_vtab_empty/*api*/,\ 0/*scope*/,\ 0/*left*/,\ 0/*right*/, \ 0/*refcount*/ \ } /** Empty-initialized cwal_value object. */ extern const cwal_value cwal_value_empty; struct cwal_native { /** base MUST be the first member for casting reasons. */ cwal_obase base; void * native; void const * typeID; cwal_finalizer_f finalize; /** If this member is non-NULL then it must have the following semantics: - Use cwal_native_value() to get the underlying value. - Use cwal_value_scope() to get the underlying scope. - Call cwal_value_rescope(scope, CHILD) for each "unmanaged" (non-property) child which might need to be rescoped. That function only fails on serious usage errors, so its result code can normally be ignored (but to be pedantic, if it fails then return its result code). - Return 0 on success, non-0 on error. Errors at this level should be interpretted as fatal to the overlying operations. */ int (*rescope_children)( struct cwal_native * ); }; #define cwal_native_empty_m {\ cwal_obase_empty_m, \ 0/*native*/,\ 0/*typeID*/,\ 0/*finalize*/, \ 0/*rescope_children*/ \ } extern const cwal_native cwal_native_empty; struct cwal_hash { cwal_obase base; cwal_kvp ** list; cwal_size_t hashSize; cwal_size_t entryCount; }; #define cwal_hash_empty_m {\ cwal_obase_empty_m, \ 0/*list*/, \ 27/*hashSize*/, \ 0/*entryCount*/ \ } extern const cwal_hash cwal_hash_empty; /** @internal If v is-a obase then its obase part is returned, else NULL is returned. */ cwal_obase * cwal_value_obase( cwal_value * v ); /** @internal If v is-a obase then this function sets/unsets the is-visiting flag on its obase part and returns CWAL_RC_OK, else it returns CWAL_RC_TYPE. This should be called just prior to the start of any traversal into an obase's children, passing a value of 1 for isVisiting. It should be called, passing 0 for isVisiting, immediately after the traversal returns. */ int cwal_value_set_visiting( cwal_value * v, char isVisiting ); /** @internal Given a pointer, returns true (non-0) if m refers to one of the built-in/constant/shared (cwal_value*) instances, else returns 0. Is tolerant of NULL (returns 0). This determination is O(1) or 2x O(1) (only pointer range comparisons), depending on whether we have to check both sets of builtins. If this returns true, m MUST NOT EVER be cwal_free()d because it refers to stack memory! It MAY be (harmlessly) cwal_value_ref()'d and cwal_value_unref()'d, which are no-ops for built-ins. */ char cwal_value_is_builtin( void const * m ); /** @internal Internal debuggering function which MIGHT dump some useful info about v (not recursively) to some of the standard streams. Do not rely on this function from client code. The File/Line params are expected to be the __FILE__/__LINE__ macros. If msg is not NULL then it is included in the output (the exact placement, beginning or end, is unspecified). */ void cwal_dump_value( char const * File, int Line, cwal_value const * v, char const * msg ); /** @def cwal_dump_v(V,M) Internal debuggering macro which calls cwal_dump_value() with the current file/line/function info. */ #if 1 # define cwal_dump_v(V,M) cwal_dump_value(__func__,__LINE__,(V),(M)) #else # define cwal_dump_v(V,M) assert(1) #endif /** Dumps e's internalized strings table to stdout. If showEntries is true it lists all entries. If includeStrings is not 0 then strings of that length or less are also output (longer ones are not shown). If includeStrings is 0 then the strings are not output. Note that the strings are listed in an unspecified order (actually orded by (hash page number/hash code), ascending, but that's an implementation detail). */ void cwal_dump_interned_strings_table( cwal_engine * e, char showEntries, cwal_size_t includeStrings ); /** Searches e for an internalized string matching (zKey,nKey). If found, it returns 0 and sets any of the output parameters which are not NULL. On success, *out (if out is not NULL) be set to the value matching the key. Note that only String values can compare equal here, even if the key would normally compare as equivalent to a value of another type. e.g. 1=="1" when using cwal_value_compare(), but using that comparison here would not be possible unless we allocated a temporary string to compare against. It sets *itemIndex (if not NULL) to the index in the strings table for the given key, regardless of success of failure. The other output params are only set on success. *out (if not NULL) is set to the value in the table. pageIndex (if *not NULL) is set to the page in which the entry was found. Reminder to self: we might be able to stack-allocate an x-string around zKey and do a cwal_value-keyed search on that. That would work around the (1!="1") inconsistency. */ int cwal_interned_search( cwal_engine * e, char const * zKey, cwal_size_t nKey, cwal_value ** out, cwal_ptr_page ** pageIndex, uint16_t * itemIndex ); /** Equivalent to cwal_interned_search() except that it takes a cwal_value parameter and uses cwal_value_compare() for the hashing comparisons. A cwal String value inserted this way _will_ compare */ int cwal_interned_search_val( cwal_engine * e, cwal_value const * v, cwal_value ** out, cwal_ptr_page ** pageIndex, uint16_t * itemIndex ); /** Removes the string matching (zKey,nKey) from e's interned strings table. If an entry is found 0 is returned and *out (if not NULL) is set to the entry. Returns CWAL_RC_NOT_FOUND if no entry is found. */ int cwal_interned_remove( cwal_engine * e, cwal_value const * v, cwal_value ** out ); /** Inserts the given value, which must be-a String, into e's in interned strings list. Returns 0 on success. Returns CWAL_RC_ALREADY_EXISTS if the entry's string value is already in the table. */ int cwal_interned_insert( cwal_engine * e, cwal_value * v ); /** Pushes the given cwal_value into e->gc for later destruction. We do this to avoid prematurely stepping on a being-destroyed container when visiting cycles. If insertion of p into the gc list fails then this function frees it immediately. We "could" try to stuff it in the recycle bin, but that would only potentially delay the problem (of stepping on a freed value while cycling). This function asserts that e->gcLevel is not 0. */ int cwal_gc_push( cwal_engine * e, cwal_value * p ); /** Passes all entries in e->gc to cwal_value_recycle() for recycling or freeing. */ int cwal_gc_flush( cwal_engine * e ); /** If v->vtab->typeID (T) is of a recyclable type and e->recycler entry number cwal_recycler_index(T) has space, v is put there, otherwise it is cwal_free()'d. This is ONLY to be called when v->refcount drops to 0 (in place of calling cwal_free()), or results are undefined. If e->gcLevel is not 0 AND v is-a obase then v is placed in e->gc instead of being recycled. That is a bit of a shame, actually, but the good news is that cwal_gc_flush() will try to stick it back in the recycle bin. Note that non-Objects do not need to be delayed-destroyed because they cannot contribute to cycles. Returns 0 if the value is freed immediately, 1 if it is recycled, and -1 if v is placed in e->gc. If insertion into e->gc is called for an fails, v is freed immediately (and 0 is returned). */ char cwal_value_recycle( cwal_engine * e, cwal_value * v ); #if CWAL_ENABLE_TRACE #define CWAL_ETR(E) (E)->trace #define CWAL_TR_SRC(E) CWAL_ETR(E).cFile=__FILE__; CWAL_ETR(E).cLine=__LINE__; CWAL_ETR(E).cFunc=__FUNCTION__ #define CWAL_TR_MSG(E,MSG) CWAL_ETR(E).msg = MSG; if((MSG) && *(MSG)) CWAL_ETR(E).msgLen = strlen(MSG) #define CWAL_TR_EV(E,EV) CWAL_ETR(E).event = (EV); /*if(!(CWAL_ETR(E).msg)) { CWAL_TR_MSG(E,#EV); }*/ #define CWAL_TR_RC(E,RC) CWAL_ETR(E).code = (RC) #define CWAL_TR_V(E,V) CWAL_ETR(E).value = (V); #define CWAL_TR_MEM(E,M,SZ) CWAL_ETR(E).memory = (M); CWAL_ETR(E).memorySize = (SZ) #define CWAL_TR_S(E,S) CWAL_ETR(E).scope = (S) #define CWAL_TR_SV(E,S,V) CWAL_TR_V(E,V); CWAL_TR_S(E,S) #define CWAL_TR_VCS(E,V) CWAL_TR_V(E,V); CWAL_TR_S(E,(E)->current) #define CWAL_TR3(E,EV,MSG) \ if(MSG && *MSG) { CWAL_TR_MSG(E,MSG); } \ CWAL_TR_EV(E,EV); \ if(!(CWAL_ETR(E).scope)) { \ if(CWAL_ETR(E).value) CWAL_ETR(E).scope = CWAL_ETR(E).value->scope; \ if(!(CWAL_ETR(E).scope)) CWAL_ETR(E).scope=(E)->current; \ } \ CWAL_TR_SRC(E); \ cwal_trace(E) #define CWAL_TR2(E,EV) CWAL_TR3(E,EV,"") #else #define CWAL_ETR(E) #define CWAL_TR_SRC(E) #define CWAL_TR_MSG(E,MSG) #define CWAL_TR_EV(E,EV) #define CWAL_TR_RC(E,RC) #define CWAL_TR_V(E,V) #define CWAL_TR_S(E,S) #define CWAL_TR_MEM(E,M,SZ) #define CWAL_TR_SV(E,S,V) #define CWAL_TR_VCS(E,V) #define CWAL_TR3(E,EV,MSG) #define CWAL_TR2(E,EV) #endif void cwal_trace( cwal_engine * e ); typedef struct cwal_hash_entry cwal_hash_entry; /** The hashtable is not currently used, but "might" be interesting to provide as an alternative to cwal_object (which has value ordering requirements which hinder its speed but make it relatively memory-light). */ struct cwal_hash_entry { cwal_string * key; void * data; cwal_hash_entry * next; }; struct cwal_hash_table { cwal_hash_entry ** table; uint16_t hashSize; }; /** DON'T USE THIS. i'm not sure yet if the public API can handle this. cwal_value_take() "takes" a value away from its owning scope, transfering the scope's reference count point to the caller. There is currently little or no reason to prefer this over cwal_value_unref(), as the underlying algorithms are unaffected by the number of values managed by a scope. On error non-0 is returned and ownership is not modified. This function works only on "managed" values (with an owning scope) because the API has not yet defined how to create/manage unmanged values from client code. Each allocated value, regardless of its reference count, has only one reference from its parent scope, and this function effectively steals that reference count from the owning scope, effectively transfering that one reference point to the caller. This function must not be passed the same value instance more than once unless the value has been re-scoped since calling this (it will fail on subsequent calls). In all cases, if this function returns 0 the caller effectively receives ownership of 1 reference point for the value. Note that adding the "taken" value to a container will re-scope it as part of the process. Error conditions: CWAL_RC_RANGE: means that v has no owning scope. This constellation is highly unlikely but could happen once the API allows "unscoped" values (which is on the TODO list). @see cwal_value_unref() */ int cwal_value_take( cwal_engine * e, cwal_value * v ); /* LICENSE This software's source code, including accompanying documentation and demonstration applications, are licensed under the following conditions... Certain files are imported from external projects and have their own licensing terms. Namely, the JSON_parser.* files. See their files for their official licenses, but the summary is "do what you want [with them] but leave the license text and copyright in place." The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/]) explicitly disclaims copyright in all jurisdictions which recognize such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 2011), this software is Copyright (c) 2011 by Stephan G. Beal, and is released under the terms of the MIT License (see below). In jurisdictions which recognize Public Domain property, the user of this software may choose to accept it either as 1) Public Domain, 2) under the conditions of the MIT License (see below), or 3) under the terms of dual Public Domain/MIT License conditions described here, as they choose. The MIT License is about as close to Public Domain as a license can get, and is described in clear, concise terms at: http://en.wikipedia.org/wiki/MIT_License The full text of the MIT License follows: -- Copyright (c) 2011 Stephan G. Beal (http://wanderinghorse.net/home/stephan/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --END OF MIT LICENSE-- For purposes of the above license, the term "Software" includes documentation and demonstration source code which accompanies this software. ("Accompanies" = is contained in the Software's primary public source code repository.) */ #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED */ /* end of file cwal_internal.h */ /* start of file cwal.c */ #include #include /* malloc()/free() */ #include #include #include #define CWAL_KVP_TRY_SORTING 0 #if CWAL_KVP_TRY_SORTING #include /* qsort() */ #endif #if 1 #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #else #define MARKER(exp) if(0) printf #endif #if defined(__cplusplus) extern "C" { #endif /** Convenience typedef. typedef enum cwal_e_options cwal_e_options; */ /* Public-API constant/shared objects. */ const cwal_state cwal_state_empty = cwal_state_empty_m; const cwal_function cwal_function_empty = cwal_function_empty_m; const cwal_engine_vtab cwal_engine_vtab_empty = cwal_engine_vtab_empty_m; const cwal_list cwal_list_empty = cwal_list_empty_m; const cwal_scope cwal_scope_empty = cwal_scope_empty_m; const cwal_engine cwal_engine_empty = cwal_engine_empty_m; const cwal_ptr_table cwal_ptr_table_empty = cwal_ptr_table_empty_m; const cwal_exception_info cwal_exception_info_empty = cwal_exception_info_empty_m; const cwal_native cwal_native_empty = cwal_native_empty_m; const cwal_hash cwal_hash_empty = cwal_hash_empty_m; const cwal_buffer cwal_buffer_empty = cwal_buffer_empty_m; const cwal_engine_tracer cwal_engine_tracer_empty = cwal_engine_tracer_empty_m; const cwal_trace_state cwal_trace_state_empty = cwal_trace_state_empty_m; const cwal_recycler cwal_recycler_empty = cwal_recycler_empty_m; #define cwal_string_empty_m {0U/*length*/} static const cwal_string cwal_string_empty = cwal_string_empty_m; const cwal_array cwal_array_empty = cwal_array_empty_m; const cwal_object cwal_object_empty = cwal_object_empty_m; const cwal_kvp cwal_kvp_empty = cwal_kvp_empty_m; const cwal_callback_args cwal_callback_args_empty = cwal_callback_args_empty_m; const cwal_value_vtab cwal_value_vtab_empty = cwal_value_vtab_empty_m; const cwal_exception cwal_exception_empty = cwal_exception_empty_m; const cwal_output_buffer_state cwal_output_buffer_state_empty = {NULL/*e*/, NULL/*b*/}; #if 0 const cwal_var cwal_var_empty = cwal_var_empty_m; #endif const cwal_engine_tracer cwal_engine_tracer_FILE = { cwal_engine_tracer_f_FILE, cwal_engine_tracer_close_FILE, 0 }; const cwal_allocator cwal_allocator_empty = cwal_allocator_empty_m; const cwal_allocator cwal_allocator_std = { cwal_realloc_f_std, cwal_state_empty_m }; const cwal_outputer cwal_outputer_empty = cwal_outputer_empty_m; const cwal_outputer cwal_outputer_FILE = { cwal_output_f_FILE, cwal_output_flush_f_FILE, cwal_state_empty_m }; #if 0 const cwal_outputer cwal_outputer_buffer = { cwal_output_f_buffer, NULL, cwal_state_empty_m }; #endif const cwal_engine_vtab cwal_engine_vtab_basic = { { /*allocator*/ cwal_realloc_f_std, cwal_state_empty_m }, {/* outputer */ cwal_output_f_FILE, cwal_output_flush_f_FILE, cwal_state_empty_m }, cwal_engine_tracer_empty_m, cwal_state_empty_m }; /** CwalConsts holds some library-level constants and default values which have no better home. */ static const struct { /** The minimum/default hash size used by cwal_ptr_table. */ const uint16_t MinimumHashSize; /** The minimum step/span used by cwal_ptr_table. */ const uint16_t MinimumStep; /** Default length of some arrays on the first memory reservation op. */ const uint16_t InitialArrayLength; /** Used as the "allocStamp" value for values which the library allocates, in order to differentiate them from those allocated via stack or being embedded in another object. Should point to some library-internal static/constant pointer (any one will do). */ void const * const AllocStamp; /** Largest allowable size for cwal_size_t values (e.g. cwal_value::refcount). If it gets this high then something is probably very wrong or the client is trying too hard to push the boundaries. */ const cwal_size_t MaxSizeT; /** If true then newly-create cwal_string instances will be auto-interned. All strings with the same bytes will be shared across a single cwal_string instance. */ char AutoInternStrings; /** Property name used to store the message part of an Exception value. */ char const * ExceptionMessageKey; char const * ExceptionCodeKey; /** If >0, this sets the maximum size for interning strings. Larger strings will not be interned. */ cwal_size_t MaxInternedStringSize; /** The maximum length of string to recycle. Should be relatively small unless we implement a close-fit strategy for recycling. Currently we can only recycle strings for use with the exact same size string. */ cwal_size_t MaxRecycledStringLen; /** If StringPadSize is not 0... When strings are allocated, their sizes is padded to be evenly divisible by this many bytes. When recycling strings, we use this padding to allow the strings to be re-used for any length which is within 1 of these increments. Expected to be an even value, relatively small (under 32, in any case). Tests have shown 4-8 to be good values, saving anywhere from a few to 36%(!!!) of the total allocations in the th1ish test scripts (compared to a value of 0). Switching from 0 to 4 gives a notable improvement on the current test scripts. 8 or 12 don't reduce allocations all that much compared to 4, and _normally_ (not always) cost more total memory. */ cwal_size_t StringPadSize; } CwalConsts = { 13 /* MinimumHashSize. Chosen almost arbitrarily, weighted towards small memory footprint per table. */, sizeof(void*) /* MinimumStep. */, 8 /*InitialArrayLength, starting length of various arrays */, &cwal_engine_vtab_empty/*AllocStamp - any internal ptr will do.*/, #if 16 == CWAL_SIZE_T_BITS (cwal_size_t)0xCFFF/*MaxSizeT*/, #else (cwal_size_t)0xCFFFFFFF/*MaxSizeT*/, #endif 0/*AutoInternStrings*/, "message"/*ExceptionMessageKey*/, "code"/*ExceptionCodeKey*/, 32/*MaxInternedStringSize*/, 32/*MaxRecycledStringLen*/, 4/*StringPadSize*/ }; struct cwal_weak_ref { void * value; cwal_type_id typeID; cwal_size_t refcount; struct cwal_weak_ref * next; }; #define cwal_weak_ref_empty_m {NULL, CWAL_TYPE_UNDEF, 0U, NULL} static const cwal_weak_ref cwal_weak_ref_empty = cwal_weak_ref_empty_m; /** Internal impl for cwal_value_unref(). */ static int cwal_value_unref2(cwal_engine * e, cwal_value *v ); /** Internal impl for cwal_value_ref(). */ static int cwal_value_ref2( cwal_engine *e, cwal_value * cv ); /** Does nothing. Intended as a cwal_value_vtab::cleanup handler for types which need not cleanup. TODO: re-add the zeroing of such values (lost in porting from cson). */ static void cwal_value_cleanup_noop( cwal_engine * e, void * self ); /** Requires that self is-a CWAL_TYPE_INTEGER cwal_value. This function zeros its numeric value but goes not free() self. */ static void cwal_value_cleanup_integer( cwal_engine * e, void * self ); /** Requires that self is-a CWAL_TYPE_DOUBLE cwal_value. This function zeros its numeric value but goes not free() self. */ static void cwal_value_cleanup_double( cwal_engine * e, void * self ); /** Requires that self is-a cwal_array. This function destroys its contents but goes not free() self. */ static void cwal_value_cleanup_array( cwal_engine * e, void * self ); /** Requires that self is-a cwal_object. This function destroys its contents but goes not free() self. */ static void cwal_value_cleanup_object( cwal_engine * e, void * self ); /** Requires that self is-a cwal_native. This function destroys its contents but goes not free() self. */ static void cwal_value_cleanup_native( cwal_engine * e, void * self ); /** Requires that self is-a cwal_string. This function removes the string from e's intering table. */ static void cwal_value_cleanup_string( cwal_engine * e, void * self ); /** Requires that self is-a cwal_buffer. This function calls cwal_buffer_reserve() to clear the memory owned by the buffer. */ static void cwal_value_cleanup_buffer( cwal_engine * e, void * self ); /** Requires that self is-a cwal_function. This function destroys its contents but goes not free() self. */ static void cwal_value_cleanup_function( cwal_engine * e, void * self ); /** Requires that self is-a cwal_exception. This function destroys its contents but goes not free() self. */ static void cwal_value_cleanup_exception( cwal_engine * e, void * self ); /** Requires that self is-a cwal_hash. This function destroys its contents but goes not free() self. */ static void cwal_value_cleanup_hash( cwal_engine * e, void * V ); /* Hash/compare routines for cwal_value_vtab. */ static cwal_hash_t cwal_value_hash_null_undef( cwal_value const * v ); static cwal_hash_t cwal_value_hash_bool( cwal_value const * v ); static cwal_hash_t cwal_value_hash_int( cwal_value const * v ); static cwal_hash_t cwal_value_hash_double( cwal_value const * v ); static cwal_hash_t cwal_value_hash_string( cwal_value const * v ); static cwal_hash_t cwal_value_hash_ptr( cwal_value const * v ); static int cwal_value_cmp_bool( cwal_value const * lhs, cwal_value const * rhs ); #if 0 static int cwal_value_cmp_val_bool( cwal_value const * lhs, cwal_value const * rhs ); #endif static int cwal_value_cmp_int( cwal_value const * lhs, cwal_value const * rhs ); static int cwal_value_cmp_double( cwal_value const * lhs, cwal_value const * rhs ); static int cwal_value_cmp_string( cwal_value const * lhs, cwal_value const * rhs ); static int cwal_value_cmp_type_only( cwal_value const * lhs, cwal_value const * rhs ); static int cwal_value_cmp_ptr_only( cwal_value const * lhs, cwal_value const * rhs ); static int cwal_value_cmp_func( cwal_value const * lhs, cwal_value const * rhs ); static int cwal_rescope_children_obase( cwal_value * v ); static int cwal_rescope_children_array( cwal_value * v ); static int cwal_rescope_children_native( cwal_value * v ); static int cwal_rescope_children_hash( cwal_value * n ); /* Internal "API" (a.k.a. "VTab") instances. One per core type. */ static const cwal_value_vtab cwal_value_vtab_null = { CWAL_TYPE_NULL, "null", CWAL_F_NONE, cwal_value_cleanup_noop, cwal_value_hash_null_undef, cwal_value_cmp_type_only, /* cwal_value_cmp_val_bool, */ NULL/*rescope_children()*/ }; static const cwal_value_vtab cwal_value_vtab_undef = { CWAL_TYPE_UNDEF, "undefined", CWAL_F_NONE, cwal_value_cleanup_noop, cwal_value_hash_null_undef, cwal_value_cmp_type_only, /* cwal_value_cmp_val_bool, */ NULL/*rescope_children()*/ }; static const cwal_value_vtab cwal_value_vtab_bool = { CWAL_TYPE_BOOL, "bool", CWAL_F_NONE, cwal_value_cleanup_noop, cwal_value_hash_bool, cwal_value_cmp_bool, NULL/*rescope_children()*/ }; static const cwal_value_vtab cwal_value_vtab_integer = { CWAL_TYPE_INTEGER, "integer", CWAL_F_NONE, cwal_value_cleanup_integer, cwal_value_hash_int, cwal_value_cmp_int, NULL/*rescope_children()*/ }; static const cwal_value_vtab cwal_value_vtab_double = { CWAL_TYPE_DOUBLE, "double", CWAL_F_NONE, cwal_value_cleanup_double, cwal_value_hash_double, cwal_value_cmp_double, NULL/*rescope_children()*/ }; static const cwal_value_vtab cwal_value_vtab_string = { CWAL_TYPE_STRING, "string", CWAL_F_NONE, cwal_value_cleanup_string, cwal_value_hash_string, cwal_value_cmp_string, NULL/*rescope_children()*/ }; static const cwal_value_vtab cwal_value_vtab_array = { CWAL_TYPE_ARRAY, "array", CWAL_F_ISA_OBASE, cwal_value_cleanup_array, cwal_value_hash_ptr, cwal_value_cmp_ptr_only, cwal_rescope_children_array }; static const cwal_value_vtab cwal_value_vtab_object = { CWAL_TYPE_OBJECT, "object", CWAL_F_ISA_OBASE, cwal_value_cleanup_object, cwal_value_hash_ptr, cwal_value_cmp_ptr_only, cwal_rescope_children_obase }; static const cwal_value_vtab cwal_value_vtab_native = { CWAL_TYPE_NATIVE, "native", CWAL_F_ISA_OBASE, cwal_value_cleanup_native, cwal_value_hash_ptr, cwal_value_cmp_ptr_only, cwal_rescope_children_native }; static const cwal_value_vtab cwal_value_vtab_hash = { CWAL_TYPE_HASH, "hash", CWAL_F_ISA_OBASE, cwal_value_cleanup_hash, cwal_value_hash_ptr, cwal_value_cmp_ptr_only, cwal_rescope_children_hash }; static const cwal_value_vtab cwal_value_vtab_buffer = { CWAL_TYPE_BUFFER, "buffer", CWAL_F_NONE, cwal_value_cleanup_buffer, cwal_value_hash_ptr, cwal_value_cmp_ptr_only /*TODO: compare w/ memcmp() vs. other buffers.*/, NULL/*rescope_children()*/ }; static const cwal_value_vtab cwal_value_vtab_function = { CWAL_TYPE_FUNCTION, "function", CWAL_F_ISA_OBASE, cwal_value_cleanup_function, cwal_value_hash_ptr, cwal_value_cmp_func, cwal_rescope_children_obase }; static const cwal_value_vtab cwal_value_vtab_exception = { CWAL_TYPE_EXCEPTION, "exception", CWAL_F_ISA_OBASE, cwal_value_cleanup_exception, cwal_value_hash_ptr, cwal_value_cmp_ptr_only, cwal_rescope_children_obase }; /* Internal constant cwal_values, a malloc() reduction optimization. */ static const cwal_value cwal_value_undef = { &cwal_value_vtab_undef, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_null_empty = { &cwal_value_vtab_null, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_bool_empty = { &cwal_value_vtab_bool, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_integer_empty = { &cwal_value_vtab_integer, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_double_empty = { &cwal_value_vtab_double, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_string_empty = { &cwal_value_vtab_string, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_array_empty = { &cwal_value_vtab_array, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_object_empty = { &cwal_value_vtab_object, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_native_empty = { &cwal_value_vtab_native, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_hash_empty = { &cwal_value_vtab_hash, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_buffer_empty = { &cwal_value_vtab_buffer, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_function_empty = { &cwal_value_vtab_function, NULL, NULL, NULL, 0 }; static const cwal_value cwal_value_exception_empty = { &cwal_value_vtab_exception, NULL, NULL, NULL, 0 }; /* Internal convenience macros... */ /** Cast ((cwal_value*)V)->value to (T*). It assumes the caller knows WTF he is doing. Evaluates to 0 if !(V). */ #if 1 #define CWAL_VVPCAST(T,V) ((V) ? ((T*)((V)+1)) : 0) #else /** If i can get this to work (this doesn't) then we can get rid of cwal_value::value to shave 4-8 bytes per instance. Strangely... this one works in the cwal test app but not in the th1ish. There it causes mis-evaluations in some cases: !(3-3.0) evaluates to false. */ #define CWAL_VVPCAST(T,V) ((V) ? ((T*)((unsigned char *)((V)+1))) : 0) #endif /** CWAL_VALPART(CONCRETE) Cast (concrete_value_type*) CONCRETE to a (cwal_value*). CWAL_VALPART() relies on the fact that ALL cwal_value types which use it are allocated in a single memory chunk with (cwal_value,concrete_type), in that order. Do not use this macro for types which are not allocated that way. AFAIK, it is also legal for CONCRETE to be (cwal_obase*), provided that pointer was allocated as part of one of the container types (object, array, etc.). This relies on them having their (cwal_obase base) member as the first member of the struct. e.g., assuming an Object value: cwal_obase * base = CWAL_VOBASE(val); cwal_object * obj = cwal_value_get_object(val); assert( CWAL_VALPART(base) == v ); assert( base == &obj->base ); */ #if 1 #define CWAL_VALPART(CONCRETE) ((CONCRETE) ? ((cwal_value *)(((unsigned char *)(CONCRETE))-sizeof(cwal_value))) : 0) #else /* i'm not convinced that this one is standards-conformant. But it looks nicer. */ #define CWAL_VALPART(CONCRETE) ((CONCRETE) ? (((cwal_value *)(CONCRETE))-1) : 0) #endif /** CWAL_INT(V) casts CWAL_TYPE_INTEGER (cwal_value*) V to a (cwal_int_t*). How we store integer values differs depending on whether the value can be fit in the platform's (void*) (meaning we don't require extra memory for it). */ #define CWAL_INT(V) (CWAL_VVPCAST(cwal_int_t,(V))) /** Requires that V be one of the special cwal_values TRUE or FALSE. Evaluates to 1 if it is the TRUE value, else false. */ #define CWAL_BOOL(V) ((&CWAL_BUILTIN_VALS.vTrue==(V)) ? 1 : 0) /** CWAL_DBL(V) casts CWAL_TYPE_DOUBLE (cwal_value*) V to a (cwal_double_t*). */ #define CWAL_DBL(V) CWAL_VVPCAST(cwal_double_t,(V)) /** For cwal_string we store flags in the cwal_string::length member, rather than add a flag to them (for memory reasons). We reserve the top two and encode the length in the remaining ones. */ #if 16 == CWAL_SIZE_T_BITS # define CWAL_STRLEN_MASK 0x3FFF # define CWAL_STR_XMASK 0x8000 # define CWAL_STR_ZMASK 0x4000 #elif 32 == CWAL_SIZE_T_BITS # define CWAL_STRLEN_MASK 0x3FFFFFFF # define CWAL_STR_XMASK 0x80000000 # define CWAL_STR_ZMASK 0x40000000 #elif 64 == CWAL_SIZE_T_BITS # define CWAL_STRLEN_MASK 0x3FFFFFFFFFFFFFFF # define CWAL_STR_XMASK 0x8000000000000000 # define CWAL_STR_ZMASK 0x4000000000000000 #endif /** CWAL_STR(V) casts CWAL_TYPE_STRING (cwal_value*) V to a (cwal_string*). */ #define CWAL_STR(V) (((V) && (V)->vtab && (CWAL_TYPE_STRING==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_string,(V)) : 0) /** Evaluates to true if S (which must be a valid (cwal_string*)) is an x-string, else false. */ #define CWAL_STR_ISX(S) (CWAL_STR_XMASK & (S)->length) #define CWAL_STR_ISZ(S) (CWAL_STR_ZMASK & (S)->length) #define CWAL_STR_ISXZ(S) (~CWAL_STRLEN_MASK & (S)->length) /** Evaluates to the absolute value of S->length, in bytes, where S must be a non-NULL (cwal_string [const]*). This is preferred over direct access to S->length because we encode non-size info in the length field for x-strings. */ #define CWAL_STRLEN(S) ((cwal_size_t)((S)->length & CWAL_STRLEN_MASK)) /** CWAL_OBJ(V) casts CWAL_TYPE_OBJECT (cwal_value*) V to a (cwal_object*). */ #define CWAL_OBJ(V) (((V) && (V)->vtab && (CWAL_TYPE_OBJECT==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_object,(V)) : 0) /** CWAL_ARRAY(V) casts CWAL_TYPE_ARRAY (cwal_value*) V to a (cwal_array*). */ #define CWAL_ARRAY(V) (((V) && (V)->vtab && (CWAL_TYPE_ARRAY==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_array,(V)) : 0) /** CWAL_HASH(V) casts CWAL_TYPE_HASH (cwal_value*) V to a (cwal_hash*). */ #define CWAL_HASH(V) (((V) && (V)->vtab && (CWAL_TYPE_HASH==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_hash,(V)) : 0) /** CWAL_OBASE(O) casts the OBase-type pointer O to a (cwal_obase*). This relies on O being an obase-compliant type and a cwal_obase member being the first struct member of O. */ #define CWAL_OBASE(O) ((cwal_obase*)(O)) /** Evaluates to true if ((cwal_value*) V)->vtab has the CWAL_F_ISA_OBASE flag, else false. */ #define CWAL_V_IS_OBASE(V) ((V) && (V)->vtab && (CWAL_F_ISA_OBASE & (V)->vtab->flags)) /** If CWAL_V_IS_OBASE(V), evaluates to V's (cwal_obase*) part, else it evaluates to 0. For this to work all "object base" types must have a cwal_obase member named 'base' as their VERY FIRST structure member because we rely on a C's rule/allowance that a struct can be cast to a pointer to its first member. */ #define CWAL_VOBASE(V) (CWAL_V_IS_OBASE(V) ? CWAL_VVPCAST(cwal_obase,(V)) : 0) /** Casts CWAL_TYPE_NATIVE value V to (cwal_native*). */ #define CWAL_V2NATIVE(V) CWAL_VVPCAST(cwal_native,(V)) /** CWAL_BUF(V) casts CWAL_TYPE_BUFFER (cwal_value*) V to a (cwal_buffer*). */ #define CWAL_BUF(V) (((V) && (V)->vtab && (CWAL_TYPE_BUFFER==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_buffer,(V)) : 0) #define CWAL_VENGINE(V) ((V) ? ((V)->scope ? (V)->scope->e : 0) : 0) /** Some "special" shared cwal_value instances. Note that they are not const because they are used as shared-allocation objects in non-const contexts. However, the public API provides no way of modifying them, and clients who modify values directly are subject to The Wrath of Undefined Behaviour. */ static struct CWAL_BUILTIN_VALS_ { char inited; /** Each of the memXXX entries holds the raw memory for a block of memory intended for (cwal_value + concrete_type). These are initialized by cwal_init_empty_values(). */ unsigned char memIntM1[sizeof(cwal_value)+sizeof(cwal_int_t)]; unsigned char memInt0[sizeof(cwal_value)+sizeof(cwal_int_t)]; unsigned char memInt1[sizeof(cwal_value)+sizeof(cwal_int_t)]; unsigned char memDblM1[sizeof(cwal_value)+sizeof(cwal_double_t)]; unsigned char memDbl0[sizeof(cwal_value)+sizeof(cwal_double_t)]; unsigned char memDbl1[sizeof(cwal_value)+sizeof(cwal_double_t)]; /** Each of the vXXX pointers points to memory held in the similarly-named memXXX member. */ cwal_value * vIntM1; cwal_value * vInt0; cwal_value * vInt1; cwal_value * vDblM1; cwal_value * vDbl0; cwal_value * vDbl1; cwal_value vTrue; cwal_value vFalse; cwal_value vNull; cwal_value vUndef; cwal_value vStrEmpty; /** Reminder: any C-string conversion on this value is strictly invalid because that conversion expects the trailing bytes of the cwal_string to contain string->length bytes of memory. */ cwal_string emptyString; struct { cwal_int_t mOne; cwal_int_t zero; cwal_int_t one; } ints; struct { cwal_int_t mOne; cwal_int_t zero; cwal_int_t one; } dbls; /** Each of the wref.wXXX entries is a shared cwal_weak_ref instance pointing to a similarly-named vXXX entry. */ struct { cwal_weak_ref wTrue; cwal_weak_ref wFalse; cwal_weak_ref wNull; cwal_weak_ref wUndef; cwal_weak_ref wStrEmpty; cwal_weak_ref wIntM1; cwal_weak_ref wInt0; cwal_weak_ref wInt1; cwal_weak_ref wDblM1; cwal_weak_ref wDbl0; cwal_weak_ref wDbl1; } wref; } CWAL_BUILTIN_VALS = { 0/*inited*/, {/*memIntM1*/ 0}, {/*memInt0*/ 0}, {/*memInt1*/ 0}, {/*memDblM1*/ 0}, {/*memDbl0*/ 0}, {/*memDbl1*/ 0}, NULL/*vIntM1*/, NULL/*vInt0*/, NULL/*vInt1*/, NULL/*vDblM1*/, NULL/*vDbl0*/, NULL/*vDbl1*/, {/*vTrue*/ &cwal_value_vtab_bool, NULL, NULL, NULL, 0 }, {/*vFalse*/ &cwal_value_vtab_bool, NULL, NULL, NULL, 0 }, {/*vNull*/ &cwal_value_vtab_null, NULL, NULL, NULL, 0 }, {/*vUndef*/ &cwal_value_vtab_undef, NULL, NULL, NULL, 0 }, {/*vStrEmpty*/ &cwal_value_vtab_string, NULL, NULL, NULL, 0 }, cwal_string_empty_m/*emptyString*/, {/*ints*/-1,0,1}, #if CWAL_DISABLE_FLOATING_POINT {/*dbls*/-1,0,1}, #else {/*dbls*/-1.0,0.0,1.0}, #endif {/*wref*/ cwal_weak_ref_empty_m/* wTrue */, cwal_weak_ref_empty_m/* wFalse */, cwal_weak_ref_empty_m/* wNull */, cwal_weak_ref_empty_m/* wUndef */, cwal_weak_ref_empty_m/* wStrEmpty */, cwal_weak_ref_empty_m/* wIntM1 */, cwal_weak_ref_empty_m/* wInt0 */, cwal_weak_ref_empty_m/* wInt1 */, cwal_weak_ref_empty_m/* wDblM1 */, cwal_weak_ref_empty_m/* wDbl0 */, cwal_weak_ref_empty_m/* wDbl1 */ } }; static void cwal_init_empty_values(){ struct CWAL_BUILTIN_VALS_ * h = &CWAL_BUILTIN_VALS; cwal_value * v; /* Set up integers (-1, 0, 1) */ #define NUM(N,V) h->vInt##N = v = (cwal_value*)h->memInt##N; \ *v = cwal_value_integer_empty; \ *CWAL_INT(v) = V NUM(M1,-1); NUM(0,0); NUM(1,1); #undef NUM /* Set up doubles (-1, 0, 1) */ #if CWAL_DISABLE_FLOATING_POINT #define dbl_m1 -1 #define dbl_0 0 #define dbl_1 1 #else #define dbl_m1 -1.0 #define dbl_0 0.0 #define dbl_1 1.0 #endif #define NUM(N,V) h->vDbl##N = v = (cwal_value*)h->memDbl##N; \ *v = cwal_value_double_empty; \ *CWAL_DBL(v) = V; NUM(M1,dbl_m1); NUM(0,dbl_0); NUM(1,dbl_1); #undef NUM #undef dbl_m1 #undef dbl_0 #undef dbl_1 { cwal_weak_ref * r; #define REF(N,V) r = &h->wref.N; *r = cwal_weak_ref_empty; \ r->value = V; r->typeID = (V)->vtab->typeID REF(wTrue,&h->vTrue); REF(wFalse,&h->vFalse); REF(wNull,&h->vNull); REF(wUndef,&h->vUndef); REF(wStrEmpty,&h->vStrEmpty); REF(wIntM1,h->vIntM1); REF(wInt0,h->vInt0); REF(wInt1,h->vInt1); REF(wDblM1,h->vDblM1); REF(wDbl0,h->vDbl0); REF(wDbl1,h->vDbl1); #undef REF } h->inited = 1; } #define CWAL_INIT_EMPTY_VALUES if(!CWAL_BUILTIN_VALS.inited) cwal_init_empty_values() /** V_IS_BUILTIN(V) determines if the (cwal_value*) V is one of the special built-in/constant values. It does so by simply seeing if its address lies within the special-value holders, so it's about as fast as it can be. Maintenance reminders: - i would like to patch the empty-holder and special-values into the same structure to cut the comparisons in half, but there's a chicken/egg problem there somewhere. - The comparisons are done in the order which they are expected to be most likely to produce a positive result most quickly. - Profiling shows that cwal_value_is_builtin() is by far the most-called routine in the library and accounts for about 1% of the total calls in my test app. That is the only reason it has become a macro. */ #define V_IS_BUILTIN(V) \ (((void const *)(V) >= (void const *)&CWAL_BUILTIN_VALS) \ && ( (void const *)(V) < (void const *)(&CWAL_BUILTIN_VALS+1)) \ ) char cwal_value_is_builtin( void const * m ) { return V_IS_BUILTIN(m); } /** Returns the index in cwal_engine::recycler[] for the given type ID, or -1 if there is no recycler for that type. */ static int cwal_recycler_index( cwal_type_id typeID ){ /* Something to consider: Now that cwal_recycler::id exists we could take a cwal_engine param and walk its list to find the type. It would be O(N) instead of O(1) would be mean this function no longer has to be maintained for new types. */ switch( typeID ){ case CWAL_TYPE_INTEGER: return 0; case CWAL_TYPE_DOUBLE: return 1; case CWAL_TYPE_ARRAY: return 2; case CWAL_TYPE_OBJECT: return 3; case CWAL_TYPE_NATIVE: return 4; case CWAL_TYPE_BUFFER: return 5; case CWAL_TYPE_FUNCTION: return 6; case CWAL_TYPE_EXCEPTION: return 7; case CWAL_TYPE_KVP: return 8; case CWAL_TYPE_SCOPE: return 9; case CWAL_TYPE_STRING: /* handled differently */ default: return -1; } } /** @internal Checks whether child refers to a higher scope than parent, and if it does then it moves child to par. If par is 0 this function sets *res (if not NULL) to 1 and returns. Returns 0 on success and the only error case is if popping child from its parent or pushing to its new parent fails (in which case there will be a memory-management mess left over!). It sets res (if not NULL) to one of: -1: means that par takes over child, adding it to its list setting child->scope to par. 0: no action is necessary. Child already belongs to par. 1: child is a built-in, belongs to par, or belongs to a scope with a lower scope->level (in which case that scope is its owner). Note that this does NOT change child's reference count in any case. */ static int cwal_value_xscope( cwal_engine * e, cwal_scope * par, cwal_value * ch, int * res ); /** Internal debuggering routine which dumps out info about the value v (which must not be NULL). If msg is provided it is appended to the output. */ void cwal_dump_value( char const * File, int Line, cwal_value const * v, char const * msg ){ FILE * out = stdout; cwal_obase const * b = CWAL_VOBASE(v); if(File && *File && (Line>0)){ fprintf(out, "%s():%d: ", File, Line ); } fprintf(out, "%s@%p (scope=#%u@%p) refCount=%"CWAL_SIZE_T_PFMT, v->vtab->typeName, (void*)v, v->scope ? (unsigned)v->scope->level : 0U, (void*)v->scope, v->refcount); if(b){ fprintf( out, " flags=%02x", b->flags ); } if( cwal_value_is_string(v) ){ cwal_string const * s = cwal_value_get_string(v); if(s && CWAL_STRLEN(s)){ const cwal_size_t len = CWAL_STRLEN(s); fprintf( out, " strlen=%"CWAL_SIZE_T_PFMT, len); if( len <= 10 ){ fprintf( out, " bytes=[%s]", cwal_string_cstr(s) ); } }else{ fprintf( out, " (STILL INITIALIZING?)" ); } } else if(cwal_value_is_integer(v)){ fprintf( out, " int=%"CWAL_INT_T_PFMT, cwal_value_get_integer(v)); } else if(cwal_value_is_double(v)){ fprintf( out, " double=%"CWAL_DOUBLE_T_PFMT, cwal_value_get_double(v)); } else if(cwal_value_is_bool(v)){ fprintf( out, " bool=%s", cwal_value_get_double(v) ? "true" : "false"); } if(msg && *msg) fprintf( out, "\t%s\n", msg ); else fputc( '\n', out ); } #define dump_val(V,M) cwal_dump_value(__FILE__,__LINE__,(V),(M)) static cwal_recycler * cwal_recycler_get( cwal_engine * e, cwal_type_id t ){ if(CWAL_TYPE_STRING == t){ return &e->reString; } else { const int ndx = cwal_recycler_index(t); cwal_recycler * re; assert(ndx >= 0); re = &e->recycler[ndx]; assert( re->id == t ); return re; } } cwal_kvp * cwal_kvp_alloc(cwal_engine *e){ const int ndx = cwal_recycler_index(CWAL_TYPE_KVP); cwal_kvp * kvp; cwal_recycler * re; assert(ndx>=0); re = &e->recycler[ndx]; if(re->list){ kvp = (cwal_kvp*)re->list; re->list = kvp->right; kvp->right = 0; --re->count; } else{ kvp = (cwal_kvp*)cwal_malloc(e, sizeof(cwal_kvp)); } if( kvp ) { *kvp = cwal_kvp_empty; } return kvp; } /** Works similarly to cwal_value_unref2() except that it first checks if v lives in a scope higher than p. If it does then this function will not destroy it but will remove a reference point. Destruction will be up to v's owning scope or further references. If v->scope==p then this functions just like cwal_value_unref2(). This must be called from ANY container-level unref operations, in place of calling cwal_value_unref2(), and it must be passed the owning scope of the container on whose behalf the value is being unreferenced. The purpose of this is to allow the following client-side pseudo-script to work: (false || { var a = {"1":[3,2,1]} a.1 }) That normally (without this feature) leads to an implicit return value which outlives its owning scope. If the return value (a.1 in this case) is moved to a higher scope before 'a' is cleaned up, this function is there to make sure that the value gets placed back into probationary state in the new parent scope, if needed. The end effect is that the above implicit return value has a refcount of 0 instead of 1 (without this hack the client is forced to add a reference), meaning it is back in probationary state and can be swept up. */ static void cwal_unref_from( cwal_scope * p, cwal_value * v ); void cwal_kvp_clean( cwal_engine * e, cwal_scope * fromScope, cwal_kvp * kvp ){ if( kvp ){ cwal_value * key = kvp->key; cwal_value * value = kvp->value; *kvp = cwal_kvp_empty; if(key){ if(fromScope) cwal_unref_from(fromScope, key); else cwal_value_unref2(e, key); } if(value){ if(fromScope) cwal_unref_from(fromScope, value); else cwal_value_unref2(e, value); } } } void cwal_kvp_free( cwal_engine * e, cwal_scope * fromScope, cwal_kvp * kvp, char allowRecycle ){ if( kvp ){ cwal_kvp_clean(e, fromScope, kvp); if(allowRecycle){ int const ndx = cwal_recycler_index(CWAL_TYPE_KVP); cwal_recycler * re; assert(ndx>=0); re = &e->recycler[ndx]; if(re->count < re->maxLength){ assert(!kvp->right); kvp->right = (cwal_kvp*)re->list; re->list = kvp; ++re->count; }else{ cwal_free(e, kvp); } } else{ cwal_free(e, kvp); } } } cwal_value * cwal_kvp_key( cwal_kvp const * kvp ){ return kvp ? kvp->key : NULL; } cwal_value * cwal_kvp_value( cwal_kvp const * kvp ){ return kvp ? kvp->value : NULL; } /** @internal Searchs for the given key in the given kvp list. If keyLen is 0 and key is neither NULL nor starts with a NUL byte, strlen() is used to calculate its length. Returns the found item on success, NULL on error. If ndx is not NULL, it is set to the index (in obj->base.kvp) of the found (cwal_kvp*) item. *ndx is not modified if no entry is found. If prev is not NULL then prev is set to the returned value's left-hand-side kvp from the linked list. a *prev value of NULL indicates that the value found is the head of the list (==obj->base.kvp). Note that *prev is only set if an entry is found. TODO: a variant of this which takes a cwal_string, as an optimization: interning will ensure that like strings have the same address, which allows us to compare much faster (but we can only use that as a contingency, and cannot rely on interning being on). */ static cwal_kvp * cwal_kvp_search( cwal_kvp * kvp, char const * key, cwal_size_t keyLen, cwal_size_t * ndx, cwal_kvp ** prev){ if(!kvp || !key || !*key || !keyLen ) return NULL; else{ cwal_size_t i; cwal_kvp * left; cwal_string const * sKey; int cmp; /* if(0==keyLen) keyLen = strlen(key); */ for( i = 0, left = 0; kvp; left = kvp, kvp = kvp->right, ++i ){ assert( kvp->key ); assert(kvp->right != kvp); sKey = cwal_value_get_string(kvp->key); if(!sKey) continue; else if( CWAL_STRLEN(sKey) != keyLen ) continue; cmp = memcmp(key,cwal_string_cstr(sKey),keyLen); if(0==cmp){ if(ndx) *ndx = i; if(prev) *prev=left; return kvp; } #if CWAL_KVP_TRY_SORTING else if(cmp>0) break; #endif } return 0; } } /** Variant of cwal_kvp_search() which takes a cwal_value key. */ static cwal_kvp * cwal_kvp_search_v( cwal_kvp * kvp, cwal_value const * key, cwal_size_t * ndx, cwal_kvp ** prev){ if(!kvp || !key ) return NULL; else{ cwal_size_t i; cwal_kvp * left; int cmp; for( i = 0, left = 0; kvp; left = kvp, kvp = kvp->right, ++i ){ assert(kvp->key); assert(kvp->right != kvp); cmp = (key==kvp->key) ? 0 : key->vtab->compare(key, kvp->key); if(0==cmp){ if(ndx) *ndx = i; if(prev) *prev=left; return kvp; } #if CWAL_KVP_TRY_SORTING else if(cmp>0) break; #endif } return 0; } } /** Functionally identical to cwal_kvp_unset_v() but takes its key in C-string form. */ static int cwal_kvp_unset( cwal_engine * e, cwal_scope * fromScope, cwal_kvp **list, char const * key, cwal_size_t keyLen ) { if( !e || !list || !key ) return CWAL_RC_MISUSE; else if(!*list) return CWAL_RC_NOT_FOUND; else { cwal_size_t ndx = 0; cwal_kvp * left = 0; cwal_kvp * kvp; if(!keyLen) keyLen = strlen(key); kvp = cwal_kvp_search( *list, key, keyLen, &ndx, &left ); if( ! kvp ) return CWAL_RC_NOT_FOUND; else if(left) left->right = kvp->right; else { assert(*list==kvp); *list = kvp->right; } kvp->right = NULL; cwal_kvp_free( e, fromScope, kvp, 1 ); return 0; } } /** Unsets a the given key in the given kvp list. Modifies *list to point to the new head if key's value is the head of the list. fromScope should be the owning scope of the container on whose behalf this function is being called - see cwal_unref_from() for why. Returns 0 on successs. */ static int cwal_kvp_unset_v( cwal_engine * e, cwal_scope * fromScope, cwal_kvp **list, cwal_value * key ) { if( !e || !list || !key ) return CWAL_RC_MISUSE; else if(!*list) return CWAL_RC_NOT_FOUND; else { cwal_size_t ndx = 0; cwal_kvp * left = 0; cwal_kvp * kvp; kvp = cwal_kvp_search_v( *list, key, &ndx, &left ); if( ! kvp ) return CWAL_RC_NOT_FOUND; if(left) left->right = kvp->right; else { assert(*list==kvp); *list = kvp->right ? kvp->right : NULL; } kvp->right = NULL; cwal_kvp_free( e, fromScope, kvp, 1 ); return 0; } } /** Returns the client-supplied hash table size "trimmed" to some "acceptable" range. */ static uint16_t cwal_trim_hash_size( uint16_t hashSize ){ if(hashSize < CwalConsts.MinimumHashSize) return CwalConsts.MinimumHashSize; #define P(N) else if( hashSize <= N ) return N /* FIXME: add more granularity here. */ P(17); P(37); P(53); P(71); P(151); P(211); P(281); P(311); P(379); P(433); P(503); P(547); P(587); P(613); P(683); P(719); P(751); P(1033); P(1549); P(2153); else return 3163; #undef P } /** Returns the client-supplied table step size "trimmed" to some "acceptable" range. */ static uint16_t cwal_trim_step( uint16_t step ){ if(step < CwalConsts.MinimumStep) step = CwalConsts.MinimumStep; return step; } /** Allocates a new cwal_ptr_page object, owned by e. Returned NULL on error. On success the returned value is empty-initialized. The page has n entries (hash table size) and the given step size. Both n and step MUST currently be the same as the owning table's hash/step sizes, but there are plans to potentially change that, allowing new page sizes to grow if collision counts become too high. */ static cwal_ptr_page * cwal_ptr_page_create( cwal_engine * e, uint16_t n, uint16_t step ){ const uint32_t asz = (sizeof(void*) * n); const uint32_t psz = asz + sizeof(cwal_ptr_page); cwal_ptr_page * p = (cwal_ptr_page*)cwal_malloc(e, psz); if(p){ memset( p, 0, psz ); p->list = (void**)(p+1); p->entryCount = 0; } return p; } /** Allocates a new page for the given table, using the given step value (if 0 then t->step is used). Returns CWAL_RC_OK on success. The only conceivable non-bug error case here is CWAL_RC_OOM. */ static int cwal_ptr_table_add_page( cwal_engine * e, cwal_ptr_table * t, uint16_t step ){ cwal_ptr_page * p = cwal_ptr_page_create( e, t->hashSize, step ? step : t->step ); if(!p) return CWAL_RC_OOM; else { cwal_ptr_page * tail = t->pg.tail; if(!tail){ assert(!t->pg.head); t->pg.head = t->pg.tail = p; }else{ assert(t->pg.head); assert(!tail->next); t->pg.tail = tail->next = p; } assert(t->pg.tail->next != t->pg.head); return 0; } } int cwal_ptr_table_create( cwal_engine * e, cwal_ptr_table ** T, uint16_t hashSize, uint16_t step){ int rc; cwal_ptr_table * t; if(!e || !T) return CWAL_RC_MISUSE; if(!hashSize) hashSize = cwal_trim_hash_size(hashSize); step = cwal_trim_step(step); t = *T; if(t){ *t = cwal_ptr_table_empty; }else { t = (cwal_ptr_table*)cwal_malloc(e, sizeof(cwal_ptr_table)); if(!t) { rc = CWAL_RC_OOM; goto error; } *t = cwal_ptr_table_empty; t->allocStamp = CwalConsts.AllocStamp; } t->hashSize = hashSize; t->step = step; rc = cwal_ptr_table_add_page( e, t, 0 ); if(rc) goto error; assert( t->pg.head ); if(!*T) *T = t; return CWAL_RC_OK; error: assert(0 != rc && "You seem to have failed to set an error code!"); if(t && (t!=*T)){ cwal_free(e, t); } return rc; } int cwal_ptr_table_destroy( cwal_engine * e, cwal_ptr_table * t ){ if(!e || !t) return CWAL_RC_MISUSE; else{ void const * stamp = t->allocStamp; cwal_ptr_page * p = t->pg.head; t->pg.head = t->pg.tail = NULL; while( p ){ cwal_ptr_page * n = p->next; cwal_free(e, p); p = n; } *t = cwal_ptr_table_empty; if( CwalConsts.AllocStamp == stamp ){ cwal_free( e, t ); }else{ /* Assume t was stack allocated or is part of another object.*/ t->allocStamp = stamp; } return CWAL_RC_OK; } } int cwal_ptr_table_visit( cwal_ptr_table * t, cwal_ptr_table_visitor_f f, void * state ){ if(!t || !f) return CWAL_RC_MISUSE; else{ cwal_size_t i; cwal_ptr_page * page = t->pg.head; cwal_value ** val; cwal_size_t seen; int rc = 0; for( ; page; page = page->next ){ seen = 0; for( i = 0; (0==rc) && (i < t->hashSize) && (seen < page->entryCount); ++i ){ val = (cwal_value**)&page->list[i]; if(!*val) continue; ++seen; rc = f( val, state ); page->list[i] = *val; } } return rc; } } int cwal_ptr_table_mem_cost( cwal_ptr_table const * t, uint32_t * mallocs, uint32_t * memory ){ enum { SV = sizeof(void*) }; uint32_t a = 1; uint32_t m = sizeof(cwal_ptr_table); if(!t) return CWAL_RC_MISUSE; else{ cwal_ptr_page const * p = t->pg.head; for( ; p; p = p->next ){ ++a; m += sizeof(cwal_ptr_page) + (t->hashSize*SV); } } if(mallocs) *mallocs = a; if(memory) *memory = m; return CWAL_RC_OK; } #define HASH_PTR(P,S,H) \ ((((uint64_t/*FIXME: PORTABILITY*/)(P)) / (S)) % (H)) static uint16_t cwal_ptr_table_hash( void const * key, uint16_t step, uint16_t hashSize){ #if CWAL_VOID_PTR_IS_BIG /* IF THE DEBUGGER LEADS YOU NEAR HERE... try changing ptr_int_t back to uint64_t. */ typedef uint64_t ptr_int_t; #else typedef uint32_t ptr_int_t; #endif #if 1 const ptr_int_t k = (ptr_int_t)key; const uint32_t v1 = (uint32_t)(k / step); const uint16_t v2 = v1 % hashSize; /* MARKER("key=%p step=%u hashSize=%u k=%lu v1=%u v2=%u\n", key, step, hashSize, k, v1, v2); */ return v2; #else return ((ptr_int_t)key) / step % hashSize; #endif } int cwal_ptr_table_op( cwal_engine * e, cwal_ptr_table * t, void * key, cwal_ptr_table_ops op ){ cwal_ptr_page * pPage; cwal_value * pRet = NULL; uint16_t iKey; int rc; if(!e || !t || !key) return CWAL_RC_MISUSE; iKey = cwal_ptr_table_hash( key, t->step, t->hashSize ); assert( iKey < t->hashSize ); assert( t->pg.head ); /* TODO?: honor page-specific hashSize values, re-calculating the hash value. if the step value changes while iterating. */ switch( op ){ /* FIXME: refactor the 3 loops below into one loop at the start. */ case CWAL_PTR_TABLE_OP_SEARCH: rc = CWAL_RC_NOT_FOUND; for(pPage = t->pg.head; pPage; pPage = pPage->next ){ pRet = pPage->list[iKey]; if(!pRet) break; else if(pRet == key){ rc = CWAL_RC_OK; break; } } break; case CWAL_PTR_TABLE_OP_INSERT:{ assert(t->pg.head); rc = CWAL_RC_OK; for(pPage = t->pg.head; pPage; pPage = pPage->next ){ pRet = pPage->list[iKey]; #if 0 MARKER("COMPARING STASHED %p AGAINST KEY %p\n", (void*)pRet, (void*)key); #endif if(!pRet) goto insert; else if(pRet == key){ rc = CWAL_RC_ALREADY_EXISTS; break; } } #if 0 MARKER("INSERT NO AVAILABLE SLOT CONTAINS %p. " "ADDING PAGE for hash %u rc=%d=%s\n", (void *)key, iKey, rc, cwal_rc_cstr(rc)); #endif if(rc) break; /* We reached the end of the tables and found no empty slot. Add a page and insert it there. */ rc = cwal_ptr_table_add_page( e, t, 0 ); if(rc) break; pPage = t->pg.tail; insert: #if 0 MARKER("INSERTING %p (hash=%u) in list %p\n", (void*)key, iKey, (void *)pPage); #endif rc = CWAL_RC_OK; assert(NULL != pPage); pPage->list[iKey] = key; ++pPage->entryCount; } break; case CWAL_PTR_TABLE_OP_REMOVE: rc = CWAL_RC_NOT_FOUND; for(pPage = t->pg.head; pPage; pPage = pPage->next ){ pRet = pPage->list[iKey]; if(!pRet) break; else if(pRet == key){ cwal_ptr_page * prevPage = pPage; assert(pPage->entryCount>0); /* hijack the loop to shift all other entries down... */ for(pPage = pPage->next; pPage; pPage = pPage->next ){ if(!(prevPage->list[iKey] = pPage->list[iKey])){ --prevPage->entryCount; #if 0 /* FIXME: add this when we have a case to test it with. */ /* if prevPage is empty, move it towards the back. */ if(!prevPage->entryCount){ MARKER("MOVING prevPage B/C IT'S EMPTY.\n"); assert(prevPage == t->pages.list[i-1]); t->pages.list[i-1] = pPage; t->pages.list[i] = prevPage; } #endif break; } prevPage = pPage; } pPage = prevPage; pPage->list[iKey] = NULL; if(prevPage == pPage) --pPage->entryCount; rc = CWAL_RC_OK; #if 0 MARKER("REMOVING ENTRY %p for hash %u FROM PAGE #%u\n", (void*)pRet, iKey, x+1); #endif break; } } break; } return rc; } #undef HASH_PTR int cwal_ptr_table_search( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ){ return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_SEARCH ); } int cwal_ptr_table_insert( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ){ return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_INSERT ); } int cwal_ptr_table_remove( cwal_engine * e, cwal_ptr_table * t, cwal_value * key ){ return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_REMOVE ); } /** Adds an entry in e->weakp for p, initializing e->weakp if needed. */ static int cwal_weak_annotate( cwal_engine * e, void * p ){ int rc; cwal_ptr_table * pt = &e->weakp; if(!pt->pg.head){ cwal_size_t const hashSize = 31 /* We don't expect many weak refs, so we'll try a small hash size. */; rc = cwal_ptr_table_create(e, &pt, hashSize, sizeof(void *) ); if(rc) return rc; assert(pt->pg.head); } rc = cwal_ptr_table_op( e, pt, p, CWAL_PTR_TABLE_OP_INSERT ); /* MARKER(("Annotating %s weak ptr @%p insert rc=%d\n",cwal_value_type_id_name(tid),p,rc)); */ switch(rc){ case 0: case CWAL_RC_ALREADY_EXISTS: return 0; default: return rc; } } static cwal_weak_ref * cwal_weak_ref_search( cwal_engine * e, void *p, cwal_type_id tid ){ cwal_weak_ref * r; assert(e && p && (tid>=CWAL_TYPE_UNDEF && tidweakr[tid]; for( ; r && (r->value != p); r = r->next ){} return r; } /** Removes the entry for p added by cwal_weak_annotate() and unsets the value field of each cwal_weak_ref in e->weakr[t] where value==p. */ static int cwal_weak_unregister( cwal_engine * e, void * p, cwal_type_id t ){ assert(p && (t>=CWAL_TYPE_UNDEF && tweakp.pg.head) return 0; else{ int const rc = cwal_ptr_table_op( e, &e->weakp, p, CWAL_PTR_TABLE_OP_REMOVE ); switch(rc){ case 0:{ /* MARKER(("Invalidating %s weak ptr to %p\n",cwal_value_type_id_name(t),p)); */ cwal_weak_ref * r = cwal_weak_ref_search( e, p, t ); if(r) r->value = NULL /* because we share instances for any given (p,t) combination, there can be no more matches in the list. */ ; return 0; } case CWAL_RC_NOT_FOUND: return 0; default: return rc; } } } char cwal_is_weak_referenced( cwal_engine * e, void * p ){ return (e && p) ? (0==cwal_ptr_table_op( e, &e->weakp, p, CWAL_PTR_TABLE_OP_SEARCH )) : 0; } /** Makes wr the head of the cwal_weak_ref chain starting at e->weakr[wr->typeID]. Returns 0 on success, and the only non-bug error cases are CWAL_RC_OOM. */ static int cwal_weak_ref_insert( cwal_engine * e, cwal_weak_ref * wr ){ int rc; assert(wr && (wr->typeID>=CWAL_TYPE_UNDEF && wr->typeIDvalue); if(rc) return rc; else{ cwal_weak_ref * list; assert(!wr->next); list = e->weakr[wr->typeID]; wr->next = list; e->weakr[wr->typeID] = wr; return 0; } } /** Removes wr from e->weakr[wr->typeID] but does not free wr. */ static void cwal_weak_ref_remove( cwal_engine * e, cwal_weak_ref * wr ){ cwal_weak_ref * list; cwal_weak_ref * prev = NULL; assert(wr && (wr->typeID>=CWAL_TYPE_UNDEF && wr->typeIDweakr[wr->typeID]; for( ; list; prev = list, list = list->next ){ if(wr != list) continue; else if(prev) prev->next = wr->next; else e->weakr[wr->typeID] = wr->next; wr->next = NULL; break; } assert(wr == list); } /** Zeroes out r and frees it. */ static void cwal_weak_ref_free2( cwal_engine * e, cwal_weak_ref * r ){ assert(e && r && !r->next); assert(!V_IS_BUILTIN(r)); if(e->reWeak.count < e->reWeak.maxLength){ r->next = (cwal_weak_ref*)e->reWeak.list; e->reWeak.list = r; ++e->reWeak.count; } else { *r = cwal_weak_ref_empty; cwal_free( e, r ); } } void cwal_weak_ref_free( cwal_engine * e, cwal_weak_ref * r ){ assert(e && r); if(!e || !r || V_IS_BUILTIN(r)) return; else if(0==r->refcount || 0==--r->refcount){ cwal_weak_ref_remove( e, r ); cwal_weak_ref_free2(e, r); } } cwal_value * cwal_weak_ref_value( cwal_weak_ref * r ){ if(!r||!r->value) return NULL; else switch(r->typeID){ case CWAL_TYPE_BOOL: case CWAL_TYPE_NULL: case CWAL_TYPE_UNDEF: assert(r->value) /* Because of how we allocate/share these, this will always be true if r was validly allocated. */; case CWAL_TYPE_ARRAY: case CWAL_TYPE_BUFFER: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_EXCEPTION: case CWAL_TYPE_FUNCTION: case CWAL_TYPE_HASH: case CWAL_TYPE_INTEGER: case CWAL_TYPE_NATIVE: case CWAL_TYPE_OBJECT: case CWAL_TYPE_STRING: return (cwal_value*)r->value; case CWAL_TYPE_KVP: case CWAL_TYPE_SCOPE: case CWAL_TYPE_WEAK_REF: return NULL; case CWAL_TYPE_XSTRING: case CWAL_TYPE_ZSTRING: assert(!"Not possible"); default: assert(!"Unhandled type!"); return NULL; } } /** Allocates a new cwal_weak_ref. If possible, it takes the head of e->reWeak.list, else is cwal_malloc()s it. The returned value must eventually be passed to cwal_weak_ref_free() or cwal_weak_ref_free2(), depending on its state at cleanup time. If cwal_weak_ref_search(e,ptr,tid) returns a value then this function returns the same one. It is up to the caller to increment the return'd object's refcount. */ static cwal_weak_ref * cwal_weak_ref_alloc( cwal_engine * e, void * ptr, cwal_type_id tid ){ cwal_weak_ref * r; r = cwal_weak_ref_search(e, ptr, tid); if(!r){ r = (cwal_weak_ref *)e->reWeak.list; if(r){ assert(e->reWeak.count); e->reWeak.list = r->next; r->next = NULL; --e->reWeak.count; assert(!r->refcount); }else{ r = cwal_malloc(e, sizeof(cwal_weak_ref)); } if(r){ *r = cwal_weak_ref_empty; r->value = ptr; r->typeID = tid; } if(cwal_weak_ref_insert(e, r)){ cwal_weak_ref_free2(e, r); r = NULL; } } return r; } cwal_weak_ref * cwal_weak_ref_new( cwal_value * v ){ cwal_engine * e = v ? ((v && v->scope) ? v->scope->e : NULL) : NULL; if(!e && !V_IS_BUILTIN(v)) return NULL; else{ cwal_type_id const tid = v->vtab->typeID; cwal_weak_ref * r = NULL; switch(tid){ case CWAL_TYPE_UNDEF: r = &CWAL_BUILTIN_VALS.wref.wUndef; break; case CWAL_TYPE_NULL: r = &CWAL_BUILTIN_VALS.wref.wNull; break; case CWAL_TYPE_BOOL: r = (&CWAL_BUILTIN_VALS.vTrue == v) ? &CWAL_BUILTIN_VALS.wref.wTrue : &CWAL_BUILTIN_VALS.wref.wFalse; break; case CWAL_TYPE_STRING: if(&CWAL_BUILTIN_VALS.vStrEmpty==v) r = &CWAL_BUILTIN_VALS.wref.wStrEmpty; break; case CWAL_TYPE_INTEGER: if(CWAL_BUILTIN_VALS.vIntM1==v) r = &CWAL_BUILTIN_VALS.wref.wIntM1; else if(CWAL_BUILTIN_VALS.vInt0==v) r = &CWAL_BUILTIN_VALS.wref.wInt0; else if(CWAL_BUILTIN_VALS.vInt1==v) r = &CWAL_BUILTIN_VALS.wref.wInt1; break; case CWAL_TYPE_DOUBLE: if(CWAL_BUILTIN_VALS.vDblM1==v) r = &CWAL_BUILTIN_VALS.wref.wDblM1; else if(CWAL_BUILTIN_VALS.vDbl0==v) r = &CWAL_BUILTIN_VALS.wref.wDbl0; else if(CWAL_BUILTIN_VALS.vDbl1==v) r = &CWAL_BUILTIN_VALS.wref.wDbl1; break; default: break; } if(!r){ r = cwal_weak_ref_alloc(e, v, v->vtab->typeID); if(r){ assert(r->value == v); assert(r->typeID == v->vtab->typeID); ++r->refcount; } } return r; } } cwal_weak_ref * cwal_weak_ref_custom_new( cwal_engine * e, void * p ){ if(!e || V_IS_BUILTIN(p)) return NULL; else{ cwal_weak_ref * r = cwal_weak_ref_alloc(e, p, CWAL_TYPE_WEAK_REF); if(r){ assert(r->value == p); assert(r->typeID == CWAL_TYPE_WEAK_REF); ++r->refcount; } return r; } } void * cwal_weak_ref_custom_ptr( cwal_weak_ref * r ){ return (r && (CWAL_TYPE_WEAK_REF==r->typeID)) ? r->value : NULL; } void * cwal_weak_ref_custom_check( cwal_engine * e, void * p ){ if(!e || !p) return NULL; else{ cwal_weak_ref * r = cwal_weak_ref_search(e, p, CWAL_TYPE_WEAK_REF); if(r){ assert(r->value==p); } return r ? r->value : NULL; } } char cwal_weak_ref_custom_invalidate( cwal_engine * e, void * p ){ if(!e || !p) return 0; else{ cwal_weak_ref * r = cwal_weak_ref_search(e, p, CWAL_TYPE_WEAK_REF); if(r) r->value = NULL; return r ? 1 : 0; } } static int cwal_engine_init_interning(cwal_engine * e){ cwal_ptr_table * t = &e->interned; /* notes to self regarding table size... The following gives me a really good fill rate for the low page (over 90%) for 500 random strings: entries= 3*1024/(2*sizeof(void*)) hashSize= trim_hash(entriesPerPage*33/50) But lots of tables (9 on average, it seems) */ uint16_t const entriesPerPage = #if 0 /* testing a leak of interned strings. */ 3 #elif 0 1024 * 4 / sizeof(void*) #else /*cwal_trim_hash_size(200)*/ /* not bad 240 */ /* 281 seems to give a good size for 500-string tests. */ 512 * 5 / (sizeof(void*)) /* also not too bad, about 2kb/page, low page count. */ /* One test: with a table size of 3163 we got over 90% in the first hit and 99% in the second hit with 478 strings. With 433 entries/page we got about 58% in the 1st hit, 85 in the 2nd hit. After much experimentation, a starting page size of about 550 seems to provide a good basis here. Increasing the size by 8 (later: 8 what?) doesn't give us many fewer tables and does cost a lot more memory. */ #endif ; uint16_t const hashSize = cwal_trim_hash_size(entriesPerPage); uint16_t const stepSize = 0 /* not actually used by this particular table */; /*MARKER("INTERNED STRING TABLE hashSize=%u stepSize=%u\n", hashSize, stepSize);*/ return cwal_ptr_table_create(e, &t, hashSize, stepSize ); } static cwal_hash_t cwal_hash_cstr( char const * zKey, cwal_size_t nKey ){ #if 0 /* just a tiny tick faster than option #3. */ /* this one (taken from th1) provides fairly unpredictable results in my tests, with +/-2 tables on any given run. */ cwal_hash_t hash = 0; cwal_size_t i; for(i=0; i> shift)) & mask; } #elif 1 /* This one seems to perform (hash-wise) pretty well, by just a tick, based on simple tests with the string interning table. It's just a tiny tick slower than option #1. This hash in combination with a table size of 547 performs quite well for my pseudo-scientific random-strings tests. */ /* "djb2" algo code taken from: http://www.cse.yorku.ca/~oz/hash.html */ static const cwal_hash_t seed = 5381; char const * vstr = (char const *)zKey; cwal_hash_t hash = seed; cwal_size_t i = 0; for( ; iinterned; if( !t->pg.head ) return CWAL_RC_NOT_FOUND; iKey = (uint16_t)cwal_hash_cstr( zKey, nKey ) % t->hashSize; if(itemIndex) *itemIndex = iKey; for(pPage = t->pg.head; pPage; pPage = pPage->next ){ pRet = (cwal_value*)pPage->list[iKey]; if(!pRet) break /* end of the table list */; else if(pRet){ cwal_string * sRet = CWAL_STR(pRet); if(sRet && (CWAL_STRLEN(sRet) == nKey) && (0 == memcmp( cwal_string_cstr(sRet), zKey, nKey ))){ if(out) *out = pRet; if(pageIndex) *pageIndex = pPage; return CWAL_RC_OK; } } } return CWAL_RC_NOT_FOUND; } int cwal_interned_search_val( cwal_engine * e, cwal_value const * v, cwal_value ** out, cwal_ptr_page ** pageIndex, uint16_t * itemIndex ){ cwal_ptr_page * pPage = NULL; cwal_value * pRet = NULL; uint16_t iKey = 0; cwal_ptr_table *t; if(!e || !v) return CWAL_RC_MISUSE; t = &e->interned; if( !t->pg.head ) return CWAL_RC_NOT_FOUND; iKey = (uint16_t)v->vtab->hash(v) % t->hashSize; if(itemIndex) *itemIndex = iKey; for(pPage = t->pg.head; pPage; pPage = pPage->next ){ pRet = (cwal_value*)pPage->list[iKey]; if(!pRet) break /* end of the table list for this hash */; else if(pRet){ if((v==pRet) || ((v->vtab == pRet->vtab /* enforce strict type comparisons */) && (0 == v->vtab->compare(v, pRet)))){ if(out) *out = pRet; if(pageIndex) *pageIndex = pPage; return CWAL_RC_OK; } } } return CWAL_RC_NOT_FOUND; } int cwal_interned_insert( cwal_engine * e, cwal_value * v ){ cwal_ptr_page * pPage = NULL; cwal_ptr_page * pageIndex = NULL; cwal_value * pRet = NULL; uint16_t iKey = 0; int rc; cwal_ptr_table * t; if(!(CWAL_FEATURE_INTERN_STRINGS & e->flags)) return CWAL_RC_UNSUPPORTED; else if(!e || !v) return CWAL_RC_MISUSE; t = &e->interned; if(!t->pg.head){ rc = cwal_engine_init_interning( e ); if(rc) return rc; assert(t->pg.head); } rc = cwal_interned_search_val( e, v, &pRet, &pageIndex, &iKey ); if( pRet ) { assert(pageIndex); assert( (pRet == v) && "i'm fairly sure this holds with the current code."); return CWAL_RC_ALREADY_EXISTS; } /** Reminder: it might be interesting to only intern strings of a relatively small size, e.g. 32 bytes or less. For non-string interning (which is internally accounted for but not used by the library) we don't have such a heuristic, though. */ assert(!pageIndex); /** Search just failed, so we need to add an entry. Check if one of our existing pages has a slot... */ for(pPage = t->pg.head; pPage; pPage = pPage->next ){ pRet = pPage->list[iKey]; if(!pRet) goto insert; } if(!pPage){ /* We reached the end of the table and found no empty slot or we had a collision. Add a page and insert the value there. */ rc = cwal_ptr_table_add_page( e, t, 0 ); if(rc) return rc; pPage = t->pg.tail; } insert: rc = CWAL_RC_OK; assert(NULL != pPage); pPage->list[iKey] = v; CWAL_TR_VCS(e,v); CWAL_TR2(e,CWAL_TRACE_VALUE_INTERNED); ++pPage->entryCount; return rc; } int cwal_interned_remove( cwal_engine * e, cwal_value const * v, cwal_value ** out ){ cwal_ptr_page * pPage = 0; cwal_value * pRet = NULL; cwal_ptr_table * t; uint16_t iKey = 0; cwal_ptr_page * pageIndex = NULL; int rc; if(!e || !v) return CWAL_RC_MISUSE; t = &e->interned; if(!t->pg.head) return CWAL_RC_NOT_FOUND; rc = cwal_interned_search_val( e, v, &pRet, &pageIndex, &iKey ); if( !pRet ) { assert( CWAL_RC_NOT_FOUND == rc ); return rc; } else if(rc){ assert(!"Cannot happen"); return rc; } if( out ) *out = pRet; CWAL_TR_VCS(e,pRet); CWAL_TR2(e,CWAL_TRACE_VALUE_UNINTERNED); assert( pageIndex ); for( pPage = pageIndex; pPage; pPage = pPage->next ){ /* Remove the entry. If any higher-level pages contain that key, move it into this page (up through to the top-most page which has a matching key). */ pPage->list[iKey] = pPage->next ? pPage->next->list[iKey] : 0; if( !pPage->list[iKey] ) { --pPage->entryCount; break; } } assert( pPage && (0 == pPage->list[iKey]) ); return CWAL_RC_OK; } cwal_obase * cwal_value_obase( cwal_value * v ){ return CWAL_VOBASE(v); } static void cwal_obase_set_visiting( cwal_obase * b, char isVisiting ){ assert(b); if(isVisiting) b->flags |= CWAL_F_IS_VISITING; else b->flags &= ~CWAL_F_IS_VISITING; return; } #define CWAL_OBASE_IS_VISITING(OB) (OB ? (CWAL_F_IS_VISITING&(OB)->flags) : 0) #define CWAL_VAL_IS_VISITING(V) CWAL_OBASE_IS_VISITING(CWAL_VOBASE(V)) int cwal_value_set_visiting( cwal_value * v, char isVisiting ){ cwal_obase * b = CWAL_VOBASE(v); return !b ? CWAL_RC_TYPE : (cwal_obase_set_visiting( b, isVisiting ), CWAL_RC_OK); } void cwal_value_cleanup_noop( cwal_engine * e, void * v ){ } void cwal_value_cleanup_integer( cwal_engine * e, void * self ){ cwal_value * v = (cwal_value *)self; *CWAL_INT(v) = 0; } void cwal_value_cleanup_double( cwal_engine * e, void * self ){ cwal_value * v = (cwal_value *)self; *CWAL_DBL(v) = 0.0; } cwal_type_id cwal_value_type_id( cwal_value const * v ){ return (v && v->vtab) ? v->vtab->typeID : CWAL_TYPE_UNDEF; } void cwal_engine_type_name_proxy( cwal_engine * e, cwal_value_type_name_proxy_f f ){ if(e) e->type_name_proxy = f; } char const * cwal_value_type_name2( cwal_value const * v, cwal_size_t * len){ if(!v || !v->vtab) return NULL; else{ cwal_engine const * e = v->scope ? v->scope->e : NULL; char const * rc = NULL; if(e && e->type_name_proxy){ rc = e->type_name_proxy(v, len); } if(!rc){ rc = v->vtab->typeName; if(len) *len = (cwal_size_t)strlen(rc); } return rc; } } char const * cwal_value_type_name( cwal_value const * v ){ return v ? cwal_value_type_name2(v, NULL) : NULL; } char const * cwal_value_type_id_name( cwal_type_id id ){ cwal_value_vtab const * t = 0; switch(id){ case CWAL_TYPE_BOOL: t = &cwal_value_vtab_bool; break; case CWAL_TYPE_UNDEF: t = &cwal_value_vtab_undef; break; case CWAL_TYPE_NULL: t = &cwal_value_vtab_null; break; case CWAL_TYPE_STRING: t = &cwal_value_vtab_string; break; case CWAL_TYPE_INTEGER: t = &cwal_value_vtab_integer; break; case CWAL_TYPE_DOUBLE: t = &cwal_value_vtab_double; break; case CWAL_TYPE_ARRAY: t = &cwal_value_vtab_array; break; case CWAL_TYPE_OBJECT: t = &cwal_value_vtab_object; break; case CWAL_TYPE_NATIVE: t = &cwal_value_vtab_native; break; case CWAL_TYPE_BUFFER: t = &cwal_value_vtab_buffer; break; case CWAL_TYPE_FUNCTION: t = &cwal_value_vtab_function; break; case CWAL_TYPE_EXCEPTION: t = &cwal_value_vtab_exception; break; case CWAL_TYPE_HASH: t = &cwal_value_vtab_hash; break; case CWAL_TYPE_WEAK_REF: return "cwal_weak_ref"; default: break; } return t ? t->typeName : NULL; } char cwal_value_is_undef( cwal_value const * v ){ return ( !v || !v->vtab || (v->vtab==&cwal_value_vtab_undef)) ? 1 : 0; } char cwal_value_can_have_properties( cwal_value const * v ){ return CWAL_V_IS_OBASE(v); } #define ISA(T,TID) char cwal_value_is_##T( cwal_value const * v ) { \ /*return (v && v->vtab) ? cwal_value_is_a(v,CWAL_TYPE_##TID) : 0;*/ \ return (v && (v->vtab == &cwal_value_vtab_##T)) ? 1 : 0; \ } static const char bogusPlaceHolderForEmacsIndention##TID = CWAL_TYPE_##TID ISA(null,NULL); ISA(bool,BOOL); ISA(integer,INTEGER); ISA(double,DOUBLE); ISA(string,STRING); ISA(array,ARRAY); ISA(object,OBJECT); ISA(native,NATIVE); ISA(buffer,BUFFER); ISA(function,FUNCTION); ISA(exception,EXCEPTION); #undef ISA char cwal_value_is_number( cwal_value const * v ){ if(!v) return 0; else switch(v->vtab->typeID){ case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_BOOL: return 1; default: return 0; } } void cwal_finalizer_f_fclose( cwal_engine * e, void * m ){ if(m && (m != stdout) && (m != stderr) && (m != stdin)){ fclose( (FILE*)m ); } } void * cwal_realloc_f_std( void * state, void * m, cwal_size_t n ){ if( 0 == n ){ free( m ); return NULL; }else if( !m ){ return malloc( n ); }else{ return realloc( m, n ); } } int cwal_output_f_FILE( void * state, void const * src, cwal_size_t n ){ if( !src || !n ) return 0; return (1 == fwrite( src, n, 1, state ? (FILE*)state : stdout )) ? CWAL_RC_OK : CWAL_RC_IO; } int cwal_output_flush_f_FILE( void * f ){ return fflush(f ? (FILE*)f : stdout) ? CWAL_RC_IO : 0 ; } int cwal_output_f_buffer( void * state, void const * src, cwal_size_t n ){ cwal_output_buffer_state * ob = (cwal_output_buffer_state*) state; #if 1 return cwal_buffer_append(ob->e, ob->b, src, n); #else int rc = 0; cwal_output_buffer_state * ob = (cwal_output_buffer_state *)state_; cwal_size_t sz = ob->b->used+n+1; if(sz > ob->b->capacity){ /* TODO? expand by some factor */ /* sz = sz * 13 / 10; */ rc = cwal_buffer_reserve(ob->e, ob->b, sz); } if(!rc){ rc = cwal_buffer_append( ob->e, ob->b, n ); } return rc; #endif } void cwal_output_buffer_finalizer( cwal_engine * e, void * m ){ cwal_output_buffer_state * ob = (cwal_output_buffer_state *)m; cwal_buffer_reserve(ob->e, ob->b, 0); ob->e = NULL; *ob = cwal_output_buffer_state_empty; } void * cwal_malloc( cwal_engine * e, cwal_size_t n ){ if(!e || !n) return 0; else if( n > CwalConsts.MaxSizeT ) return NULL; else{ void * rc; rc = e->vtab->allocator.realloc( e->vtab->allocator.state.data, NULL, n ); CWAL_TR_MEM(e,rc,n); CWAL_TR2(e,CWAL_TRACE_MEM_MALLOC); return rc; } } void cwal_free( cwal_engine * e, void * m ){ if(e && m){ CWAL_TR_MEM(e,m,0); CWAL_TR2(e,CWAL_TRACE_MEM_FREE); e->vtab->allocator.realloc( e->vtab->allocator.state.data, m, 0 ); } } void * cwal_realloc( cwal_engine * e, void * m, cwal_size_t n ){ if( 0 == n ){ cwal_free( e, m ); return NULL; }else if( !m ){ return cwal_malloc( e, n ); }else{ CWAL_TR_MEM(e,m,n); CWAL_TR2(e,CWAL_TRACE_MEM_REALLOC); return ( n > CwalConsts.MaxSizeT ) ? NULL : e->vtab->allocator.realloc( e->vtab->allocator.state.data, m, n ); } } static cwal_size_t cwal_scope_sweep_r0( cwal_scope * s ){ cwal_engine * e = s->e; cwal_value * v; cwal_value * n; cwal_size_t rc = 0; cwal_value * r0 = s->mine.r0; assert(e); CWAL_TR_SV(e,s,s->mine.r0); CWAL_TR_MSG(e,"s->mine.r0"); CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK); #if 1 s->mine.r0 = NULL; for(v = r0; v; v = n ){ n = v->right; if(v->scope!=s) { dump_val(v,"Check for scope mismatch"); assert(!v->left); if(v->right) dump_val(v->right,"v->right"); } assert(v->scope==s); v->left = v->right = 0; if(n){ assert(n->scope==s); n->left = 0; } if(e->exception == v){ e->exception = 0; } cwal_value_unref2(e,v); ++rc; } #else while( (v = s->mine.r0) ){ CWAL_TR_SV(e,s,v); CWAL_TR_MSG(e,"Scope is about to unref r0 value"); CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK); assert(!V_IS_BUILTIN(v)); assert(0==v->refcount); if(v->scope!=s) { dump_val(v,"Check for scope mismatch"); if(v->left) dump_val(v->left,"v->left"); if(v->right) dump_val(v->right,"v->right"); } assert(v->scope==s); #if 1 if(e->exception == v){ e->exception = 0; } #endif if(v->right) assert(v->right->scope==s); cwal_value_unref2(e, v); ++rc; } #endif return rc; } static int cwal_engine_sweep_impl( cwal_engine * e, cwal_scope * s ){ return cwal_scope_sweep_r0(s); } #if 0 cwal_size_t cwal_engine_sweep2( cwal_engine * e, char allScopes ){ if(!e) return 0; else if(!allScopes){ return cwal_engine_sweep_impl(e, e->current); } else { cwal_scope * s = e->current; cwal_size_t rc = 0; for( ; s; s = s->parent ){ rc += cwal_engine_sweep_impl(e, s); } return rc; } } #else cwal_size_t cwal_engine_sweep( cwal_engine * e ){ return e ? cwal_engine_sweep_impl(e, e->current) : 0; } #endif cwal_size_t cwal_scope_sweep( cwal_scope * s ){ return (s && s->e) ? cwal_engine_sweep_impl( s->e, s ) : 0; } static void cwal_scope_to_r0( cwal_scope * s, cwal_value * v ){ cwal_engine * e = s->e; cwal_value * list = list; assert( s && v && e ); assert(0==v->refcount); list = s->mine.r0; for( ; list && list->left; list = list->left ) {} if(list){ list->left = v; v->right = list; } v->scope = s; s->mine.r0 = v; } /** Removes v from its scope and places it in s->mine.r0, puting it back in the probationary state. v MUST have a refcount of 0 and V_IS_BUILTIN(v) MUST be false. s MUST be the scope to reprobate v to. */ static void cwal_value_reprobate( cwal_scope * s, cwal_value * v){ int rc; assert(v); assert(!v->refcount); assert(!V_IS_BUILTIN(v)); assert(s); assert(s->e); /* dump_val(v,"Re-probating value"); */ rc = cwal_value_take(s->e, v); assert(!rc); cwal_scope_to_r0(s, v); /* dump_val(v,"Re-probated value"); */ } void cwal_unref_from( cwal_scope * p, cwal_value * v ){ assert(p && v); if(V_IS_BUILTIN(v)) return; else if(v->scope && (v->scope->level < p->level)){ assert(v->scope->level != p->level); /* Leave v in v->scope and let its scope clean it up. That resolves a corner case in client code where they must add an "artificial" ref to certain return values to ensure that they live past their scope. */ #if 0 MARKER("v level=%u, ref-from level=%u\n", (unsigned)v->scope->level, (unsigned)p->level); #endif assert(v->scope->level < p->level); /* assert(v->refcount); interned strings break this */ if(v->refcount){ /*MARKER("v level=%u, vSelf level=%u\n", (unsigned)v->scope->level, (unsigned)p->level);*/ /* dump_val(v,"Pseudo-unreffing value"); */ --v->refcount; if(0==v->refcount){ cwal_value_reprobate(v->scope, v); } } else{ assert(!"?Can this happen? Maybe on first-time interned strings?"); cwal_value_unref2(p->e, v); } } else cwal_value_unref2(p->e, v); } /** "Snips" v from its left/right neighbors. If v->scope and v is the head of one of v->scope->mine's ownership lists then the list is adjusted to point to v->left (if set) or v->right. Returns the right-hand neighbor of v, or 0 if it has no neighbor. */ static cwal_value * cwal_value_snip( cwal_value * v ); /** calls cwal_value_snip() to snip v from any chain and moves v to the head of one of s->mine's members (depending on a couple of factors). Does not modify refcount. If v->scope==s then it returns without any side-effects. */ static void cwal_scope_insert( cwal_scope * s, cwal_value * v ); /** Internal helper to move a refcount==0 value from the r0 list to one of the "longer-lived" lists. Returns 0 on success. */ static int cwal_scope_from_r0( cwal_value * v ){ cwal_scope * s = v->scope; assert(1==v->refcount); if(!s->mine.r0) return CWAL_RC_NOT_FOUND; else if(1!=v->refcount) return CWAL_RC_RANGE /* Only to ensure that caller ++'s it before calling this, so that the cwal_scope_insert() call below can DTRT. */ ; cwal_value_snip(v); cwal_scope_insert( s, v ); assert(v->scope == s); return 0; } /** This function frees the internal state of s but does not free s. */ int cwal_scope_clean( cwal_engine * e, cwal_scope * s ){ int rc; cwal_value * v; CWAL_TR_S(e,s); CWAL_TR3(e,CWAL_TRACE_SCOPE_CLEAN_START,"Scope cleanup starting."); assert(e->current != s); /* Special treatment of e->exception. */ if(e->exception && (e->exception->scope == s)){ cwal_scope * parent = e->current ? e->current : 0; CWAL_TR_SV(e,s,e->exception); CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,"Relocating engine->exception..."); if(!parent){ e->exception = 0 /* Hopefully someone owns this thing. */ ; } else { rc = cwal_value_xscope( e, parent, e->exception, NULL ); assert(!rc); assert(e->exception->scope && (e->exception->scope!=s)); CWAL_TR_SV(e,e->exception->scope,e->exception); CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,"Moved EXCEPTION to next scope up."); } } cwal_scope_sweep_r0( s ); #if 1 s->props = 0 /* remember that s->props can get moved into a higher scope. */; #else if(s->props){ assert(1==cwal_object_value(s->props)->refcount && "Unexpected reference on s->props."); cwal_object_unref( s->props ); } #endif /** Reminder: we HAVE to clean up the containers first to ensure that cleanup of PODs during container cleanup does not step on dead POD refs we already cleaned. We could get around this ordering if we included PODs in the gc queue, but we do not. Algorith: keep reducing each value's refcount by 1 until it's dead. This weeds out cycles one step at a time. Notes: For PODs we REALLY want to unref them only once here, BUT internalized strings screw that up for us (but are too cool to outright give up, yet i also don't want to special-case them). The destruction order is not QUITE what i want (i would prefer reverse-allocation order, but we cannot guaranty that ordering once objects move between scopes, anyway). What we're doing here is unref'ing the head (last-added) item. If that item still has references (it was not destroyed) then we proceed to unref subsequent items in the list until one is destroyed. Value destruction modifies the list we are traversing, forcing a re-start of the traversal if any item is actually finalized by the unref. As values are cleaned up they remove themselves from s->mine, so we can simply walk the list until it's empy. For "normal use" the destruction order will be the referse of allocation, but once references are held that doesn't... well, hold. Remember that this ONLY works because of our scoping rules: - All values in a scope when it is cleaned up must not (cannot) reference values in lower (newer) scopes because performing such a reference transfers the being-referenced value (recursively for containers) into the referencing value's owning scope. */ while( (v = s->mine.headObj ? s->mine.headObj : s->mine.headPod) ){ CWAL_TR_SV(e,s,v); CWAL_TR_MSG(e,"Scope is about to unref value"); CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK); assert(!V_IS_BUILTIN(v)); assert(v->scope==s); if(v->right) assert(v->right->scope==s); #if 0 cwal_value_unref2(e, v); #elif 0 for( ; v; v = v->right ){ if( CWAL_RC_HAS_REFERENCES == cwal_value_unref2(e, v) ) continue; else break; } #elif 1 while(v){ cwal_value * n = v->right; if( CWAL_RC_HAS_REFERENCES == cwal_value_unref2(e, v) ) { v = n; continue; } else { if(n && (s == n->scope)){ /* We're still in THIS list, else n is in the gc queue and we need to re-start traversal. */ v = n; continue; } else break; } } #else for( ; v && (CWAL_RC_HAS_REFERENCES == cwal_value_unref2(e, v)); v = v->right ){ assert(v->scope==s); /* If the value still has references, we know it's okay to move on to v->right. Destroying an item modifies the list we're in, so we HAVE to re-start for that case, but we don't have to restart for the CWAL_RC_HAS_REFERENCES case because we know nobody was actually destructed. Recall that the builtin/constant values never get ref'd, unref'd, nor put in this list. */ } #endif } assert(0 == s->mine.headPod); assert(0 == s->mine.headObj); CWAL_TR3(e,CWAL_TRACE_SCOPE_CLEAN_END,"Scope cleanup finished."); /*MARKER("ALLOCS LIST SIZES: compound=%u simple=%u\n", s->mine.compound.count, s->mine.simple.count );*/ return CWAL_RC_OK; } static int cwal_scope_free( cwal_engine * e, cwal_scope * s, char allowRecycle ){ void const * stamp; assert( e && s ); stamp = s->allocStamp; if(!e->gcInitiator) { e->gcInitiator = s; CWAL_TR_S(e,s); CWAL_TR3(e,CWAL_TRACE_MESSAGE,"Initializing gc capture."); } cwal_scope_clean(e, s); assert(s/*now effectively dead!*/ == e->gcInitiator); if(s/*now effectively dead!*/ == e->gcInitiator) { e->gcInitiator = 0; cwal_gc_flush( e ); } *s = cwal_scope_empty; if(CwalConsts.AllocStamp == stamp){ /* This API allocated the scope - recycle or free it. */ cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_SCOPE); assert(re); if( allowRecycle && (re->count < re->maxLength) ){ s->parent = (cwal_scope*)re->list; re->list = s; ++re->count; } else cwal_free(e, s); }else{ /* it was allocated elsewhere */ s->allocStamp = stamp; } return CWAL_RC_OK; } cwal_exception_info * cwal_engine_err_get( cwal_engine * e ){ return e ? &e->err : 0; } int cwal_exception_info_clear( cwal_engine * e, cwal_exception_info * err ){ if(!e || !err) return CWAL_RC_MISUSE; else{ int rc = CWAL_RC_OK; if( err->zMsg ) cwal_free( e, err->zMsg ); if( err->value ) rc = cwal_value_unref2( e, err->value ); if(CWAL_RC_DESTRUCTION_RUNNING == rc) { assert( 0 && "i don't _think_ this can happen." ); rc = 0; } assert(0 == rc); *err = cwal_exception_info_empty; return CWAL_RC_OK; } } /** Passes all values in the given linked value list to cwal_free(). The values MUST have been properly cleaned up via the cwal_unref() mechanism. */ static void cwal_value_list_free( cwal_engine * e, cwal_value * list){ cwal_value * v, * next; for( v = list; v; v = next ){ assert(0==v->scope); assert(0==v->refcount); next = v->right; cwal_free(e, v); } } /** If s->finalize is not NULL then s->finalize(e, s->state) is called, else this is a no-op. */ static void cwal_state_cleanup( cwal_engine * e, cwal_state * s ); static int cwal_engine_destroy_impl( cwal_engine * e, cwal_engine_vtab * vtab ){ if(!e || !vtab) return CWAL_RC_MISUSE; else{ void const * stamp = e->allocStamp; cwal_state_cleanup( e, &e->client ); if(!e->vtab ) e->vtab = vtab /* only happens during on-init errors. */ ; e->gcInitiator = 0; CWAL_TR2(e,CWAL_TRACE_ENGINE_SHUTDOWN_START); cwal_exception_info_clear( e, &e->err ) /* must come before scope cleanup! */; e->reWeak.maxLength = 0; e->exception = 0 /* its scope will clean it up */; e->prototypes = 0 /* its scope will clean it up */; while( e->current ){ cwal_scope_pop(e); } {/* Cleanup recyclers (AFTER scopes have been popped)... */ int i; cwal_kvp * kvp, * next; cwal_recycler * re; cwal_scope * s, * snext; /* This "should" be a loop, but our use of mixed types screws that up. */ #define RE(T) re = &e->recycler[ cwal_recycler_index(T) ] #define UNCYCLE(T) RE(T); cwal_value_list_free(e, (cwal_value*)re->list); \ re->list = 0; re->count = 0 UNCYCLE(CWAL_TYPE_INTEGER); UNCYCLE(CWAL_TYPE_DOUBLE); UNCYCLE(CWAL_TYPE_OBJECT); UNCYCLE(CWAL_TYPE_ARRAY); UNCYCLE(CWAL_TYPE_NATIVE); UNCYCLE(CWAL_TYPE_BUFFER); UNCYCLE(CWAL_TYPE_FUNCTION); UNCYCLE(CWAL_TYPE_EXCEPTION); /* KVP and SCOPE are handled below... */ #undef UNCYCLE cwal_value_list_free(e, e->reString.list); e->reString.list = 0; e->reString.count = 0; RE(CWAL_TYPE_KVP); kvp = (cwal_kvp*) re->list; re->list = 0; for( ; kvp; kvp = next ){ next = kvp->right; assert(!kvp->key); assert(!kvp->value); kvp->right = NULL; cwal_kvp_free( e, NULL, kvp, 0 ); --re->count; } assert(0==re->count); RE(CWAL_TYPE_SCOPE); s = (cwal_scope*) re->list; re->list = 0; for( ; s; s = snext ){ snext = s->parent; s->parent = 0; cwal_free( e, s ); --re->count; } assert(0==re->count); #undef RE { /* Clean up weak ref recycler... */ cwal_weak_ref * wr; cwal_weak_ref * wnext; re = &e->reWeak; wr = (cwal_weak_ref*) re->list; re->list = 0; for( ; wr; wr = wnext ){ wnext = wr->next; assert(!wr->value); wr->next = NULL; cwal_free(e, wr); --re->count; } assert(0==re->count); } /* sanity-check to make sure we didn't leave any new additions out of the cleanup... */ for( i = 0; i < (sizeof(e->recycler)/sizeof(e->recycler[0])); ++i ){ re = &e->recycler[i]; assert(re->id>0); assert(0==re->list); assert(0==re->count); } } { /* Clean up any dangling cwal_weak_refs if the client failed to do so... */ cwal_size_t i = 0; for( i = 0; i < sizeof(e->weakr)/sizeof(e->weakr[0]); ++i ){ cwal_weak_ref * list = e->weakr[i]; e->weakr[i] = NULL; while( list ){ cwal_weak_ref * next = list->next; list->next = NULL; cwal_weak_ref_free2(e, list); list = next; } } } cwal_ptr_table_destroy(e, &e->weakp); cwal_ptr_table_destroy( e, &e->interned ); cwal_buffer_reserve(e, &e->buffer, 0); cwal_gc_flush( e ); { /* Clean up e->reList... */ int i = 0; const int n = sizeof(e->reList.lists)/sizeof(e->reList.lists[0]); for( ; i <= n; ++i ){ cwal_list * li = &e->reList.lists[i]; if(li->list) cwal_list_reserve(e, li, 0); } memset(&e->reList.lists, 0, sizeof(e->reList.lists)); } if(vtab->tracer.close){ vtab->tracer.close( vtab->tracer.state ); vtab->tracer.state = 0; } if(vtab->outputer.state.finalize){ vtab->outputer.state.finalize( e, vtab->outputer.state.data ); vtab->outputer.state.data = 0; } CWAL_TR2(e,CWAL_TRACE_ENGINE_SHUTDOWN_END); *e = cwal_engine_empty; if( stamp == CwalConsts.AllocStamp ){ vtab->allocator.realloc( vtab->state.data, e, 0 ); }else{ /* client allocated it or it was part of another object. */ e->allocStamp = stamp; } if(vtab->state.finalize){ vtab->state.finalize( NULL, vtab->state.data ); } /** TODO: call vtab->shutdown() once that's added to the vtab interface. */ return CWAL_RC_OK; } } int cwal_engine_destroy( cwal_engine * e ){ assert( NULL != e->vtab ); return e ? cwal_engine_destroy_impl(e, e->vtab) : CWAL_RC_MISUSE; } int cwal_engine_init( cwal_engine ** E, cwal_engine_vtab * vtab ){ unsigned const sz = sizeof(cwal_engine); cwal_engine * e; int rc; if(!E || !vtab){ return CWAL_RC_MISUSE; } CWAL_INIT_EMPTY_VALUES; e = *E; if(!e){ e = vtab->allocator.realloc( vtab->allocator.state.data, NULL, sz ); if(!e){ return CWAL_RC_OOM; } } *e = cwal_engine_empty; memset(&e->reList.lists, 0, sizeof(e->reList.lists)); { cwal_recycler * re = &e->recycler[sizeof(e->recycler)/sizeof(e->recycler[0])-1]; assert( (re->maxLength > 0) && "Steve forgot to properly initialize a new e->recycler element again." ); assert( !re->count); } if(!*E) e->allocStamp = CwalConsts.AllocStamp /* we use this later to know whether or not we need to free() e. */; e->vtab = vtab; if(CwalConsts.AutoInternStrings){ e->flags |= CWAL_FEATURE_INTERN_STRINGS; } /* Tag e->recycler[*].id, just for sanity checking. The order MUST coincide with cwal_recycler_index(). i do recognize the irony that by adding this sanity checking i am actually increasing the possiblity of error by adding yet another place where this list of types (and its exact ordering) must be maintained :/. */ rc = 0; #define CY(T) e->recycler[rc++].id = T CY(CWAL_TYPE_INTEGER); CY(CWAL_TYPE_DOUBLE); CY(CWAL_TYPE_ARRAY); CY(CWAL_TYPE_OBJECT); CY(CWAL_TYPE_NATIVE); CY(CWAL_TYPE_BUFFER); CY(CWAL_TYPE_FUNCTION); CY(CWAL_TYPE_EXCEPTION); CY(CWAL_TYPE_KVP); CY(CWAL_TYPE_SCOPE); #undef CY rc = 0; #if CWAL_ENABLE_TRACE e->trace.e = e; #endif rc = cwal_scope_push( e, NULL ); #if 0 /* defer interned strings init until needed. */ if(!rc && (CWAL_FEATURE_INTERN_STRINGS & e->flags)) { rc = cwal_engine_init_interning(e); } #endif e->prototypes = cwal_new_array(e); if(!e->prototypes){ rc = CWAL_RC_OOM; goto error; } cwal_value_ref2(e, CWAL_VALPART(e->prototypes)) /* So that it cannot get sweep()'d up. */ ; cwal_array_reserve(e->prototypes, 16); if(!rc && vtab->hook.on_init){ rc = vtab->hook.on_init( e, vtab ); } if(rc) goto error; else if(!*E){ *E = e; } return CWAL_RC_OK; error: assert(0 != rc); /* assert(NULL == e->top); */ e->vtab = NULL; cwal_engine_destroy_impl( e, vtab ); return rc; } /*static int cwal_list_visit_FreeFunction( void * S, void * E ){ cwal_engine * e = (cwal_engine *)E; cwal_function_info * f = (cwal_function_info *)S; cwal_state_cleanup( e, &f->state ); cwal_string_unref( e, f->name ); cwal_free( e, S ); return CWAL_RC_OK; } */ void cwal_state_cleanup( cwal_engine * e, cwal_state * s ){ if( s ){ if( s->finalize ) s->finalize( e, s->data ); *s = cwal_state_empty; } } int cwal_output( cwal_engine * e, void const * src, cwal_size_t n ){ return (e && src && n) ? (e->vtab->outputer.output ? e->vtab->outputer.output( e->vtab->outputer.state.data, src, n ) : CWAL_RC_OK) : CWAL_RC_MISUSE; } int cwal_output_flush( cwal_engine * e ){ return (e && e->vtab) ? (e->vtab->outputer.flush ? e->vtab->outputer.flush( e->vtab->outputer.state.data ) : CWAL_RC_OK) : CWAL_RC_MISUSE; } static long cwal_printfv_appender_cwal_output( void * S, char const * s, long n ) { cwal_engine * e = (cwal_engine *)S; int rc = cwal_output( e, s, (cwal_size_t)n ); return rc ? -1 : 0; } int cwal_outputfv( cwal_engine * e, char const * fmt, va_list args ){ if(!e || !fmt) return CWAL_RC_MISUSE; else{ long const prc = cwal_printfv( cwal_printfv_appender_cwal_output, e, fmt, args ); return prc>=0 ? 0 : CWAL_RC_IO; } } int cwal_outputf( cwal_engine * e, char const * fmt, ... ){ if(!e || !fmt) return CWAL_RC_MISUSE; else{ int rc; va_list args; va_start(args,fmt); rc = cwal_outputfv( e, fmt, args ); va_end(args); return rc; } } typedef struct BufferAppender { cwal_engine * e; cwal_buffer * b; int rc; } BufferAppender; static long cwal_printfv_appender_buffer( void * arg, char const * data, long n ) { BufferAppender * ba = (BufferAppender*)arg; cwal_buffer * sb = ba->b; if( !sb || (n<0) ) return -1; else if( ! n ) return 0; else { long rc; size_t npos = sb->used + n; if( npos >= sb->capacity ) { const size_t asz = npos ? ((3 * npos / 2) + 1) : 32; if( asz < npos ) { ba->rc = CWAL_RC_RANGE; return -1; /* overflow */ } else { rc = cwal_buffer_reserve( ba->e, sb, asz ); if(rc) { ba->rc = CWAL_RC_OOM; return -1; } } } rc = 0; for( ; rc < n; ++rc, ++sb->used ) { sb->mem[sb->used] = data[rc]; } sb->mem[sb->used] = 0; return rc; } } int cwal_buffer_append( cwal_engine * e, cwal_buffer * b, void const * data, cwal_size_t len ){ BufferAppender ba; long rc; cwal_size_t sz; ba.b = b; ba.e = e; ba.rc = 0; if(!b || !data) return CWAL_RC_MISUSE; sz = b->used + len + 1/*NUL*/; rc = cwal_buffer_reserve( e, b, sz ); if(rc) return rc; rc = cwal_printfv_appender_buffer(&ba, (char const *)data, (long)len ); if( !ba.rc && (rc>=0) ) { b->mem[b->used] = 0; return 0; } else return ba.rc ? ba.rc : CWAL_RC_OOM; } int cwal_buffer_printfv( cwal_engine * e, cwal_buffer * b, char const * fmt, va_list args){ if(!e || !b || !fmt) return CWAL_RC_MISUSE; else{ BufferAppender ba; ba.b = b; ba.e = e; ba.rc = 0; cwal_printfv( cwal_printfv_appender_buffer, &ba, fmt, args ); return ba.rc; } } int cwal_buffer_printf( cwal_engine * e, cwal_buffer * b, char const * fmt, ... ){ if(!e || !b || !fmt) return CWAL_RC_MISUSE; else{ int rc; va_list args; va_start(args,fmt); rc = cwal_buffer_printfv( e, b, fmt, args ); va_end(args); return rc; } } static int cwal_scope_alloc( cwal_engine * e, cwal_scope ** S ){ cwal_scope * s; assert( S && "Invalid NULL ptr."); s = *S; if(!s){ cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_SCOPE); assert(re); if(re->count){ s = (cwal_scope*)re->list; assert(s); if(s->parent){ re->list = s->parent; s->parent = 0; } else re->list = 0; --re->count; } else{ s = (cwal_scope *)cwal_malloc( e, sizeof(cwal_scope) ); } } if(s){ *s = cwal_scope_empty; s->e = e; if(*S != s) { s->allocStamp = CwalConsts.AllocStamp /* we use this later to know whether or not we need to free() s. */;; *S = s; } } return s ? CWAL_RC_OK : CWAL_RC_OOM; } int cwal_scope_push( cwal_engine * e, cwal_scope ** S ){ if(!e) return CWAL_RC_MISUSE; else{ cwal_scope * s = S ? *S : NULL; int rc; enum { Incr = 10 }; rc = cwal_scope_alloc(e, &s); if(rc){ assert(NULL == s); return rc; }else{ assert(NULL != s); s->level = e->current ? (1 + e->current->level) : 1; s->parent = e->current; if(!e->top){ assert(NULL == e->current); e->top = s; } e->current = s; if(S) *S = s; return CWAL_RC_OK; } } } int cwal_scope_pop( cwal_engine * e ){ if(!e) return CWAL_RC_MISUSE; else if( 0 == e->current ) return CWAL_RC_RANGE; else{ int rc; cwal_scope * p; cwal_scope * s = e->current; assert(s->e == e); p = s->parent; assert(p || (e->top==s)); e->current = p; if(e->top==s) e->top = 0; rc = cwal_scope_free( e, s, 1 ); assert(!rc && "Freeing scope failed?") /* we have no recovery strategy for this case. But i "don't think it can fail" since the transition to linked lists.*/; return rc; } } static cwal_value * cwal_value_snip( cwal_value * v ){ cwal_scope * p = v->scope; cwal_value * l = v->left; cwal_value * r = v->right; v->scope = 0; v->right = v->left = 0; assert(r != v); assert(l != v); if(l) l->right = r; if(r) r->left = l; if(p){ l = l ? l : r; if(l) while(l->left) l=l->left; if(p->mine.headObj==v){ p->mine.headObj = l; CWAL_TR_SV(p->e,p,l); CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headObj."); if(p->mine.headObj) assert(0==p->mine.headObj->left); }else if(p->mine.headPod==v){ p->mine.headPod = l; CWAL_TR_SV(p->e,p,l); CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headPod."); if(p->mine.headPod) assert(0==p->mine.headPod->left); }else if(p->mine.r0==v){ p->mine.r0 = l; CWAL_TR_SV(p->e,p,l); CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->r0."); if(p->mine.r0) assert(0==p->mine.r0->left); } } return r; } /** Inserts v so that v->right is now l, adjusting v and l as necessary. v->right and v->left must be 0 before calling this. */ static void cwal_value_insert_before( cwal_value * l, cwal_value * v ){ assert(0 == v->right); assert(0 == v->left); if(l != v){ if( l->left ){ l->left->right = v; } l->left = v; v->right = l; } } cwal_value * cwal_string_from_recycler( cwal_engine * e, cwal_size_t len ){ cwal_value * li = (cwal_value *)e->reString.list; cwal_string * s; cwal_value * prev = 0; cwal_size_t slen; cwal_size_t paddedLen; for( ; li; prev = li, li = li->right ){ s = CWAL_STR(li) /*cwal_value_get_string(li)*/; assert(s); assert(0 < CWAL_STRLEN(s)); assert(!CWAL_STR_ISXZ(s)); slen = CWAL_STRLEN(s); if(!CwalConsts.StringPadSize){ if(len!=slen) continue; /* Else fall through */ }else if(len!=slen){ /** If s's "padded length" is large enough for the request, but not "too large" (within 1 increment of CwalConsts.StringPadSize) then we will re-use it. */ cwal_size_t const mod = (slen % CwalConsts.StringPadSize); paddedLen = mod ? (slen + (CwalConsts.StringPadSize - mod)) : slen; if(paddedLen < len) continue; else if(paddedLen > len){ if((paddedLen - CwalConsts.StringPadSize) > len) continue; } } if(prev){ prev->right = li->right; } else { assert(e->reString.list == li); e->reString.list = li->right; } li->right = 0; --e->reString.count; if(CwalConsts.StringPadSize){ s->length = CWAL_STRLEN_MASK & len; } /* MARKER("Pulling string of len %u from recycle bin.\n", (unsigned)len); */ CWAL_TR_V(e,li); CWAL_TR3(e,CWAL_TRACE_MEM_FROM_RECYCLER, "Pulled string from recycle bin."); return li; } return NULL; } static char cwal_string_recycle( cwal_engine * e, cwal_value * v ){ cwal_string * s = cwal_value_get_string(v); cwal_size_t const slen = CWAL_STRLEN(s); char const * freeMsg; cwal_value * li; assert(s); if( slen > CwalConsts.MaxRecycledStringLen ){ freeMsg = "String too long to recycle - freeing."; goto freeit; } else if(s->length & ~CWAL_STRLEN_MASK){ freeMsg = "Cannot currently recycle x/z-strings."; goto freeit; } else if( 0 == e->reString.maxLength ){ freeMsg = "String recycling disabled - freeing."; goto freeit; } #if 0 if(CwalConsts.StringPadSize){ /** Re-set s->length to its padded length, to help ensure that pulling from the recycle doesn't suffer from an off-by-one. Nope... causes an off-by-one instead. */ s->length = CWAL_STRLEN_MASK & (slen + (CwalConsts.StringPadSize - (slen % CwalConsts.StringPadSize))); } #endif li = (cwal_value *)e->reString.list; if(e->reString.count>=e->reString.maxLength){ /* Remove the oldest entries from the list. They have not been recycled in a while, and are not likely needed any more. To avoid unduly high N on the O(N) linked list traversal, when trimming the list we trim some percentage of it, as opposed to only the last (oldest) element. Otherwise this algo is remarkably slow on large recycle lists or when rapidly freeing many strings. TODO?: sort the strings by size(?) in order to optimize the from-recycle-bin size lookup? */ #if 0 cwal_value * x = li; cwal_value * prev = 0; for( ;x && x->right; prev = x, x = x->right ){} if(prev) prev->right = 0; MARKER("Trimming last entry.\n"); if(x == li) e->reString.list = li = 0; CWAL_TR_V(e,x); CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, "Popping stale string from recycler."); cwal_free(e,x); --e->reString.count; #else cwal_size_t keep = e->reString.maxLength * 3 / 4; cwal_size_t i; cwal_value * x = li; cwal_value * prev = 0; cwal_value * cut = 0; for( i = 0; x; prev = x, x = x->right, ++i ){ if(i==keep){ assert(!x->left); cut = x; break; } } if(prev) prev->right = 0; else e->reString.list = 0; #if 0 MARKER("Trimming list to %u of %u entries cut=%c.\n", (unsigned)i, (unsigned) e->reString.maxLength, cut?'y':'n'); #endif if(cut){ #if 0 e->reString.count = i; CWAL_TR_V(e,x); CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, "Popping stale string(s) from recycler."); cwal_value_list_free(e, cut); #else /* We "could" just use cwal_value_list_free(), but i want more tracing info. */ for( x = cut; x ; x = cut ){ cut = x->right; x->right = 0; --e->reString.count; CWAL_TR_V(e,x); CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, "Popping stale string from recycler."); cwal_free(e,x); } #endif } assert(e->reString.count < e->reString.maxLength); #endif } /* MARKER("String to recyler...\n"); */ assert(!v->right); assert(!v->scope); assert(!v->refcount); li = (cwal_value*)e->reString.list; v->right = li; e->reString.list = v; ++e->reString.count; CWAL_TR_V(e,v); CWAL_TR3(e, CWAL_TRACE_MEM_TO_RECYCLER, "Placed string in recycling bin."); return 1; freeit: CWAL_TR_V(e,v); CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, freeMsg); cwal_free( e, v ); return 0; } char cwal_value_recycle( cwal_engine * e, cwal_value * v ){ int ndx; cwal_recycler * re; cwal_size_t max; cwal_obase * base; #if CWAL_ENABLE_TRACE char const * freeMsg = "==> CANNOT RECYCLE. FREEING."; # define MSG(X) freeMsg = X #else # define MSG(X) #endif assert(0==v->refcount); if(CWAL_TYPE_STRING == v->vtab->typeID){ return cwal_string_recycle( e, v ); } else if(e->gcInitiator && CWAL_V_IS_OBASE(v)){ /** This means a scope cleanup is running and deallocation must be delayed until the cleanup is finished. Move the value into the GC queue. We only need this for types which can participate in cycles, as it's easy to step on a destroyed value via a cycle during value destruction. */ CWAL_TR_V(e,v); CWAL_TR3(e,CWAL_TRACE_MEM_TO_GC_QUEUE, "Redirecting value to gc queue"); ndx = cwal_gc_push( e, v ); assert(0==ndx && "cwal_gc_push() can (now) only fail if !e->gcInitiator."); return (0==ndx) ? -1 : 0; } assert(e && v && v->vtab); assert( 0 == v->refcount ); assert( 0 == v->right ); assert( 0 == v->scope ); ndx = cwal_recycler_index( v->vtab->typeID ); if( ndx < 0 ) { MSG("==> Unrecyclable type. FREEING."); goto freeit; } re = &e->recycler[ndx]; max = re->maxLength; assert(re->id == v->vtab->typeID); if(!max) { MSG("==> Recyling disabled for this type. FREEING."); goto freeit /* */; } else if( re->count >= max ){ MSG("==> Recyling bin for this type is full. FREEING."); goto freeit /* bin is full */; } if( (base = CWAL_VOBASE(v)) ){ base->flags |= CWAL_F_IS_RECYCLED; } if(re->list){ cwal_value_insert_before( re->list, v ); } ++re->count; re->list = v; if(re->count > 1){ assert(((cwal_value*)re->list)->right); } CWAL_TR_V(e,v); CWAL_TR3(e, CWAL_TRACE_MEM_TO_RECYCLER, "Placed in recycling bin."); return 1; freeit: CWAL_TR_V(e,v); CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER,freeMsg); *v = cwal_value_undef; cwal_free(e, v); return 0; #undef MSG } int cwal_gc_push( cwal_engine * e, cwal_value * v ){ cwal_obase * base; assert( e->gcInitiator ); assert(0 == v->scope); assert(0 == v->right); if(!e->gcInitiator) return CWAL_RC_MISUSE; if(e->gcList){ cwal_value_insert_before( e->gcList, v ); } if( (base = CWAL_VOBASE(v)) ) base->flags |= CWAL_F_IS_GC_QUEUED; e->gcList = v; assert(0==e->gcList->left); return CWAL_RC_OK; } int cwal_gc_flush( cwal_engine * e ){ int rc = 0; cwal_value * v; cwal_value * n; cwal_obase * base; assert( 0 == e->gcInitiator && "Otherwise we might have a loop b/t this and cwal_value_recycle()"); for( v = e->gcList; v; v = n ){ n = v->right; cwal_value_snip(v); if( (base = CWAL_VOBASE(v)) ) base->flags &= ~CWAL_F_IS_GC_QUEUED; rc = cwal_value_recycle(e, v); assert(-1!=rc && "Impossible loop!"); } e->gcList = 0; return rc; } static void cwal_scope_insert( cwal_scope * s, cwal_value * v ){ /* cwal_scope * p = v->scope; */ cwal_value * list; cwal_value ** listpp = 0; /* if(s == p) return; */ assert(s && v); assert(!V_IS_BUILTIN(v)); if(0==v->refcount){ listpp = &s->mine.r0; }else if(CWAL_VOBASE(v)){ listpp = &s->mine.headObj; }else { listpp = &s->mine.headPod; } list = *listpp; #if 1 if(list == v){ assert(v->scope == s); assert(!"Can this happen?"); return; } #endif cwal_value_snip( v ); assert(0==v->right); assert(0==v->left); if(list) { v->right = list; assert(0==list->left); list->left = v; } *listpp = v; v->scope = s; assert(0==v->left); CWAL_TR_SV(s->e,s,v); CWAL_TR3(s->e,CWAL_TRACE_VALUE_SCOPED,"Value moved to higher/older scope."); } int cwal_value_take( cwal_engine * e, cwal_value * v ){ cwal_scope * s = v ? v->scope : 0; if(!e || !v) return CWAL_RC_MISUSE; else if( V_IS_BUILTIN( v ) ) return CWAL_RC_OK; else if(!s) return CWAL_RC_RANGE; else{ cwal_value_snip( v ); CWAL_TR_SV(e,s,v); CWAL_TR3(e,CWAL_TRACE_VALUE_UNSCOPED,"Removed from parent scope."); assert(!v->scope); assert(0==v->right); assert(0==v->left); assert(s->mine.headObj != v); assert(s->mine.headPod != v); assert(s->mine.r0 != v); return CWAL_RC_OK; } } int cwal_value_unref(cwal_value *v ){ if(!v) return CWAL_RC_MISUSE; else if( V_IS_BUILTIN( v ) ) return CWAL_RC_OK; else if(!v->scope) return CWAL_RC_MISUSE; else return cwal_value_unref2(v->scope->e, v); } int cwal_value_unref2(cwal_engine * e, cwal_value *v ){ #if 0 if(!v) return CWAL_RC_MISUSE; assert(v->scope); e = v->scope->e; #endif assert( e ); if(NULL == e || NULL == v) return CWAL_RC_MISUSE; CWAL_TR_SV(e,v->scope,v); if(V_IS_BUILTIN(v)) return 0; #if 0 else if( !v->value /* || !v->refcount possibly broken by initial refcount of 0 */ ){ if(!V_IS_BUILTIN(v)){ /*MARKER("cycled ptr=%p\n",(void*)v); fflush(stdout);*/ CWAL_TR_V(e,v); CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE, "RUN OVER BY A CYCLE WHILE FINALIZING."); assert(0 == v->scope); assert(CWAL_VOBASE(v) && "Only containers can form cycles. This was not a cycle."); return CWAL_RC_DESTRUCTION_RUNNING; } assert( V_IS_BUILTIN(v) ); return CWAL_RC_OK; } #endif else { cwal_obase * b = CWAL_VOBASE(v); CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"Unref'ing"); if(!v->refcount || !--v->refcount){ int rc; cwal_scope * vScope = v->scope; CWAL_TR_V(e,v); if(b){ if(CWAL_F_IS_GC_QUEUED & b->flags){ assert(!v->scope); CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE, "DESTRUCTION OF A GC-QUEUED OBJECT: SKIPPING"); /* Possible again since starting refcount at 0: assert(!"This is no longer possible."); */ return CWAL_RC_DESTRUCTION_RUNNING; } #if 1 else if( CWAL_F_IS_RECYCLED & b->flags ) { assert(!v->scope); CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE, "DESTRUCTION OF A RECYCLED OBJECT: SKIPPING"); /* Possible again since starting refcount at 0: assert(!"This is no longer possible."); */ return CWAL_RC_DESTRUCTION_RUNNING; } #endif #if 1 else if( CWAL_F_IS_DESTRUCTING & b->flags ) { CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE, "ENTERING DESTRUCTION A 2ND TIME (or more): SKIPPING"); /* Possible again since starting refcount at 0: assert(!"This is no longer possible."); */ return CWAL_RC_DESTRUCTION_RUNNING; } } #endif CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_START, "Starting finalization..."); #if 1 if(e->exception == v){ e->exception = 0; } #endif if(v->scope){ rc = cwal_value_take(e, v) /*ignoring rc! take() cannot fail any more under these conditions.*/; assert(0 == rc); assert(0 == v->scope ); } /* The left/right assertions help ensure we're not now traversing through the recycle list. */ if(v->left || v->right){ dump_val(v,"Item from recycler???"); if(v->left) dump_val(v->left,"v->left"); if(v->right) dump_val(v->right,"v->right"); } if(b) b->flags |= CWAL_F_IS_DESTRUCTING; assert(0 == v->right); assert(0 == v->left); cwal_weak_unregister( e, v, v->vtab->typeID ); v->scope = vScope /* KLUDGE to help support the (relatively new) behaviour that containers will not destroy values they reference which live in lower (older) scopes. Instead they unref almost as normal, except that an unref down to 0 will move the value to scope->mine.r0 in that value's owning scope. This is necessary for several script-side constructs where a property of a scope-level object is being returned from that scope and would normally (before this change) be destroyed via the parent object's scope. */; v->vtab->cleanup(e, v); v->scope = 0 /* END KLUDGE! */; if(b) b->flags &= ~CWAL_F_IS_DESTRUCTING; CWAL_TR_V(e,v); CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_END, "Cleanup complete. Sending to recycler..."); assert(0 == v->refcount); cwal_value_recycle(e, v); return CWAL_RC_FINALIZED; } else { CWAL_TR_V(e,v); CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT, "It continues to live"); return CWAL_RC_HAS_REFERENCES; } } } /** Increments cv's reference count by 1. Asserts that !V_IS_BUILTIN(v) and (NULL!=cv). Code which might be dealing with those value should call the public-API-equivalent of this, cwal_value_ref(). */ static void cwal_refcount_incr( cwal_engine *e, cwal_value * cv ) { assert( NULL != cv ); assert(!V_IS_BUILTIN(cv)); assert(cv->scope); if(1 == ++cv->refcount){ int const rc = cwal_scope_from_r0(cv); /* If this is failing then internal preconditions/assumptions have not been met. */ assert(0==rc); if(!rc){/*avoid unused var warning*/} } #if 1 /* Is this sane? up-scope cv to e->current now if needed? */ if(!cv->scope && e->current){ assert(!"NULL cv->scope cannot happen anymore(?)"); cwal_value_xscope( e, e->current, cv, NULL ); } #endif CWAL_TR_V(e,cv); CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"++Refcount"); } int cwal_value_ref2( cwal_engine *e, cwal_value * cv ){ if( NULL == cv ) return CWAL_RC_MISUSE; else if( V_IS_BUILTIN(cv) ) return CWAL_RC_OK; else return CwalConsts.MaxSizeT <= cv->refcount ? CWAL_RC_RANGE : (cwal_refcount_incr( e, cv ),0); } int cwal_value_ref( cwal_value * cv ){ if( NULL == cv ) return CWAL_RC_MISUSE; else if( V_IS_BUILTIN(cv) ) return CWAL_RC_OK; else if( !cv->scope ){ assert(!"It should not be possible to have no owning scope here."); return CWAL_RC_MISUSE; } else { assert( cv->scope->e ); cwal_refcount_incr( cv->scope->e, cv ); return 0; } } cwal_size_t cwal_value_refcount( cwal_value const * v ){ return v ? v->refcount : 0; } int cwal_scope_current( cwal_engine * e, cwal_scope ** s ){ if(!e || !s) return CWAL_RC_MISUSE; else if(!e->current) return CWAL_RC_RANGE; else{ *s = e->current; return CWAL_RC_OK; } } cwal_scope * cwal_scope_current_get( cwal_engine * e ){ return e ? e->current : 0; } /** Allocates a new value of the specified type. The value is pushed onto e->current, effectively transfering ownership to that scope. extra is a number of extra bytes to allocate after the "concrete type part" of the allocation. It is only valid for type CWAL_TYPE_STRING, and must be the length of the string to allocate (NOT included the terminating NUL byte - this function adds that byte itself). The returned value->vtab member will be set appropriately and. Use the internal CWAL_VVPCAST() family of macros to convert the cwal_values to their corresponding native representation. Returns NULL on allocation error or if adding the new value to s fails. @see cwal_new_array() @see cwal_new_object() @see cwal_new_string() @see cwal_new_integer() @see cwal_new_double() @see cwal_new_function() @see cwal_new_native() @see cwal_new_buffer() @see cwal_new_hash() @see cwal_value_unref() */ static cwal_value * cwal_value_new(cwal_engine * e, cwal_scope * s, cwal_type_id t, cwal_size_t extra){ enum { vsz = sizeof(cwal_value) }; const cwal_size_t sz = vsz + extra /* base amount of memory to allocate */; cwal_size_t tx = 0 /* number of extra bytes to allocate for the concrete value type */; cwal_value def = cwal_value_undef; cwal_value * v = NULL; switch(t) { case CWAL_TYPE_DOUBLE: assert( 0 == extra ); def = cwal_value_double_empty; tx = sizeof(cwal_double_t); break; case CWAL_TYPE_INTEGER: assert( 0 == extra ); def = cwal_value_integer_empty; /* We might want to consider using double for integers on these platforms, which would allow us to support larger-precision integers in the public interface by hiding the doubles behind them. With that we could effectively declare cwal_int_t as guarantying, say 53 or 48 bits of precision. But would break if we add disable-doubles support (which i would like to eventually add as an option). */ tx = sizeof(cwal_int_t); break; case CWAL_TYPE_STRING:{ assert( (extra > 0) && "empty strings are handled elsewhere" ); def = cwal_value_string_empty; tx = sizeof(cwal_string) + 1 /*NUL byte*/; if(CwalConsts.StringPadSize){ int const pad = extra % CwalConsts.StringPadSize; if(pad) tx += CwalConsts.StringPadSize - pad; } break; } case CWAL_TYPE_XSTRING: case CWAL_TYPE_ZSTRING: assert( !extra && "x/z-string length is handled elsewhere" ); def = cwal_value_string_empty; tx = sizeof(cwal_string) + sizeof(unsigned char **) /* x/z-strings are stored like (cwa_value+cwal_string+(cstring-ptr)), and we stuff the external string pointer in the cstring-ptr part. */; break; case CWAL_TYPE_ARRAY: assert( 0 == extra ); def = cwal_value_array_empty; tx = sizeof(cwal_array); break; case CWAL_TYPE_OBJECT: assert( 0 == extra ); def = cwal_value_object_empty; tx = sizeof(cwal_object); break; case CWAL_TYPE_FUNCTION: assert( 0 == extra ); def = cwal_value_function_empty; tx = sizeof(cwal_function); break; case CWAL_TYPE_NATIVE: assert( 0 == extra ); def = cwal_value_native_empty; tx = sizeof(cwal_native); break; case CWAL_TYPE_EXCEPTION: assert( 0 == extra ); def = cwal_value_exception_empty; tx = sizeof(cwal_exception); break; case CWAL_TYPE_BUFFER: assert( 0 == extra ); def = cwal_value_buffer_empty; tx = sizeof(cwal_buffer); break; case CWAL_TYPE_HASH: assert( 0 != extra ); def = cwal_value_hash_empty; tx = sizeof(cwal_hash); break; default: assert(0 && "Unhandled type in cwal_value_new()!"); /* FIXME: set e error state here. */ return NULL; } assert( def.vtab->typeID != CWAL_TYPE_UNDEF ); if(CWAL_TYPE_STRING == t){ v = cwal_string_from_recycler( e, extra ); } else if((CWAL_TYPE_XSTRING != t) && (CWAL_TYPE_ZSTRING != t)){ int const recycleListIndex = cwal_recycler_index( def.vtab->typeID ); if(0<=recycleListIndex){ cwal_recycler * re = &e->recycler[recycleListIndex]; /*MARKER("BIN #%d(%s) LENGTH=%u\n", recycleListIndex, def.vtab->typeName, (unsigned)li->count);*/ if(re->count){ cwal_obase * base; v = (cwal_value*)re->list; assert(NULL != v); re->list = cwal_value_snip( v ); --re->count; CWAL_TR_V(e,v); CWAL_TR3(e,CWAL_TRACE_MEM_FROM_RECYCLER, "RECYCLED FROM BIN."); if( (base = CWAL_VOBASE(v)) ){ base->flags &= ~CWAL_F_IS_RECYCLED; } assert(v->vtab->typeID == def.vtab->typeID); assert(v->vtab->typeID == re->id); } } } if(!v) v = (cwal_value *)cwal_malloc(e, sz+tx); if( v ) { int rc = 0; *v = def; if(tx || extra){ memset(v+1, 0, tx + extra); } assert(0 == v->scope); assert(0 == v->refcount); CWAL_TR_V(e, v); CWAL_TR_S(e, s); CWAL_TR2(e, CWAL_TRACE_VALUE_CREATED); if(s) { int check = 0; rc = cwal_value_xscope( e, s, v, &check ) /* reminder: xscope "cannot fail" in this case. */ ; assert(!rc); assert(0 == v->refcount); assert(s == v->scope); assert(-1==check); } if(rc){ /* Reminder to self: since the port to linked lists for values, this scenario essentially cannot happen for a new value. */ v->vtab->cleanup( e, v ); cwal_free(e, v); v = NULL; } else if(e->prototypes /* Will only be false once, while creating e->prototypes! This also means e->prototypes will not get the built-in prototype for arrays unless we special-case that, but clients do not have access to e->prototypes, anyway, so that shouldn't be a problem/limitation. */){ cwal_obase const * base = CWAL_VOBASE(v); if(base){ cwal_value * proto = cwal_prototype_base_get(e, t); if(proto){ /*MARKER(("Setting %s prototype from base: @%p\n", cwal_value_type_id_name(t), (void const*)proto));*/ cwal_value_prototype_set(v, proto); assert(proto == cwal_value_prototype_get(e, v)); } } } #if !defined(NDEBUG) { cwal_obase const * baseCheck = CWAL_VOBASE(v); if(baseCheck){ /*MARKER("v=@%p, baseCheck=@%p\n", (void const *)v, (void const *)baseCheck);*/ assert( CWAL_VALPART(baseCheck) == v ); } } #endif } return v; } cwal_value * cwal_new_bool( char v ){ return v ? &CWAL_BUILTIN_VALS.vTrue : &CWAL_BUILTIN_VALS.vFalse; } cwal_value * cwal_value_true(){ return &CWAL_BUILTIN_VALS.vTrue; } cwal_value * cwal_value_false(){ return &CWAL_BUILTIN_VALS.vFalse; } cwal_value * cwal_value_null(){ return &CWAL_BUILTIN_VALS.vNull; } cwal_value * cwal_value_undefined(){ return &CWAL_BUILTIN_VALS.vUndef; } cwal_value * cwal_new_integer( cwal_engine * e, cwal_int_t v ){ CWAL_INIT_EMPTY_VALUES; if(!e) return NULL; else switch(v){ case -1: return CWAL_BUILTIN_VALS.vIntM1; case 0: return CWAL_BUILTIN_VALS.vInt0; case 1: return CWAL_BUILTIN_VALS.vInt1; default: { cwal_value * c = cwal_value_new(e, e->current, CWAL_TYPE_INTEGER,0); if( c ){ *CWAL_INT(c) = v; } return c; } } } cwal_value * cwal_new_double( cwal_engine * e, cwal_double_t v ) { CWAL_INIT_EMPTY_VALUES; if( CWAL_BUILTIN_VALS.dbls.zero == v ) return CWAL_BUILTIN_VALS.vDbl0; else if( CWAL_BUILTIN_VALS.dbls.one == v ) return CWAL_BUILTIN_VALS.vDbl1; else if( CWAL_BUILTIN_VALS.dbls.mOne == v ) return CWAL_BUILTIN_VALS.vDblM1; else{ cwal_value * c = cwal_value_new(e, e->current, CWAL_TYPE_DOUBLE, 0); if( c ){ *CWAL_DBL(c) = v; } return c; } } cwal_value * cwal_new_number( cwal_engine * e, cwal_double_t v ){ return ((v > (cwal_double_t)CWAL_INT_T_MAX) || (v < (cwal_double_t)CWAL_INT_T_MIN)) ? cwal_new_double(e, v) : cwal_new_integer(e, (cwal_int_t)v); } cwal_value * cwal_new_array_value(cwal_engine *e){ cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_ARRAY,0); if( NULL != v ){ cwal_array * ar = CWAL_ARRAY(v); cwal_value * proto = ar->base.prototype; *ar = cwal_array_empty; ar->base.prototype = proto; } return v; } cwal_array * cwal_new_array(cwal_engine *e){ return cwal_value_get_array(cwal_new_array_value(e)); } cwal_value * cwal_new_object_value(cwal_engine *e){ cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_OBJECT,0); if( NULL != v ) { cwal_object * ar = CWAL_OBJ(v); cwal_value * proto = ar->base.prototype; *ar = cwal_object_empty; ar->base.prototype = proto; } return v; } cwal_object * cwal_new_object(cwal_engine *e){ return cwal_value_get_object(cwal_new_object_value(e)); } /** Cleanup routine for the cwal_obase part of classes which use that. Cleans up the contents of b->kvp and sets b->kvp to NULL. Also unrefs/NULLs b->prototype if unrefProto is true. */ static void cwal_cleanup_obase( cwal_engine * e, cwal_obase * b, char unrefProto ){ while(b->kvp){ cwal_value const * bv = CWAL_VALPART(b); cwal_kvp * kvp = b->kvp; cwal_kvp * next = 0; cwal_value * proto = b->prototype; b->kvp = 0; /* In theory the is-visiting flag is not needed here because b->kvp=0 means we cannot possibly traverse the property list as a side-effect of this cleanup. Well, we cannot travse THIS property list. We can only hope that cleanup does not then add properties to b->kvp, but that's why we loop on b->kvp. */ /*b->flags |= CWAL_F_IS_VISITING;*/ for( ; kvp; kvp = next ){ next = kvp->right; kvp->right = 0; cwal_kvp_free( e, bv->scope, kvp, 1 ); } if( unrefProto && proto ){ b->prototype = NULL; cwal_unref_from( bv->scope, proto ); } /*b->flags &= ~CWAL_F_IS_VISITING; */ } } static void cwal_value_cleanup_array_impl( cwal_engine * e, void * self, char freeList, char freeProps, char freeProto ){ cwal_value * vSelf = (cwal_value *)self; cwal_array * ar = cwal_value_get_array(vSelf); assert(NULL!=ar); cwal_value_set_visiting( vSelf, 1 ); if( ar->list.count ){ cwal_value * v; cwal_size_t i = 0, x = ar->list.count -1; for( ; i < ar->list.count; ++i, --x ){ v = (cwal_value*)ar->list.list[x]; if(v){ ar->list.list[x] = NULL; #if 1 cwal_unref_from( vSelf->scope, v ); #else cwal_value_unref2(e, v); #endif } } ar->list.count = 0; } if(freeList && ar->list.list){ const int listCount = sizeof(e->reList.lists)/sizeof(e->reList.lists[0]); if(e->reList.cursor < (listCount-1)){ e->reList.lists[++e->reList.cursor] = ar->list; ar->list = cwal_list_empty; /* MARKER(("Giving back array list memory to slot #%d of %d\n", e->reList.cursor, listCount)); */ }else{ cwal_list_reserve(e, &ar->list, 0); } /**ar = cwal_array_empty;*/ } cwal_value_set_visiting( vSelf, 0 ); if(freeProps) { cwal_cleanup_obase( e, &ar->base, 0 ); } if(freeProto && ar->base.prototype){ cwal_value * p = ar->base.prototype; assert(vSelf->scope); /* dump_val(vSelf,"Unref'ing this one's prototype"); */ /* dump_val(p,"Unref'ing prototype"); */ cwal_unref_from(vSelf->scope, p); ar->base.prototype = 0; } } /** cwal_value_vtab::cleanup() impl for Array values. Cleans up self-owned memory, but does not free self. */ void cwal_value_cleanup_array( cwal_engine * e, void * self ){ cwal_value_cleanup_array_impl( e, self, 1, 1, 1 ); } void cwal_array_clear( cwal_array * ar, char freeList, char freeProps ){ cwal_scope * s = ar ? CWAL_VALPART(ar)->scope : 0; cwal_engine * e = s ? s->e : 0; if(e){ cwal_value_cleanup_array_impl( e, cwal_array_value(ar), freeList, freeProps, 0 ); } } void cwal_value_cleanup_object( cwal_engine * e, void * self ){ cwal_value * vSelf = (cwal_value *)self; cwal_object * ar = cwal_value_get_object(vSelf); assert(vSelf && ar); cwal_cleanup_obase( e, &ar->base, 1 ); *ar = cwal_object_empty; } void cwal_value_cleanup_function( cwal_engine * e, void * self ){ cwal_value * v = (cwal_value*)self; cwal_function * f = CWAL_VVPCAST(cwal_function,v); assert(v && f); if(f->state.finalize){ f->state.finalize( e, f->state.data ); } cwal_cleanup_obase( e, &f->base, 1 ); *f = cwal_function_empty; } int cwal_value_fetch_function( cwal_value const * val, cwal_function ** x){ if( ! val ) return CWAL_RC_MISUSE; else if( !cwal_value_is_function(val) ) return CWAL_RC_TYPE; else{ if(x) *x = CWAL_VVPCAST(cwal_function,val); return 0; } } cwal_value * cwal_new_function_value(cwal_engine * e, cwal_callback_f callback, void * state, cwal_finalizer_f stateDtor, void const * stateTypeID ){ if(!e || !callback) return NULL; else{ cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_FUNCTION, 0); if( NULL != v ) { cwal_function * f = CWAL_VVPCAST(cwal_function,v); cwal_value * proto = f->base.prototype; *f = cwal_function_empty; f->base.prototype = proto; f->state.data = state; f->state.finalize = stateDtor; f->state.typeID = stateTypeID; f->callback = callback; } return v; } } cwal_function * cwal_new_function(cwal_engine * e, cwal_callback_f callback, void * state, cwal_finalizer_f stateDtor, void const * stateTypeID ){ cwal_value * v = cwal_new_function_value(e, callback, state, stateDtor, stateTypeID); return v ? cwal_value_get_function(v) : NULL; } int cwal_function_unref(cwal_engine *e, cwal_function *v){ return (e&&v) ? cwal_value_unref2( e, cwal_function_value(v) ) : CWAL_RC_MISUSE; } int cwal_function_call_in_scope( cwal_scope * s, cwal_function * f, cwal_value * self, cwal_value ** _rv, uint16_t argc, cwal_value ** argv){ if(!s ||!s->e || !f) return CWAL_RC_MISUSE; else { cwal_engine * e = s->e; cwal_scope * old = e->current; cwal_scope * check = 0; int rc; cwal_callback_args args = cwal_callback_args_empty; cwal_value * rv = 0; args.engine = e; args.scope = s; args.self = self; args.callee = f; args.state = f->state.data; args.stateTypeID = f->state.typeID; args.argc = argc; args.argv = argv; rc = f->callback( &args, &rv ); check = e->current; if(old != check){ MARKER(("WARNING: callback created scopes without popping them " "(or popped too many!)." " Cleaning them up now!\n")); while(e->current && (e->current!=old)){ cwal_scope_pop(e); } if(e->current!=old){ assert(!"Serious scope mis-management during callback."); rc = CWAL_RC_FATAL; old = 0 /* assume it was lost along the way */; } } e->current = old; assert(e->current); if(!rc){ if(!rv) rv = cwal_value_undefined(); if(_rv) *_rv = rv; /* Reminder: don't automatically unref it on error. That would be bad in certain cases. Worst-case now is that we leave rv orphaned in the current scope. */ } return rc; } } void * cwal_args_state( cwal_callback_args const * args, void const * stateTypeID ){ return (args && (args->stateTypeID==stateTypeID)) ? args->state : NULL; } void * cwal_function_state_get( cwal_function * f, void const * stateTypeID ){ return (f && (f->state.typeID==stateTypeID)) ? f->state.data : NULL; } void * cwal_args_callee_state( cwal_callback_args const * args, void const * stateTypeID ){ return (args && args->callee) ? cwal_function_state_get(args->callee, stateTypeID) : NULL; } int cwal_function_call_in_scopef( cwal_scope * s, cwal_function * f, cwal_value * self, cwal_value ** rv, ... ){ if(!s || !s->e || !f) return CWAL_RC_MISUSE; else { int rc = CWAL_RC_OK; cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS+1] = {0,}; cwal_value * v; uint16_t argc = 0; va_list args; memset( argv, 0, sizeof(argv)/sizeof(argv[0]) ); va_start(args, rv); while( (v=va_arg(args,cwal_value*)) ){ if(argc==CWAL_OPT_MAX_FUNC_CALL_ARGS){ rc = CWAL_RC_RANGE; break; } else argv[argc++] = v; } va_end(args); if(CWAL_RC_OK==rc){ argv[argc] = 0; rc = cwal_function_call_in_scope( s, f, self, rv, argc, argv ); } return rc; } } int cwal_function_call( cwal_function * f, cwal_value * self, cwal_value ** rv, uint16_t argc, cwal_value ** argv ){ cwal_value * fv = f ? cwal_function_value(f) : NULL; cwal_engine * e = (fv && fv->scope) ? fv->scope->e : NULL; if(!e) return CWAL_RC_MISUSE; else{ int rc, rc2; cwal_scope * oldS = e->current; cwal_scope * s = 0; cwal_value * v = 0; rc = cwal_scope_push(e, &s); if(rc) return rc; rc = cwal_function_call_in_scope( s, f, self, &v, argc, argv ); if(!rc && rv && v) { cwal_value_rescope(oldS, v); *rv = v; } rc2 = cwal_scope_pop(e); return rc ? rc : rc2; } } int cwal_function_callv( cwal_function * f, cwal_value * self, cwal_value ** rv, va_list args ){ cwal_value * fv = f ? cwal_function_value(f) : NULL; cwal_engine * e = (fv && fv->scope) ? fv->scope->e : NULL; if(!e) return CWAL_RC_MISUSE; else { cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS]; cwal_value * v; uint16_t argc = 0; int rc = 0; memset( argv, 0, sizeof(argv) ); while( (v=va_arg(args,cwal_value*)) ){ if(argc==CWAL_OPT_MAX_FUNC_CALL_ARGS){ rc = CWAL_RC_RANGE; break; } else argv[argc++] = v; } if(rc) return rc; else{ argv[argc] = 0; return cwal_function_call( f, self, rv, argc, argv ); } } } int cwal_function_callf( cwal_function * f, cwal_value * self, cwal_value ** rv, ... ){ int rc = 0; va_list args; va_start(args, rv); rc = cwal_function_callv( f, self, rv, args ); va_end(args); return rc; } cwal_function * cwal_value_get_function( cwal_value const * v ) { cwal_function * ar = NULL; cwal_value_fetch_function( v, &ar ); return ar; } cwal_value * cwal_function_value(cwal_function const * s){ return s ? CWAL_VALPART(s) : NULL; } cwal_value * cwal_new_buffer_value(cwal_engine *e, cwal_size_t startingSize){ cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_BUFFER,0); if( NULL != v ) { cwal_buffer * b = CWAL_BUF(v); assert(NULL != b); *b = cwal_buffer_empty; if(startingSize && (startingSize < cwal_buffer_reserve(e, b, startingSize))){ cwal_value_unref2(e, v); v = NULL; } } return v; } int cwal_buffer_unref(cwal_engine *e, cwal_buffer *v){ return (e&&v) ? cwal_value_unref2( e, cwal_buffer_value(v) ) : CWAL_RC_MISUSE; } int cwal_value_fetch_buffer( cwal_value const * val, cwal_buffer ** x){ if( ! val ) return CWAL_RC_MISUSE; else if( !cwal_value_is_buffer(val) ) return CWAL_RC_TYPE; else{ if(x) *x = CWAL_VVPCAST(cwal_buffer,val); return 0; } } cwal_buffer * cwal_value_get_buffer( cwal_value const * v ) { cwal_buffer * ar = NULL; cwal_value_fetch_buffer( v, &ar ); return ar; } cwal_buffer * cwal_new_buffer(cwal_engine *e, cwal_size_t startingSize){ return cwal_value_get_buffer(cwal_new_buffer_value(e, startingSize)); } cwal_value * cwal_buffer_value(cwal_buffer const * s){ return s ? CWAL_VALPART(s) : NULL; } /** cwal_value_vtab::destroy_value() impl for Buffer values. Cleans up self-owned memory, but does not free self. */ void cwal_value_cleanup_buffer( cwal_engine * e, void * self ){ cwal_value * v = (cwal_value*)self; cwal_buffer_reserve(e, CWAL_BUF(v), 0 ); } void cwal_value_cleanup_exception( cwal_engine * e, void * self ){ cwal_value * v = (cwal_value*)self; cwal_exception * f = CWAL_VVPCAST(cwal_exception,v); cwal_cleanup_obase(e, &f->base, 1); *f = cwal_exception_empty; } cwal_value * cwal_new_exception_value( cwal_engine * e, int code, cwal_value * msg ){ cwal_value * v = e ? cwal_value_new(e, e->current, CWAL_TYPE_EXCEPTION, 0 ) : NULL; if(v){ cwal_value * proto; cwal_exception * r; static int codeKeyLen = 0; static int msgKeyLen = 0; int rc; if(!codeKeyLen){ codeKeyLen = strlen(CwalConsts.ExceptionCodeKey); msgKeyLen = strlen(CwalConsts.ExceptionMessageKey); } /* Reminder: i would prefer to have a cwal_exception::message member, but lifetime of it gets problematic. One solution would be to move the xscope() operation into cwal_value_vtab, so that we can generically xscope Exception values without having to know that they have a free-floating member (not in a cwal_obase::kvp list). */ r = cwal_value_get_exception(v); assert(r); proto = r->base.prototype; *r = cwal_exception_empty; r->base.prototype = proto; r->code = code; rc = cwal_prop_set(v, CwalConsts.ExceptionCodeKey, codeKeyLen, cwal_new_integer(e, (cwal_int_t)code)); if(!rc && msg) rc = cwal_prop_set(v, CwalConsts.ExceptionMessageKey, msgKeyLen, msg); if(0!=rc){ cwal_value_unref2(e, v); v = 0; } } return v; } cwal_exception * cwal_new_exceptionfv(cwal_engine * e, int code, char const * fmt, va_list args ){ if(!e) return 0; else if(!fmt || !*fmt) return cwal_new_exception(e,code, NULL); else{ cwal_string * s = cwal_new_stringfv( e, fmt, args); cwal_exception * x; if(!s) return NULL; x = cwal_new_exception(e, code, cwal_string_value(s)); if(!x) cwal_string_unref(s); return x; } } cwal_exception * cwal_new_exceptionf(cwal_engine * e, int code, char const * fmt, ...){ if(!e) return 0; else if(!fmt || !*fmt) return cwal_new_exception(e,code, NULL); else{ cwal_exception * x; va_list args; va_start(args,fmt); x = cwal_new_exceptionfv(e, code, fmt, args); va_end(args); return x; } } int cwal_exception_unref(cwal_engine *e, cwal_exception *v){ return (e&&v) ? cwal_value_unref2( e, cwal_exception_value(v) ) : CWAL_RC_MISUSE; } int cwal_value_fetch_exception( cwal_value const * val, cwal_exception ** x){ if( ! val ) return CWAL_RC_MISUSE; else if( !cwal_value_is_exception(val) ) return CWAL_RC_TYPE; else{ if(x) *x = CWAL_VVPCAST(cwal_exception,val); return 0; } } cwal_exception * cwal_value_get_exception( cwal_value const * v ){ cwal_exception * r = 0; cwal_value_fetch_exception( v, &r ); return r; } cwal_exception * cwal_new_exception( cwal_engine * e, int code, cwal_value * msg ){ cwal_value * v = cwal_new_exception_value(e, code, msg); return v ? cwal_value_get_exception(v) : NULL; } cwal_value * cwal_exception_value(cwal_exception const * s){ return s ? CWAL_VALPART(s) : NULL; } int cwal_exception_code_get( cwal_exception const * r ){ return r ? r->code : cwal_exception_empty.code; } int cwal_exception_code_set( cwal_exception * r, int code ){ return r ? (r->code=code, 0) : CWAL_RC_MISUSE; } cwal_value * cwal_exception_message_get( cwal_exception const * r ){ if(!r || !r->base.kvp) return NULL; else{ cwal_kvp * kvp = cwal_kvp_search( r->base.kvp, CwalConsts.ExceptionMessageKey, strlen(CwalConsts.ExceptionMessageKey), NULL, NULL ); return kvp ? kvp->value : NULL; } } int cwal_exception_message_set( cwal_engine * e, cwal_exception * r, cwal_value * msg ){ if(!e || !r) return CWAL_RC_MISUSE; else return cwal_prop_set( cwal_exception_value(r), CwalConsts.ExceptionMessageKey, strlen(CwalConsts.ExceptionMessageKey), msg ); } /** Fetches v's string value as a non-const string. cwal_strings are supposed to be immutable, but this form provides access to the immutable bits, which are v->length bytes long. A length-0 string is returned as NULL from here, as opposed to "". (This is a side-effect of the string allocation mechanism.) Returns NULL if !v or if v is the internal empty-string singleton. */ static char * cwal_string_str(cwal_string *v){ /* See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a */ assert(v && !(v->length & CWAL_STR_XMASK) /* not allowed for xstrings */); return CWAL_STR_ISZ(v) ? (char *)*((unsigned char **)(v+1)) : (!v->length /*(&CWAL_BUILTIN_VALS.emptyString == v)*/ ? NULL : (char *)((unsigned char *)( v+1 )) ); } char const * cwal_string_cstr(cwal_string const *v){ /* See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a */ return (NULL == v) ? NULL : (!v->length ? "" : (CWAL_STR_ISXZ(v) ? (char const *) *((unsigned char const **)(v+1)) : (char const *) ((unsigned char const *)(v+1))) ); } void cwal_value_cleanup_string( cwal_engine * e, void * V ){ cwal_value * v = (cwal_value*)V; cwal_string * s = cwal_value_get_string(v); if(!s) return; else if(CWAL_STR_ISZ(s)){ char * cs = cwal_string_str(s); unsigned char ** pos = (unsigned char **)(s+1); assert(cs == (char *)*pos); *pos = NULL; cwal_free(e, cs); } else if(!(s->length & ~CWAL_STRLEN_MASK)){ /* bug reminder: valgrind reports the following when we search the interened list for an x-string: ==6792== Invalid read of size 1 ==6792== at 0x411C78: cwal_hash_cstr (cwal.c:1383) ==6792== by 0x42195F: cwal_value_hash_string (cwal.c:6305) ==6792== by 0x411F1C: cwal_interned_search_val (cwal.c:1452) ==6792== by 0x412411: cwal_interned_remove (cwal.c:1547) ==6792== by 0x41299C: cwal_value_cleanup_string (cwal.c:1660) ==6792== by 0x4181E2: cwal_value_unref2 (cwal.c:2986) ==6792== by 0x4106C7: cwal_kvp_clean (cwal.c:754) ==6792== by 0x410716: cwal_kvp_free (cwal.c:769) ==6792== by 0x419662: cwal_cleanup_obase (cwal.c:3380) ==6792== by 0x419B1F: cwal_value_cleanup_function (cwal.c:3471) ==6792== by 0x4181E2: cwal_value_unref2 (cwal.c:2986) ==6792== by 0x413DA6: cwal_scope_clean (cwal.c:2019) ==6792== Address 0x51f4dea is 362 bytes inside a block of size 4,096 free'd ==6792== at 0x4C2A82E: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==6792== by 0x412E30: cwal_realloc_f_std (cwal.c:1740) ==6792== by 0x413177: cwal_free (cwal.c:1787) ==6792== by 0x4226BA: cwal_buffer_reserve (cwal.c:6565) ==6792== by 0x40BFE2: th1ish_eval_file (th1ish.c:5400) ==6792== by 0x40D2BB: test_1 (th1ish.c:5734) ==6792== by 0x40D73B: main (th1ish.c:5798) */ cwal_interned_remove( e, v, 0 ); } } int cwal_string_unref(cwal_string * s){ cwal_value * v = cwal_string_value(s); return v ? cwal_value_unref2( cwal_value_engine(v), v ) : CWAL_RC_MISUSE; } cwal_size_t cwal_string_length_bytes( cwal_string const * str ){ return (!str || !str->length) ? 0U : CWAL_STRLEN(str); } cwal_value * cwal_new_string_value(cwal_engine * e, char const * str, cwal_size_t len){ return cwal_string_value( cwal_new_string(e, str, len) ); } cwal_string * cwal_new_string(cwal_engine * e, char const * str, cwal_size_t len){ CWAL_INIT_EMPTY_VALUES; if(!e) return NULL; else if( !str || !*str ) return &CWAL_BUILTIN_VALS.emptyString; else{ int rc; cwal_value * c = 0; cwal_string * s = 0; if(!len) { len = strlen(str); if(!len){ return &CWAL_BUILTIN_VALS.emptyString; } } if(CWAL_FEATURE_INTERN_STRINGS & e->flags){ cwal_interned_search( e, str, len, &c, 0, 0 ); } if(c){ /* Got an interned string... */ s = cwal_value_get_string(c); assert(0 != s); CWAL_TR_V(e,c); CWAL_TR3(e,CWAL_TRACE_VALUE_INTERNED, "RE-USING INTERNED STRING"); /* assert(!"Internalizing of strings was broken in recent refcount changes."); */ /* assert( c->refcount > 0 ) */ /* possibly broken by recent change to initial refcount=0 */ ; /* Reminders to self: Now that we start at refcount==0 for new values, we might not need to add a refcount here. Hmmm. s1 ==> new_string("foo"); // refcount==0 s2 ==> new_string("foo"); // refcount==1 s3 ==> new_string("foo"); // refcount==2 obj.set(s1,s1) obj.clear_props(); That would destroy all 3 references, which, i think, is the proper behaviour. If we don't incr the refcount, the string stays forever in a scope->mine.r0 somewhere and will get freed by a sweep. If we increase the refcount we risk only orphaning one (previously) shared instance with a high refcount but no real references. It will get cleaned up at scope-pop, but potentially never swept up. If we don't increase the ref we have this problem: "hi" { "hi" } would clean up both instances when the scope pop... NO, OMG, it WON'T because the string is rooted in the first instance's scope! i'll probably regret this later, but for now let's NOT increment them and see what happens. i can imagine a case like: funcCall( "hi", // refcount==0 { { var x = "hi" // refcount==1 b/c of assignment } // refcount reduced to 0 }); Now, if those two elements were part of, e.g., an argument list, the first instance of "hi" would be cleaned up after evaluation of the inner-most scope. No. No, it doesn't because "hi" lives in the scope of the func arg list evaluation and containers (now) never clean up such values - they "re-probate" them back into scope->mine.r0 in the higher scope (where the value lives). If a sweep happened in the argument evaluation scope during that time then blammo, "hi" would be nuked, but doing that would be generically a big no-no, i think. ...later... If we do NOT ref() here then we end up with interned strings possibly getting sweep()'d up or otherwise cleaned up too soon. The worst that happens if we ref is that we almost guaranty that the value will get stranded in its owning scope because its refcount will always be twice (or nearly that) what it should be. So let's try this: if the value has a refcount, don't ref it again, otherwise ref it only to move it out of the scope's r0 list. We might orphan it still, but it will, at most, have a refcount of only 1 higher than it "should" be. */ if(!c->refcount){ cwal_refcount_incr(e,c); } cwal_value_xscope( e, e->current, c, 0 ); } else{ /* Create new string... */ /*MARKER("len=%"CWAL_SIZE_T_PFMT"\n",len);*/ c = cwal_value_new(e, e->current, CWAL_TYPE_STRING, len); if( c ){ char * dest = NULL; s = CWAL_STR(c); *s = cwal_string_empty; assert( NULL != s ); s->length = CWAL_STRLEN_MASK & len; assert(CWAL_STRLEN(s) == len); dest = cwal_string_str(s) /* maintenance note: this is the only place in the library where _writing_ to a cwal_string is allowed. */; assert( (NULL != dest) && "Empty string should have been caught earlier!"); memcpy( dest, str, len ); dest[len] = 0; if( (CWAL_FEATURE_INTERN_STRINGS & e->flags) #if 0 && (0==c->refcount /* this is a new string, not a re-used interned string*/) #endif #if 1 && (!CwalConsts.MaxInternedStringSize || (s->length <= CwalConsts.MaxInternedStringSize) ) #endif ){ rc = cwal_interned_insert( e, c ) /* This insertion effectively controls whether or not interning of strings is on. If it fails, the string is effectively not interned, but otherwise no harm is done. Allocation of a new interning table could fail, but that's about the only conceivable error condition here (and we can ignore it by not interning). */; assert(0 == rc); } } } return s; } } cwal_string * cwal_new_xstring(cwal_engine * e, char const * str, cwal_size_t len){ if( !str || !*str ) return &CWAL_BUILTIN_VALS.emptyString; else { cwal_value * c = NULL; cwal_string * s = NULL; if(!len) { len = strlen(str); assert(len && "Length-0 strings are handled above."); } c = cwal_value_new(e, e->current, CWAL_TYPE_XSTRING, 0); if( c ){ unsigned char const ** dest; s = CWAL_STR(c); assert( NULL != s ); *s = cwal_string_empty; s->length = CWAL_STR_XMASK | len; assert(s->length > len); assert(CWAL_STRLEN(s) == len); assert(CWAL_STR_ISX(s)); assert(CWAL_STR_ISXZ(s)); dest = (unsigned char const **)(s+1); *dest = (unsigned char const *)str; } return s; } } cwal_value * cwal_new_xstring_value(cwal_engine * e, char const * str, cwal_size_t len){ cwal_string * s = cwal_new_xstring(e, str, len); return s ? cwal_string_value(s) : NULL; } cwal_string * cwal_new_zstring(cwal_engine * e, char * str, cwal_size_t len){ if( !str ) return &CWAL_BUILTIN_VALS.emptyString; else { cwal_value * c = NULL; cwal_string * s = NULL; if(!len) len = strlen(str); c = cwal_value_new(e, e->current, CWAL_TYPE_ZSTRING, 0); if( c ){ unsigned char ** dest; s = CWAL_STR(c); assert( NULL != s ); *s = cwal_string_empty; s->length = CWAL_STR_ZMASK | len; assert(s->length > len); assert(CWAL_STRLEN(s) == len); assert(CWAL_STR_ISZ(s)); assert(CWAL_STR_ISXZ(s)); dest = (unsigned char **)(s+1); *dest = (unsigned char *)str; }else{ /* See the API docs for why we do this. */ cwal_free( e, str ); } return s; } } cwal_value * cwal_new_zstring_value(cwal_engine * e, char * str, cwal_size_t len){ cwal_string * s = cwal_new_zstring(e, str, len); return s ? cwal_string_value(s) : NULL; } int cwal_buffer_reset( cwal_buffer * b ){ if(!b) return CWAL_RC_MISUSE; else{ if(b->capacity){ assert(b->mem); b->mem[0] = 0; } b->used = 0; return 0; } } cwal_string * cwal_new_stringfv(cwal_engine * e, char const * fmt, va_list args ){ if(!e || !fmt) return 0; else if(!*fmt) return cwal_new_string(e,"",0); else{ int rc; /* = cwal_buffer_reserve(e, &e->buffer, 32); if(rc) return NULL; cwal_buffer_reset(&e->buffer); */ #if 0 cwal_buffer buf = cwal_buffer_empty; cwal_string * sv; rc = cwal_buffer_printfv(e, &buf, fmt, args); sv = rc ? NULL : cwal_new_string(e, buf.used ? (char const*)(buf.mem) : NULL, buf.used); cwal_buffer_reserve(e, &buf, 0); return sv; #else cwal_size_t const oldUsed = e->buffer.used; cwal_size_t slen; rc = cwal_buffer_printfv(e, &e->buffer, fmt, args); slen = e->buffer.used - oldUsed; e->buffer.used = oldUsed; return rc ? NULL : cwal_new_string(e, slen ? (char const*)(e->buffer.mem+oldUsed) : NULL, slen); ; #endif } } cwal_string * cwal_new_stringf(cwal_engine * e, char const * fmt, ...){ if(!e || !fmt) return 0; else if(!*fmt) return cwal_new_string(e,"",0); else{ cwal_string * str; va_list args; va_start(args,fmt); str = cwal_new_stringfv(e, fmt, args); va_end(args); return str; } } cwal_value * cwal_string_value(cwal_string const * s){ return s ? (CWAL_STRLEN(s) ? CWAL_VALPART(s) : &CWAL_BUILTIN_VALS.vStrEmpty) : NULL; } cwal_engine * cwal_value_engine( cwal_value const * v ){ return (v && v->scope) ? v->scope->e : 0; } cwal_scope * cwal_value_scope( cwal_value const * v ){ return v ? v->scope : NULL; } cwal_value * cwal_string_concat( cwal_string const * s1, cwal_string const * s2 ){ if(!s1 || !s2) return NULL; else { cwal_size_t newLen; int rc; cwal_engine * e = cwal_value_engine(cwal_string_value(s1)); assert(e); newLen = CWAL_STRLEN(s1) + CWAL_STRLEN(s2) + 1/*NUL byte*/; rc = cwal_buffer_reserve( e, &e->buffer, newLen ); if(rc) return NULL; e->buffer.used = 0; rc = cwal_buffer_append( e, &e->buffer, cwal_string_cstr(s1), CWAL_STRLEN(s1) ); if(rc) return NULL; rc = cwal_buffer_append( e, &e->buffer, cwal_string_cstr(s2), CWAL_STRLEN(s2) ); if(rc) return NULL; e->buffer.mem[e->buffer.used] = 0; return cwal_new_string_value( e, (char const *)e->buffer.mem, e->buffer.used ); } } int cwal_value_fetch_bool( cwal_value const * val, char * v ) { /** FIXME: move the to-bool operation into cwal_value_vtab, like we do in the C++ API. */ if( ! val || !val->vtab ) return CWAL_RC_MISUSE; else { int rc = 0; char b = NULL != CWAL_VOBASE(val); if(!b) switch( val->vtab->typeID ){ case CWAL_TYPE_BUFFER: b = 1; break; case CWAL_TYPE_STRING: { char const * str = cwal_string_cstr(cwal_value_get_string(val)); b = (str && *str) ? 1 : 0; break; } case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL: break; case CWAL_TYPE_BOOL: b = CWAL_BOOL(val); break; case CWAL_TYPE_INTEGER: { cwal_int_t i = 0; cwal_value_fetch_integer( val, &i ); b = i ? 1 : 0; break; } case CWAL_TYPE_DOUBLE: { cwal_double_t d = 0.0; cwal_value_fetch_double( val, &d ); b = (0.0==d) ? 0 : 1; break; } default: rc = CWAL_RC_TYPE; break; } if( !rc && v ) *v = b; return rc; } } char cwal_value_get_bool( cwal_value const * val ) { char i = 0; cwal_value_fetch_bool( val, &i ); return i; } int cwal_value_fetch_integer( cwal_value const * val, cwal_int_t * v ) { if( ! val || !val->vtab ) return CWAL_RC_MISUSE; else { cwal_int_t i = 0; int rc = 0; switch(val->vtab->typeID){ case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL: i = 0; break; case CWAL_TYPE_BOOL:{ char b = 0; cwal_value_fetch_bool( val, &b ); i = b; break; } case CWAL_TYPE_INTEGER: { i = *CWAL_INT(val); break; } case CWAL_TYPE_DOUBLE:{ cwal_double_t d = 0.0; cwal_value_fetch_double( val, &d ); i = (cwal_int_t)d; break; } case CWAL_TYPE_STRING: rc = cwal_string_to_int( cwal_value_get_string(val), &i ); break; default: rc = CWAL_RC_TYPE; break; } if(!rc && v) *v = i; return rc; } } cwal_int_t cwal_value_get_integer( cwal_value const * val ) { cwal_int_t i = 0; cwal_value_fetch_integer( val, &i ); return i; } int cwal_value_fetch_double( cwal_value const * val, cwal_double_t * v ) { if( ! val || !val->vtab ) return CWAL_RC_MISUSE; else { cwal_double_t d = 0.0; int rc = 0; switch(val->vtab->typeID) { case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL: d = 0; break; case CWAL_TYPE_BOOL: { char b = 0; cwal_value_fetch_bool( val, &b ); d = b ? 1.0 : 0.0; break; } case CWAL_TYPE_INTEGER: { cwal_int_t i = 0; cwal_value_fetch_integer( val, &i ); d = i; break; } case CWAL_TYPE_DOUBLE: d = *CWAL_DBL(val); break; case CWAL_TYPE_STRING: rc = cwal_string_to_double( cwal_value_get_string(val), &d ); break; default: rc = CWAL_RC_TYPE; break; } if(!rc && v) *v = d; return rc; } } cwal_double_t cwal_value_get_double( cwal_value const * val ) { cwal_double_t i = 0.0; cwal_value_fetch_double( val, &i ); return i; } int cwal_value_fetch_string( cwal_value const * val, cwal_string ** dest ) { if( ! val || ! dest ) return CWAL_RC_MISUSE; else if( ! cwal_value_is_string(val) ) return CWAL_RC_TYPE; else { if( dest ) *dest = CWAL_STR(val); return CWAL_RC_OK; } } cwal_string * cwal_value_get_string( cwal_value const * val ) { cwal_string * rc = NULL; cwal_value_fetch_string( val, &rc ); return rc; } char const * cwal_value_get_cstr( cwal_value const * val, cwal_size_t * len ) { switch(val ? val->vtab->typeID : 0){ case CWAL_TYPE_STRING:{ cwal_string const * s = cwal_value_get_string(val); if(len) *len = CWAL_STRLEN(s); return cwal_string_cstr(s); } case CWAL_TYPE_BUFFER:{ cwal_buffer const * b = cwal_value_get_buffer(val); if(len) *len = b->used; return b->used ? (char const *)b->mem : ""; } default: return NULL; } } int cwal_value_fetch_array( cwal_value const * val, cwal_array ** ar) { if( ! val ) return CWAL_RC_MISUSE; else if( !cwal_value_is_array(val) ) return CWAL_RC_TYPE; else { if(ar) *ar = CWAL_ARRAY(val); return 0; } } cwal_array * cwal_value_get_array( cwal_value const * v ) { cwal_array * ar = NULL; cwal_value_fetch_array( v, &ar ); return ar; } cwal_value * cwal_array_value(cwal_array const * s) { return s ? CWAL_VALPART(s) : NULL; } int cwal_array_unref(cwal_array *x) { cwal_value * v = CWAL_VALPART(x); return (v && v->scope) ? cwal_value_unref2(v->scope->e, v) : CWAL_RC_MISUSE; } int cwal_array_value_fetch( cwal_array const * ar, cwal_size_t pos, cwal_value ** v ) { if( !ar) return CWAL_RC_MISUSE; if( pos >= ar->list.count ) return CWAL_RC_RANGE; else { if(v) *v = (cwal_value*)ar->list.list[pos]; return 0; } } cwal_value * cwal_array_get( cwal_array const * ar, cwal_size_t pos ) { cwal_value *v = NULL; cwal_array_value_fetch(ar, pos, &v); return v; } int cwal_array_length_fetch( cwal_array const * ar, cwal_size_t * v ) { if( ! ar || !v ) return CWAL_RC_MISUSE; else { if(v) *v = ar->list.count; return 0; } } cwal_size_t cwal_array_length_get( cwal_array const * ar ) { cwal_size_t i = 0; cwal_array_length_fetch(ar, &i); return i; } int cwal_array_length_set( cwal_array * ar, cwal_size_t newSize ){ cwal_size_t i; if(!ar) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS; else if(ar->list.count == newSize) return 0; if( newSize < ar->list.count ){ int rc = 0; for( i = newSize; !rc && (i < ar->list.count); ++i ){ rc = cwal_array_set( ar, i, NULL ); } ar->list.count = newSize; return rc; } else { /* grow */ int rc = cwal_array_reserve( ar, newSize ); if(!rc){ ar->list.count = newSize; } return rc; } } /** Internal helper macro for array-centric functions. */ #define SETUP_ARRAY_ARGS \ cwal_scope * s = ar ? CWAL_VALPART(ar)->scope : 0; \ cwal_engine * e = s ? s->e : 0; \ if(!s || !e) return CWAL_RC_MISUSE int cwal_array_reserve( cwal_array * ar, cwal_size_t size ) { SETUP_ARRAY_ARGS; if( ! ar ) return CWAL_RC_MISUSE; else if( size <= ar->list.alloced ) { /* We don't want to introduce a can of worms by trying to handle the cleanup from here. */ return 0; } else { return (ar->list.alloced > cwal_list_reserve( e, &ar->list, size )) ? CWAL_RC_OOM : 0 ; } } /** @internal cwal_list_visitor_f which expects V to be a (cwal_value*) and and VParent to be its (cwal_value*) scoping parent. This makes sure that (sub)child are properly up-scoped if needed. Returns 0 on success. */ static int cwal_xscope_visitor_children_array( void * V, void * VParent ){ cwal_value * par = (cwal_value*)VParent; cwal_value * child = (cwal_value*)V; assert(par && par->scope); return cwal_value_xscope( par->scope->e, par->scope, child, 0 ); } int cwal_rescope_children_obase( cwal_value * v ){ cwal_obase * b = CWAL_VOBASE(v); cwal_kvp * kvp = b->kvp; cwal_value * proto = b->prototype; int rc = CWAL_RC_OK; assert(b); assert(!(CWAL_F_IS_VISITING & b->flags)); #if 1 if(proto && (proto->scope->level > v->scope->level)){ /* dump_val(proto,"Rescoping prototype"); */ cwal_value_xscope( v->scope->e, v->scope, proto, 0 ); } #else for( ; proto; proto = CWAL_VOBASE(proto)->prototype){ if(proto->scope->level > v->scope->level){ /* dump_val(proto,"Rescoping prototype"); */ cwal_value_xscope( v->scope->e, v->scope, proto, 0 ); } } #endif assert(v->scope); for( ; kvp && (0==rc); kvp = kvp->right ){ if(kvp->key){ rc = cwal_value_xscope( v->scope->e, v->scope, kvp->key, 0 ); } if((0==rc) && kvp->value){ rc = cwal_value_xscope( v->scope->e, v->scope, kvp->value, 0 ); } } return rc; } int cwal_rescope_children_native( cwal_value * v ){ int rc; cwal_native * n = cwal_value_get_native(v); assert(n); rc = cwal_rescope_children_obase(v); if(!rc && n->rescope_children){ rc = n->rescope_children( n ); } return rc; } int cwal_rescope_children_array( cwal_value * v ){ int rc; cwal_array * ar = cwal_value_get_array(v); assert(ar); assert(!(CWAL_F_IS_VISITING & ar->base.flags)); rc = cwal_rescope_children_obase( v ); if(rc) return rc; rc = cwal_list_visit( &ar->list, -1, cwal_xscope_visitor_children_array, v ); return rc; } int cwal_value_rescope( cwal_scope * s, cwal_value * v ){ return (!s || !s->e || !v) ? CWAL_RC_MISUSE : cwal_value_xscope( s->e, s, v, NULL ); } /** Transfers child to the given scope if child is in a lower-level scope. Returns 0 on success and theoretically has no error cases except bad arguments or as side-effects of serious internal errors elsewhere. If res is not NULL, it will be set to one of these values: -1=child was moved to a higher-level scope (with a lower scope->level value). 0=child was kept where it is. 1=child was... hmm... damn, i should have written the docs as i wrote the code :/. */ static int cwal_value_xscope( cwal_engine * e, cwal_scope * par, cwal_value * child, int * res ){ cwal_obase * chb; int RC = res ? *res : 0; if(!res) res = &RC/*simplifies some code below*/; if(!par) { *res = 1; return 0;/*par = e->current;*/ } assert( e && par && child ); if( V_IS_BUILTIN(child) ) { *res = 1; return CWAL_RC_OK; } else if(child->scope == par) { *res = 0; return CWAL_RC_OK; } chb = CWAL_VOBASE(child); if( chb && ( CWAL_F_IS_VISITING & chb->flags ) ){ *res = 0; return 0 /* Normally we would return CWAL_RC_CYCLES_DETECTED, but for this special case we need to return 0 to keep list iteration from aborting. */; } else{ int rc = CWAL_RC_OK; if( child->scope ){ CWAL_TR_V(e,child); if( child->scope->level <= par->level ){ CWAL_TR3(e,CWAL_TRACE_MESSAGE, "Keeping value in its current scope."); *res = 1; return 0; } else{ CWAL_TR3(e,CWAL_TRACE_MESSAGE, "Migrating value to older scope."); rc = cwal_value_take( e, child ); if(rc){ CWAL_TR_SV(e,child->scope,child); CWAL_TR3(e,CWAL_TRACE_ERROR, "POPPING FROM ITS PARENT SCOPE IS NO " "LONGER SUPPOSED TO BE ABLE TO FAIL " "IN THESE CONDITIONS."); assert(!"Pop child from its scope failed."); return rc; } } } assert(!child->scope); *res = -1; cwal_scope_insert( par, child ); if(child->vtab->rescope_children){ /* For containers we now, for the sake of cross-scope cycles, probably need to ensure that any sub-(sub...)children are up-scoped. Or maybe we could do it before (or during?) destruction. This seems like the most logical place for it, but makes up-scoping a fairly expensive operation (which destruction already is). */ rc = child->vtab->rescope_children(child); if(0!=rc){ /* Reminder: now that values are tracked in linked lists, xscope can only fail if some assertion fails. There is no longer the potential for an OOM case. */ MARKER(("xscope returned %d/%s\n", rc, cwal_rc_cstr(rc))); assert(!"NO RECOVERY STRATEGY HERE!"); return CWAL_RC_CANNOT_HAPPEN; } } return rc; } } int cwal_value_upscope( cwal_value * v ){ cwal_engine * e = CWAL_VENGINE(v); assert(!"don't use cwal_value_upscope(). Use cwal_value_rescope() instead."); if(!e || !v->scope) return CWAL_RC_MISUSE; else { if(!v->scope->parent){ return CWAL_RC_RANGE; }else { int rc, dir = 0; rc = cwal_value_xscope( e, v->scope->parent, v, &dir); assert(-1==dir); return rc; } } } int cwal_exception_set( cwal_engine * e, cwal_value * v ){ if(!e || !e->current) return CWAL_RC_MISUSE; else if(v && (v == e->exception)) /* This is ok. */ return CWAL_RC_EXCEPTION; else if(!v) { #if 0 if(e->exception && e->exception->scope==e->current && 1==e->exception->refcount){ /* Should we really be doing this? Assuming the scope reference is the only ref? Previous attempts to do this have bitten me in the buttox, but i "think" i can get away with it for exceptions. Note that this routine does NOT add a ref to v, but instead of taking back our own ref we're stealing that of the owning scope. Nonono... this could open up cases where an exception is moved around and set/unset as the exception state multiple times, and we'd unref it too many times. So we'll only do this "optimization" to when the scopes match and 1==refcount, which we can fairly safely assume is the scope-held ref unless the client has done something silly, like... set exception state, move it into an object owned by the same scope, unref the exception (refcount==1), set the object as the exception state... blammo. Okay, turn this off but leave the commentary as yet another reminder of why we don't just generically unref values. */ cwal_value_unref2(e, e->exception); } #endif /* cwal_value * x = e->exception; */ e->exception = 0 /* its scope owns it */; /* cwal_value_unref(x); */ return 0; } else{ cwal_scope * s = e->current; if(!s->parent || V_IS_BUILTIN(v) || (v->scope && ( (v->scope==s) || (v->scope->level < s->level) )) ){ /* Don't re-scope it! We'll take "very shallow" ownership here.*/ e->exception = v; return CWAL_RC_EXCEPTION; } else{ int rc; int xfer = 0; assert(e->current->parent); CWAL_TR_SV(e, s, v); CWAL_TR3(e,CWAL_TRACE_ENGINE_MASK, "Transfering exception to parent scope."); rc = cwal_value_xscope(e, s, v, &xfer ); assert((-1 == xfer) && "Highly Unexpected: xscope indicates >=0 for xfer to parent scope."); assert(!rc && "i don't think xscope can fail in this case."); if(!rc){ e->exception = v; /* cwal_value_ref(v); */ } return rc ? rc : CWAL_RC_EXCEPTION; } } } int cwal_exception_setfv(cwal_engine * e, int code, char const * fmt, va_list args){ if(!e) return CWAL_RC_MISUSE; else{ int rc; cwal_exception * x; x = (fmt && *fmt) ? cwal_new_exceptionfv(e, code, fmt, args) : cwal_new_exception(e, code, NULL); if(!x) return CWAL_RC_OOM; rc = cwal_exception_set( e, cwal_exception_value(x) ); if(CWAL_RC_EXCEPTION != rc) cwal_exception_unref(e, x); assert(0!=rc); return rc; } } int cwal_exception_setf(cwal_engine * e, int code, char const * fmt, ...){ if(!e || !fmt) return 0; else{ int rc; va_list args; va_start(args,fmt); rc = cwal_exception_setfv(e, code, fmt, args); va_end(args); return rc; } } cwal_value * cwal_exception_get( cwal_engine * e ){ /** FIXME: this is 98% identical to cwal_result_get(). Consolidate them. */ if(!e) return NULL; else if(!e->exception) return NULL /*??? cwal_value_undefined() ???*/; else if(V_IS_BUILTIN(e->exception)) return e->exception; else{ #if 1 return e->exception; #else int xfer = 0; cwal_value * v = e->exception; cwal_scope * s = e->current; if(!s) { /* top scope is destructing. */ e->exception = 0; return NULL; } cwal_value_xscope( e, s, v, &xfer ); if(xfer<0){ CWAL_TR_SV(e,s,v); CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,"Moved EXCEPTION to next scope up."); } return v; #endif } } static void cwal_array_steal_list( cwal_engine * e, cwal_array * ar ){ #if 0 /*Enable this section to effectively disable array->list recycling for memory cost/savings comparisons. */ return; #else if(ar->list.list) return; else if(e->reList.cursor>=0){ ar->list = e->reList.lists[e->reList.cursor]; e->reList.lists[e->reList.cursor] = cwal_list_empty; /* MARKER(("Re-using array list memory (%"CWAL_SIZE_T_PFMT" slots) from slot #%d\n", ar->list.alloced, e->reList.cursor)); */ --e->reList.cursor; } #endif } int cwal_array_set( cwal_array * ar, cwal_size_t ndx, cwal_value * v ) { SETUP_ARRAY_ARGS; if( !ar ) return CWAL_RC_MISUSE; else if( (ndx+1) > CwalConsts.MaxSizeT) /* overflow */return CWAL_RC_RANGE; else{ cwal_size_t len; if(!ar->list.list) cwal_array_steal_list(e, ar); len = cwal_list_reserve( e, &ar->list, ar->list.count ? ndx+1 : CwalConsts.InitialArrayLength ); if( len <= ndx ) return CWAL_RC_OOM; else{ int rc; cwal_value * arV = cwal_array_value( ar ); cwal_value * old = (cwal_value*)ar->list.list[ndx]; assert( arV && (CWAL_TYPE_ARRAY==arV->vtab->typeID) ); if( old ){ if(old == v) return 0; } if(v){ rc = cwal_value_xscope( e, arV->scope, v, 0 ); if(rc){ assert(!"WARNING: xscope failed! " "THIS IS SUPPOSED TO BE IMPOSSIBLE :(\n"); return rc; } cwal_value_ref2( e, v ); } if(old) cwal_value_unref2(e, old); ar->list.list[ndx] = v; if( ndx >= ar->list.count ){ ar->list.count = ndx+1; } return 0; } } } int cwal_array_append( cwal_array * ar, cwal_value * v ){ SETUP_ARRAY_ARGS; if( !ar ) return CWAL_RC_MISUSE; else if( (ar->list.count+1) < ar->list.count ) return CWAL_RC_RANGE; else{ if(!ar->list.list) cwal_array_steal_list(e, ar); if( !ar->list.alloced || (ar->list.count == ar->list.alloced-1)){ unsigned const int n = ar->list.count ? ar->list.alloced * 2 : CwalConsts.InitialArrayLength; if( n > cwal_list_reserve( e, &ar->list, n ) ){ return CWAL_RC_OOM; } } return cwal_array_set( ar, ar->list.count, v ); } } int cwal_array_prepend( cwal_array * ar, cwal_value * v ){ SETUP_ARRAY_ARGS; if( !ar || !v ) return CWAL_RC_MISUSE; else{ int rc; cwal_value ** vlist; if(!ar->list.list) cwal_array_steal_list(e, ar); if( !ar->list.alloced || (ar->list.count == ar->list.alloced-1)){ unsigned const int n = ar->list.count ? ar->list.alloced * 2 : CwalConsts.InitialArrayLength; if( n > cwal_list_reserve( e, &ar->list, n ) ){ return CWAL_RC_OOM; } } if(ar->list.count){ unsigned char * mem = (unsigned char *)ar->list.list; memmove( mem+sizeof(cwal_value*), mem, sizeof(cwal_value*)*ar->list.count); } vlist = (cwal_value **)ar->list.list; vlist[0] = NULL; ++ar->list.count; rc = cwal_array_set( ar, 0, v ); /* A recovery here on error would be messy, but the set() can only fail on allocation error and we've already done the allocation. */ assert(!rc); assert(v == *((cwal_value**)ar->list.list)); return rc; } } int cwal_array_shift( cwal_array * ar, cwal_value **rv ){ SETUP_ARRAY_ARGS; if( !ar ) return CWAL_RC_MISUSE; else{ cwal_value ** vlist; unsigned char * mem; cwal_value * v; if(!ar->list.count) return CWAL_RC_RANGE; vlist = (cwal_value **)ar->list.list; v = vlist[0]; if(rv) *rv = v; mem = (unsigned char *)ar->list.list; memmove( mem, mem+sizeof(cwal_value*), sizeof(cwal_value*)*(ar->list.count-1)); vlist[--ar->list.count] = NULL; if(v){ /* We need to do some non-kosher tinkering of the refcount to ensure that we neither nuke the value we're returning nor strand it with too many refs... */ if(rv && (1==v->refcount)){/* This is our ref */ --v->refcount; cwal_value_reprobate(v->scope, v); }else if(!v->refcount){ assert(V_IS_BUILTIN(v)); }else{ cwal_unref_from(s, v); } } return 0; } } int cwal_array_copy_range( cwal_array * ar, cwal_size_t offset, cwal_size_t count, cwal_array **dest ){ SETUP_ARRAY_ARGS; if( !ar || !dest || (ar==*dest) ) return CWAL_RC_MISUSE; else{ int rc = 0; cwal_size_t i; cwal_array * tgt = *dest ? *dest : cwal_new_array(e); cwal_value ** vlist; vlist = (cwal_value **)ar->list.list; if(offsetlist.count){ if(!count) count = ar->list.count - offset; rc = cwal_array_reserve( tgt, count ); for( i = offset; !rc && (ilist.count); ++i ){ cwal_value * v = vlist[i]; rc = cwal_array_append( tgt, v ); } } if(!rc) *dest = tgt; else if(rc && (*dest != tgt)) cwal_array_unref(tgt); return rc; } } int cwal_value_fetch_object( cwal_value const * val, cwal_object ** ar){ if( ! val ) return CWAL_RC_MISUSE; else if( !cwal_value_is_object(val) ) return CWAL_RC_TYPE; else{ if(ar) *ar = CWAL_OBJ(val); return 0; } } cwal_object * cwal_value_get_object( cwal_value const * v ) { cwal_object * ar = NULL; cwal_value_fetch_object( v, &ar ); return ar; } cwal_value * cwal_object_value(cwal_object const * s){ return s ? CWAL_VALPART(s) : NULL; } int cwal_object_unref(cwal_object *x){ cwal_value * v = CWAL_VALPART(x); return (v && v->scope) ? cwal_value_unref2(v->scope->e, v) : CWAL_RC_MISUSE; } /** Searches base->kvp for the given property, recurisively up the base->prototype chain if the key is not found. */ static cwal_value * cwal_obase_search( cwal_obase const * base, char searchProto, char const * key, cwal_size_t keyLen, cwal_size_t * ndx, cwal_kvp ** prev){ if(!base || !key || !*key || (CWAL_F_IS_VISITING & base->flags)) return NULL; else { cwal_kvp * kvp; cwal_value * rc = NULL; /* cwal_obase const * origBase = base; */ if(!keyLen) keyLen = strlen(key); /* cwal_obase_set_visiting(base, 1); */ while(base){ kvp = cwal_kvp_search( base->kvp, key, keyLen, ndx, prev ); if(kvp) { rc = kvp->value; break; } else base = searchProto ? CWAL_VOBASE(base->prototype) : 0; } /* cwal_obase_set_visiting(origBase, 0); */ return rc; } } /** Counterpart of cwal_kvp_search_v(). Searches for key in base->kvp. If not found and searchProto is true, it searches base->prototype (recursively). If it encounters cycles while traversing the prototype is returned NULL. */ static cwal_value * cwal_obase_search_v( cwal_obase const * base, char searchProto, cwal_value const * key, cwal_size_t * ndx, cwal_kvp ** prev){ if(!base || !key || (CWAL_F_IS_VISITING & base->flags)) return NULL; else { cwal_kvp * kvp; cwal_value * rc = NULL; /* cwal_obase const * origBase = base; */ /* cwal_obase_set_visiting(base, 1); */ while(base){ kvp = cwal_kvp_search_v( base->kvp, key, ndx, prev ); if(kvp) { rc = kvp->value; break; } else { base = searchProto ? CWAL_VOBASE(base->prototype) : 0; } } /* cwal_obase_set_visiting(origBase, 0); */ return rc; } } cwal_value * cwal_object_get( cwal_object const * obj, char const * key, cwal_size_t keyLen ) { if(!obj || !key || !*key) return NULL; else{ return cwal_obase_search( &obj->base, 1, key, keyLen, NULL, NULL ); } } cwal_value * cwal_object_get_s( cwal_object const * obj, cwal_string const *key ) { if(!obj || !key) return NULL; else { return cwal_obase_search_v( &obj->base, 1, cwal_string_value(key), NULL, NULL ); } } int cwal_object_unset( cwal_engine * e, cwal_object * obj, char const * key, cwal_size_t keyLen ) { return (!e || !obj || !key) ? CWAL_RC_MISUSE : cwal_kvp_unset( e, CWAL_VALPART(obj)->scope, &obj->base.kvp, key, keyLen ) ; } char cwal_prop_has( cwal_value const * v, char searchPrototype, char const * key, cwal_size_t keyLen ) { cwal_obase * base = CWAL_VOBASE(v); if(!base || !key || !*key) return 0; else{ return (searchPrototype ? (void const *)cwal_obase_search(base, 1, key, keyLen, NULL, NULL ) : (void const *)cwal_kvp_search(base->kvp, key, keyLen, NULL, NULL) ) ? 1 : 0; } } char cwal_prop_has_s( cwal_value const * v, char searchPrototype, cwal_string const *key ){ if(!v || !key || !key->length) return 0; else return cwal_prop_has( v, searchPrototype, cwal_string_cstr(key), CWAL_STRLEN(key) ); } char cwal_prop_has_v( cwal_value const * v, char searchPrototype, cwal_value const * key ){ cwal_obase const * base = CWAL_VOBASE(v); return (!base || !key) ? 0 : ((searchPrototype ? (void const *)cwal_obase_search_v(base, 1, key, NULL, NULL ) : (void const *)cwal_kvp_search_v(base->kvp, key, NULL, NULL) ) ? 1 : 0) ; } cwal_value * cwal_prop_get( cwal_value const * v, char const * key, cwal_size_t keyLen ) { cwal_obase const * base = CWAL_VOBASE(v); if(!base || !key || !*key) return NULL; else{ return cwal_obase_search( base, 1, key, keyLen, NULL, NULL ); } } cwal_value * cwal_prop_get_v( cwal_value const * c, cwal_value const * key ) { cwal_obase const * base = CWAL_VOBASE(c); if(!base || !key) return NULL; else{ return cwal_obase_search_v( base, 1, key, NULL, NULL ); } } cwal_value * cwal_prop_get_s( cwal_value const * v, cwal_string const *key ) { cwal_obase const * base = CWAL_VOBASE(v); if(!base || !key || !key->length) return NULL; else{ return cwal_obase_search( base, 1, cwal_string_cstr(key), CWAL_STRLEN(key), NULL, NULL ); } } int cwal_prop_unset( cwal_value * c, char const * key, cwal_size_t keyLen ) { cwal_obase * b = CWAL_VOBASE(c); cwal_engine * e = b ? CWAL_VENGINE(c) : NULL; return (e && b) ? cwal_kvp_unset( e, c->scope/*?e->current?*/, &b->kvp, key, keyLen ) : CWAL_RC_MISUSE; } int cwal_prop_unset_v( cwal_value * c, cwal_value * key ) { cwal_obase * b = CWAL_VOBASE(c); cwal_engine * e = b ? CWAL_VENGINE(c) : NULL; return (e && b) ? cwal_kvp_unset_v( e, c->scope/*?e->current?*/, &b->kvp, key ) : CWAL_RC_MISUSE; } /** Adds an entry or updates an existing one in the given kvp list. listOwner must be the value which owns/manages list. key is the key to search for and value is the value to set it to. On success *list is modified to point to the new kvp, and that kvp->right will point to the old value of *list. Returns 0 on success, CWAL_RC_MISUSE if any arguments are NULL, and CWAL_RC_OOM on allocation error. */ static int cwal_kvp_set_v( cwal_engine * e, cwal_value * listOwner, cwal_kvp ** list, cwal_value * key, cwal_value * v, uint16_t flags ) { if( !e || !key || !list ||!listOwner || !v ) return CWAL_RC_MISUSE; else { int rc; /*char const * cKey;*/ cwal_kvp * kvp = 0; cwal_kvp * left = 0; char i = 0; char iAlloccedKvp = 0; kvp = cwal_kvp_search_v( *list, key, NULL, &left ) ; if( !kvp ){ if(!v){ return CWAL_RC_NOT_FOUND /* 0? */; } kvp = cwal_kvp_alloc( e ); if( !kvp ) return CWAL_RC_OOM; iAlloccedKvp = 1; kvp->flags = (CWAL_VAR_F_PRESERVE==flags) ? CWAL_VAR_F_NONE : flags; } else if(CWAL_VAR_F_CONST & kvp->flags){ if(kvp->value != v) return CWAL_RC_ACCESS; else { /** Special case: ignore assignment to self. This is triggered in th1ish on an assignment-to-self operation, e.g.: x += 1 where x has a '+' operator which returns x. */ return 0; } } if(CWAL_VAR_F_PRESERVE!=flags) kvp->flags = flags; /* Reminder: we have to increase the refcount of the key in even if it's the same pointer as a key we have or the same as v. Remember that interning guarantees that we'll eventually see the same key instances. */ i = 0; for( ; i < 2; ++i ){ cwal_value * vv = 0==i ? key : v; rc = cwal_value_xscope( e, listOwner->scope, vv, 0 ); if(rc){ if(iAlloccedKvp){ cwal_kvp_free( e, listOwner->scope, kvp, 1 ); } goto xferFailed; } /* The ref/unref order is important in case key==kvp->key or... in case v==kvp->value, key, or listOwner. */ if(0==i) { /* KEY part... */ cwal_value_ref2( e, vv ); if(kvp->key) cwal_unref_from( listOwner->scope, kvp->key ); kvp->key = vv; }else{ /* VALUE part... */ cwal_value_ref2( e, vv ); if(kvp->value) cwal_unref_from( listOwner->scope, kvp->value ); kvp->value = vv; break; } } assert(1 == i); assert(kvp->key != 0); assert(kvp->value != 0); if(!left){ /* This is a new entry or head of an empty list. */ #if 0 /* Make kvp the tail of the list */ if(!*list){ assert(!kvp->right); *list = kvp; } else{ cwal_kvp * head = *list; while( head->right ){ head = head->right; } head->right = kvp; } #elif !CWAL_KVP_TRY_SORTING /* Make kvp the head of the list */ if(*list != kvp){ assert(!kvp->right); kvp->right = *list; *list = kvp; } #else /* CWAL_KVP_TRY_SORTING */ /* Insert it in sorted order... */ if(!*list){ assert(!kvp->right); *list = kvp; } else if(*list != kvp){ cwal_kvp * pos = *list; cwal_kvp * prev = 0; int cmp = 0; while( 1 ){ cmp = cwal_value_compare( kvp->key, pos->key ); if(0==cmp){ /*dump_val(kvp->key,"kvp->key");*/ /*dump_val(pos->key,"pos->key");*/ assert(kvp->key != pos->key); } else if(cmp>0){ kvp->right = pos; if(prev){ prev->right = kvp; }else{ *list = kvp; } break; } prev = pos; pos = pos->right; if(!pos){ prev->right = kvp; break; } } } #endif } /*else it's already in the list. */ return CWAL_RC_OK; xferFailed: MARKER(("VERY unexpected: xscope rc=%d/%s\n", rc, cwal_rc_cstr(rc))); assert(!"xscope cannot fail in this context any more!"); return rc; } } int cwal_kvp_set( cwal_engine * e, cwal_value * listOwner, cwal_kvp ** list, char const * key, cwal_size_t keyLen, cwal_value * v, uint16_t flags ) { if( !list || !key ) return CWAL_RC_MISUSE; /*FIXME: re-factor this back in:*/ /*else if( NULL == v ) return cwal_object_unset( e, obj, key );*/ else { cwal_string * cs; cs = cwal_new_string(e, key, keyLen); if(!cs) return CWAL_RC_OOM; else { int const rc = cwal_kvp_set_v( e, listOwner, list, cwal_string_value(cs), v, flags ); #if 0 /* We need to leave cs to its parent scope just in case it's interened :/. */ if(rc) cwal_value_unref2(e, cwal_string_value(cs)) /* If kvp_set_v ever fails and cs is an interned string, this is going to bite me in the butt. */ ; #endif return rc; } } } int cwal_object_set_s( cwal_object * obj, cwal_string * key, cwal_value * v ) { return cwal_prop_set_s( cwal_object_value(obj), key, v ); } int cwal_object_set( cwal_object * obj, char const * key, cwal_size_t keyLen, cwal_value * v ) { return cwal_prop_set( cwal_object_value(obj), key, keyLen, v ); } cwal_value * cwal_object_take( cwal_object * obj, char const * key ){ return cwal_prop_take( cwal_object_value(obj), key ); } int cwal_prop_set_v( cwal_value * c, cwal_value * key, cwal_value * v ) { cwal_obase * b = CWAL_VOBASE(c); cwal_engine * e = CWAL_VENGINE(c); assert(v ? !!e : 1); if( !e || !b || !key ) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_ACCESS; else if( NULL == v ) { return cwal_prop_unset_v( c, key ); } else { return cwal_kvp_set_v( e, c, &b->kvp, key, v, CWAL_VAR_F_PRESERVE ); } } int cwal_prop_set_s( cwal_value * c, cwal_string * key, cwal_value * v ) { return cwal_prop_set_v( c, cwal_string_value(key), v ); } int cwal_prop_set( cwal_value * c, char const * key, cwal_size_t keyLen, cwal_value * v ) { cwal_obase * b = CWAL_VOBASE(c); cwal_engine * e = b ? CWAL_VENGINE(c) : 0; if( !e || !b || !key ) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_ACCESS; else if( NULL == v ) return cwal_prop_unset( c, key, keyLen ); else { cwal_value * vkey; if(!keyLen) { keyLen = strlen(key); assert(keyLen); if(!keyLen) return CWAL_RC_CANNOT_HAPPEN; } vkey = cwal_new_string_value(e, key, keyLen); if(!vkey) return CWAL_RC_OOM; else { int const rc = cwal_prop_set_v(c, vkey, v); if(rc) cwal_value_unref2(e, vkey); return rc; } } } cwal_value * cwal_prop_take( cwal_value * c, char const * key ){ cwal_obase * b = CWAL_VOBASE(c); cwal_engine * e = CWAL_VENGINE(c); assert(c ? !!e : 1); if( !e || !b || !e || !key || !*key || !b->kvp) return NULL; else { /* FIXME: this is 90% identical to cwal_kvp_unset(), only with different refcount handling. Consolidate them. */ cwal_kvp * left = 0; cwal_value * rc = NULL; cwal_kvp * kvp = cwal_kvp_search( b->kvp, key, 0, NULL, &left ); if( ! kvp ) return NULL; rc = kvp->value; if(left){ left->right = kvp->right; }else{ assert(b->kvp == kvp); b->kvp = kvp->right ? kvp->right : NULL; } kvp->right = NULL; assert( rc ); cwal_value_unref2(e, kvp->key); kvp->key = 0; kvp->value = 0; cwal_kvp_free( e, NULL, kvp, 1 ); assert( rc->refcount > 0 ); return rc; } } /** "Visits" one member of a key/value pair. If mode is 0 then the key is visited, and 1 means to visit the value. Any other value is illegal. Returns the result of func( pairPair, fState ). */ static int cwal_obase_visit_impl( cwal_kvp *kvp, char mode, cwal_value_visitor_f func, void * fState ){ cwal_value * v = 0; switch( mode ){ case 0: v = kvp->key; break; case 1: v = kvp->value; if(!v) return 0; break; default: assert(!"Invalid visit mode."); return CWAL_RC_CANNOT_HAPPEN; } assert(0 != v); return func( v, fState ); } char cwal_props_can( cwal_value const * c ){ return (c && CWAL_VOBASE(c)) ? 1 : 0; } int cwal_props_clear( cwal_value * c ){ cwal_obase * b = CWAL_VOBASE(c); cwal_engine * e = CWAL_VENGINE(c); /* dump_val(c,"props_clear"); */ if(!e || !c) return CWAL_RC_MISUSE; else if(!b) return CWAL_RC_TYPE; /** We need a mechanism for clearing properties stored in other places in higher-level types, e.g. we might need to add a public API which simply calls c->vtab->cleanup(c). For arrays, however, that would end badly. */ else { cwal_cleanup_obase( e, b, 0 ); return CWAL_RC_OK; } } static int cwal_kvp_visitor_props_copy( cwal_kvp const * kvp, void * state ){ return cwal_prop_set_v( (cwal_value *)state, kvp->key, kvp->value ); } int cwal_props_copy( cwal_value * src, cwal_value * dest ){ if(!src || !dest) return CWAL_RC_MISUSE; else if(!cwal_props_can(src) || !cwal_props_can(dest)) return CWAL_RC_TYPE; else return cwal_props_visit_kvp( src, cwal_kvp_visitor_props_copy, dest ); } #if 0 int cwal_value_clear_mutable_state( cwal_value * v ){ cwal_engine * e = CWAL_VENGINE(v); assert(e); if(!e || !v) return CWAL_RC_MISUSE; else if(V_IS_BUILTIN(v)) return 0; switch(v->vtab->typeID){ case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_STRING: return 0; case CWAL_TYPE_ARRAY: cwal_value_cleanup_array_impl( e, v, 1, 1, 0 ); return 0; case CWAL_TYPE_OBJECT: case CWAL_TYPE_BUFFER: case CWAL_TYPE_NATIVE: case CWAL_TYPE_FUNCTION: case CWAL_TYPE_EXCEPTION:{ cwal_obase * b = CWAL_VOBASE(v); cwal_value * proto = b ? b->prototype : NULL; assert(b); b->prototype = 0/* kludge */; v->vtab->cleanup( e, v ); b->prototype = proto; return 0; } default: assert(!"unhandled type"); return CWAL_RC_CANNOT_HAPPEN; } } #endif int cwal_props_visit_values( cwal_value * c, cwal_value_visitor_f f, void * state ){ cwal_obase * b = CWAL_VOBASE(c); if(!c || !f) return CWAL_RC_MISUSE; else if(!b) return CWAL_RC_TYPE; else if( CWAL_F_IS_VISITING & b->flags ) return CWAL_RC_CYCLES_DETECTED; else { int rc = 0; cwal_kvp * kvp = b->kvp; cwal_value_set_visiting( c, 1 ); for( ; kvp && (0==rc); kvp = kvp->right ){ rc = (CWAL_VAR_F_HIDDEN & kvp->flags) ? 0 : cwal_obase_visit_impl( kvp, 1, f, state ); } cwal_value_set_visiting( c, 0 ); return rc; } } int cwal_props_visit_keys( cwal_value * c, cwal_value_visitor_f f, void * state ){ cwal_obase * b = CWAL_VOBASE(c); if(!c || !f) return CWAL_RC_MISUSE; else if(!b) return CWAL_RC_TYPE; /* Reminder: we don't need (i think) to set CWAL_F_IS_VISITING for this because the keys cannot (yet!) contribute to cycles. */ /*else if(CWAL_F_IS_VISITING & o->base.flags) return CWAL_RC_CYCLES_DETECTED;*/ else{ int rc = 0; cwal_kvp * kvp = b->kvp; /*o->base.flags |= CWAL_F_IS_VISITING;*/ for( ; kvp && (0==rc); kvp = kvp->right ){ rc = (CWAL_VAR_F_HIDDEN & kvp->flags) ? 0 : cwal_obase_visit_impl( kvp, 0, f, state ); } /*o->base.flags &= ~CWAL_F_IS_VISITING;*/ return rc; } } char cwal_value_may_iterate( cwal_value const * c ){ if(!c) return 0; else { cwal_obase const * b = CWAL_OBASE(c); return b ? ((CWAL_F_IS_VISITING & b->flags) ? 0 : 1) : 0; } } int cwal_props_visit_kvp( cwal_value * c, cwal_kvp_visitor_f f, void * state ){ cwal_obase * b = CWAL_VOBASE(c); if(!c || !f) return CWAL_RC_MISUSE; else if(!b) return CWAL_RC_TYPE; else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_CYCLES_DETECTED; else { cwal_kvp * kvp; int rc = CWAL_RC_OK; cwal_value_set_visiting( c, 1 ); for( kvp = b->kvp; kvp && (0==rc); kvp = kvp->right ){ rc = (CWAL_VAR_F_HIDDEN & kvp->flags) ? 0 : f( kvp, state ); } cwal_value_set_visiting( c, 0 ); return rc; } } int cwal_array_visit( cwal_array * o, cwal_value_visitor_f f, void * state ){ if(!o || !f) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & o->base.flags) return CWAL_RC_CYCLES_DETECTED; else { cwal_size_t i; cwal_value * v; cwal_value * vSelf = cwal_array_value(o); int rc = CWAL_RC_OK; cwal_list * li = &o->list; cwal_value_set_visiting( vSelf, 1 ); for( i = 0; (CWAL_RC_OK==rc) && (i < li->count); ++i ){ v = (cwal_value*)li->list[i]; if(v) rc = f( v, state ); } cwal_value_set_visiting( vSelf, 0 ); return rc; } } int cwal_array_visit2( cwal_array * o, cwal_array_visitor_f f, void * state ){ if(!o || !f) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & o->base.flags) return CWAL_RC_CYCLES_DETECTED; else{ cwal_size_t i; cwal_value * v; cwal_value * vSelf = cwal_array_value(o); int rc = CWAL_RC_OK; cwal_list * li = &o->list; cwal_value_set_visiting( vSelf, 1 ); for( i = 0; (CWAL_RC_OK==rc) && (i < li->count); ++i ){ v = (cwal_value*)li->list[i]; if(v){ rc = f( o, v, i, state ); } } cwal_value_set_visiting( vSelf, 0 ); return rc; } } #if 0 int cwal_array_swap( cwal_array * ar, cwal_size_t left, cwal_size_t right ){ cwal_value * l = cwal_array_get(ar, left); cwal_value * r = cwal_array_get(ar, right); int rc; if(l) cwal_value_ref(l); rc = cwal_array_set(ar, left, r); if(!rc) { rc = cwal_array_set(ar, right, l); if(!rc) cwal_value_unref(l); } return rc; } #endif /** qsort implementation adapted from: http://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Quicksort#Iterative_Quicksort Based on one of the C# implementations. li is the cwal_list to sort. left is the starting left-most index, right is the final index to sort (not the one-past-the-end!). cmp is the comparison function to use and the state parameter is passed as-is to cmp(). FIXME: we need a mechanism for propagating cwal-level exceptions set by the comparison function. */ static int cwal_value_list_sort_impl( cwal_list * li, cwal_size_t left, cwal_size_t right, cwal_value_stateful_compare_f cmp, void * state ){ cwal_size_t const lhold = left; cwal_size_t const rhold = right; cwal_size_t pivot = (right - left) / 2; int rc = 0; void * swapTmp; assert(right>=left); if(!pivot) return rc; #define LIST_SWAP(LI,L,R) \ swapTmp = (LI)->list[L]; \ (LI)->list[L] = (LI)->list[R]; \ (LI)->list[R] = swapTmp; LIST_SWAP(li,pivot,left); pivot = left; ++left; while( right >= left ){ #define CV (cwal_value*) int const cmpLeft = cmp( CV li->list[left], CV li->list[pivot], state ); int const cmpRight = cmp( CV li->list[right], CV li->list[pivot], state ); #undef CV if((cmpLeft>=0) && (cmpRight<0)){ LIST_SWAP(li,left, right); }else{ if(cmpLeft>=0) --right; else{ if(cmpRight<0) ++left; else { --right; ++left; } } } } LIST_SWAP(li,pivot,right); pivot = right; /* TODO use goto instead of recursion. */ if(pivot>lhold){ cwal_value_list_sort_impl(li, lhold, pivot, cmp, state); } if(rhold>(pivot+1)){ cwal_value_list_sort_impl(li, pivot+1, rhold, cmp, state); } return rc; #undef LIST_SWAP } int cwal_array_sort_stateful( cwal_array * ar, cwal_value_stateful_compare_f cmp, void * state ){ if(!ar || !cmp) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS; else if(ar->list.count<2) return 0; else { cwal_value_list_sort_impl( &ar->list, 0, ar->list.count-1, cmp, state ); return 0; } } /** Internal state for cwal_array_sort_func() and cwal_array_sort_func_cb(). */ struct ArrayFuncState { cwal_function * f; cwal_value * self; }; typedef struct ArrayFuncState ArrayFuncState; static int cwal_array_sort_func_cb( cwal_value * lhs, cwal_value * rhs, void * state ){ cwal_value * argv[2]; ArrayFuncState * st = (ArrayFuncState *)state; cwal_value * rv = 0; int rc; argv[0] = lhs ? lhs : cwal_value_undefined(); argv[1] = rhs ? rhs : cwal_value_undefined(); rc = cwal_function_call(st->f, st->self, &rv, 2, argv); if(!rc && rv) return cwal_value_get_integer(rv); else return rc; } int cwal_array_sort_func( cwal_array * ar, cwal_value * self, cwal_function * cmp ){ if(!ar || !cmp) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS; else if(ar->list.count<2) return 0; else { ArrayFuncState st; st.f = cmp; st.self = self ? self : cwal_function_value(cmp); return cwal_array_sort_stateful( ar, cwal_array_sort_func_cb, &st ); } } int cwal_array_sort( cwal_array * ar, int(*cmp)(void const *, void const *) ){ if(!ar || !cmp) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS; else if(!ar->list.count) return 0; else { qsort( ar->list.list, ar->list.count, sizeof(void*), cmp ); return 0; } } int cwal_array_reverse( cwal_array * ar ){ if(!ar) return CWAL_RC_MISUSE; else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_MISUSE; else if(ar->list.count < 2) return 0; else{ cwal_size_t b = 0, e = ar->list.count-1; void ** li = ar->list.list; void * tmp; for( ; bbase.flags) return CWAL_RC_CYCLES_DETECTED; asrc->base.flags |= CWAL_F_IS_VISITING; #define RETURN(RC) rc=(RC); goto end assert( orig && asrc ); destA = cwal_new_array( e ); if(!destA){ RETURN(CWAL_RC_OOM); } destV = CWAL_VALPART(destA); assert( destA ); if( 0 != cwal_array_reserve( destA, alen ) ){ cwal_value_unref2( e, destV ); RETURN(CWAL_RC_OOM); } for( ; i < alen; ++i ){ cwal_value * ch = cwal_array_get( asrc, i ); if( NULL != ch ){ cwal_value * cl = 0; int rc = cwal_value_clone_ref( e, ch, &cl ); if( NULL == cl ){ cwal_value_unref2( e, destV ); assert(0 != rc); RETURN(rc); } else if( CWAL_RC_OK != (rc=cwal_array_set( destA, i, cl )) ) { cwal_value_unref2( e, cl ); cwal_value_unref2( e, destV ); RETURN(rc); } } } end: if(rc){ if(destA) cwal_array_unref(destA); }else{ *pDest = destV; } asrc->base.flags &= ~CWAL_F_IS_VISITING; return rc; #undef RETURN } static int cwal_value_clone_object( cwal_engine * e, cwal_value * orig, cwal_value ** pDest ){ cwal_object * src = cwal_value_get_object( orig ); cwal_object * dest = NULL; cwal_value * destV; cwal_kvp const * kvp = NULL; int rc = 0; assert( orig && src && pDest ); if(CWAL_F_IS_VISITING & src->base.flags) return CWAL_RC_CYCLES_DETECTED; src->base.flags |= CWAL_F_IS_VISITING; #define RETURN(RC) rc = (RC); goto end dest = cwal_new_object(e); if( NULL == dest ) { RETURN(CWAL_RC_OOM); } destV = CWAL_VALPART(dest); for( kvp = src->base.kvp; kvp; kvp = kvp->right ){ cwal_value * key = 0; cwal_value * val = 0; assert( kvp->key && (kvp->key->refcount>0) ); rc = cwal_value_clone_ref(e, kvp->key, &key); if(key) rc = cwal_value_clone_ref(e, kvp->value, &val); if(!rc) { assert(key); assert(val); rc = cwal_kvp_set_v( e, destV, &dest->base.kvp, key, val, CWAL_VAR_F_PRESERVE ); } if(rc) { cwal_value_unref2(e, key); if(key) cwal_value_unref2(e, key); if(val) cwal_value_unref2(e, val); break; } } end: src->base.flags &= ~CWAL_F_IS_VISITING; if(!rc){ if( dest ){ assert(0 == rc); *pDest = destV; } }else if(destV){ cwal_value_unref2(e, destV); } return rc; #undef RETURN } int cwal_value_clone2( cwal_value * orig, cwal_value ** dest ){ cwal_engine * e = CWAL_VENGINE(orig); int rc = CWAL_RC_OK; cwal_value * vrc = 0; cwal_scope * scopeCheck = e ? e->current : NULL; char setProto = 0; if(!e || !orig || !dest) return CWAL_RC_MISUSE; switch( orig->vtab->typeID ){ case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL: case CWAL_TYPE_BOOL: vrc = orig; break; #if TRY_CLONE_SHARING case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_STRING: vrc = orig; break; #else case CWAL_TYPE_INTEGER: vrc = cwal_new_integer(e, cwal_value_get_integer(orig)); if(!vrc) rc = CWAL_RC_OOM; break; case CWAL_TYPE_DOUBLE: vrc = cwal_new_double(e, cwal_value_get_double(orig)); if(!vrc) rc = CWAL_RC_OOM; break; case CWAL_TYPE_STRING:{ cwal_string const * str = cwal_value_get_string(orig); vrc = cwal_new_string_value(e, cwal_string_cstr(str), cwal_string_length_bytes(str)); if(!vrc) rc = CWAL_RC_OOM; break; } #endif case CWAL_TYPE_ARRAY: setProto = 1; rc = cwal_value_clone_array( e, orig, &vrc ); break; case CWAL_TYPE_OBJECT: setProto = 1; rc = cwal_value_clone_object( e, orig, &vrc ); break; /** Missing: EXCEPTION: todo BUFFER: todo NATIVE: cannot do these because we cannot know how to deal with the finalizers (we can't just copy the finalizer and the native state pointer and expect it to work). We _could_ check if they have implemented a clone operator (member function) and call that, but in doing so we might recursively call back into the function which led here. */ case CWAL_TYPE_BUFFER:{ cwal_buffer * borig = cwal_value_get_buffer(orig); cwal_buffer * b = cwal_new_buffer(e, borig->used+1); if(!b) rc = CWAL_RC_OOM; else{ cwal_buffer_append(e, b, borig->mem, borig->used); assert(b->used == borig->used); b->mem[b->used] = 0; vrc = cwal_buffer_value(b); } break; } default: rc = CWAL_RC_TYPE; break; } if(vrc){ assert(0 == rc); assert((scopeCheck==vrc->scope) || V_IS_BUILTIN(vrc) || ((e->flags & CWAL_FEATURE_INTERN_STRINGS) && CWAL_STR(vrc)) ); *dest = vrc; if(setProto){ cwal_value_prototype_set( vrc, cwal_value_prototype_get( e, orig ) ); } }else if(!rc){ assert( (NULL == vrc) && "vrc is NULL but rc says OK." ); rc = CWAL_RC_OOM; } return rc; } cwal_value * cwal_value_clone( cwal_value * orig ){ cwal_value * rc = 0; cwal_value_clone2( orig, &rc ); return rc; } int cwal_int_to_cstr( cwal_int_t iVal, char * dest, cwal_size_t * nDest ){ enum { BufLen = 30 }; /* int isNegative = 0; */ char zBuf[BufLen]; char *z = zBuf; int zLen; if( !dest || !nDest ) return CWAL_RC_MISUSE; #if 1 zLen = sprintf(z, "%"CWAL_INT_T_PFMT, iVal); assert(zLen>0); if( *nDest < zLen ) return CWAL_RC_RANGE; *nDest = (cwal_size_t)zLen; memcpy( dest, z, zLen + 1/*NUL byte*/ ); return 0; #else /*Implementation taken from th1 sources. Breaks on INT_MIN, resulting in garbage. */ if( iVal<0 ){ isNegative = 1; iVal = iVal * -1; } *(--z) = '\0'; *(--z) = (char)(48+(iVal%10)); while( (iVal = (iVal/10))>0 ){ *(--z) = (char)(48+(iVal%10)); assert(z>zBuf); } if( isNegative ){ *(--z) = '-'; } zLen = zBuf+BufLen-z - 1/*NUL byte*/; if( *nDest <= zLen ) return CWAL_RC_RANGE; else{ *nDest = zLen; memcpy( dest, z, zLen + 1/*NUL byte*/ ); MARKER("int %"CWAL_INT_T_PFMT"==>string: <<<%s>>>\n", iVal, dest ); return CWAL_RC_OK; } #endif } int cwal_double_to_cstr( cwal_double_t fVal, char * dest, cwal_size_t * nDest ){ /*Implementation taken from th1 sources.*/ enum { BufLen = 128 }; cwal_size_t i; /* Iterator variable */ cwal_double_t v = fVal; /* Input value */ char zBuf[BufLen]; /* Output buffer */ char *z = zBuf; /* Output cursor */ cwal_size_t iDot = 0; /* Digit after which to place decimal point */ cwal_int_t iExp = 0; /* Exponent (NN in eNN) */ cwal_size_t zLen; /* Precision: */ #define INSIGNIFICANT 0.000000000001 #define ROUNDER 0.0000000000005 cwal_double_t insignificant = INSIGNIFICANT; /* If the real value is negative, write a '-' character to the * output and transform v to the corresponding positive number. */ if( v<0.0 ){ *z++ = '-'; v *= -1.0; } /* Normalize v to a value between 1.0 and 10.0. Integer * variable iExp is set to the exponent. i.e the original * value is (v * 10^iExp) (or the negative thereof). */ if( v>0.0 ){ while( (v+ROUNDER)>=10.0 ) { iExp++; v *= 0.1; } while( (v+ROUNDER)<1.0 ) { iExp--; v *= 10.0; } } v += ROUNDER; /* For a small (<12) positive exponent, move the decimal point * instead of using the "eXX" notation. */ if( iExp>0 && iExp<12 ){ iDot = iExp; iExp = 0; } /* For a small (>-4) negative exponent, write leading zeroes * instead of using the "eXX" notation. */ if( iExp<0 && iExp>-4 ){ *z++ = '0'; *z++ = '.'; for(i=0; i>(iExp+1); i--){ *z++ = '0'; } iDot = -1; iExp = 0; } /* Output the digits in real value v. The value of iDot determines * where (if at all) the decimal point is placed. */ for(i=0; i<=(iDot+1) || v>=insignificant; i++){ *z++ = (char)(48 + (int)v); v = (v - ((double)(int)v)) * 10.0; insignificant *= 10.0; if( iDot==i ){ *z++ = '.'; } } /* If the exponent is not zero, add the "eXX" notation to the * end of the string. */ if( iExp!=0 ){ char zExp[50]; /* String representation of iExp */ cwal_size_t zExpLen = sizeof(zExp)/sizeof(zExp[0]); *z++ = 'e'; cwal_int_to_cstr( iExp, zExp, &zExpLen ); for(i=0; zExp[i]; i++){ *z++ = zExp[i]; } } *(z++) = '\0'; zLen = z - zBuf - 1/*NUL byte*/; if( *nDest <= zLen ){ return CWAL_RC_RANGE; }else{ *nDest = zLen; memcpy( dest, zBuf, zLen + 1/*NUL byte*/ ); return CWAL_RC_OK; } #undef INSIGNIFICANT #undef ROUNDER } int cwal_cstr_to_int( char const * cstr, cwal_size_t slen, cwal_int_t * dest ){ cwal_int_t v = 0; cwal_int_t oflow = 0; char const * p = cstr+slen-1; cwal_int_t mult = 0; if(!cstr || !slen || !*cstr) return CWAL_RC_MISUSE; for( ; p >= cstr; --p, oflow = v ){ if( (*p<'0') || (*p>'9') ) { if(cstr == p){ if('-'==*p){ v = -v; break; } else if('+'==*p){ break; } } return CWAL_RC_TYPE; } v += mult ? (mult*(*p-'0')) : (*p-'0'); if(v < oflow) return CWAL_RC_RANGE; mult = mult ? (mult*10) : 10; } if(dest) *dest = v; return 0; } int cwal_string_to_int( cwal_string const * s, cwal_int_t * dest ){ return s ? cwal_cstr_to_int( cwal_string_cstr(s), CWAL_STRLEN(s), dest ) : CWAL_RC_MISUSE; } int cwal_cstr_to_double( char const * cstr, cwal_size_t slen, cwal_double_t * dest ){ int rc; char const * p = cstr+slen-1; cwal_int_t rhs = 0; cwal_int_t lhs = 0; cwal_double_t rmult = 0.0; if(!cstr || !slen || !*cstr) return CWAL_RC_MISUSE; for( ; (p > cstr) && ('.' != *p); --p ){ rmult = rmult ? (10*rmult) : 10; } if(p==cstr){ /* Maybe it's an integer */ if('.'==*p) return CWAL_RC_TYPE /* .1234 */; else { /* Try it as an integer */ rc = cwal_cstr_to_int( p, slen, &lhs ); if(!rc && dest) *dest = lhs; return rc; } } assert('.' == *p); rc = cwal_cstr_to_int( p+1, cstr+slen-p-1, &rhs ); if(rc) return rc; else if((p>cstr) && (rhs<0)) return CWAL_RC_TYPE /* 123.-456 */; rc = cwal_cstr_to_int( cstr, p - cstr, &lhs ); if(!rc && dest){ if(lhs>=0){ *dest = (cwal_double_t)lhs + (rmult ? (((cwal_double_t)rhs) / rmult) : 0); }else{ *dest = (cwal_double_t)lhs - (rmult ? (((cwal_double_t)rhs) / rmult) : 0); } } return rc; } int cwal_string_to_double( cwal_string const * s, cwal_double_t * dest ){ return s ? cwal_cstr_to_double( cwal_string_cstr(s), CWAL_STRLEN(s), dest ) : CWAL_RC_MISUSE; } void cwal_hash_clear( cwal_hash * h, char freeProps ){ cwal_scope * sc = h ? CWAL_VALPART(h)->scope : 0; cwal_engine * e = sc ? sc->e : 0; if(!e) return; else{ cwal_size_t i; cwal_kvp * kvp; cwal_kvp * next; if(freeProps){ cwal_cleanup_obase( e, &h->base, 0 ); } /* MARKER("Freeing hash @%p\n", (void const *)h); */ if(h->entryCount) for( i = 0; i < h->hashSize; ++i ){ kvp = h->list[i]; if(kvp){ next = h->list[i] = NULL; for( ; kvp; kvp = next ){ next = kvp->right; kvp->right = NULL; cwal_kvp_free(e, sc, kvp, 1); --h->entryCount; } } } assert(0==h->entryCount); memset(h->list, 0, h->hashSize*sizeof(cwal_value*)); } } void cwal_value_cleanup_hash( cwal_engine * e, void * V ){ cwal_value * vSelf = (cwal_value *)V; cwal_hash * h = CWAL_HASH(vSelf); /* MARKER("Freeing hash @%p\n", (void const *)h); */ cwal_cleanup_obase( e, &h->base, 1 ); cwal_hash_clear( h, 0 ); *h = cwal_hash_empty; } static int cwal_rescope_children_hash( cwal_value * v ){ cwal_hash * h = CWAL_HASH(v); cwal_scope * sc = v->scope; cwal_kvp * kvp; cwal_size_t i; int rc = 0; assert(sc && h && v); rc |= cwal_rescope_children_obase(v); if(rc){ assert(!"This should not be possible."); } /* cwal_dump_v(nv,"Re-scoping hashtable children..."); */ for( i = 0; !rc && (i < h->hashSize); ++i ){ kvp = h->list[i]; if(kvp){ cwal_kvp * next = NULL; for( ; kvp; kvp = next){ next = kvp->right; assert(kvp->key); assert(kvp->value); rc = cwal_value_xscope(sc->e, sc, kvp->key, 0); rc |= cwal_value_xscope(sc->e, sc, kvp->value, 0); /* cwal_dump_v(kvp->key,"Re-scoped key"); */ /* cwal_dump_v(kvp->value,"Re-scoped value"); */ } } } return rc; } cwal_value * cwal_new_hash_value( cwal_engine * e, cwal_size_t hashSize){ if(!e || !hashSize) return NULL; else { cwal_size_t const tableSize = (hashSize*sizeof(cwal_kvp*)); cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_HASH, tableSize); cwal_hash * h; cwal_value * proto; if(!v) return NULL; assert(v->scope); h = CWAL_HASH(v); proto = h->base.prototype; *h = cwal_hash_empty; h->base.prototype = proto; h->hashSize = hashSize; h->list = (cwal_kvp**)((unsigned char *)(h+1)); memset( h->list, 0, tableSize ); return v; } } char cwal_value_is_hash( cwal_value const * v ){ return NULL != CWAL_HASH(v); } cwal_hash * cwal_value_get_hash( cwal_value * v ){ return CWAL_HASH(v); } cwal_hash * cwal_new_hash( cwal_engine * e, cwal_size_t hashSize ){ cwal_value * v = cwal_new_hash_value(e, hashSize); return v ? CWAL_HASH(v) : NULL; } cwal_value * cwal_hash_value( cwal_hash * h ){ return CWAL_VALPART(h); } /** @internal Internal impl for cwal_hash_search(). If tableIndex is not NULL then *tableIndex is assigned to the hash table index for the given key, even on search failure. If a match is found then its kvp entry is returned and if left is not NULL then *left will point to the kvp immediately to the left of the returned kvp (required for re-linking). This function is intollerant of NULLs for (h,key). */ static cwal_kvp * cwal_hash_search_impl( cwal_hash * h, cwal_value * key, cwal_hash_t * tableIndex, cwal_kvp ** left ){ cwal_hash_t const ndx = cwal_value_hash( key ) % h->hashSize /* Reminder to self: the hash code is the reason we cannot add a strict comparison toggle - such comparisons would only be made if hash codes collided. e.g. the hashes for "1" and 1 and 1.0 will be different, even though the values are equivalent using the default comparison ops. */ ; cwal_kvp * kvp = h->list[ndx]; cwal_type_id const kType = cwal_value_type_id(key); if(tableIndex) *tableIndex = ndx; for( ; kvp; (left?(*left=kvp):NULL), kvp = kvp->right ){ assert(kvp->key); if(kvp->key==key) return kvp; else if(kType != cwal_value_type_id(kvp->key)){ continue; } else if(0==cwal_value_compare(key, kvp->key)){ return kvp; } } return NULL; } cwal_value * cwal_hash_search( cwal_hash * h, char const * key, cwal_size_t keyLen ){ cwal_hash_t ndx; cwal_kvp * kvp; cwal_string * sk; if(!h || !key) return NULL; if(!keyLen && *key) keyLen = strlen(key); ndx = cwal_hash_cstr( key, keyLen ) % h->hashSize; kvp = h->list[ndx]; for( ; kvp; kvp = kvp->right ){ assert(kvp->key); if(CWAL_TYPE_STRING != cwal_value_type_id(kvp->key)) continue; sk = cwal_value_get_string(kvp->key); if(keyLen != CWAL_STRLEN(sk)) continue; else if(0==cwal_compare_str_cstr(sk, key, keyLen)){ return kvp->value; } } return NULL; } cwal_value * cwal_hash_search_v( cwal_hash * h, cwal_value * key ){ cwal_kvp * kvp = (h && key) ? cwal_hash_search_impl(h, key, NULL, NULL) : NULL; return kvp ? kvp->value : NULL; } int cwal_hash_insert_v( cwal_hash * h, cwal_value * key, cwal_value * v, char allowOverwrite ){ cwal_size_t ndx = 0; cwal_kvp * left = 0; cwal_kvp * kvp; cwal_value * hv = CWAL_VALPART(h); if(!hv || !key || !v) return CWAL_RC_MISUSE; else if(CWAL_VAL_IS_VISITING(hv)) return CWAL_RC_ACCESS; kvp = cwal_hash_search_impl( h, key, &ndx, &left ); if(kvp){ if(!allowOverwrite) return CWAL_RC_ALREADY_EXISTS; if(kvp->key != key){ cwal_value * old = kvp->key; kvp->key = key; cwal_value_xscope(hv->scope->e, hv->scope, key, 0); cwal_value_ref(key); cwal_value_unref(old); } if(kvp->value != v){ cwal_value * old = kvp->value; kvp->value = v; cwal_value_xscope(hv->scope->e, hv->scope, v, 0); cwal_value_ref(v); cwal_value_unref(old); } }else{ cwal_engine * e = CWAL_VALPART(h)->scope->e; kvp = cwal_kvp_alloc(e); if(!kvp) return CWAL_RC_OOM; assert(!kvp->right); if(left){ kvp->right = left->right; left->right = kvp; }else{ h->list[ndx] = kvp; } kvp->key = key; kvp->value = v; cwal_value_xscope(hv->scope->e, hv->scope, key, 0); cwal_value_xscope(hv->scope->e, hv->scope, v, 0); cwal_value_ref(key); cwal_value_ref(v); ++h->entryCount; } return 0; } int cwal_hash_insert( cwal_hash * h, char const * key, cwal_size_t keyLen, cwal_value * v, char allowOverwrite ){ cwal_value * check; if(!h || !key || !v) return CWAL_RC_MISUSE; else if(CWAL_VAL_IS_VISITING(CWAL_VALPART(h))) return CWAL_RC_ACCESS; else if(!keyLen && *key) keyLen = strlen(key); check = cwal_hash_search(h, key, keyLen); if(check && !allowOverwrite) return CWAL_RC_ALREADY_EXISTS; else { cwal_value * hv = CWAL_VALPART(h); check = cwal_new_string_value(CWAL_VENGINE(hv), key, keyLen); if(!check) return CWAL_RC_OOM; else { int rc = cwal_hash_insert_v( h, check, v, allowOverwrite ); if(rc) cwal_value_unref(check); return rc; } } } int cwal_hash_remove( cwal_hash * h, cwal_value * key ){ if(!h || !key) return CWAL_RC_MISUSE; else if(CWAL_VAL_IS_VISITING(CWAL_VALPART(h))) return CWAL_RC_ACCESS; else{ cwal_size_t ndx = 0; cwal_kvp * left = 0; cwal_kvp * kvp = cwal_hash_search_impl( h, key, &ndx, &left ); cwal_value * vSelf = CWAL_VALPART(h); cwal_engine * e = vSelf->scope->e; cwal_scope * sc = vSelf->scope; if(!kvp) return CWAL_RC_NOT_FOUND; else{ if(left){ left->right = kvp->right; }else{ h->list[ndx] = kvp->right; } kvp->right = NULL; --h->entryCount; cwal_kvp_free(e, sc, kvp, 1); return 0; } } } cwal_size_t cwal_hash_entry_count(cwal_hash *h){ return h ? h->entryCount : 0; } int cwal_hash_visit_kvp( cwal_hash * h, cwal_kvp_visitor_f f, void * state ){ cwal_value * hv = (h && f) ? CWAL_VALPART(h) : NULL; cwal_obase * b = CWAL_VOBASE(hv); if(!hv || !f) return CWAL_RC_MISUSE; else if(!b) return CWAL_RC_TYPE; else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_CYCLES_DETECTED; else { cwal_kvp * kvp; cwal_size_t i; int rc = CWAL_RC_OK; cwal_value_set_visiting( hv, 1 ); for( i = 0; !rc && (i < h->hashSize); ++i ){ kvp = (cwal_kvp*)h->list[i]; if(!kvp) continue; else{ cwal_kvp * next = 0; for( ; !rc && kvp; kvp = next ){ next = kvp->right; rc = (CWAL_VAR_F_HIDDEN & kvp->flags) ? 0 : f( kvp, state ); } } } cwal_value_set_visiting( hv, 0 ); return rc; } } /** mode: 1==visit the keys, 0==the values. */ static int cwal_hash_visit_value_impl( cwal_hash * h, char mode, cwal_value_visitor_f f, void * state ){ cwal_value * hv = (h && f) ? CWAL_VALPART(h) : NULL; cwal_obase * b = CWAL_VOBASE(hv); if(!hv || !f) return CWAL_RC_MISUSE; else if(!b) return CWAL_RC_TYPE; else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_CYCLES_DETECTED; else { cwal_kvp * kvp; cwal_size_t i; int rc = CWAL_RC_OK; rc = cwal_value_set_visiting( hv, 1 ); assert(0==rc); for( i = 0; !rc && (i < h->hashSize); ++i ){ kvp = (cwal_kvp*)h->list[i]; if(!kvp) continue; else{ cwal_kvp * next = 0; for( ; !rc && kvp; kvp = next ){ next = kvp->right; rc = (CWAL_VAR_F_HIDDEN & kvp->flags) ? 0 : f( mode ? kvp->value : kvp->key, state ); } } } rc = cwal_value_set_visiting( hv, 0 ); assert(rc==0); return rc; } } int cwal_hash_visit_keys( cwal_hash * h, cwal_value_visitor_f f, void * state ){ return (!h || !f) ? CWAL_RC_MISUSE : cwal_hash_visit_value_impl(h, 0, f, state); } int cwal_hash_visit_values( cwal_hash * h, cwal_value_visitor_f f, void * state ){ return (!h || !f) ? CWAL_RC_MISUSE : cwal_hash_visit_value_impl(h, 1, f, state); } void cwal_value_cleanup_native( cwal_engine * e, void * V ){ cwal_value * v = (cwal_value *)V; cwal_native * n = v ? CWAL_V2NATIVE(v) : 0; assert(v && n); #if 0 if(n->native){ cwal_weak_unregister(e, n->native, CWAL_TYPE_UNDEF ); } #endif if(n->finalize){ n->finalize( e, n->native ); n->finalize = NULL; n->native = NULL; } cwal_cleanup_obase( e, &n->base, 1 ); *n = cwal_native_empty; } cwal_value * cwal_new_native_value( cwal_engine * e, void * N, cwal_finalizer_f dtor, void const * typeID ){ if(!e || !N || !typeID) return NULL; else{ cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_NATIVE, 0); if( NULL != v ){ cwal_native * n = CWAL_V2NATIVE(v); cwal_value * proto = n->base.prototype; #if 0 cwal_weak_annotate(e, N); #endif *n = cwal_native_empty; n->base.prototype = proto; n->native = N; n->finalize = dtor; n->typeID = typeID; } return v; } } int cwal_native_set_rescoper( cwal_native * nv, int (*rescoper)(cwal_native *)){ if(!nv) return CWAL_RC_MISUSE; else { nv->rescope_children = rescoper; return 0; } } int cwal_value_fetch_native( cwal_value const * val, cwal_native ** ar){ if( ! val ) return CWAL_RC_MISUSE; else if( CWAL_TYPE_NATIVE != val->vtab->typeID ) return CWAL_RC_TYPE; else{ if(ar) *ar = CWAL_V2NATIVE(val); return 0; } } int cwal_native_fetch( cwal_native const * n, void const * typeID, void ** dest){ if( !n) return CWAL_RC_MISUSE; else if(!typeID || n->typeID==typeID){ if(dest) *dest = n->native; return 0; }else return CWAL_RC_TYPE; } void * cwal_native_get( cwal_native const * n, void const * typeID){ void * x = NULL; cwal_native_fetch( n, typeID, &x ); return x; } void cwal_native_clear( cwal_native * n, char callFinalizer ){ if(n && n->native){ if(callFinalizer && n->finalize){ cwal_value * nv = CWAL_VALPART(n); assert(nv->scope && nv->scope->e); n->finalize( nv->scope->e, n->native ); } n->native = NULL; n->finalize = NULL; n->typeID = NULL; } } cwal_value * cwal_native_value( cwal_native const * n ){ return n ? CWAL_VALPART(n) : 0; } cwal_native * cwal_new_native( cwal_engine * e, void * n, cwal_finalizer_f dtor, void const * typeID ){ cwal_value * v = cwal_new_native_value( e, n, dtor, typeID ); return v ? CWAL_V2NATIVE(v) : 0; } cwal_native * cwal_value_get_native( cwal_value const * v ) { cwal_native * ar = NULL; cwal_value_fetch_native( v, &ar ); return ar; } cwal_size_t cwal_engine_recycle_max_get( cwal_engine * e, cwal_type_id type ){ if(!e) return 0; else switch(type){ case CWAL_TYPE_STRING: return e->reString.maxLength; case CWAL_TYPE_WEAK_REF: return e->reWeak.maxLength; default:{ int const ndx = cwal_recycler_index( type ); return (ndx<0) ? 0 : e->recycler[ndx].maxLength; } } } int cwal_engine_recycle_max( cwal_engine * e, cwal_type_id typeID, cwal_size_t max ){ if(!e) return CWAL_RC_MISUSE; else if( CWAL_TYPE_UNDEF == typeID ){ int rc = 0; #define DO(T) rc = cwal_engine_recycle_max(e, CWAL_TYPE_##T, max ); if(rc) return rc DO(ARRAY); DO(BUFFER); DO(DOUBLE); DO(EXCEPTION); DO(FUNCTION); DO(INTEGER); DO(KVP); DO(NATIVE); DO(OBJECT); DO(SCOPE); DO(STRING); DO(WEAK_REF); #undef DO return rc; } else { cwal_recycler * li; void * mem; switch(typeID){ case CWAL_TYPE_STRING: li = &e->reString; break; case CWAL_TYPE_WEAK_REF: li = &e->reWeak; break; default:{ int const ndx = cwal_recycler_index( typeID ); if( ndx < 0 ) return CWAL_RC_TYPE; li = &e->recycler[ndx]; } } li->maxLength = max; while( li->count > max ){ /* If we're over our limit, give them back... */ assert(li->list); switch(typeID){ case CWAL_TYPE_WEAK_REF:{ cwal_weak_ref * r = (cwal_weak_ref *)li->list; mem = r; li->list = r->next; r->next = NULL; break; } case CWAL_TYPE_KVP:{ cwal_kvp * kvp = (cwal_kvp *)li->list; mem = kvp; li->list = kvp->right; kvp->right = 0; break; } default: mem = (cwal_value *)li->list; li->list = cwal_value_snip((cwal_value *)li->list); break; } --li->count; cwal_free( e, mem ); } return CWAL_RC_OK; } } /** Ensures that s->props is initialized. Returns 0 on success, non-0 on error (which is serious and must not be ignored). After a successful call it "cannot fail." */ static int cwal_scope_init_props(cwal_scope *s){ if(s->props) return 0; assert( s->e ); s->props = cwal_new_object(s->e); if(s->props){ cwal_value * pv; pv = cwal_object_value(s->props); cwal_value_ref2(s->e, pv) /* we do this so that sweep() cannot wipe it out. Yes, i've actually see this object get swept up before. */ ; if(pv->scope->level > s->level){ cwal_value_xscope(s->e, s, pv, NULL); } #if 1 if(s->props->base.prototype){ /** If we don't do this then cwal_scope_chain_search_v() and friends will resolve prototype members installed for the default prototype for s->proto's type by the client. It might be interesting to install our own prototype for these objects, but i'm not sure what we might do with it. */ cwal_value_prototype_set(pv, NULL); } #endif } return s->props ? 0 : CWAL_RC_OOM; } /** Temporary internal macro used to set up the engine/scope arguments for the cwal_var_xxx() family of functions. */ #define SETUP_VAR_E_S if(!e) e=s?s->e:0; if(!s)s=e?e->current:0; \ if(!s && !e) return CWAL_RC_MISUSE static int cwal_var_set_v_impl( cwal_engine * e, cwal_scope * s, cwal_value * key, cwal_value * v, char searchParents, uint16_t flags ){ if(!e || !key) return CWAL_RC_MISUSE; SETUP_VAR_E_S; else{ /* We need to take parent scopes into account when assigning a variable. This causes duplicate lookup of the var via cwal_kvp_set_v() (which also has to search). FIXME: add a setter which accounts for this stuff in one go. We need an internal cwal_var_get_kvp() which gives us the cwal_kvp associated with the var. */ cwal_scope * foundIn = 0; int rc = 0; cwal_value * got = 0; if(!v) searchParents = 0 /* arguable! */; rc = cwal_var_get_v( e, s, key, &got, searchParents, &foundIn ); if(CWAL_RC_NOT_FOUND==rc){ assert(!foundIn); if(!v) return rc; } else if(!rc){ assert(foundIn && foundIn->props); } else if(rc) return rc; else if(searchParents && s->parent #if 0 && !(CWAL_VAR_F_CONST & flags/*KLUDGE to avoid a loop!*/) #endif ){ rc = cwal_var_get_v( e, s, key, &got, searchParents, &foundIn ); if((CWAL_RC_NOT_FOUND==rc) && !v) { return 0; } else if(rc) return rc; else { assert(foundIn && foundIn->props); } } if(!foundIn){ foundIn = s; if(!s->props){ rc = cwal_scope_init_props(s); if(rc) return rc; } } assert(foundIn && foundIn->props); #if 0 MARKER("Setting [opaque key type] in scope#%"CWAL_SIZE_T_PFMT" via scope#%"CWAL_SIZE_T_PFMT".\n", foundIn->level, s->level); #endif return v ? cwal_kvp_set_v( e, cwal_object_value(foundIn->props), &foundIn->props->base.kvp, key, v, flags ) : cwal_kvp_unset_v( e, foundIn, &foundIn->props->base.kvp, key ) ; } } int cwal_var_set_v( cwal_engine * e, cwal_scope * s, cwal_value * key, cwal_value * v ){ return cwal_var_set_v_impl(e, s, key, v, 1, CWAL_VAR_F_PRESERVE); } int cwal_var_set_s( cwal_engine * e, cwal_scope * s, cwal_string * key, cwal_value * v ){ return cwal_var_set_v_impl(e, s, cwal_string_value(key), v, 1, CWAL_VAR_F_PRESERVE); } int cwal_var_set( cwal_engine * e, cwal_scope * s, char const * key, cwal_size_t keyLen, cwal_value * v ){ if(!key) return CWAL_RC_MISUSE; SETUP_VAR_E_S; else{ cwal_string * sKey; cwal_value * sv; int rc; sKey = cwal_new_string(e, key, keyLen); if(!s) return CWAL_RC_OOM; sv = cwal_string_value(sKey); rc = cwal_var_set_v_impl( e, s, sv, v, 1, CWAL_VAR_F_PRESERVE ); /* Note that i "would very much like" to generically unref sKey now, but interning of values messes with that. */ if(rc #if 0 || (/*special-case hack b/c we know there cannot be any other references in this case. i will likely regret this at some point. */ 2==sv->refcount /*1==s, 1==s->props*/) /* and indeed i did regret it. */ #endif ) { cwal_value_unref2( e, sv ); } return rc; } } cwal_scope * cwal_scope_parent( cwal_scope * s ){ return s ? s->parent : 0; } cwal_scope * cwal_scope_top( cwal_scope * s ){ for( ; s && s->parent; s = s->parent ){} return s; } cwal_value * cwal_scope_properties( cwal_scope * s ){ if(!s) return NULL; else if(!s->props) cwal_scope_init_props(s); return CWAL_VALPART(s->props); } cwal_value * cwal_scope_chain_search_v( cwal_scope * s, int upToDepth, cwal_value const * key, cwal_scope ** foundIn ){ if(!s || !key) return NULL; else if(!s->props && !upToDepth) return NULL; else { cwal_scope * os; cwal_value * v = 0; if( upToDepth<0 ){ assert(s->level); upToDepth = (int)(s->level-1); } for( os = s; os && (upToDepth>=0); --upToDepth ){ v = os->props ? cwal_prop_get_v( cwal_object_value(os->props), key ) : NULL; if(v){ if(foundIn) *foundIn = os; break; } else os = os->parent; } return v; } } cwal_value * cwal_scope_chain_search( cwal_scope * s, int upToDepth, char const * key, cwal_size_t keyLen, cwal_scope ** foundIn ){ if(!s || !key ) return NULL; else if(!s->props && !upToDepth) return NULL; else{ cwal_scope * os; cwal_value * v = 0; cwal_scope * _foundIn = 0; if(!foundIn) foundIn = &_foundIn; if( upToDepth<0 ){ assert(s->level); upToDepth = (int)(s->level - 1); } for( os = s; os && (upToDepth>=0); --upToDepth ){ v = os->props ? cwal_prop_get( cwal_object_value(os->props), key, keyLen ) : NULL; if(v){ *foundIn = os; break; } else os = os->parent; } return v; } } int cwal_scope_chain_set_v( cwal_scope * s, int upToDepth, cwal_value * k, cwal_value * v ){ if(!s || !k) return CWAL_RC_MISUSE; else { int rc; /* cwal_value * oldV = 0; */ cwal_scope * os = NULL; /* oldV = */cwal_scope_chain_search_v( s, upToDepth, k, &os ); if(/*!oldV ||*/ !os){ os = s; } if(!os->props){ rc = cwal_scope_init_props( os ); if(rc) return rc; assert(os->props); } rc = cwal_prop_set_v( cwal_object_value(os->props), k, v ); return rc; } } int cwal_scope_chain_set( cwal_scope * s, int upToDepth, char const * k, cwal_size_t keyLen, cwal_value * v ){ if(!s || !k) return CWAL_RC_MISUSE; else { int rc; /* cwal_value * oldV = 0; */ cwal_scope * os = NULL; /* oldV = */cwal_scope_chain_search( s, upToDepth, k, keyLen, &os ); if(/*!oldV ||*/ !os) os = s; if(!os->props){ rc = cwal_scope_init_props( os ); if(rc) return rc; assert(os->props); } rc = cwal_prop_set( cwal_object_value(os->props), k, keyLen, v ); return rc; } } int cwal_var_decl_v( cwal_engine * e, cwal_scope * s, cwal_value * key, cwal_value * v, uint16_t flags ){ if(!e || !key) return CWAL_RC_MISUSE; SETUP_VAR_E_S; else{ int const rc = cwal_scope_init_props(s); return rc ? rc : (cwal_kvp_search_v(s->props->base.kvp, key, 0, 0) ? CWAL_RC_ALREADY_EXISTS : cwal_var_set_v_impl( e, s, key, v ? v : cwal_value_undefined(), 0, flags)); } } int cwal_var_decl_s( cwal_engine * e, cwal_scope * s, cwal_string * key, cwal_value * v, uint16_t flags ){ return cwal_var_decl_v( e, s, cwal_string_value(key), v, flags ); } int cwal_var_decl( cwal_engine * e, cwal_scope * s, char const * key, cwal_size_t keyLen, cwal_value * v, uint16_t flags ){ if(!e || !key || !*key) return CWAL_RC_MISUSE; SETUP_VAR_E_S; else { int rc; if(!keyLen) keyLen = strlen(key); rc = cwal_scope_init_props(s); if(rc) return rc; else if( cwal_kvp_search(s->props->base.kvp, key, keyLen, 0, 0) ) { return CWAL_RC_ALREADY_EXISTS; } else{ cwal_value * skey = cwal_new_string_value(e, key, keyLen); if(!skey) return CWAL_RC_OOM; rc = cwal_var_set_v_impl( e, s, skey, v ? v : cwal_value_undefined(), 0, flags); if(rc) cwal_value_unref(skey); return rc; } } } int cwal_var_unset_v( cwal_engine * e, cwal_scope * s, cwal_value * key ){ return cwal_var_set_v_impl( e, s, key, 0, 0, 0 ); } int cwal_var_unset_s( cwal_engine * e, cwal_scope * s, cwal_string * key ){ return cwal_var_unset_v( e, s, cwal_string_value(key) ); } int cwal_var_unset( cwal_engine * e, cwal_scope * s, char const * key, cwal_size_t keyLen ){ return cwal_var_set( e, s, key, keyLen, 0 ); } int cwal_var_get( cwal_engine * e, cwal_scope * s, char const * key, cwal_size_t keyLen, cwal_value ** v, char searchParents, cwal_scope ** containingScope ){ if(!e || !key) return CWAL_RC_MISUSE; SETUP_VAR_E_S; else if(!s->props && (!searchParents || !s->parent)) return CWAL_RC_NOT_FOUND; else{ cwal_scope * foundIn = 0; cwal_kvp * kvp; if(!keyLen) keyLen = strlen(key); kvp = (s->props && s->props->base.kvp) ? cwal_kvp_search( s->props->base.kvp, key, keyLen, NULL, NULL ) : 0; if(kvp) foundIn = s; else if(searchParents && s->parent){ cwal_scope * p; int rc = 0; for( p = s->parent; p && (0==rc); p = p->parent ){ if(!p->props || !p->props->base.kvp) continue; kvp = cwal_kvp_search( p->props->base.kvp, key, keyLen, 0, 0 ); if(kvp){ foundIn = p; break; } } if(rc) return rc; } if(!kvp) return CWAL_RC_NOT_FOUND; else{ assert(foundIn); if(v) *v = kvp->value; #if 0 /* This breaks w/ s1 func calls when a param names match a deeper scope's var. */ /* nonono - assignment over the parent value would not be seen here. We need a mechanism for linking vars like TH1 has. Or we can at least apply this hack for CONST vars, which we "know" cannot be overwritten... */ if((s != foundIn) && (CWAL_VAR_F_CONST & kvp->flags)){ CWAL_TR_S(e,s); CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK, "Setting local var alias for CONST value found in parent."); /* Add an alias in this scope for future reference */ cwal_var_set_s_impl( e, s, cwal_value_get_string(kvp->key), kvp->value, 0, kvp->flags ) /* If it fails, no big deal for this case. */; } #endif if(containingScope) *containingScope = foundIn; return 0; } } } int cwal_var_get_v( cwal_engine * e, cwal_scope * s, cwal_value * key, cwal_value ** v, char searchParents, cwal_scope ** containingScope ){ SETUP_VAR_E_S; else if(!key) return CWAL_RC_MISUSE; else if(!s->props && (!searchParents || !s->parent)) return CWAL_RC_NOT_FOUND; else{ cwal_scope * foundIn = 0; cwal_kvp * kvp; kvp = (s->props && s->props->base.kvp) ? cwal_kvp_search_v( s->props->base.kvp, key, NULL, NULL ) : 0; if(kvp) foundIn = s; else if(searchParents && s->parent){ cwal_scope * p; int rc = 0; for( p = s->parent; p && (0==rc); p = p->parent ){ if(!p->props || !p->props->base.kvp) continue; kvp = cwal_kvp_search_v( p->props->base.kvp, key, 0, 0 ); if(kvp){ foundIn = p; break; } } if(rc) return rc; } if(!kvp) return CWAL_RC_NOT_FOUND; else{ assert(foundIn); if(v) *v = kvp->value; if(containingScope) *containingScope = foundIn; return 0; } } } int cwal_var_get_s( cwal_engine * e, cwal_scope * s, cwal_string * key, cwal_value ** v, char searchParents, cwal_scope ** containingScope ){ return cwal_var_get_v( e, s, cwal_string_value(key), v, searchParents, containingScope ); } #undef SETUP_VAR_E_S cwal_hash_t cwal_value_hash_null_undef( cwal_value const * v ){ return (cwal_hash_t) ((CWAL_TYPE_NULL==v->vtab->typeID) ? -1 : -2); } cwal_hash_t cwal_value_hash_bool( cwal_value const * v ){ return (cwal_hash_t) (CWAL_BOOL(v) ? -4 : -3); } cwal_hash_t cwal_value_hash_int( cwal_value const * v ){ return (cwal_hash_t) *CWAL_INT(v); } cwal_hash_t cwal_value_hash_double( cwal_value const * v ){ return (cwal_hash_t) *CWAL_DBL(v); } cwal_hash_t cwal_value_hash_string( cwal_value const * v ){ cwal_string const * s = CWAL_STR(v); assert(s); return cwal_hash_cstr( cwal_string_cstr(s), CWAL_STRLEN(s) ); } cwal_hash_t cwal_value_hash_ptr( cwal_value const * v ){ #if CWAL_VOID_PTR_IS_BIG /* IF THE DEBUGGER LEADS YOU NEAR HERE... try changing ptr_int_t back to uint64_t. */ typedef uint64_t ptr_int_t; #else typedef uint32_t ptr_int_t; #endif return (cwal_hash_t)((ptr_int_t)v); } cwal_hash_t cwal_value_hash( cwal_value const * v ){ return (v && v->vtab && v->vtab->hash) ? v->vtab->hash(v) : 0; } int cwal_value_compare( cwal_value const * lhs, cwal_value const * rhs ){ if(lhs == rhs) return 0; else if(!lhs) return -1; else if(!rhs) return 1; else return lhs->vtab->compare( lhs, rhs ); } #define COMPARE_TYPE_IDS(L,R) ((L)->vtab->typeID - (R)->vtab->typeID) /* (((L)->vtab->typeID < (R)->vtab->typeID) ? -1 : 1) */ int cwal_value_cmp_ptr_only( cwal_value const * lhs, cwal_value const * rhs ){ if(lhs==rhs) return 0; else if(lhs->vtab->typeID==rhs->vtab->typeID) return ((void const*)lhs < (void const *)rhs) ? -1 : 1 /* Why do this? Because it at least provides consistent ordering for two different instances within a given value's lifetime. i can't think of any saner alternative :/. */ ; else return COMPARE_TYPE_IDS(lhs,rhs); } int cwal_value_cmp_func( cwal_value const * lhs, cwal_value const * rhs ){ if(lhs==rhs) return 0; else if(lhs->vtab->typeID!=rhs->vtab->typeID) return COMPARE_TYPE_IDS(lhs,rhs); else { cwal_function const * l = cwal_value_get_function(lhs); cwal_function const * r = cwal_value_get_function(rhs); assert(l); if(!l) return r ? -1 : 0; else if(!r) return 1; else if((l->callback == r->callback) && (l->state.data==r->state.data)) return 0; else return ((void const*)lhs < (void const *)rhs) ? -1 : 1 /* see comments in cwal_value_cmp_ptr_only() */; } } int cwal_compare_cstr( char const * s1, cwal_size_t len1, char const * s2, cwal_size_t len2 ){ if(!len1) return len2 ? -1 : 0; else if(!len2) return 1; else if(!s1) return s2 ? -1 : 0; else if(!s2) return 1; else { cwal_size_t const max = (len1vtab->typeID==rhs->vtab->typeID) ? 0 : COMPARE_TYPE_IDS(lhs,rhs); } int cwal_value_cmp_string( cwal_value const * lhs, cwal_value const * rhs ){ cwal_string const * s = CWAL_STR(lhs); assert(s && "lhs is not-a string"); switch(rhs->vtab->typeID){ #if 0 case CWAL_TYPE_OBJECT: case CWAL_TYPE_ARRAY: case CWAL_TYPE_FUNCTION: case CWAL_TYPE_NATIVE: case CWAL_TYPE_BUFFER: case CWAL_TYPE_EXCEPTION: /* Seems to be how JS does it. */ return CWAL_STRLEN(s) ? -1 : 1; #endif case CWAL_TYPE_STRING:{ cwal_string const * r = CWAL_STR(rhs); return cwal_compare_cstr( cwal_string_cstr(s), CWAL_STRLEN(s), cwal_string_cstr(r), CWAL_STRLEN(r) ); } case CWAL_TYPE_BOOL: /* BOOL here is sooo arguable. */ case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE:{ /* FIXME: for number-string conversions it would make more sense to do it the other way around: convert the string to a number, then compare. That way we have no problems with trailing 0's and whatnot. */ enum { BufSize = 100 }; char buf[BufSize] = {0,}; cwal_size_t sz = BufSize; switch(rhs->vtab->typeID){ case CWAL_TYPE_BOOL: /* FIXME? Use "true" and "false" instead of 0 and 1? */ case CWAL_TYPE_INTEGER: cwal_int_to_cstr( cwal_value_get_integer(rhs), buf, &sz); break; case CWAL_TYPE_DOUBLE: cwal_double_to_cstr( cwal_value_get_double(rhs), buf, &sz); break; default: assert(!"CANNOT HAPPEN"); break; } return cwal_compare_cstr( cwal_string_cstr(s), CWAL_STRLEN(s), buf, sz ); } default: return COMPARE_TYPE_IDS(lhs,rhs); } } int cwal_value_cmp_bool( cwal_value const * lhs, cwal_value const * rhs ){ assert(CWAL_TYPE_BOOL==lhs->vtab->typeID); switch( rhs->vtab->typeID ){ case CWAL_TYPE_BOOL: /* reminder: all booleans point either to the same value (for true) or NULL (for false). */ return (lhs==rhs) ? 0 : (CWAL_BOOL(lhs) ? 1 : -1); case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE:{ char const l = CWAL_BOOL(lhs); char const r = cwal_value_get_bool(rhs); return l==r ? 0 : (lvtab->compare(rhs, lhs)); #if 0 case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL: return -1; #endif default: return COMPARE_TYPE_IDS(lhs,rhs); }; } #if 0 int cwal_value_cmp_val_bool( cwal_value const * lhs, cwal_value const * rhs ){ cwal_value const * bl = cwal_value_get_bool(lhs) ? cwal_value_true() : cwal_value_false(); return cwal_value_cmp_bool(bl, rhs); } #endif int cwal_value_cmp_int( cwal_value const * lhs, cwal_value const * rhs ){ assert(CWAL_TYPE_INTEGER==lhs->vtab->typeID); switch( rhs->vtab->typeID ){ case CWAL_TYPE_BOOL: case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE:{ cwal_int_t const l = *CWAL_INT(lhs); cwal_int_t const r = cwal_value_get_integer(rhs); return l==r ? 0 : (lvtab->compare(rhs, lhs)); #if 0 case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL:{ cwal_int_t const l = *CWAL_INT(lhs); return (l==0) ? 0 : (l<0 ? -1 : 1) /* in xemacs's font that looks like one's instead of ell's. */ ; } #endif default: return COMPARE_TYPE_IDS(lhs,rhs); }; } int cwal_value_cmp_double( cwal_value const * lhs, cwal_value const * rhs ){ assert(CWAL_TYPE_DOUBLE==lhs->vtab->typeID); switch( rhs->vtab->typeID ){ case CWAL_TYPE_BOOL: case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE:{ cwal_double_t const l = *CWAL_DBL(lhs); cwal_double_t const r = cwal_value_get_double(rhs); return l==r ? 0 : (lvtab->compare(rhs, lhs)); #if 0 case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL:{ cwal_double_t const l = *CWAL_DBL(lhs); return (l==0.0) ? 0 : (l<0.0 ? -1 : 1); } #endif default: return COMPARE_TYPE_IDS(lhs,rhs); }; } int cwal_buffer_fill_from( cwal_engine * e, cwal_buffer * dest, cwal_input_f src, void * state ) { int rc; enum { BufSize = 512 * 8 }; char rbuf[BufSize]; cwal_size_t total = 0; cwal_size_t rlen = 0; if( ! dest || ! src ) return CWAL_RC_MISUSE; dest->used = 0; while(1) { rlen = BufSize; rc = src( state, rbuf, &rlen ); if( rc ) break; total += rlen; if(totalcapacity < (total+1) ) { rc = cwal_buffer_reserve( e, dest, total + ((rlenmem + dest->used, rbuf, rlen ); dest->used += rlen; if( rlen < BufSize ) break; } if( !rc && dest->used ) { assert( dest->used < dest->capacity ); dest->mem[dest->used] = 0; } return rc; } int cwal_input_FILE( void * state, void * dest, cwal_size_t * n ){ FILE * f = (FILE*) state; if( ! state || ! n || !dest ) return CWAL_RC_MISUSE; else if( !*n ) return CWAL_RC_RANGE; *n = (cwal_size_t)fread( dest, 1, *n, f ); return !*n ? (feof(f) ? 0 : CWAL_RC_IO) : 0; } int cwal_buffer_fill_from_FILE( cwal_engine * e, cwal_buffer * dest, FILE * src ){ if(!e || !dest || !src) return CWAL_RC_MISUSE; else{ int const rc = cwal_buffer_fill_from( e, dest, cwal_input_FILE, src ); return rc; } } int cwal_buffer_fill_from_filename( cwal_engine * e, cwal_buffer * dest, char const * filename ){ if(!e || !dest || !filename || !*filename) return CWAL_RC_MISUSE; else{ int rc; FILE * src = (('-'==*filename)&&!*(filename+1)) ? stdin : fopen(filename,"rb"); if(!src) return CWAL_RC_IO; rc = cwal_buffer_fill_from( e, dest, cwal_input_FILE, src ); if(stdin!=src) fclose(src); return rc; } } int cwal_buffer_reserve( cwal_engine * e, cwal_buffer * buf, cwal_size_t n ){ if( ! buf ) return CWAL_RC_MISUSE; else if( 0 == n ){ cwal_free(e, buf->mem); *buf = cwal_buffer_empty; return 0; } else if( buf->capacity >= n ){ return 0; } else{ unsigned char * x = (unsigned char *)cwal_realloc( e, buf->mem, n ); if( ! x ) return CWAL_RC_OOM; memset( x + buf->used, 0, n - buf->used ); buf->mem = x; buf->capacity = n; return 0; } } cwal_size_t cwal_buffer_fill( cwal_buffer * buf, unsigned char c ){ if( !buf || !buf->capacity || !buf->mem ) return 0; else{ memset( buf->mem, c, buf->capacity ); return buf->capacity; } } void cwal_dump_interned_strings_table( cwal_engine * e, char showEntries, cwal_size_t includeStrings ){ enum { BufSize = 1024 }; char buf[BufSize] = {0,}; cwal_ptr_table * t = &e->interned; uint16_t i, x; int rc; cwal_output_f f = e->vtab->outputer.output; void * outState = e->vtab->outputer.state.data; uint32_t total = 0; unsigned int pageSize = t->hashSize*sizeof(void*); unsigned int pageCount = 0; cwal_ptr_page const * p; assert( NULL != f ); assert(0==buf[5]); assert(e && f); for( p = t->pg.head; p; p = p->next ) ++pageCount; rc = sprintf( buf, "Interned value table for engine@%p: ", (void*)e); #define RC (cwal_size_t)rc f( outState, buf, RC ); rc = sprintf( buf, " Page size=%"PRIu16" (%u bytes) " "count=%u (%u bytes)\n", t->hashSize, (unsigned)pageSize, (unsigned)pageCount, (unsigned)(pageSize * pageCount)); f( outState, buf, RC ); for( i = 0, p = t->pg.head; p; p = p->next, ++i ){ uint16_t seen = 0; rc = sprintf( buf, " Page #%"PRIu16": entry count=%"PRIu16"\n", i+1, p->entryCount ); f( outState, buf, RC ); if(!showEntries){ total += p->entryCount; continue; } for( x = 0; (x < t->hashSize); /*&& (seenentryCount);*/ ++x ){ cwal_value * v = (cwal_value *)p->list[x]; if(!v) continue; ++seen; rc = sprintf( buf, " #%"PRIu16":\t %s@%p refcount=%"CWAL_SIZE_T_PFMT, x, v->vtab->typeName, (void const *)v, v->refcount); f( outState, buf, RC ); if(includeStrings){ switch(v->vtab->typeID){ case CWAL_TYPE_STRING:{ cwal_string const * s = cwal_value_get_string(v); rc = sprintf(buf, " len=%"CWAL_SIZE_T_PFMT" bytes=[", CWAL_STRLEN(s)); f( outState, buf, RC ); if(includeStrings >= CWAL_STRLEN(s)){ f(outState, cwal_string_cstr(s), CWAL_STRLEN(s)); }else{ f(outState, cwal_string_cstr(s), includeStrings); f(outState, ">>>", 3); } f( outState, "]", 1 ); break; } case CWAL_TYPE_INTEGER: rc = sprintf(buf, " value=%"CWAL_INT_T_PFMT, cwal_value_get_integer(v)); f( outState, buf, RC ); break; case CWAL_TYPE_DOUBLE: rc = sprintf(buf, " value=%"CWAL_DOUBLE_T_PFMT, cwal_value_get_double(v)); f( outState, buf, RC ); break; default: break; } } f( outState, "\n", 1 ); } assert( (seen == p->entryCount) && "Mis-management of p->entryCount detected." ); if(seen){ total += seen; rc = sprintf( buf, " End Page #%"PRIu16" (%"PRIu16" entry(ies))\n", i+1, p->entryCount ); f( outState, buf, RC ); } } rc = sprintf(buf, " Total entry count=%"PRIu32"\n", total); f(outState, buf, RC ); #undef RC } void cwal_engine_tracer_close_FILE( void * filePtr ){ FILE * f = (FILE*)filePtr; if(f && (f!=stdout) && (f!=stderr) && (f!=stdin)){ fclose(f); } } #if CWAL_ENABLE_TRACE static char const * cwal_tr_cstr( cwal_trace_flags ev ){ switch(ev){ #define CASE(X) case CWAL_TRACE_##X: return #X CASE(NONE); CASE(ALL); CASE(GROUP_MASK); CASE(MEM_MASK); CASE(MEM_MALLOC); CASE(MEM_REALLOC); CASE(MEM_FREE); CASE(MEM_TO_RECYCLER); CASE(MEM_FROM_RECYCLER); CASE(MEM_TO_GC_QUEUE); CASE(VALUE_MASK); CASE(VALUE_CREATED); CASE(VALUE_SCOPED); CASE(VALUE_UNSCOPED); CASE(VALUE_CLEAN_START); CASE(VALUE_CLEAN_END); CASE(VALUE_CYCLE); CASE(VALUE_INTERNED); CASE(VALUE_UNINTERNED); CASE(VALUE_VISIT_START); CASE(VALUE_VISIT_END); CASE(VALUE_REFCOUNT); CASE(SCOPE_MASK); CASE(SCOPE_PUSHED); CASE(SCOPE_CLEAN_START); CASE(SCOPE_CLEAN_END); CASE(SCOPE_SWEEP_START); CASE(SCOPE_SWEEP_END); CASE(ENGINE_MASK); CASE(ENGINE_STARTUP); CASE(ENGINE_SHUTDOWN_START); CASE(ENGINE_SHUTDOWN_END); CASE(FYI_MASK); CASE(MESSAGE); CASE(ERROR_MASK); CASE(ERROR); }; assert(!"MISSING cwal_tr_cstr() ENTRY!"); return NULL; #undef CASE } #endif /*CWAL_ENABLE_TRACE*/ void cwal_engine_tracer_f_FILE( void * filePtr, cwal_trace_state const * ev ){ #if CWAL_ENABLE_TRACE FILE * f = (FILE*)filePtr; if(!f) f = stdout; fprintf(f, "%s\tengine@%p scope#%"CWAL_SIZE_T_PFMT"@%p", cwal_tr_cstr(ev->event), (void const *)ev->e, ev->scope ? ev->scope->level : 0, (void const *)ev->scope ); fprintf(f, "\t%s():%d", ev->cFunc, ev->cLine ); if(ev->msg && *ev->msg) fprintf( f, "\n\t%s", ev->msg ); fputc( '\n', f ); if(ev->value){ cwal_obase const * b = CWAL_VOBASE(ev->value); cwal_value const * v = ev->value; fprintf(f, "\t%s@%p", v->vtab->typeName, (void*)v); if(v->scope){ fprintf(f, "(->scope#%"CWAL_SIZE_T_PFMT"@%p)", v->scope->level, (void*)v->scope); } fprintf(f, " refCount=%"CWAL_SIZE_T_PFMT, v->refcount); if(b){ fprintf( f, " flags=%02x", b->flags ); #if 0 fprintf( f, " childCount=%"CWAL_SIZE_T_PFMT, b->list.count ); #endif } if( cwal_value_is_string(v) ){ const int truncLen = 16; cwal_string const * s = cwal_value_get_string(v); if(s && CWAL_STRLEN(s)){ fprintf( f, " strlen=%"CWAL_SIZE_T_PFMT, CWAL_STRLEN(s) ); if( CWAL_STRLEN(s) <= truncLen ){ fprintf( f, " bytes=[%s]", cwal_string_cstr(s) ); }else{ fprintf( f, " bytes=[%*.*s...] (truncated)", truncLen, truncLen, cwal_string_cstr(s) ); /*fprintf( f, " bytes=[%s]", cwal_string_cstr(s) );*/ } }else{ fprintf( f, " (STILL INITIALIZING!)" ); } }else if(cwal_value_is_integer(v)){ fprintf( f, " value=[%"CWAL_INT_T_PFMT"]",cwal_value_get_integer(v)); }else if(cwal_value_is_double(v)){ fprintf( f, " value=[%"CWAL_DOUBLE_T_PFMT"]", cwal_value_get_double(v)); }else if(cwal_value_is_bool(v)){ fprintf( f, " value=[%s]", cwal_value_get_bool(v) ? "true" : "false" ); } fputc( '\n', f ); } if(ev->memory||ev->memorySize){ fputc('\t',f); if( ev->memory) fprintf(f, "memory=%p ", ev->memory); if( ev->memorySize) fprintf(f, "(%"CWAL_SIZE_T_PFMT" bytes)", ev->memorySize); fputc('\n',f); } fflush(f); #endif } int32_t cwal_engine_feature_flags( cwal_engine * e, int32_t mask ){ if(!e) return -1; else{ int32_t const rc = e->flags & CWAL_FEATURE_MASK; if(-1!=mask){ e->flags &= ~CWAL_FEATURE_MASK; e->flags |= CWAL_FEATURE_MASK & mask; } /** If auto-interning was disabled, free up the table memory. */ if((CWAL_FEATURE_INTERN_STRINGS & e->flags) && !(CWAL_FEATURE_INTERN_STRINGS & rc)){ cwal_ptr_table_destroy( e, &e->interned ); } return rc; } } int32_t cwal_engine_trace_flags( cwal_engine * e, int32_t mask ){ #if !CWAL_ENABLE_TRACE return -1; #else if(!e) return -1; else if(-1==mask) return e->trace.mask; else { int32_t const rc = e->trace.mask; e->trace.mask = mask; return rc; } #endif } void cwal_trace( cwal_engine * e ){ #if CWAL_ENABLE_TRACE int32_t const m = e->trace.mask; /*MARKER("TRACE()? ev=%08x mask=%08x\n", e->trace.event, e->trace.mask);*/ if(! ((m & (CWAL_TRACE_GROUP_MASK&e->trace.event)) /*&& (e->trace.mask & (~CWAL_TRACE_GROUP_MASK&e->trace.event))*/ )){ goto end; } else if(!e->vtab->tracer.trace) goto end; else { if( e->trace.msg && !e->trace.msgLen ) e->trace.msgLen = strlen(e->trace.msg); e->vtab->tracer.trace( e->vtab->tracer.state, &e->trace ); /* fall through */ } end: e->trace = cwal_trace_state_empty; e->trace.e = e; e->trace.mask = m; assert(0==e->trace.msg); assert(0==e->trace.msgLen); #endif } char const * cwal_rc_cstr(int rc) { if(0 == rc) return "OK"; switch(rc){ #define C(N) case N: return #N C(CWAL_RC_OK); C(CWAL_RC_ERROR); C(CWAL_RC_OOM); C(CWAL_RC_FATAL); C(CWAL_RC_CONTINUE); C(CWAL_RC_BREAK); C(CWAL_RC_RETURN); C(CWAL_RC_EXIT); C(CWAL_RC_EXCEPTION); C(CWAL_RC_ASSERT); C(CWAL_RC_MISUSE); C(CWAL_RC_NOT_FOUND); C(CWAL_RC_ALREADY_EXISTS); C(CWAL_RC_RANGE); C(CWAL_RC_TYPE); C(CWAL_RC_UNSUPPORTED); C(CWAL_RC_ACCESS); C(CWAL_RC_CYCLES_DETECTED); C(CWAL_RC_DESTRUCTION_RUNNING); C(CWAL_RC_FINALIZED); C(CWAL_RC_HAS_REFERENCES); C(CWAL_RC_INTERRUPTED); C(CWAL_RC_CANCELLED); C(CWAL_RC_IO); C(CWAL_RC_CANNOT_HAPPEN); C(CWAL_RC_JSON_INVALID_CHAR); C(CWAL_RC_JSON_INVALID_KEYWORD); C(CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE); C(CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE); C(CWAL_RC_JSON_INVALID_NUMBER); C(CWAL_RC_JSON_NESTING_DEPTH_REACHED); C(CWAL_RC_JSON_UNBALANCED_COLLECTION); C(CWAL_RC_JSON_EXPECTED_KEY); C(CWAL_RC_JSON_EXPECTED_COLON); C(CWAL_SCR_CANNOT_CONSUME); C(CWAL_SCR_INVALID_OP); C(CWAL_SCR_UNKNOWN_IDENTIFIER); C(CWAL_SCR_CALL_OF_NON_FUNCTION); C(CWAL_SCR_MISMATCHED_BRACE); C(CWAL_SCR_MISSING_SEPARATOR); C(CWAL_SCR_UNEXPECTED_TOKEN); C(CWAL_SCR_UNEXPECTED_EOF); C(CWAL_SCR_DIV_BY_ZERO); C(CWAL_SCR_SYNTAX); C(CWAL_SCR_EOF); C(CWAL_SCR_TOO_MANY_ARGUMENTS); C(CWAL_SCR_EXPECTING_IDENTIFIER); default: return "Unknown result code"; } #undef C } int cwal_prototype_base_set( cwal_engine * e, cwal_type_id t, cwal_value * proto ){ if(!e || !e->prototypes) return CWAL_RC_MISUSE; else{ assert((t>=0) && (tprototypes, (cwal_size_t)t, proto ); } } cwal_value * cwal_prototype_base_get( cwal_engine * e, cwal_type_id t ){ if(!e || !e->prototypes) return NULL; else{ assert((t>=0) && (tprototypes, (cwal_size_t)t); } } int cwal_value_prototype_set( cwal_value * v, cwal_value * prototype ){ cwal_obase * child = CWAL_VOBASE(v); cwal_obase * chPro = child ? CWAL_VOBASE(prototype) : NULL; cwal_obase * pBase; cwal_value * pCheck; if((prototype && !chPro) || !child || (child == chPro)){ return CWAL_RC_MISUSE; } #if 1 if( prototype == child->prototype ){ return 0; } #endif /* Ensure that v is nowhere in prototype's chain. */ for( pCheck = prototype; pCheck ; pBase = CWAL_VOBASE(pCheck), pCheck = (pBase?pBase->prototype:NULL)){ if(pCheck == v) return CWAL_RC_CYCLES_DETECTED; } if(prototype){ cwal_value_ref2( v->scope->e, prototype ); cwal_value_rescope( v->scope, prototype ); } if(child->prototype){ cwal_unref_from( v->scope/* ??? */, child->prototype ); } child->prototype = prototype; return 0; } cwal_value * cwal_value_prototype_get(cwal_engine * e, cwal_value const * v){ if(!v) return NULL; else{ cwal_obase const * b = CWAL_VOBASE(v); if(b) return b->prototype; else{ if(!e && v->scope) e = v->scope->e; return cwal_prototype_base_get(e, v->vtab->typeID); } } } char cwal_value_derives_from( cwal_engine * e, cwal_value const * v, cwal_value const * p ){ if(!e || !v || !p) return 0; else if(v==p) return 1; else { cwal_type_id const tid = v->vtab->typeID; while(v){ cwal_obase const * base = CWAL_VOBASE(v); cwal_value const * theP = base ? base->prototype : cwal_prototype_base_get(e, tid); if(p==theP) return 1; else if(theP==p) return 0 /* break endless loop caused by (1.0 instanceof Double)*/; else v = theP; } return 0; } } cwal_buffer * cwal_value_buffer_part( cwal_engine * e, cwal_value * v ){ do{ cwal_buffer * f = cwal_value_get_buffer(v); if(f) return f; else v = cwal_value_prototype_get(e, v); }while(v); return NULL; } cwal_native * cwal_value_native_part( cwal_engine * e, cwal_value * v ){ do{ cwal_native * n = cwal_value_get_native(v); if(n) return n; v = cwal_value_prototype_get(e, v); }while(v); return NULL; } cwal_function * cwal_value_function_part( cwal_engine * e, cwal_value * v ){ do{ cwal_function * f = cwal_value_get_function(v); if(f) return f; else v = cwal_value_prototype_get(e, v); }while(v); return NULL; } cwal_array * cwal_value_array_part( cwal_engine * e, cwal_value * v ){ do{ if(cwal_value_is_array(v)) return cwal_value_get_array(v); v = cwal_value_prototype_get(e, v); }while(v); return NULL; } cwal_value * cwal_value_exception_part( cwal_engine * e, cwal_value * v ){ do{ cwal_exception * f = cwal_value_get_exception(v); if(f) return v; else v = cwal_value_prototype_get(e, v); }while(v); return NULL; } cwal_hash * cwal_value_hash_part( cwal_engine * e, cwal_value * v ){ do{ cwal_hash * f = cwal_value_get_hash(v); if(f) return f; else v = cwal_value_prototype_get(e, v); }while(v); return NULL; } cwal_string * cwal_value_string_part( cwal_engine * e, cwal_value * v ){ do{ cwal_string * f = cwal_value_get_string(v); if(f) return f; else v = cwal_value_prototype_get(e, v); }while(v); return NULL; } int cwal_engine_sweep_non_vars( cwal_engine * e ){ cwal_scope xfer = cwal_scope_empty; cwal_scope * S = &xfer; cwal_value * pSrc; cwal_value * pDest; cwal_scope * current; cwal_scope * parent; uint16_t newLevel; uint16_t oldLevel; int rc; cwal_scope tmpScope; if(!e || !e->current) return CWAL_RC_MISUSE; current = e->current; parent = current->parent; pSrc = cwal_scope_properties(current); if(!pSrc) return CWAL_RC_OOM; assert(pSrc->scope == current); rc = cwal_scope_push(e, &S); if(rc) return rc; assert(e->current == S); newLevel = xfer.level; oldLevel = current->level; pDest = cwal_scope_properties(S); if(!pDest){ rc = CWAL_RC_OOM; goto end; } assert(pDest->scope == S); tmpScope = *current; xfer = tmpScope; xfer.parent = current; pSrc->scope = S; xfer.level = newLevel; *current = cwal_scope_empty; current->e = e; current->parent = parent; current->level = oldLevel; current->props = cwal_value_get_object(pDest); pDest->scope = current; rc = cwal_props_copy(pSrc, pDest); #if 1 if(!rc){ cwal_value * v; for( v = current->mine.r0; v; v = v->right ) v->scope = current; for( v = current->mine.headPod; v; v = v->right ) v->scope = current; for( v = current->mine.headObj; v; v = v->right ) v->scope = current; for( v = S->mine.r0; v; v = v->right ) v->scope = S; for( v = S->mine.headPod; v; v = v->right ) v->scope = S; for( v = S->mine.headObj; v; v = v->right ) v->scope = S; } #endif end: cwal_scope_pop( e ); assert(current == e->current); return rc; } cwal_size_t cwal_list_reserve( cwal_engine * e, cwal_list * self, cwal_size_t n ) { if( !self ) return 0; else if(0 == n) { if(0 == self->alloced) return 0; cwal_free(e, self->list); self->list = NULL; self->alloced = self->count = 0; return 0; } else if( self->alloced >= n ) { return self->alloced; } else { size_t const sz = sizeof(void*) * n; void* * m = (void**)cwal_realloc( e, self->list, sz ); if( ! m ) return self->alloced; memset( m + self->alloced, 0, (sizeof(void*)*(n-self->alloced))); self->alloced = n; self->list = m; return n; } } int cwal_list_append( cwal_engine * e, cwal_list * self, void* cp ) { if( !e || !self || !cp ) return CWAL_RC_MISUSE; else if( self->alloced > cwal_list_reserve(e, self, self->count+1) ) { return CWAL_RC_OOM; } else { self->list[self->count++] = cp; if(self->count< self->alloced-1) self->list[self->count]=0; return 0; } } int cwal_list_visit( cwal_list * self, char order, cwal_list_visitor_f visitor, void * visitorState ) { int rc = CWAL_RC_OK; if( self && self->count && visitor ) { int i = 0; int pos = (order<0) ? self->count-1 : 0; int step = (order<0) ? -1 : 1; for( rc = 0; (i < self->count) && (0 == rc); ++i, pos+=step ) { void* obj = self->list[pos]; if(obj) rc = visitor( obj, visitorState ); if( obj != self->list[pos] ){ --i; if(order>=0) pos -= step; } } } return rc; } int cwal_list_visit_p( cwal_list * self, char order, char shiftIfNulled, cwal_list_visitor_f visitor, void * visitorState ) { int rc = CWAL_RC_OK; if( self && self->count && visitor ) { int i = 0; int pos = (order<0) ? self->count-1 : 0; int step = (order<0) ? -1 : 1; for( rc = 0; (i < (int)self->count) && (0 == rc); ++i, pos+=step ) { void* obj = self->list[pos]; if(obj) { assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!"); rc = visitor( &self->list[pos], visitorState ); if( shiftIfNulled && !self->list[pos]){ int x = pos; int const to = self->count-pos; assert( to < (int) self->alloced ); for( ; x < to; ++x ) self->list[x] = self->list[x+1]; if( x < (int)self->alloced ) self->list[x] = 0; --i; --self->count; if(order>=0) pos -= step; } } } } return rc; } cwal_value * cwal_value_from_arg(cwal_engine * e, char const *arg){ char * end = NULL; cwal_size_t sLen; if(!e) return NULL; else if(!arg) return cwal_value_null(); else if(('0'>*arg) || ('9'<*arg)){ goto do_string; } else{ /* try numbers... */ long const val = strtol(arg, &end, 10); if(!*end){ return cwal_new_integer( e, (cwal_int_t)val); } else if( '.' != *end ) { goto do_string; } else { double const val = strtod(arg, &end); if(!*end){ return cwal_new_double(e, val); } } } do_string: sLen = strlen(arg); if(4==sLen){ if(0==strcmp("true",arg)) return cwal_value_true(); if(0==strcmp("null",arg)) return cwal_value_null(); } else if(5==sLen && 0==strcmp("false",arg)) return cwal_value_false(); return cwal_new_string_value(e, arg, sLen); } int cwal_parse_argv_flags( cwal_engine * e, int argc, char const * const * argv, cwal_value ** tgt ){ cwal_value * o = NULL; cwal_value * flags = NULL; cwal_array * nonFlags = NULL; int rc = 0; int i = 0; if(!e || !tgt) return CWAL_RC_MISUSE; else if(*tgt && !cwal_props_can(*tgt)) return CWAL_RC_MISUSE; flags = cwal_new_object_value(e); if(!flags) return CWAL_RC_OOM; nonFlags = cwal_new_array(e); if(!nonFlags) { cwal_value_unref(flags); return CWAL_RC_OOM; } o = *tgt ? *tgt : cwal_new_object_value(e); if(!o){ cwal_value_unref(flags); cwal_value_unref(cwal_array_value(nonFlags)); return CWAL_RC_OOM; } rc = cwal_prop_set(o, "flags", 5, flags); if(rc){ cwal_value_unref(flags); goto end; } rc = cwal_prop_set(o, "nonFlags", 8, cwal_array_value(nonFlags)); if(rc){ cwal_value_unref(cwal_array_value(nonFlags)); goto end; } for( i = 0; i < argc; ++i ){ char const * arg = argv[i]; char const * key = arg; char const * pos; cwal_value * k = NULL; cwal_value * v = NULL; if('-' != *arg){ v = cwal_new_string_value(e, arg, strlen(arg)); if(!v){ rc = CWAL_RC_OOM; break; } rc = cwal_array_append(nonFlags, v); if(rc){ cwal_value_unref(v); break; } continue; } while('-'==*key) ++key; if(!*key) continue; pos = key; while( *pos && ('=' != *pos)) ++pos; k = cwal_new_string_value(e, key, pos-key); if(!k){ rc = CWAL_RC_OOM; break; } if(!*pos){ /** --key */ v = cwal_value_true(); }else{ /** --key=...*/ assert('=' == *pos); ++pos /*skip '='*/; v = cwal_value_from_arg(e, pos); } if(0 != (rc=cwal_prop_set_v(flags, k, v))){ cwal_value_unref(k); cwal_value_unref(v); break; } } end: if(o != *tgt){ if(rc) cwal_value_unref(o); else *tgt = o; } return rc; } char cwal_strtok( char const ** inp, char separator, char const ** end ){ char const * pos = NULL; assert( inp && end && *inp ); if( ! inp || !end ) return 0; else if( *inp == *end ) return 0; pos = *inp; if( !*pos ) { *end = pos; return 0; } for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ } *inp = pos; for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ } *end = pos; return (pos > *inp) ? 1 : 0; } int cwal_prop_fetch_sub( cwal_value * obj, cwal_value ** tgt, char const * path, char sep ) { if( ! obj || !path ) return CWAL_RC_MISUSE; else if( !*path || !sep ) return CWAL_RC_RANGE; else{ char const * beg = path; char const * end = NULL; int rc; unsigned int i, len; unsigned int tokenCount = 0; cwal_value * cv = NULL; cwal_value * curObj = obj; enum { BufSize = 128 }; char buf[BufSize]; memset( buf, 0, BufSize ); while( cwal_strtok( &beg, sep, &end ) ){ if( beg == end ) break; else{ ++tokenCount; beg = end; end = NULL; } } if( 0 == tokenCount ) return CWAL_RC_RANGE; beg = path; end = NULL; for( i = 0; i < tokenCount; ++i, beg=end, end=NULL ){ rc = cwal_strtok( &beg, sep, &end ); assert( 1 == rc ); assert( beg != end ); assert( end > beg ); len = end - beg; if( len > (BufSize-1) ) return CWAL_RC_RANGE; /* memset( buf, 0, len + 1 ); */ memcpy( buf, beg, len ); buf[len] = 0; cv = cwal_prop_get( curObj, buf, len ); if( NULL == cv ) return CWAL_RC_NOT_FOUND; else if( i == (tokenCount-1) ){ if(tgt) *tgt = cv; return 0; } else if( cwal_props_can(cv) ){ curObj = cv; } /* TODO: arrays. Requires numeric parsing for the index. */ else{ return CWAL_RC_NOT_FOUND; } } assert( i == tokenCount ); return CWAL_RC_NOT_FOUND; } } int cwal_prop_fetch_sub2( cwal_value * obj, cwal_value ** tgt, char const * path ) { if( ! obj || !path ) return CWAL_RC_MISUSE; else if( !*path || !*(1+path) ) return CWAL_RC_RANGE; else return cwal_prop_fetch_sub(obj, tgt, path+1, *path); } cwal_value * cwal_prop_get_sub( cwal_value * obj, char const * path, char sep ) { cwal_value * v = NULL; cwal_prop_fetch_sub( obj, &v, path, sep ); return v; } cwal_value * cwal_prop_get_sub2( cwal_value * obj, char const * path ) { cwal_value * v = NULL; cwal_prop_fetch_sub2( obj, &v, path ); return v; } #undef MARKER #undef cwal_string_empty_m #undef cwal_obase_empty_m #undef cwal_array_empty_m #undef cwal_kvp_empty_m #undef cwal_object_empty_m #undef cwal_value_vtab_empty_m #undef cwal_value_empty_m #undef CWAL_VVPCAST #undef CWAL_VALPART #undef CWAL_INT #undef CWAL_DBL #undef CWAL_BOOL #undef CWAL_STR #undef CWAL_OBASE #undef CWAL_VOBASE #undef CWAL_OBJ #undef CWAL_ARRAY #undef CWAL_HASH #undef CWAL_V2NATIVE #undef dump_val #undef V_IS_BUILTIN #undef CWAL_VENGINE #undef SETUP_ARRAY_ARGS #undef CWAL_STRLEN #undef CWAL_STRLEN_MASK #undef CWAL_STR_ISX #undef CWAL_STR_ISZ #undef CWAL_STR_ISXZ #undef CWAL_STR_XMASK #undef CWAL_STR_ZMASK #undef CWAL_INIT_EMPTY_VALUES #undef CWAL_KVP_TRY_SORTING #undef TRY_CLONE_SHARING #undef COMPARE_TYPE_IDS #undef CWAL_OBASE_IS_VISITING #undef CWAL_VAL_IS_VISITING #if defined(__cplusplus) } //extern "C" LOL - the C++-style comments do not upset C89 here. #endif /* end of file cwal.c */ /* start of file cwal_format.c */ /** This file contains the impl for cwal_buffer_format() and friends. */ #include /* memcpy() */ #include /* strtol() */ #include #if 1 /* Only for debuggering */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #endif /** Internal type IDs for cwal_format_info instances. Maintenance reminder: these MUST be aligned with the CWAL_FORMAT_INFO array. */ enum cwal_format_t { CWAL_FMT_UNKNOWN = 0, CWAL_FMT_BOOL, CWAL_FMT_NULL, CWAL_FMT_UNDEF, CWAL_FMT_VOIDP, CWAL_FMT_INT_DEC, CWAL_FMT_INT_HEXLC, CWAL_FMT_INT_HEXUC, CWAL_FMT_INT_OCTAL, CWAL_FMT_DOUBLE, CWAL_FMT_STRING, CWAL_FMT_STRING_SQL, CWAL_FMT_CHAR, CWAL_FMT_TYPENAME, CWAL_FMT_BLOB, CWAL_FMT_JSON, CWAL_FMT_MAX }; typedef enum cwal_format_t cwal_format_t; enum cwal_format_flags { CWAL_FMT_F_NONE = 0, CWAL_FMT_F_SIGNED = 0x01, CWAL_FMT_F_ALT_FORM = 0x02, CWAL_FMT_F_SQL_QUOTES_DOUBLE = 0x01 }; typedef enum cwal_format_flags cwal_format_flags; typedef struct cwal_format_info cwal_format_info; /** Callback for concrete cwal_buffer_format() handlers. fi holds the formatting details, v the value. Implementations must interpret v for formatting purposes, following the settings in fi (insofar as possible for the given type). On error the handler must return non-0 and may set fi->errMsg to a string describing the problem. Handlers are allowed to silently ignore small misuses like invalid width/precision values. */ typedef int (*cwal_format_part_f)( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); /** Holds information related to the handling of a format specifier in cwal_buffer_format(). */ struct cwal_format_info { /** The format type for which this handler is intended. */ cwal_format_t type; /** Type-specific flags, from cwal_format_flags. */ int flags; /** Precision part of the format string. */ int precision; /** Width part of the format string. */ int width; /** Ordinal position in the args processing, 1-based. */ int position; /** Can be set to propagate an error message in certai contexts. */ char const * errMsg; /** Padding char. TODO: change to int and output it as a UTF8 char. TODO: split into left/right padding. Currently we hard-code space as right-side padding. */ char pad; /** Callback handler for this formatter. */ cwal_format_part_f f; }; static int cwal_format_part_int( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static int cwal_format_part_double( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static int cwal_format_part_string( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static int cwal_format_part_sql( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static int cwal_format_part_char( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static int cwal_format_part_basics( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static int cwal_format_part_blob( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static int cwal_format_part_json( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ); static const cwal_format_info CWAL_FORMAT_INFO[CWAL_FMT_MAX+1] = { /* Maintenance reminder: these MUST be in the same order the entries are defined in cwal_format_t. */ /* {type,flags,precision,width,position,errMsg,pad,f} */ {CWAL_FMT_UNKNOWN, 0, 0, 0, 0,NULL,' ', NULL}, #define FMT(T,CB) {CWAL_FMT_##T, 0, 0, 0, 0,NULL,' ', cwal_format_part_ ## CB} FMT(BOOL,basics), FMT(NULL,basics), FMT(UNDEF,basics), FMT(VOIDP,basics), FMT(INT_DEC,int), FMT(INT_HEXLC,int), FMT(INT_HEXUC,int), FMT(INT_OCTAL,int), FMT(DOUBLE,double), FMT(STRING,string), FMT(STRING_SQL,sql), FMT(CHAR,char), FMT(TYPENAME,basics), FMT(BLOB,blob), FMT(JSON,json), #undef FMT {CWAL_FMT_MAX, 0, 0, 0, 0,NULL,' ', NULL}, }; static const cwal_format_info cwal_format_info_empty = { CWAL_FMT_UNKNOWN, 0/*flags*/, 0/*precision*/, 0/*width*/, 0/*position*/, NULL/*errMsg*/, ' '/*pad*/ }; int cwal_format_part_basics( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ switch(fi->type){ case CWAL_FMT_BOOL:{ char const b = cwal_value_get_bool(v); return cwal_buffer_append( e, tgt, b ? "true" : "false", b ? 4 : 5); } case CWAL_FMT_UNDEF: return cwal_buffer_append( e, tgt, "undefined", 9); case CWAL_FMT_VOIDP: return cwal_buffer_printf( e, tgt, "%s@%p", cwal_value_type_name(v), (void const*)v); case CWAL_FMT_TYPENAME: return cwal_buffer_printf(e, tgt, "%s", cwal_value_type_name(v)); case CWAL_FMT_NULL: default: /* This can happen for %N when the value is-not NULL. */ return cwal_buffer_append( e, tgt, "null", 4); } } int cwal_format_part_int( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ enum { BufSize = 100 }; char fmtBuf[BufSize+1] = {'%',0}; char * b = fmtBuf + 1; int rc; cwal_int_t const i = cwal_value_get_integer(v); if(i && (CWAL_FMT_F_SIGNED & fi->flags)){ *(b++) = '+'; } if(0 != fi->width){ if('0'==fi->pad) *(b++) = '0'; b += sprintf(b, "%d", fi->width); } if(0 != fi->precision){ b += sprintf(b, ".%d", fi->precision); } switch(fi->type){ case CWAL_FMT_INT_DEC: *(b++) = 'd'; break; case CWAL_FMT_INT_HEXLC: *(b++) = 'x'; break; case CWAL_FMT_INT_HEXUC: *(b++) = 'X'; break; case CWAL_FMT_INT_OCTAL: *(b++) = 'o'; break; default: assert(!"CANNOT HAPPEN"); break; } *b = 0; rc = cwal_buffer_printf(e, tgt, fmtBuf, i ); return rc; } int cwal_format_part_double( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ enum { BufSize = 120 }; static unsigned int fmtLen = sizeof(CWAL_DOUBLE_T_PFMT)-1; char fmtBuf[BufSize+1] = {'%',0}; cwal_size_t fmtBufLen = BufSize; char * b = fmtBuf + 1; int rc = 0; cwal_double_t const d = cwal_value_get_double(v); if((d!=0) && (CWAL_FMT_F_SIGNED & fi->flags)){ *(b++) = '+'; } if(fi->width){ if('0'==fi->pad){ *(b++) = '0'; --fmtBufLen; } cwal_int_to_cstr( fi->width, b, &fmtBufLen ); b += fmtBufLen; } if(fi->precision){ fmtBufLen = BufSize - (b - fmtBuf); *(b++) = '.'; cwal_int_to_cstr( fi->precision, b, &fmtBufLen); b += fmtBufLen; } assert(b < (fmtBuf+BufSize+fmtLen)); memcpy(b, CWAL_DOUBLE_T_PFMT, fmtLen); b += fmtLen; *b = 0; if(!rc){ rc = cwal_buffer_printf(e, tgt, fmtBuf, d ); if(!rc && tgt->used && !fi->precision){ /* Trim trailing zeroes when no precision is specified... */ b = (char *)(tgt->mem + tgt->used - 1); for( ; ('0'==*b) && tgt->used; --b, --tgt->used){} if('.'==*b) ++tgt->used /* leave one trailing 0 if we just stripped the last one. */; } } return rc; } int cwal_format_part_string( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ int rc = 0; cwal_size_t slen = 0; cwal_size_t ulen; char const * cstr = cwal_value_get_cstr(v, &slen); int width = fi->width; if(!cstr){ cstr = "(nil)"; ulen = slen = 5; } else{ ulen = cwal_cstr_length_utf8( (unsigned char const *)cstr, slen ); } if((fi->precision>0) && (fi->precision<(int)ulen)){ int i = 0; unsigned char const * ustr = (unsigned char const *)cstr; unsigned char const * uend = (unsigned char const *)cstr + slen; unsigned char const * upos = ustr; for( ; i < fi->precision; ++i, ustr = upos ){ cwal_utf8_read_char( ustr, uend, &upos ); assert(upos > ustr); } ulen = (cwal_size_t)fi->precision; slen = upos - (unsigned char const *)cstr; /* slen = (cwal_size_t)fi->precision; */ } if(width>0 && ((int)ulen < width)){ /* right alignment */ int i = (int)ulen - width; for( ; !rc && (i < 0); ++i){ rc = cwal_buffer_append(e, tgt, &fi->pad, 1); } } if(!rc){ rc = cwal_buffer_append(e, tgt, cstr, slen); if(!rc && (width<0)){ /* right padding */ int i = (int)ulen - -width; for( ; !rc && (i < 0); ++i){ rc = cwal_buffer_append(e, tgt, &fi->pad, 1); } } } return rc; } int cwal_format_part_sql( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ int rc = 0; cwal_size_t slen = 0; unsigned int i, n; cwal_size_t j; int ch, isnull; int needQuote; char const q = ((CWAL_FMT_F_SQL_QUOTES_DOUBLE & fi->flags)?'"':'\''); /* Quote character */ char const * escarg = cwal_value_get_cstr(v, &slen); char * bufpt = NULL; cwal_size_t const oldUsed = e->buffer.used; isnull = escarg==0; if( isnull ) { escarg = (CWAL_FMT_F_ALT_FORM & fi->flags) ? "NULL" : "(NULL)"; } for(i=n=0; (iflags); n += i + 1 + needQuote*2; /* FIXME: use a static buffer here instead of malloc()! Shame on you! */ rc = cwal_buffer_reserve( e, &e->buffer, oldUsed + n ); if(rc) return rc; bufpt = (char *)e->buffer.mem + oldUsed; if( ! bufpt ) return CWAL_RC_OOM; j = 0; if( needQuote ) bufpt[j++] = q; for(i=0; (ch=escarg[i])!=0; i++){ bufpt[j++] = ch; if( ch==q ) bufpt[j++] = ch; } if( needQuote ) bufpt[j++] = q; bufpt[j] = 0; rc = cwal_buffer_append(e, tgt, bufpt, j); e->buffer.used = oldUsed; e->buffer.mem[e->buffer.used] = 0; return 0; } int cwal_format_part_blob( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ int rc = 0; cwal_size_t slen = 0; char const * cstr = cwal_value_get_cstr(v, &slen); int width = fi->width; if(!cstr) return 0; else if((fi->precision>0) && (fi->precision<(int)slen)){ slen = (cwal_size_t)fi->precision; } if(width>0 && ((int)slen < width)){ /* right alignment */ int i = (int)slen - width; for( ; !rc && (i < 0); ++i){ rc = cwal_buffer_append(e, tgt, &fi->pad, 1); } } if(!rc){ rc = cwal_buffer_reserve(e, tgt, tgt->used + (slen*2) + 1); if(!rc){ char const * hchar = "0123456789ABCDEF"; cwal_size_t i; char hex[2] = {0,0}; for( i = 0; !rc && (i < slen); ++i ){ hex[0] = hchar[(cstr[i] & 0xF0)>>4]; hex[1] = hchar[cstr[i] & 0x0F]; rc = cwal_buffer_append(e, tgt, hex, 2); } if(!rc && (width<0)){ /* right padding */ int i = (int)slen - -width; for( ; !rc && (i < 0); ++i){ rc = cwal_buffer_append(e, tgt, &fi->pad, 1); } } } } return rc; } int cwal_format_part_json( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ cwal_json_output_opt outOpt = cwal_json_output_opt_empty; outOpt.indent = (char)(fi->width & 0xFF); return cwal_json_output_buffer(e, (cwal_value *)/*EVIL!*/v, tgt, &outOpt); } int cwal_format_part_char( cwal_engine * e, cwal_buffer * tgt, cwal_format_info * fi, cwal_value const * v ){ unsigned char intBuf[10] = {0,0,0,0,0,0,0,0,0,0}; int rc = 0; cwal_size_t slen = 0; char const * cstr = cwal_value_get_cstr(v, &slen); cwal_int_t i; if(!cstr){ i = cwal_value_get_integer(v); if(i<0){ fi->errMsg = "Invalid (negative) character value."; return CWAL_RC_RANGE; } slen = (cwal_size_t)cwal_utf8_char_to_cstr( (unsigned int)i, intBuf, sizeof(intBuf)/sizeof(intBuf[0])); if(slen>4){ fi->errMsg = "Invalid UTF character value."; return CWAL_RC_RANGE; } cstr = (char const *)intBuf; } if(!slen){ fi->errMsg = "Invalid (empty) character."; return CWAL_RC_RANGE; }else{ unsigned char const * begin = (unsigned char const *)cstr; unsigned char const * eof = begin + slen; unsigned char const * tail = begin; int width = !fi->width ? 0 : ((fi->width<0) ? -fi->width : fi->width); cwal_size_t chLen; cwal_int_t n; cwal_utf8_read_char( begin, eof, &tail ); if(tail == begin){ fi->errMsg = "Could not decode UTF character."; return CWAL_RC_RANGE; } chLen = tail - begin; n = (fi->precision>0) ? fi->precision : 1; /* MARKER(("n=%d, width=%d, precision=%d, \n",(int)n, fi->width, fi->precision)); */ if((fi->width>0) && (width > n)){ /* left-pad */ for( ; !rc && (width > n); --width){ rc = cwal_buffer_append( e, tgt, &fi->pad, 1 ); } } for( i = 0; !rc && (i < n); ++i ){ rc = cwal_buffer_append( e, tgt, begin, chLen ); } if(!rc && (fi->width<0) && (width > n)){ /* right-pad */ for( ; !rc && (width > n); --width){ rc = cwal_buffer_append( e, tgt, &fi->pad, 1 ); } } return rc; } } /** Parse the range [b,*e) as a java.lang.String.format()-style format string: %N$[flags][width][.precision][type] */ static int cwal_format_parse_info( char const * b, char const ** e, cwal_format_info * fi, int * index ){ char const * p = b; char const * x; char isMinus = 0; *fi = cwal_format_info_empty; for( ; (p < *e) && ('$' != *p); ++p ){ if(('0'>*p) && ('9'<*p)){ fi->errMsg = "Invalid argument index character after %."; return CWAL_RC_RANGE; } } /* MARKER(("fmt= b=[%s] p=[%s] e=[%c]\n",b, p, **e)); */ if((p==b) || (p==*e)) { fi->errMsg = "Invalid format token."; return CWAL_RC_RANGE; } else if('$'!=*p) { /* MARKER(("fmt=[%s] [%s]\n",b, p)); */ fi->errMsg = "Missing '$' after %N."; return CWAL_RC_RANGE; } *index = (int)strtol( b, (char **)&p, 10 ); if(*index < 1){ fi->errMsg = "Argument index must be greater than 0."; return CWAL_RC_RANGE; } *index = *index - 1 /* make it zero-based */; ++p /* skip '$' */; /* MARKER(("p=[%s]\n",p)); */ if(*e == p){ no_type: fi->errMsg = "No type specifier given in format token."; return CWAL_RC_TYPE; } /* Check for flag. */ switch(*p){ case '+': fi->flags |= CWAL_FMT_F_SIGNED; ++p; break; default: break; } /* Read [[-]width] ... */ if('-'==*p){ isMinus = 1; ++p; } x = p; if('0' == *x){ /* Make it 0-padded */ fi->pad = '0'; ++x; ++p; } for( ; (x<*e) && ('1'<=*x) && ('9'>=*x); ++x ){} /* MARKER(("x=[%s]\n",x)); */ if(x==*e){ fi->errMsg = "Unexpected end of width specifier."; return CWAL_RC_RANGE; } else if(x!=p){ fi->width = (int)strtol( p, (char **)&p, 10 ); if(p==*e) goto no_type; if(isMinus) fi->width = -fi->width; /* MARKER(("p=[%s], width=%d\n",p, fi->width)); */ } /* Read [.precision] ... */ /* MARKER(("p=[%s]\n",p)); */ if('.'==*p){ ++p; x = p; for( ; (x < *e) && ('0'<=*x) && ('9'>=*x); ++x ){} if(x==*e){ fi->errMsg = "Unexpected end of precision specifier."; return CWAL_RC_RANGE; } else if(x==p){ fi->errMsg = "Missing digits after '.'."; return CWAL_RC_RANGE; } else{ fi->precision = (int)strtol( p, (char**)&p, 10 ); /* MARKER(("p=[%s], precision=%d\n",p, fi->precision)); */ if(p==*e) goto no_type; } } switch( *p ){ case 'b': fi->type = CWAL_FMT_BOOL; ++p; break; case 'B': fi->type = CWAL_FMT_BLOB; ++p; break; case 'c': fi->type = CWAL_FMT_CHAR; ++p; break; case 'd': fi->type = CWAL_FMT_INT_DEC; ++p; break; case 'f': fi->type = CWAL_FMT_DOUBLE; ++p; break; case 'J': fi->type = CWAL_FMT_JSON; ++p; break; case 'N': fi->type = CWAL_FMT_NULL; ++p; break; case 'o': fi->type = CWAL_FMT_INT_OCTAL; ++p; break; case 'p': fi->type = CWAL_FMT_VOIDP; ++p; break; case 'q': fi->type = CWAL_FMT_STRING_SQL; ++p; break; case 'Q': fi->type = CWAL_FMT_STRING_SQL; fi->flags |= CWAL_FMT_F_ALT_FORM; ++p; break; case 's': fi->type = CWAL_FMT_STRING; ++p; break; case 'y': fi->type = CWAL_FMT_TYPENAME; ++p; break; case 'U': fi->type = CWAL_FMT_UNDEF; ++p; break; case 'x': fi->type = CWAL_FMT_INT_HEXLC; ++p; break; case 'X': fi->type = CWAL_FMT_INT_HEXUC; ++p; break; default: fi->errMsg = "Unknown format type specifier."; return CWAL_RC_RANGE; } switch(fi->type){ case CWAL_FMT_INT_DEC: case CWAL_FMT_INT_HEXLC: case CWAL_FMT_INT_HEXUC: if(fi->precision){ fi->errMsg = "Integer conversion does not support precision."; return CWAL_RC_MISUSE; } case CWAL_FMT_CHAR: if(fi->precision<0){ fi->errMsg = "Character precision (repetition) may not be negative."; return CWAL_RC_MISUSE; } default: break; } *e = p; fi->f = CWAL_FORMAT_INFO[fi->type].f; return 0; } int cwal_buffer_format( cwal_engine * e, cwal_buffer * tgt, char const * fmt, cwal_size_t fmtLen, uint16_t argc, cwal_value * const * const argv){ int rc = 0; char const * fpos = fmt; char const * fend = fpos + fmtLen; char const * tstart = fmt; cwal_format_info fi = cwal_format_info_empty; int index; int gotFmt = 0; cwal_size_t oldUsed; int ordPos; if(!e || !tgt || !fmt ) return CWAL_RC_MISUSE; else if(!fmtLen) return 0; oldUsed = tgt->used; rc = cwal_buffer_reserve(e, tgt, oldUsed + fmtLen); if(rc) return rc; for( ; !rc && (fpos < fend); ){ index = -1; fi = cwal_format_info_empty; switch( *fpos ){ case '%':{ ++fpos; ++gotFmt; if(fpos==fend){ fi.errMsg = "Unexpected end of input after '%'."; rc = CWAL_RC_RANGE; goto end; } else if('%'==*fpos){ rc = cwal_buffer_append( e, tgt, tstart, fpos - tstart); tstart = ++fpos; continue; } else{ char const * pend = fend; fi.position = ++ordPos; rc = cwal_format_parse_info( fpos, &pend, &fi, &index ); tstart = fpos = pend; if(!rc){ assert(index>=0); if(index>=(int)argc){ fi.errMsg = "Argument index out of range."; rc = CWAL_RC_RANGE; goto end; } else if(!argv[index]){ fi.f = CWAL_FORMAT_INFO[CWAL_FMT_NULL].f; } if(!fi.f){ fi.errMsg = "Missing formatting function for " "type specifier."; rc = CWAL_RC_ERROR; goto end; } rc = fi.f( e, tgt, &fi, argv[index] ); if(!rc) continue; } break; } }/* case '%' */ default: ++fpos; break; }/*switch(*fpos)*/ if(!rc){ rc = cwal_buffer_append( e, tgt, tstart, fpos - tstart); tstart = fpos; } else break; } end: if(rc) switch(rc){ case CWAL_RC_OOM: /* Pass through errors which might be introduced indirectly via client code if we expand this support to include custom formatters or callbacks. Note that if other errors pass through here we'll end up "overwriting" them as a formatting error. */ case CWAL_RC_ASSERT: case CWAL_RC_EXIT: case CWAL_RC_FATAL: case CWAL_RC_EXCEPTION: break; default:{ int errPos = (int)(fpos - fmt); int pedanticRc; tgt->used = oldUsed; pedanticRc = cwal_buffer_printf( e, tgt, "Formatting failed with " "code %d (%s) at format string " "byte offset %d.", rc, cwal_rc_cstr(rc), errPos - 1); if(!pedanticRc && fi.errMsg){ cwal_buffer_printf(e, tgt, " Format token #%d%s%s", gotFmt, fi.errMsg ? ": " : ".", fi.errMsg ? fi.errMsg : ""); } } } return rc; } #undef MARKER /* end of file cwal_format.c */ /* start of file JSON_parser/JSON_parser.h */ /* See JSON_parser.c for copyright information and licensing. */ #ifndef JSON_PARSER_H #define JSON_PARSER_H /* JSON_parser.h */ #include /* Windows DLL stuff */ #ifdef JSON_PARSER_DLL # ifdef _MSC_VER # ifdef JSON_PARSER_DLL_EXPORTS # define JSON_PARSER_DLL_API __declspec(dllexport) # else # define JSON_PARSER_DLL_API __declspec(dllimport) # endif # else # define JSON_PARSER_DLL_API # endif #else # define JSON_PARSER_DLL_API #endif /* Determine the integer type use to parse non-floating point numbers */ #if __STDC_VERSION__ >= 199901L || HAVE_LONG_LONG == 1 typedef long long JSON_int_t; #define JSON_PARSER_INTEGER_SSCANF_TOKEN "%lld" #define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%lld" #else typedef long JSON_int_t; #define JSON_PARSER_INTEGER_SSCANF_TOKEN "%ld" #define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%ld" #endif #ifdef __cplusplus extern "C" { #endif typedef enum { JSON_E_NONE = 0, JSON_E_INVALID_CHAR, JSON_E_INVALID_KEYWORD, JSON_E_INVALID_ESCAPE_SEQUENCE, JSON_E_INVALID_UNICODE_SEQUENCE, JSON_E_INVALID_NUMBER, JSON_E_NESTING_DEPTH_REACHED, JSON_E_UNBALANCED_COLLECTION, JSON_E_EXPECTED_KEY, JSON_E_EXPECTED_COLON, JSON_E_OUT_OF_MEMORY } JSON_error; typedef enum { JSON_T_NONE = 0, JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END, JSON_T_INTEGER, JSON_T_FLOAT, JSON_T_NULL, JSON_T_TRUE, JSON_T_FALSE, JSON_T_STRING, JSON_T_KEY, JSON_T_MAX } JSON_type; typedef struct JSON_value_struct { union { JSON_int_t integer_value; double float_value; struct { const char* value; size_t length; } str; } vu; } JSON_value; typedef struct JSON_parser_struct* JSON_parser; /*! \brief JSON parser callback \param ctx The pointer passed to new_JSON_parser. \param type An element of JSON_type but not JSON_T_NONE. \param value A representation of the parsed value. This parameter is NULL for JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END, JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always returned as zero-terminated C strings. \return Non-zero if parsing should continue, else zero. */ typedef int (*JSON_parser_callback)(void* ctx, int type, const struct JSON_value_struct* value); /** A typedef for allocator functions semantically compatible with malloc(). */ typedef void* (*JSON_malloc_t)(size_t n); /** A typedef for deallocator functions semantically compatible with free(). */ typedef void (*JSON_free_t)(void* mem); /*! \brief The structure used to configure a JSON parser object */ typedef struct { /** Pointer to a callback, called when the parser has something to tell the user. This parameter may be NULL. In this case the input is merely checked for validity. */ JSON_parser_callback callback; /** Callback context - client-specified data to pass to the callback function. This parameter may be NULL. */ void* callback_ctx; /** Specifies the levels of nested JSON to allow. Negative numbers yield unlimited nesting. If negative, the parser can parse arbitrary levels of JSON, otherwise the depth is the limit. */ int depth; /** To allow C style comments in JSON, set to non-zero. */ int allow_comments; /** To decode floating point numbers manually set this parameter to non-zero. */ int handle_floats_manually; /** The memory allocation routine, which must be semantically compatible with malloc(3). If set to NULL, malloc(3) is used. If this is set to a non-NULL value then the 'free' member MUST be set to the proper deallocation counterpart for this function. Failure to do so results in undefined behaviour at deallocation time. */ JSON_malloc_t malloc; /** The memory deallocation routine, which must be semantically compatible with free(3). If set to NULL, free(3) is used. If this is set to a non-NULL value then the 'alloc' member MUST be set to the proper allocation counterpart for this function. Failure to do so results in undefined behaviour at deallocation time. */ JSON_free_t free; } JSON_config; /*! \brief Initializes the JSON parser configuration structure to default values. The default configuration is - 127 levels of nested JSON (depends on JSON_PARSER_STACK_SIZE, see json_parser.c) - no parsing, just checking for JSON syntax - no comments - Uses realloc() for memory de/allocation. \param config. Used to configure the parser. */ JSON_PARSER_DLL_API void init_JSON_config(JSON_config * config); /*! \brief Create a JSON parser object \param config. Used to configure the parser. Set to NULL to use the default configuration. See init_JSON_config. Its contents are copied by this function, so it need not outlive the returned object. \return The parser object, which is owned by the caller and must eventually be freed by calling delete_JSON_parser(). */ JSON_PARSER_DLL_API JSON_parser new_JSON_parser(JSON_config const* config); /*! \brief Destroy a previously created JSON parser object. */ JSON_PARSER_DLL_API void delete_JSON_parser(JSON_parser jc); /*! \brief Parse a character. \return Non-zero, if all characters passed to this function are part of are valid JSON. */ JSON_PARSER_DLL_API int JSON_parser_char(JSON_parser jc, int next_char); /*! \brief Finalize parsing. Call this method once after all input characters have been consumed. \return Non-zero, if all parsed characters are valid JSON, zero otherwise. */ JSON_PARSER_DLL_API int JSON_parser_done(JSON_parser jc); /*! \brief Determine if a given string is valid JSON white space \return Non-zero if the string is valid, zero otherwise. */ JSON_PARSER_DLL_API int JSON_parser_is_legal_white_space_string(const char* s); /*! \brief Gets the last error that occurred during the use of JSON_parser. \return A value from the JSON_error enum. */ JSON_PARSER_DLL_API int JSON_parser_get_last_error(JSON_parser jc); /*! \brief Re-sets the parser to prepare it for another parse run. \return True (non-zero) on success, 0 on error (e.g. !jc). */ JSON_PARSER_DLL_API int JSON_parser_reset(JSON_parser jc); #ifdef __cplusplus } #endif #endif /* JSON_PARSER_H */ /* end of file JSON_parser/JSON_parser.h */ /* start of file cwal_json.c */ #include #include /* for a single sprintf() need :/ */ #include /* toupper(), tolower() */ #if defined(__cplusplus) extern "C" { #endif const cwal_json_output_opt cwal_json_output_opt_empty = cwal_json_output_opt_empty_m; /** Escapes the first len bytes of the given string as JSON and sends it to the given output function (which will be called often - once for each logical character). The output is also surrounded by double-quotes. A NULL str will be escaped as an empty string, though we should arguably export it as "null" (without quotes). We do this because in JavaScript (typeof null === "object"), and by outputing null here we would effectively change the data type from string to object. */ static int cwal_str_to_json( char const * str, unsigned int len, char escapeFwdSlash, cwal_output_f f, void * state ) { if( NULL == f ) return CWAL_RC_MISUSE; else if( !str || !*str || (0 == len) ) { /* special case for 0-length strings. */ return f( state, "\"\"", 2 ); } else { unsigned char const * pos = (unsigned char const *)str; unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL); unsigned char const * next = NULL; int ch; unsigned char clen = 0; char escChar[3] = {'\\',0,0}; enum { UBLen = 8 }; char ubuf[UBLen]; int rc = 0; rc = f(state, "\"", 1 ); for( ; (pos < end) && (0 == rc); pos += clen ) { ch = cwal_utf8_read_char(pos, end, &next); if( 0 == ch ) break; assert( next > pos ); clen = next - pos; assert( clen ); if( 1 == clen ) { /* ASCII */ assert( *pos == ch ); escChar[1] = 0; switch(ch) { case '\t': escChar[1] = 't'; break; case '\r': escChar[1] = 'r'; break; case '\n': escChar[1] = 'n'; break; case '\f': escChar[1] = 'f'; break; case '\b': escChar[1] = 'b'; break; case '/': /* Regarding escaping of forward-slashes. See the main exchange below... -------------- From: Douglas Crockford To: Stephan Beal Subject: Re: Is escaping of forward slashes required? It is allowed, not required. It is allowed so that JSON can be safely embedded in HTML, which can freak out when seeing strings containing " Hello, Jsonites, > > i'm a bit confused on a small grammatic detail of JSON: > > if i'm reading the grammar chart on http://www.json.org/ correctly, > forward slashes (/) are supposed to be escaped in JSON. However, the > JSON class provided with my browsers (Chrome and FF, both of which i > assume are fairly standards/RFC-compliant) do not escape such characters. > > Is backslash-escaping forward slashes required? If so, what is the > justification for it? (i ask because i find it unnecessary and hard to > look at.) -------------- */ if( escapeFwdSlash ) escChar[1] = '/'; break; case '\\': escChar[1] = '\\'; break; case '"': escChar[1] = '"'; break; default: break; } if( escChar[1]) { rc = f(state, escChar, 2); } else { rc = f(state, (char const *)pos, clen); } continue; } else { /* UTF: transform it to \uXXXX */ memset(ubuf,0,UBLen); rc = sprintf(ubuf, "\\u%04x",ch); if( rc != 6 ) { rc = CWAL_RC_RANGE; break; } rc = f( state, ubuf, 6 ); continue; } } if( 0 == rc ) rc = f(state, "\"", 1 ); return rc; } } static int cwal_json_output_null( cwal_output_f f, void * state ){ return f(state, "null", 4); } static int cwal_json_output_bool( cwal_value const * src, cwal_output_f f, void * state ) { char const v = cwal_value_get_bool(src); return f(state, v ? "true" : "false", v ? 4 : 5); } static int cwal_json_output_integer( cwal_value const * src, cwal_output_f f, void * state ) { if( !f ) return CWAL_RC_MISUSE; else if( !cwal_value_is_integer(src) ) return CWAL_RC_TYPE; else { char b[100]; cwal_size_t bLen = sizeof(b)/sizeof(b[0]); int rc; memset( b, 0, bLen ); rc = cwal_int_to_cstr( cwal_value_get_integer(src), b, &bLen ); return rc ? rc : f( state, b, bLen ); } } static int cwal_json_output_double( cwal_value const * src, cwal_output_f f, void * state ) { if( !f ) return CWAL_RC_MISUSE; else if( !cwal_value_is_double(src) ) return CWAL_RC_TYPE; else { enum { BufLen = 128 /* this must be relatively large or huge doubles can cause us to overrun here, resulting in stack-smashing errors. */}; char b[BufLen]; cwal_size_t bLen = BufLen; int rc; memset( b, 0, BufLen ); rc = cwal_double_to_cstr( cwal_value_get_double(src), b, &bLen ); if( rc ) return rc; else if(0) { /* Strip trailing zeroes before passing it on... */ char * pos = b + bLen - 1; for( ; ('0' == *pos) && bLen && (*(pos-1) != '.'); --pos, --bLen ){ *pos = 0; } assert(bLen && *pos); return f( state, b, bLen ); } else{ return f( state, b, bLen ); } return 0; } } static int cwal_json_output_string( cwal_value const * src, char escapeFwdSlash, cwal_output_f f, void * state ) { char const * cstr; cwal_size_t strLen = 0; if( !f ) return CWAL_RC_MISUSE; else if(!cwal_value_is_string(src) && !cwal_value_is_buffer(src)){ return CWAL_RC_TYPE; }else{ cstr = cwal_value_get_cstr(src,&strLen); return cstr ? cwal_str_to_json(cstr, strLen, escapeFwdSlash, f, state) : /*only applies to buffers:*/ cwal_json_output_null( f, state ); } } /** Outputs indention spacing to f(). blanks: (0)=no indentation, (-N)=-N TABs per/level, (+N)=n spaces/level depth is the current depth of the output tree, and determines how much indentation to generate. If blanks is 0 this is a no-op. Returns non-0 on error, and the error code will always come from f(). */ static int cwal_json_output_indent( cwal_output_f f, void * state, char blanks, unsigned int depth ) { if( 0 == blanks ) return 0; else { #if 0 /* FIXME: stuff the indention into the buffer and make a single call to f(). We need a dyanmic buffer for that to keep it across calls, but we have no place to store it here. That said... we could keep two static buffers pre-filled with some amount of padding (one for spaces and one for tabs) and output them. */ enum { BufLen = 200 }; char buf[BufLen]; #endif unsigned int i; unsigned int x; char const ch = (blanks<0) ? '\t' : ' '; int rc = f(state, "\n", 1 ); if(blanks<0) blanks = -blanks; for( i = 0; (i < depth) && (0 == rc); ++i ){ for( x = 0; (x < blanks) && (0 == rc); ++x ){ rc = f(state, &ch, 1); } } return rc; } } static int cwal_json_output_array( cwal_value * src, cwal_output_f f, void * state, cwal_json_output_opt const * fmt, unsigned int level ); #if 0 static int cwal_json_output_object( cwal_value * src, cwal_output_f f, void * state, cwal_json_output_opt const * fmt, unsigned int level ); #endif /** Outputs a JSON Object from the base->kvp list. */ static int cwal_json_output_obase( cwal_value * base, cwal_output_f f, void * state, cwal_json_output_opt const * fmt, unsigned int level ); /** Main cwal_json_output() implementation. Dispatches to a different impl depending on src->vtab->typeID. Returns 0 on success. */ static int cwal_json_output_impl( cwal_value * src, cwal_output_f f, void * state, cwal_json_output_opt const * fmt, unsigned int level ) { int rc; cwal_type_id const tid = cwal_value_type_id( src ); #if 0 cwal_obase * base = cwal_value_obase(src); if( base ) { if( CWAL_F_IS_VISITING & base->flags ){ return CWAL_RC_CYCLES_DETECTED; } cwal_value_set_visiting(src, 1); } #endif switch( tid ){ case CWAL_TYPE_UNDEF: #if 0 rc = f( state, "undefined", 9); break; #endif /* Fall through to null */ case CWAL_TYPE_NULL: rc = cwal_json_output_null(f, state); break; case CWAL_TYPE_BOOL: rc = cwal_json_output_bool(src, f, state); break; case CWAL_TYPE_INTEGER: rc = cwal_json_output_integer(src, f, state); break; case CWAL_TYPE_DOUBLE: rc = cwal_json_output_double(src, f, state); break; case CWAL_TYPE_BUFFER: case CWAL_TYPE_STRING: rc = cwal_json_output_string(src, fmt->escapeForwardSlashes, f, state); break; case CWAL_TYPE_ARRAY: rc = cwal_json_output_array( src, f, state, fmt, level ); break; /* case CWAL_TYPE_FUNCTION: */ case CWAL_TYPE_EXCEPTION: case CWAL_TYPE_OBJECT:{ return cwal_json_output_obase( src, f, state, fmt, level ); } default: rc = CWAL_RC_TYPE; break; } #if 0 if( base ) cwal_value_set_visiting(src, 0); #endif return rc; } static int cwal_json_output_array( cwal_value * src, cwal_output_f f, void * state, cwal_json_output_opt const * fmt, unsigned int level ) { if( !src || !f || !fmt ) return CWAL_RC_MISUSE; else if( ! cwal_value_is_array(src) ) return CWAL_RC_TYPE; else if( level > fmt->maxDepth ) return CWAL_RC_RANGE; else { int rc; unsigned int i; cwal_value * v; char doIndent = fmt->indent ? 1 : 0; cwal_list const * li; cwal_array const * ar = cwal_value_get_array(src); li = &ar->list; assert( NULL != li ); if( 0 == li->count ) { return f(state, "[]", 2 ); } else if( (1 == li->count) && !fmt->indentSingleMemberValues ) doIndent = 0; rc = f(state, "[", 1); ++level; if( doIndent ){ rc = cwal_json_output_indent( f, state, fmt->indent, level ); } for( i = 0; (i < li->count) && (0 == rc); ++i ){ v = (cwal_value *) li->list[i]; rc = v ? cwal_json_output_impl( v, f, state, fmt, level ) : cwal_json_output_null( f, state ); if( 0 == rc ){ if(i < (li->count-1)){ rc = f(state, ",", 1); if( 0 == rc ){ rc = doIndent ? cwal_json_output_indent( f, state, fmt->indent, level ) : (fmt->addSpaceAfterComma ? f( state, " ", 1 ) : 0); } } } } --level; if( doIndent && (0 == rc) ){ rc = cwal_json_output_indent( f, state, fmt->indent, level ); } return (0 == rc) ? f(state, "]", 1) : rc; } } static char cwal_json_can_output( cwal_value const * v ){ switch(cwal_value_type_id(v)){ case CWAL_TYPE_ARRAY: case CWAL_TYPE_OBJECT: case CWAL_TYPE_EXCEPTION: case CWAL_TYPE_STRING: case CWAL_TYPE_INTEGER: case CWAL_TYPE_BOOL: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_NULL: return 1; default: return 0; } } int cwal_json_output_obase( cwal_value * self, cwal_output_f f, void * state, cwal_json_output_opt const * fmt, unsigned int level ) { if( !self || !f || !fmt ) return CWAL_RC_MISUSE; else if( level > fmt->maxDepth ) return CWAL_RC_RANGE; else { int rc = 0; unsigned int i; cwal_kvp const * kvp; cwal_string const * sKey; cwal_value * val; char doIndent = fmt->indent ? 1 : 0; cwal_obase * base = cwal_value_obase(self); if(!base) return CWAL_RC_MISUSE; assert( (NULL != fmt)); kvp = base->kvp; if( 0 == kvp ) return f(state, "{}", 2 ); if( CWAL_F_IS_VISITING & base->flags ){ return CWAL_RC_CYCLES_DETECTED; } cwal_value_set_visiting(self, 1); if( !kvp->right && !fmt->indentSingleMemberValues ) doIndent = 0; rc = f(state, "{", 1); ++level; if( doIndent ){ rc = cwal_json_output_indent( f, state, fmt->indent, level ); } for( i = 0; (0==rc) && kvp; kvp = kvp->right, ++i ){ char const * cKey; sKey = kvp ? cwal_value_get_string(cwal_kvp_key( kvp )) : 0; if(!sKey){ /*assert(sKey && "FIXME: cannot handle non-string keys.");*/ continue; } val = cwal_kvp_value(kvp); if(!cwal_json_can_output(val)) continue; cKey = cwal_string_cstr(sKey); rc = cwal_str_to_json(cKey, cwal_string_length_bytes(sKey), fmt->escapeForwardSlashes, f, state); if(0 == rc ) rc = fmt->addSpaceAfterColon ? f(state, ": ", 2 ) : f(state, ":", 1 ) ; if(0 == rc) { rc = ( val ) ? cwal_json_output_impl( val, f, state, fmt, level ) : cwal_json_output_null( f, state ); } if( (0 == rc) && kvp->right){ rc = f(state, ",", 1); if( 0 == rc ){ rc = doIndent ? cwal_json_output_indent( f, state, fmt->indent, level ) : (fmt->addSpaceAfterComma ? f( state, " ", 1 ) : 0); } } } --level; if( doIndent && (0 == rc) ){ rc = cwal_json_output_indent( f, state, fmt->indent, level ); } cwal_value_set_visiting(self, 0); return (0 == rc) ? f(state, "}", 1) : rc; } } #if 0 static int cwal_json_output_object( cwal_value * src, cwal_output_f f, void * state, cwal_json_output_opt const * fmt, unsigned int level ) { if( !src || !f || !fmt ) return CWAL_RC_MISUSE; else if( ! cwal_value_is_object(src) ) return CWAL_RC_TYPE; { return cwal_json_output_obase( src, f, state, fmt, level ); } } #endif int cwal_json_output( cwal_value * src, cwal_output_f f, void * state, cwal_json_output_opt const * fmt ) { int rc; if(! fmt ) fmt = &cwal_json_output_opt_empty; rc = cwal_json_output_impl(src, f, state, fmt, 0 ); if( (0 == rc) && fmt->addNewline ) { rc = f(state, "\n", 1); } return rc; } int cwal_json_output_FILE( cwal_value * src, FILE * dest, cwal_json_output_opt const * fmt ){ static cwal_json_output_opt sopt = cwal_json_output_opt_empty_m; int rc = 0; if(!sopt.addNewline){ sopt.addNewline = 1; } if(!fmt) fmt = &sopt; rc = cwal_json_output( src, cwal_output_f_FILE, dest, fmt ); if( 0 == rc ) fflush( dest ); return rc; } int cwal_json_output_filename( cwal_value * src, char const * fn, cwal_json_output_opt const * fmt ) { if( !src || !fn || !*fn ) return CWAL_RC_MISUSE; else { FILE * f = (*fn && !fn[1] && ('-'==*fn)) ? stdout : fopen( fn, "wb" ); if( !f ) return CWAL_RC_IO; else { int const rc = cwal_json_output_FILE( src, f, fmt ); if(stdout != f) fclose(f); return rc; } } } int cwal_json_output_buffer( cwal_engine * e, cwal_value * src, cwal_buffer * dest, cwal_json_output_opt const * fmt ){ static cwal_json_output_opt const outOpt = cwal_json_output_opt_empty_m; cwal_output_buffer_state job = cwal_output_buffer_state_empty; if(!e || !src || !dest) return CWAL_RC_MISUSE; else if(!fmt) fmt = &outOpt; job.e = e; job.b = dest; return cwal_json_output( src, cwal_output_f_buffer, &job, fmt ); } int cwal_json_output_engine( cwal_engine * e, cwal_value * src, cwal_json_output_opt const * fmt ){ return (src && e && e->vtab && e->vtab->outputer.output) ? cwal_json_output( src, e->vtab->outputer.output, e->vtab->outputer.state.data, fmt ) : CWAL_RC_MISUSE; } struct cwal_json_parser{ JSON_parser p; cwal_engine * e; cwal_value * root; cwal_value * node; cwal_string * ckey; int errNo; char const * errMsg; cwal_list stack; }; typedef struct cwal_json_parser cwal_json_parser; static const cwal_json_parser cwal_json_parser_empty = { NULL/*e*/, NULL/*p*/, NULL/*root*/, NULL/*node*/, NULL/*ckey*/, 0/*errNo*/, NULL/*errMsg*/, cwal_list_empty_m/*stack*/ }; /** Converts a JSON_error code to one of the cwal_rc values. */ static int cwal_json_err_to_rc( JSON_error jrc ) { switch(jrc) { case JSON_E_NONE: return 0; case JSON_E_INVALID_CHAR: return CWAL_RC_JSON_INVALID_CHAR; case JSON_E_INVALID_KEYWORD: return CWAL_RC_JSON_INVALID_KEYWORD; case JSON_E_INVALID_ESCAPE_SEQUENCE: return CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE; case JSON_E_INVALID_UNICODE_SEQUENCE: return CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE; case JSON_E_INVALID_NUMBER: return CWAL_RC_JSON_INVALID_NUMBER; case JSON_E_NESTING_DEPTH_REACHED: return CWAL_RC_JSON_NESTING_DEPTH_REACHED; case JSON_E_UNBALANCED_COLLECTION: return CWAL_RC_JSON_UNBALANCED_COLLECTION; case JSON_E_EXPECTED_KEY: return CWAL_RC_JSON_EXPECTED_KEY; case JSON_E_EXPECTED_COLON: return CWAL_RC_JSON_EXPECTED_COLON; case JSON_E_OUT_OF_MEMORY: return CWAL_RC_OOM; default: return CWAL_RC_ERROR; } } /** @internal Cleans up all contents of p but does not free p. To properly take over ownership of the parser's root node on a successful parse: - Copy p->root's pointer and set p->root to NULL. - Eventually free up p->root with cwal_value_free(). If you do not set p->root to NULL, p->root will be freed along with any other items inserted into it (or under it) during the parsing process. */ static void cwal_json_parser_clean( cwal_json_parser * p ){ if(p->p) delete_JSON_parser(p->p); if(p->ckey) cwal_value_unref(cwal_string_value(p->ckey)); cwal_list_reserve(p->e, &p->stack, 0); if(p->root) cwal_value_unref( p->root ); *p = cwal_json_parser_empty; } /** Empty-initialized cwal_json_parse_info object. */ const cwal_json_parse_info cwal_json_parse_info_empty = cwal_json_parse_info_empty_m; /** @internal If p->node is-a Object then value is inserted into the object using p->key. In any other case cwal_rc.InternalError is returned. Returns cwal_rc.AllocError if an allocation fails. Returns 0 on success. On error, parsing must be ceased immediately. Ownership of val is ALWAYS TRANSFERED to this function. If this function fails, val will be cleaned up and destroyed. (This simplifies error handling in the core parser.) */ static int cwal_json_parser_set_key( cwal_json_parser * p, cwal_value * val ) { assert( p && val ); if( p->ckey && cwal_value_is_object(p->node) ){ int rc; cwal_object * obj = cwal_value_get_object(p->node); rc = cwal_object_set_s( obj, p->ckey, val ); if(rc){ cwal_value_unref(val); cwal_value_unref(cwal_string_value(p->ckey)); } p->ckey = NULL /* required to avoid mis-cleanup */; return rc; } else{ if(val) cwal_value_unref(val); return p->errNo = CWAL_RC_ERROR; } } /** @internal Pushes val into the current object/array parent node, depending on the internal state of the parser. Ownership of val is always transfered to this function, regardless of success or failure. Returns 0 on success. On error, parsing must be ceased immediately. */ static int cwal_json_parser_push_value( cwal_json_parser * p, cwal_value * val ){ if( p->ckey ){ /* we're in Object mode */ assert( cwal_value_is_object( p->node ) ); return cwal_json_parser_set_key( p, val ); } else if( cwal_value_is_array( p->node ) ){ /* we're in Array mode */ int rc; cwal_array * ar = cwal_value_get_array( p->node ); rc = cwal_array_append( ar, val ); if(rc) cwal_value_unref(val); return rc; } else { /* WTF? */ assert( 0 && "Internal error in cwal_parser code" ); return p->errNo = CWAL_RC_ERROR; } } /** Callback for JSON_parser API. Reminder: it returns 0 (meaning false) on error! */ static int cwal_json_parse_callback( void * cx, int type, JSON_value const * value ){ cwal_json_parser * p = (cwal_json_parser *)cx; int rc = 0; cwal_value * v = NULL; #define CHECKV if( !v ) { rc = CWAL_RC_OOM; break; } else switch(type) { case JSON_T_ARRAY_BEGIN: case JSON_T_OBJECT_BEGIN: { cwal_value * obja = (JSON_T_ARRAY_BEGIN == type) ? cwal_new_array_value(p->e) : cwal_new_object_value(p->e); if( ! obja ){ p->errNo = CWAL_RC_OOM; break; } if( ! p->root ){ p->root = p->node = obja; rc = cwal_list_append( p->e, &p->stack, obja ); if( 0 != rc ){ /* work around a (potential) corner case in the cleanup code. */ p->root = NULL; } } else{ rc = cwal_list_append( p->e, &p->stack, obja ); if(rc) cwal_value_unref( obja ); else{ rc = cwal_json_parser_push_value( p, obja ); if( 0 == rc ) p->node = obja; } } break; } case JSON_T_ARRAY_END: case JSON_T_OBJECT_END: { if(!p->stack.count){ rc = CWAL_RC_RANGE; break; } /* Reminder: do not use cwal_array_pop_back( &p->stack ) because that will clean up the object, and we don't want that. We just want to forget this reference to it. The object is either the root or was pushed into an object/array in the parse tree (and is owned by that object/array). */ --p->stack.count; assert( p->node == p->stack.list[p->stack.count] ); p->stack.list[p->stack.count] = NULL; if( p->stack.count ){ p->node = (cwal_value *)p->stack.list[p->stack.count-1]; } else p->node = p->root; break; } case JSON_T_INTEGER: { v = cwal_new_integer(p->e, value->vu.integer_value); CHECKV { rc = cwal_json_parser_push_value( p, v ); break; } } case JSON_T_FLOAT: { v = cwal_new_double(p->e, value->vu.float_value); CHECKV { rc = cwal_json_parser_push_value( p, v ); break; } } case JSON_T_NULL: { rc = cwal_json_parser_push_value( p, cwal_value_null() ); break; } case JSON_T_TRUE: { rc = cwal_json_parser_push_value( p, cwal_value_true() ); break; } case JSON_T_FALSE: { rc = cwal_json_parser_push_value( p, cwal_value_false() ); break; } case JSON_T_KEY: { assert(!p->ckey); p->ckey = cwal_new_string( p->e, value->vu.str.length ? value->vu.str.value : NULL, (cwal_size_t)value->vu.str.length ); if( ! p->ckey ){ rc = CWAL_RC_OOM; break; } break; } case JSON_T_STRING: { v = cwal_new_string_value( p->e, value->vu.str.length ? value->vu.str.value : NULL, (cwal_size_t)value->vu.str.length ); CHECKV { rc = cwal_json_parser_push_value( p, v ); break; } } default: assert(0); rc = CWAL_RC_ERROR; break; } #undef CHECKV return ((p->errNo = rc)) ? 0 : 1; } int cwal_json_parse( cwal_engine * e, cwal_input_f src, void * state, cwal_value ** tgt, cwal_json_parse_info * pInfo){ unsigned char ch[2] = {0,0}; int rc = 0; cwal_size_t len = 1; cwal_json_parse_info info = pInfo ? *pInfo : cwal_json_parse_info_empty; cwal_json_parser p = cwal_json_parser_empty; JSON_config jopt = {0}; if( !e || !tgt || !src ) return CWAL_RC_MISUSE; init_JSON_config( &jopt ); jopt.allow_comments = 0; jopt.depth = 30; jopt.callback_ctx = &p; jopt.handle_floats_manually = 0; jopt.callback = cwal_json_parse_callback; p.p = new_JSON_parser(&jopt); if( !p.p ) return CWAL_RC_OOM; p.e = e; do { /* FIXME: buffer the input in multi-kb chunks. */ len = 1; ch[0] = 0; rc = src( state, ch, &len ); if( 0 != rc ) break; else if( !len /* EOF */ ) break; ++info.length; if('\n' == ch[0]){ ++info.line; info.col = 0; } if( ! JSON_parser_char(p.p, ch[0]) ){ rc = cwal_json_err_to_rc( JSON_parser_get_last_error(p.p) ); if(0==rc) rc = p.errNo ? p.errNo : CWAL_RC_ERROR; info.errorCode = rc; break; } if( '\n' != ch[0]) ++info.col; } while(1); if(pInfo) *pInfo = info; if( 0 != rc ){ cwal_json_parser_clean(&p); return rc; } if( ! JSON_parser_done(p.p) ){ rc = cwal_json_err_to_rc( JSON_parser_get_last_error(p.p) ); cwal_json_parser_clean(&p); if(0==rc) rc = p.errNo ? p.errNo : CWAL_RC_ERROR; } else{ cwal_value * root = p.root; p.root = NULL; cwal_json_parser_clean(&p); if( root ) *tgt = root; else{ /* this can happen on empty input. */ rc = CWAL_RC_ERROR; } } return rc; } int cwal_json_parse_FILE( cwal_engine * e, FILE * src, cwal_value ** tgt, cwal_json_parse_info * pInfo ){ return cwal_json_parse( e, cwal_input_FILE, src, tgt, pInfo ); } int cwal_json_parse_filename( cwal_engine * e, char const * src, cwal_value ** tgt, cwal_json_parse_info * pInfo ){ if( !src || !tgt ) return CWAL_RC_MISUSE; else{ FILE * f = fopen(src, "r"); if( !f ) return CWAL_RC_IO; else{ int const rc = cwal_json_parse_FILE( e, f, tgt, pInfo ); fclose(f); return rc; } } } /** Internal type to hold state for a JSON input string. */ typedef struct cwal_input_StringSource_ { /** Start of input string. */ char const * str; /** Current iteration position. Must initially be == str. */ char const * pos; /** Logical EOF, one-past-the-end of str. */ char const * end; } cwal_input_StringSource_t; /** A cwal_input_f() implementation which requires the state argument to be a properly populated (cwal_input_StringSource_t*). */ static int cwal_input_StringSource( void * state, void * dest, cwal_size_t * n ) { if( !state || !n || !dest ) return CWAL_RC_MISUSE; else if( !*n ) return 0 /* ignore this */; else { cwal_size_t i; cwal_input_StringSource_t * ss = (cwal_input_StringSource_t*) state; unsigned char * tgt = (unsigned char *)dest; for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt ) { *tgt = *ss->pos; } *n = i; return 0; } } int cwal_json_parse_cstr( cwal_engine * e, char const * src, cwal_size_t len, cwal_value ** tgt, cwal_json_parse_info * pInfo ){ if( ! tgt || !src ) return CWAL_RC_MISUSE; else if( !*src || (len<2/*2==len of {} and []*/) ) return CWAL_RC_RANGE; else{ cwal_input_StringSource_t ss; ss.str = ss.pos = src; ss.end = src + len; return cwal_json_parse( e, cwal_input_StringSource, &ss, tgt, pInfo ); } } #if defined(__cplusplus) } //extern "C" - the C++-style comments do not upset C89 here. #endif /* end of file cwal_json.c */ /* start of file cwal_printf.c */ /************************************************************************ The printf-like implementation in this file is based on the one found in the sqlite3 distribution is in the Public Domain. This copy was forked for use with the clob API in Feb 2008 by Stephan Beal (http://wanderinghorse.net/home/stephan/) and modified to send its output to arbitrary targets via a callback mechanism. Also refactored the %X specifier handlers a bit to make adding/removing specific handlers easier. All code in this file is released into the Public Domain. The printf implementation (cwal_printfv()) is pretty easy to extend (e.g. adding or removing %-specifiers for cwal_printfv()) if you're willing to poke around a bit and see how the specifiers are declared and dispatched. For an example, grep for 'etSTRING' and follow it through the process of declaration to implementation. See below for several WHPRINTF_OMIT_xxx macros which can be set to remove certain features/extensions. ************************************************************************/ #include /* FILE */ #include /* strlen() */ #include /* free/malloc() */ #include #include typedef long double LONGDOUBLE_TYPE; /* If WHPRINTF_OMIT_FLOATING_POINT is defined to a true value, then floating point conversions are disabled. */ #ifndef WHPRINTF_OMIT_FLOATING_POINT # define WHPRINTF_OMIT_FLOATING_POINT 0 #endif /* If WHPRINTF_OMIT_SIZE is defined to a true value, then the %n specifier is disabled. */ #ifndef WHPRINTF_OMIT_SIZE # define WHPRINTF_OMIT_SIZE 0 #endif /* If WHPRINTF_OMIT_SQL is defined to a true value, then the %q and %Q specifiers are disabled. */ #ifndef WHPRINTF_OMIT_SQL # define WHPRINTF_OMIT_SQL 0 #endif /* If WHPRINTF_OMIT_HTML is defined to a true value then the %h (HTML escape), %t (URL escape), and %T (URL unescape) specifiers are disabled. */ #ifndef WHPRINTF_OMIT_HTML # define WHPRINTF_OMIT_HTML 0 #endif /* Most C compilers handle variable-sized arrays, so we enable that by default. Some (e.g. tcc) do not, so we provide a way to disable it: set WHPRINTF_HAVE_VARARRAY to 0 One approach would be to look at: defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) but some compilers support variable-sized arrays even when not explicitly running in c99 mode. */ #if !defined(WHPRINTF_HAVE_VARARRAY) # if defined(__TINYC__) # define WHPRINTF_HAVE_VARARRAY 0 # else # if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # define WHPRINTF_HAVE_VARARRAY 1 /*use 1 in C99 mode */ # else # define WHPRINTF_HAVE_VARARRAY 0 # endif # endif #endif /** WHPRINTF_CHARARRAY is a helper to allocate variable-sized arrays. This exists mainly so this code can compile with the tcc compiler. */ #if WHPRINTF_HAVE_VARARRAY # define WHPRINTF_CHARARRAY(V,N) char V[N+1]; memset(V,0,N+1); # define WHPRINTF_CHARARRAY_FREE(V) #else # define WHPRINTF_CHARARRAY(V,N) char * V = (char *)malloc(N+1); memset(V,0,N+1); # define WHPRINTF_CHARARRAY_FREE(V) free(V) #endif /* Conversion types fall into various categories as defined by the following enumeration. */ enum PrintfCategory {etRADIX = 1, /* Integer types. %d, %x, %o, and so forth */ etFLOAT = 2, /* Floating point. %f */ etEXP = 3, /* Exponentional notation. %e and %E */ etGENERIC = 4, /* Floating or exponential, depending on exponent. %g */ etSIZE = 5, /* Return number of characters processed so far. %n */ etSTRING = 6, /* Strings. %s */ etDYNSTRING = 7, /* Dynamically allocated strings. %z */ etPERCENT = 8, /* Percent symbol. %% */ etCHARX = 9, /* Characters. %c */ /* The rest are extensions, not normally found in printf() */ etCHARLIT = 10, /* Literal characters. %' */ #if !WHPRINTF_OMIT_SQL etSQLESCAPE = 11, /* Strings with '\'' doubled. %q */ etSQLESCAPE2 = 12, /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ etSQLESCAPE3 = 16, /* %w -> Strings with '\"' doubled */ #endif /* !WHPRINTF_OMIT_SQL */ etPOINTER = 15, /* The %p conversion */ etORDINAL = 17, /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ #if ! WHPRINTF_OMIT_HTML etHTML = 18, /* %h -> basic HTML escaping. */ etURLENCODE = 19, /* %t -> URL encoding. */ etURLDECODE = 20, /* %T -> URL decoding. */ #endif etPLACEHOLDER = 100 }; /* An "etByte" is an 8-bit unsigned value. */ typedef unsigned char etByte; /* Each builtin conversion character (ex: the 'd' in "%d") is described by an instance of the following structure */ typedef struct et_info { /* Information about each format field */ char fmttype; /* The format field code letter */ etByte base; /* The base for radix conversion */ etByte flags; /* One or more of FLAG_ constants below */ etByte type; /* Conversion paradigm */ etByte charset; /* Offset into aDigits[] of the digits string */ etByte prefix; /* Offset into aPrefix[] of the prefix string */ } et_info; /* Allowed values for et_info.flags */ enum et_info_flags { FLAG_SIGNED = 1, /* True if the value to convert is signed */ FLAG_EXTENDED = 2, /* True if for internal/extended use only. */ FLAG_STRING = 4 /* Allow infinity precision */ }; /* Historically, the following table was searched linearly, so the most common conversions were kept at the front. Change 2008 Oct 31 by Stephan Beal: we reserve an array or ordered entries for all chars in the range [32..126]. Format character checks can now be done in constant time by addressing that array directly. This takes more static memory, but reduces the time and per-call overhead costs of cwal_printfv(). */ static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; static const char aPrefix[] = "-x0\000X0"; static const et_info fmtinfo[] = { /** If WHPRINTF_FMTINFO_FIXED is 1 then we use the original implementation: a linear list of entries. Search time is linear. If WHPRINTF_FMTINFO_FIXED is 0 then we use a fixed-size array which we index directly using the format char as the key. */ #define WHPRINTF_FMTINFO_FIXED 0 #if WHPRINTF_FMTINFO_FIXED { 'd', 10, FLAG_SIGNED, etRADIX, 0, 0 }, { 's', 0, FLAG_STRING, etSTRING, 0, 0 }, { 'g', 0, FLAG_SIGNED, etGENERIC, 30, 0 }, { 'z', 0, FLAG_STRING, etDYNSTRING, 0, 0 }, { 'c', 0, 0, etCHARX, 0, 0 }, { 'o', 8, 0, etRADIX, 0, 2 }, { 'u', 10, 0, etRADIX, 0, 0 }, { 'x', 16, 0, etRADIX, 16, 1 }, { 'X', 16, 0, etRADIX, 0, 4 }, { 'i', 10, FLAG_SIGNED, etRADIX, 0, 0 }, #if !WHPRINTF_OMIT_FLOATING_POINT { 'f', 0, FLAG_SIGNED, etFLOAT, 0, 0 }, { 'e', 0, FLAG_SIGNED, etEXP, 30, 0 }, { 'E', 0, FLAG_SIGNED, etEXP, 14, 0 }, { 'G', 0, FLAG_SIGNED, etGENERIC, 14, 0 }, #endif /* !WHPRINTF_OMIT_FLOATING_POINT */ { '%', 0, 0, etPERCENT, 0, 0 }, { 'p', 16, 0, etPOINTER, 0, 1 }, { 'r', 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL, 0, 0 }, #if ! WHPRINTF_OMIT_SQL { 'q', 0, FLAG_STRING, etSQLESCAPE, 0, 0 }, { 'Q', 0, FLAG_STRING, etSQLESCAPE2, 0, 0 }, { 'w', 0, FLAG_STRING, etSQLESCAPE3, 0, 0 }, #endif /* !WHPRINTF_OMIT_SQL */ #if ! WHPRINTF_OMIT_HTML { 'h', 0, FLAG_STRING, etHTML, 0, 0 }, { 't', 0, FLAG_STRING, etURLENCODE, 0, 0 }, { 'T', 0, FLAG_STRING, etURLDECODE, 0, 0 }, #endif /* !WHPRINTF_OMIT_HTML */ #if !WHPRINTF_OMIT_SIZE { 'n', 0, 0, etSIZE, 0, 0 }, #endif #else /* WHPRINTF_FMTINFO_FIXED */ /* These entries MUST stay in ASCII order, sorted on their fmttype member! */ {' '/*32*/, 0, 0, 0, 0, 0 }, {'!'/*33*/, 0, 0, 0, 0, 0 }, {'"'/*34*/, 0, 0, 0, 0, 0 }, {'#'/*35*/, 0, 0, 0, 0, 0 }, {'$'/*36*/, 0, 0, 0, 0, 0 }, {'%'/*37*/, 0, 0, etPERCENT, 0, 0 }, {'&'/*38*/, 0, 0, 0, 0, 0 }, {'\''/*39*/, 0, 0, 0, 0, 0 }, {'('/*40*/, 0, 0, 0, 0, 0 }, {')'/*41*/, 0, 0, 0, 0, 0 }, {'*'/*42*/, 0, 0, 0, 0, 0 }, {'+'/*43*/, 0, 0, 0, 0, 0 }, {','/*44*/, 0, 0, 0, 0, 0 }, {'-'/*45*/, 0, 0, 0, 0, 0 }, {'.'/*46*/, 0, 0, 0, 0, 0 }, {'/'/*47*/, 0, 0, 0, 0, 0 }, {'0'/*48*/, 0, 0, 0, 0, 0 }, {'1'/*49*/, 0, 0, 0, 0, 0 }, {'2'/*50*/, 0, 0, 0, 0, 0 }, {'3'/*51*/, 0, 0, 0, 0, 0 }, {'4'/*52*/, 0, 0, 0, 0, 0 }, {'5'/*53*/, 0, 0, 0, 0, 0 }, {'6'/*54*/, 0, 0, 0, 0, 0 }, {'7'/*55*/, 0, 0, 0, 0, 0 }, {'8'/*56*/, 0, 0, 0, 0, 0 }, {'9'/*57*/, 0, 0, 0, 0, 0 }, {':'/*58*/, 0, 0, 0, 0, 0 }, {';'/*59*/, 0, 0, 0, 0, 0 }, {'<'/*60*/, 0, 0, 0, 0, 0 }, {'='/*61*/, 0, 0, 0, 0, 0 }, {'>'/*62*/, 0, 0, 0, 0, 0 }, {'?'/*63*/, 0, 0, 0, 0, 0 }, {'@'/*64*/, 0, 0, 0, 0, 0 }, {'A'/*65*/, 0, 0, 0, 0, 0 }, {'B'/*66*/, 0, 0, 0, 0, 0 }, {'C'/*67*/, 0, 0, 0, 0, 0 }, {'D'/*68*/, 0, 0, 0, 0, 0 }, {'E'/*69*/, 0, FLAG_SIGNED, etEXP, 14, 0 }, {'F'/*70*/, 0, 0, 0, 0, 0 }, {'G'/*71*/, 0, FLAG_SIGNED, etGENERIC, 14, 0 }, {'H'/*72*/, 0, 0, 0, 0, 0 }, {'I'/*73*/, 0, 0, 0, 0, 0 }, {'J'/*74*/, 0, 0, 0, 0, 0 }, {'K'/*75*/, 0, 0, 0, 0, 0 }, {'L'/*76*/, 0, 0, 0, 0, 0 }, {'M'/*77*/, 0, 0, 0, 0, 0 }, {'N'/*78*/, 0, 0, 0, 0, 0 }, {'O'/*79*/, 0, 0, 0, 0, 0 }, {'P'/*80*/, 0, 0, 0, 0, 0 }, #if WHPRINTF_OMIT_SQL {'Q'/*81*/, 0, 0, 0, 0, 0 }, #else {'Q'/*81*/, 0, FLAG_STRING, etSQLESCAPE2, 0, 0 }, #endif {'R'/*82*/, 0, 0, 0, 0, 0 }, {'S'/*83*/, 0, 0, 0, 0, 0 }, {'T'/*84*/, 0, FLAG_STRING, etURLDECODE, 0, 0 }, {'U'/*85*/, 0, 0, 0, 0, 0 }, {'V'/*86*/, 0, 0, 0, 0, 0 }, {'W'/*87*/, 0, 0, 0, 0, 0 }, {'X'/*88*/, 16, 0, etRADIX, 0, 4 }, {'Y'/*89*/, 0, 0, 0, 0, 0 }, {'Z'/*90*/, 0, 0, 0, 0, 0 }, {'['/*91*/, 0, 0, 0, 0, 0 }, {'\\'/*92*/, 0, 0, 0, 0, 0 }, {']'/*93*/, 0, 0, 0, 0, 0 }, {'^'/*94*/, 0, 0, 0, 0, 0 }, {'_'/*95*/, 0, 0, 0, 0, 0 }, {'`'/*96*/, 0, 0, 0, 0, 0 }, {'a'/*97*/, 0, 0, 0, 0, 0 }, {'b'/*98*/, 0, 0, 0, 0, 0 }, {'c'/*99*/, 0, 0, etCHARX, 0, 0 }, {'d'/*100*/, 10, FLAG_SIGNED, etRADIX, 0, 0 }, {'e'/*101*/, 0, FLAG_SIGNED, etEXP, 30, 0 }, {'f'/*102*/, 0, FLAG_SIGNED, etFLOAT, 0, 0}, {'g'/*103*/, 0, FLAG_SIGNED, etGENERIC, 30, 0 }, {'h'/*104*/, 0, FLAG_STRING, etHTML, 0, 0 }, {'i'/*105*/, 10, FLAG_SIGNED, etRADIX, 0, 0}, {'j'/*106*/, 0, 0, 0, 0, 0 }, {'k'/*107*/, 0, 0, 0, 0, 0 }, {'l'/*108*/, 0, 0, 0, 0, 0 }, {'m'/*109*/, 0, 0, 0, 0, 0 }, {'n'/*110*/, 0, 0, etSIZE, 0, 0 }, {'o'/*111*/, 8, 0, etRADIX, 0, 2 }, {'p'/*112*/, 16, 0, etPOINTER, 0, 1 }, #if WHPRINTF_OMIT_SQL {'q'/*113*/, 0, 0, 0, 0, 0 }, #else {'q'/*113*/, 0, FLAG_STRING, etSQLESCAPE, 0, 0 }, #endif {'r'/*114*/, 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL, 0, 0}, {'s'/*115*/, 0, FLAG_STRING, etSTRING, 0, 0 }, {'t'/*116*/, 0, FLAG_STRING, etURLENCODE, 0, 0 }, {'u'/*117*/, 10, 0, etRADIX, 0, 0 }, {'v'/*118*/, 0, 0, 0, 0, 0 }, #if WHPRINTF_OMIT_SQL {'w'/*119*/, 0, 0, 0, 0, 0 }, #else {'w'/*119*/, 0, FLAG_STRING, etSQLESCAPE3, 0, 0 }, #endif {'x'/*120*/, 16, 0, etRADIX, 16, 1 }, {'y'/*121*/, 0, 0, 0, 0, 0 }, {'z'/*122*/, 0, FLAG_STRING, etDYNSTRING, 0, 0}, {'{'/*123*/, 0, 0, 0, 0, 0 }, {'|'/*124*/, 0, 0, 0, 0, 0 }, {'}'/*125*/, 0, 0, 0, 0, 0 }, {'~'/*126*/, 0, 0, 0, 0, 0 }, #endif /* WHPRINTF_FMTINFO_FIXED */ }; #define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0])) #if ! WHPRINTF_OMIT_FLOATING_POINT /* "*val" is a double such that 0.1 <= *val < 10.0 Return the ascii code for the leading digit of *val, then multiply "*val" by 10.0 to renormalize. ** Example: input: *val = 3.14159 output: *val = 1.4159 function return = '3' ** The counter *cnt is incremented each time. After counter exceeds 16 (the number of significant digits in a 64-bit float) '0' is always returned. */ static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ int digit; LONGDOUBLE_TYPE d; if( (*cnt)++ >= 16 ) return '0'; digit = (int)*val; d = digit; digit += '0'; *val = (*val - d)*10.0; return digit; } #endif /* !WHPRINTF_OMIT_FLOATING_POINT */ /* On machines with a small(?) stack size, you can redefine the WHPRINTF_BUF_SIZE to be less than 350. But beware - for smaller values some %f conversions may go into an infinite loop. */ #ifndef WHPRINTF_BUF_SIZE # define WHPRINTF_BUF_SIZE 350 /* Size of the output buffer for numeric conversions */ #endif #if ! defined(__STDC__) && !defined(__TINYC__) #ifdef WHPRINTF_INT64_TYPE typedef WHPRINTF_INT64_TYPE int64_t; typedef unsigned WHPRINTF_INT64_TYPE uint64_t; #elif defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else typedef long long int int64_t; typedef unsigned long long int uint64_t; #endif #endif #if 0 / Not yet used. */ enum PrintfArgTypes { TypeInt = 0, TypeIntP = 1, TypeFloat = 2, TypeFloatP = 3, TypeCString = 4 }; #endif #if 0 / Not yet used. */ typedef struct cwal_printf_spec_handler_def { char letter; / e.g. %s */ int xtype; /* reference to the etXXXX values, or fmtinfo[*].type. */ int ntype; /* reference to PrintfArgTypes enum. */ } spec_handler; #endif /** cwal_printf_spec_handler is an almost-generic interface for farming work out of cwal_printfv()'s code into external functions. It doesn't actually save much (if any) overall code, but it makes the cwal_printfv() code more manageable. REQUIREMENTS of implementations: - Expects an implementation-specific vargp pointer. cwal_printfv() passes a pointer to the converted value of an entry from the format va_list. If it passes a type other than the expected one, undefined results. - If it calls pf then it must return the return value from that function. - If it calls pf it must do: pf( pfArg, D, N ), where D is the data to export and N is the number of bytes to export. It may call pf() an arbitrary number of times - If pf() successfully is called, the return value must be the accumulated totals of its return value(s), plus (possibly, but unlikely) an imnplementation-specific amount. - If it does not call pf() then it must return 0 (success) or a negative number (an error) or do all of the export processing itself and return the number of bytes exported. SIGNIFICANT LIMITATIONS: - Has no way of iterating over the format string, so handling precisions and such here can't work too well. */ typedef long (*cwal_printf_spec_handler)( cwal_printf_appender pf, void * pfArg, unsigned int pfLen, void * vargp ); /** cwal_printf_spec_handler for etSTRING types. It assumes that varg is a null-terminated (char [const] *) */ static long spech_string( cwal_printf_appender pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * ch = (char const *) varg; return ch ? pf( pfArg, ch, pfLen ) : 0; } /** cwal_printf_spec_handler for etDYNSTRING types. It assumes that varg is a non-const (char *). It behaves identically to spec_string() and then calls free() on that (char *). */ static long spech_dynstring( cwal_printf_appender pf, void * pfArg, unsigned int pfLen, void * varg ) { long ret = spech_string( pf, pfArg, pfLen, varg ); free( (char *) varg ); return ret; } #if !WHPRINTF_OMIT_HTML static long spech_string_to_html( cwal_printf_appender pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * ch = (char const *) varg; unsigned int i; long ret = 0; if( ! ch ) return 0; ret = 0; for( i = 0; (i= 32 && c <=47) || ( c>=58 && c<=64) || ( c>=91 && c<=96) || ( c>=123 && c<=126) || ( c<32 || c>=127) ); } /** The handler for the etURLENCODE specifier. It expects varg to be a string value, which it will preceed to encode using an URL encoding algothrim (certain characters are converted to %XX, where XX is their hex value) and passes the encoded string to pf(). It returns the total length of the output string. */ static long spech_urlencode( cwal_printf_appender pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * str = (char const *) varg; long ret = 0; char ch = 0; char const * hex = "0123456789ABCDEF"; #define xbufsz 10 char xbuf[xbufsz]; int slen = 0; if( ! str ) return 0; memset( xbuf, 0, xbufsz ); ch = *str; #define xbufsz 10 slen = 0; for( ; ch; ch = *(++str) ) { if( ! httpurl_needs_escape( ch ) ) { ret += pf( pfArg, str, 1 ); continue; } else { xbuf[0] = '%'; xbuf[1] = hex[((ch>>4)&0xf)]; xbuf[2] = hex[(ch&0xf)]; xbuf[3] = 0; slen = 3; ret += pf( pfArg, xbuf, slen ); } } #undef xbufsz return ret; } /* hexchar_to_int(): For 'a'-'f', 'A'-'F' and '0'-'9', returns the appropriate decimal number. For any other character it returns -1. */ static int hexchar_to_int( int ch ) { if( (ch>='a' && ch<='f') ) return ch-'a'+10; else if( (ch>='A' && ch<='F') ) return ch-'A'+10; else if( (ch>='0' && ch<='9') ) return ch-'0'; return -1; } /** The handler for the etURLDECODE specifier. It expects varg to be a ([const] char *), possibly encoded with URL encoding. It decodes the string using a URL decode algorithm and passes the decoded string to pf(). It returns the total length of the output string. If the input string contains malformed %XX codes then this function will return prematurely. */ static long spech_urldecode( cwal_printf_appender pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * str = (char const *) varg; long ret = 0; char ch = 0; char ch2 = 0; char xbuf[4]; int decoded; if( ! str ) return 0; ch = *str; while( ch ) { if( ch == '%' ) { ch = *(++str); ch2 = *(++str); if( isxdigit(ch) && isxdigit(ch2) ) { decoded = (hexchar_to_int( ch ) * 16) + hexchar_to_int( ch2 ); xbuf[0] = (char)decoded; xbuf[1] = 0; ret += pf( pfArg, xbuf, 1 ); ch = *(++str); continue; } else { xbuf[0] = '%'; xbuf[1] = ch; xbuf[2] = ch2; xbuf[3] = 0; ret += pf( pfArg, xbuf, 3 ); ch = *(++str); continue; } } else if( ch == '+' ) { xbuf[0] = ' '; xbuf[1] = 0; ret += pf( pfArg, xbuf, 1 ); ch = *(++str); continue; } xbuf[0] = ch; xbuf[1] = 0; ret += pf( pfArg, xbuf, 1 ); ch = *(++str); } return ret; } #endif /* !WHPRINTF_OMIT_HTML */ #if !WHPRINTF_OMIT_SQL /** Quotes the (char *) varg as an SQL string 'should' be quoted. The exact type of the conversion is specified by xtype, which must be one of etSQLESCAPE, etSQLESCAPE2, or etSQLESCAPE3. Search this file for those constants to find the associated documentation. */ static long spech_sqlstring_main( int xtype, cwal_printf_appender pf, void * pfArg, unsigned int pfLen, void * varg ) { unsigned int i, n; int j, ch, isnull; int needQuote; char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ char const * escarg = (char const *) varg; char * bufpt = NULL; long ret; isnull = escarg==0; if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); for(i=n=0; (i='0' && c<='9' ){ width = width*10 + c - '0'; c = *++fmt; } } if( width > WHPRINTF_BUF_SIZE-10 ){ width = WHPRINTF_BUF_SIZE-10; } /* Get the precision */ if( c=='.' ){ precision = 0; c = *++fmt; if( c=='*' ){ precision = va_arg(ap,int); if( precision<0 ) precision = -precision; c = *++fmt; }else{ while( c>='0' && c<='9' ){ precision = precision*10 + c - '0'; c = *++fmt; } } }else{ precision = -1; } /* Get the conversion type modifier */ if( c=='l' ){ flag_long = 1; c = *++fmt; if( c=='l' ){ flag_longlong = 1; c = *++fmt; }else{ flag_longlong = 0; } }else{ flag_long = flag_longlong = 0; } /* Fetch the info entry for the field */ infop = 0; #if WHPRINTF_FMTINFO_FIXED for(idx=0; idxflags & FLAG_EXTENDED)==0 ){ xtype = infop->type; }else{ WHPRINTF_RETURN; } break; } } #else #define FMTNDX(N) (N - fmtinfo[0].fmttype) #define FMTINFO(N) (fmtinfo[ FMTNDX(N) ]) infop = ((c>=(fmtinfo[0].fmttype)) && (cfmttype,infop->type);*/ if( infop ) xtype = infop->type; #undef FMTINFO #undef FMTNDX #endif /* WHPRINTF_FMTINFO_FIXED */ zExtra = 0; if( (!infop) || (!infop->type) ){ WHPRINTF_RETURN; } /* Limit the precision to prevent overflowing buf[] during conversion */ if( precision>WHPRINTF_BUF_SIZE-40 && (infop->flags & FLAG_STRING)==0 ){ precision = WHPRINTF_BUF_SIZE-40; } /* At this point, variables are initialized as follows: ** flag_alternateform TRUE if a '#' is present. flag_altform2 TRUE if a '!' is present. flag_plussign TRUE if a '+' is present. flag_leftjustify TRUE if a '-' is present or if the field width was negative. flag_zeropad TRUE if the width began with 0. flag_long TRUE if the letter 'l' (ell) prefixed the conversion character. flag_longlong TRUE if the letter 'll' (ell ell) prefixed the conversion character. flag_blanksign TRUE if a ' ' is present. width The specified field width. This is always non-negative. Zero is the default. precision The specified precision. The default is -1. xtype The class of the conversion. infop Pointer to the appropriate info struct. */ switch( xtype ){ case etPOINTER: flag_longlong = sizeof(char*)==sizeof(int64_t); flag_long = sizeof(char*)==sizeof(long int); /* Fall through into the next case */ case etORDINAL: case etRADIX: if( infop->flags & FLAG_SIGNED ){ int64_t v; if( flag_longlong ) v = va_arg(ap,int64_t); else if( flag_long ) v = va_arg(ap,long int); else v = va_arg(ap,int); if( v<0 ){ longvalue = -v; prefix = '-'; }else{ longvalue = v; if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; } }else{ if( flag_longlong ) longvalue = va_arg(ap,uint64_t); else if( flag_long ) longvalue = va_arg(ap,unsigned long int); else longvalue = va_arg(ap,unsigned int); prefix = 0; } if( longvalue==0 ) flag_alternateform = 0; if( flag_zeropad && precision=4 || (longvalue/10)%10==1 ){ x = 0; } buf[WHPRINTF_BUF_SIZE-3] = zOrd[x*2]; buf[WHPRINTF_BUF_SIZE-2] = zOrd[x*2+1]; bufpt -= 2; } { const char *cset; int base; cset = &aDigits[infop->charset]; base = infop->base; do{ /* Convert to ascii */ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } length = &buf[WHPRINTF_BUF_SIZE-1]-bufpt; for(idx=precision-length; idx>0; idx--){ *(--bufpt) = '0'; /* Zero pad */ } if( prefix ) *(--bufpt) = prefix; /* Add sign */ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ const char *pre; char x; pre = &aPrefix[infop->prefix]; if( *bufpt!=pre[0] ){ for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; } } length = &buf[WHPRINTF_BUF_SIZE-1]-bufpt; break; case etFLOAT: case etEXP: case etGENERIC: realvalue = va_arg(ap,double); #if ! WHPRINTF_OMIT_FLOATING_POINT if( precision<0 ) precision = 6; /* Set default precision */ if( precision>WHPRINTF_BUF_SIZE/2-10 ) precision = WHPRINTF_BUF_SIZE/2-10; if( realvalue<0.0 ){ realvalue = -realvalue; prefix = '-'; }else{ if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; } if( xtype==etGENERIC && precision>0 ) precision--; #if 0 /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */ for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); #else /* It makes more sense to use 0.5 */ for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1){} #endif if( xtype==etFLOAT ) realvalue += rounder; /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ exp = 0; #if 1 if( (realvalue)!=(realvalue) ){ /* from sqlite3: #define sqlite3_isnan(X) ((X)!=(X)) */ /* This weird array thing is to avoid constness violations when assinging, e.g. "NaN" to bufpt. */ static char NaN[4] = {'N','a','N','\0'}; bufpt = NaN; length = 3; break; } #endif if( realvalue>0.0 ){ while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; } while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } if( exp>350 || exp<-350 ){ if( prefix=='-' ){ static char Inf[5] = {'-','I','n','f','\0'}; bufpt = Inf; }else if( prefix=='+' ){ static char Inf[5] = {'+','I','n','f','\0'}; bufpt = Inf; }else{ static char Inf[4] = {'I','n','f','\0'}; bufpt = Inf; } length = strlen(bufpt); break; } } bufpt = buf; /* If the field type is etGENERIC, then convert to either etEXP or etFLOAT, as appropriate. */ flag_exp = xtype==etEXP; if( xtype!=etFLOAT ){ realvalue += rounder; if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } } if( xtype==etGENERIC ){ flag_rtz = !flag_alternateform; if( exp<-4 || exp>precision ){ xtype = etEXP; }else{ precision = precision - exp; xtype = etFLOAT; } }else{ flag_rtz = 0; } if( xtype==etEXP ){ e2 = 0; }else{ e2 = exp; } nsd = 0; flag_dp = (precision>0) | flag_alternateform | flag_altform2; /* The sign in front of the number */ if( prefix ){ *(bufpt++) = prefix; } /* Digits prior to the decimal point */ if( e2<0 ){ *(bufpt++) = '0'; }else{ for(; e2>=0; e2--){ *(bufpt++) = et_getdigit(&realvalue,&nsd); } } /* The decimal point */ if( flag_dp ){ *(bufpt++) = '.'; } /* "0" digits after the decimal point but before the first significant digit of the number */ for(e2++; e2<0 && precision>0; precision--, e2++){ *(bufpt++) = '0'; } /* Significant digits after the decimal point */ while( (precision--)>0 ){ *(bufpt++) = et_getdigit(&realvalue,&nsd); } /* Remove trailing zeros and the "." if no digits follow the "." */ if( flag_rtz && flag_dp ){ while( bufpt[-1]=='0' ) *(--bufpt) = 0; /* assert( bufpt>buf ); */ if( bufpt[-1]=='.' ){ if( flag_altform2 ){ *(bufpt++) = '0'; }else{ *(--bufpt) = 0; } } } /* Add the "eNNN" suffix */ if( flag_exp || (xtype==etEXP && exp) ){ *(bufpt++) = aDigits[infop->charset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; }else{ *(bufpt++) = '+'; } if( exp>=100 ){ *(bufpt++) = (exp/100)+'0'; /* 100's digit */ exp %= 100; } *(bufpt++) = exp/10+'0'; /* 10's digit */ *(bufpt++) = exp%10+'0'; /* 1's digit */ } *bufpt = 0; /* The converted number is in buf[] and zero terminated. Output it. Note that the number is in the usual order, not reversed as with integer conversions. */ length = bufpt-buf; bufpt = buf; /* Special case: Add leading zeros if the flag_zeropad flag is set and we are not left justified */ if( flag_zeropad && !flag_leftjustify && length < width){ int i; int nPad = width - length; for(i=width; i>=nPad; i--){ bufpt[i] = bufpt[i-nPad]; } i = prefix!=0; while( nPad-- ) bufpt[i++] = '0'; length = width; } #endif /* !WHPRINTF_OMIT_FLOATING_POINT */ break; #if !WHPRINTF_OMIT_SIZE case etSIZE: *(va_arg(ap,int*)) = outCount; length = width = 0; break; #endif case etPERCENT: buf[0] = '%'; bufpt = buf; length = 1; break; case etCHARLIT: case etCHARX: c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt); if( precision>=0 ){ for(idx=1; idx=0 && precision0 ){ WHPRINTF_SPACES(nspace); } } if( length>0 ){ pfrc = pfAppend( pfAppendArg, bufpt, length); WHPRINTF_CHECKERR(0); } if( flag_leftjustify ){ int nspace; nspace = width-length; if( nspace>0 ){ WHPRINTF_SPACES(nspace); } } if( zExtra ){ free(zExtra); zExtra = 0; } }/* End for loop over the format string */ WHPRINTF_RETURN; } /* End of function */ #undef WHPRINTF_SPACES #undef WHPRINTF_CHECKERR #undef WHPRINTF_RETURN #undef WHPRINTF_OMIT_FLOATING_POINT #undef WHPRINTF_OMIT_SIZE #undef WHPRINTF_OMIT_SQL #undef WHPRINTF_BUF_SIZE #undef WHPRINTF_OMIT_HTML long cwal_printf(cwal_printf_appender pfAppend, /* Accumulate results here */ void * pfAppendArg, /* Passed as first arg to pfAppend. */ const char *fmt, /* Format string */ ... ) { va_list vargs; long ret; va_start( vargs, fmt ); ret = cwal_printfv( pfAppend, pfAppendArg, fmt, vargs ); va_end(vargs); return ret; } long cwal_printf_FILE_appender( void * a, char const * s, long n ) { FILE * fp = (FILE *)a; if( ! fp ) return -1; else { long ret = fwrite( s, sizeof(char), n, fp ); return (ret >= 0) ? ret : -2; } } long cwal_printf_FILE( FILE * fp, char const * fmt, ... ) { va_list vargs; int ret; va_start( vargs, fmt ); ret = cwal_printfv( cwal_printf_FILE_appender, fp, fmt, vargs ); va_end(vargs); return ret; } /** Internal implementation details for cwal_printfv_appender_stringbuf. */ typedef struct cwal_printfv_stringbuf { /** dynamically allocated buffer */ char * buffer; /** bytes allocated to buffer */ size_t alloced; /** Current position within buffer. */ size_t pos; } cwal_printfv_stringbuf; static const cwal_printfv_stringbuf cwal_printfv_stringbuf_init = { 0, 0, 0 }; /** A cwal_printfv_appender implementation which requires arg to be a (cwal_printfv_stringbuf*). It appends n bytes of data to the cwal_printfv_stringbuf object's buffer, reallocating it as needed. Returns less than 0 on error, else the number of bytes appended to the buffer. The buffer will always be null terminated. */ static long cwal_printfv_appender_stringbuf( void * arg, char const * data, long n ) { cwal_printfv_stringbuf * sb = (cwal_printfv_stringbuf*)arg; if( ! sb || (n<0) ) return -1; else if( ! n ) return 0; else { long rc; size_t npos = sb->pos + n; if( npos >= sb->alloced ) { const size_t asz = (3 * npos / 2) + 1; if( asz < npos ) return -1; /* overflow */ else { char * buf = (char *)realloc( sb->buffer, asz ); if( ! buf ) return -1; memset( buf + sb->pos, 0, (npos + 1 - sb->pos) ); /* the +1 adds our NUL for us*/ sb->buffer = buf; sb->alloced = asz; } } rc = 0; for( ; rc < n; ++rc, ++sb->pos ) { sb->buffer[sb->pos] = data[rc]; } return rc; } } char * cwal_printfv_cstr( char const * fmt, va_list vargs ) { if( ! fmt ) return 0; else { cwal_printfv_stringbuf sb = cwal_printfv_stringbuf_init; long rc = cwal_printfv( cwal_printfv_appender_stringbuf, &sb, fmt, vargs ); if( rc <= 0 ) { free( sb.buffer ); sb.buffer = 0; } return sb.buffer; } } char * cwal_printf_cstr( char const * fmt, ... ) { va_list vargs; char * ret; va_start( vargs, fmt ); ret = cwal_printfv_cstr( fmt, vargs ); va_end( vargs ); return ret; } /* end of file cwal_printf.c */ /* start of file cwal_utf.c */ #include #include /* for a single sprintf() need :/ */ #include /* toupper(), tolower() */ /** Parts of the UTF code was originally taken from sqlite3's public-domain source code (http://sqlite.org), modified only slightly for use here. This code generates some "possible data loss" warnings on MSC, but if this code is good enough for sqlite3 then it's damned well good enough for me, so we disable that warning for MSC builds. */ #ifdef _MSC_VER # if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ # define PUSHED_MSC_WARNING # pragma warning( push ) # pragma warning(disable:4244) /* complaining about data loss due to integer precision in the sqlite3 utf decoding routines */ # endif #endif /* ** This lookup table is used to help decode the first byte of ** a multi-byte UTF8 character. */ static const unsigned char cwal_utfTrans1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00 }; int cwal_utf8_read_char( const unsigned char *zIn, /* First byte of UTF-8 character */ const unsigned char *zTerm, /* Pretend this byte is 0x00 */ const unsigned char **pzNext /* Write first byte past UTF-8 char here */ ){ int c; c = *(zIn++); if( c>=0xc0 ){ c = cwal_utfTrans1[c-0xc0]; while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ) c = (c<<6) + (0x3f & *(zIn++)); if( c<0x80 || (c&0xFFFFF800)==0xD800 || (c&0xFFFFFFFE)==0xFFFE ) c = 0xFFFD; } *pzNext = zIn; return c; } cwal_size_t cwal_cstr_length_utf8( unsigned char const * str, cwal_size_t len ){ if( ! str || !len ) return 0; else{ char unsigned const * pos = str; char unsigned const * end = pos + len; cwal_size_t rc = 0; for( ; (pos < end) && cwal_utf8_read_char(pos, end, &pos); ++rc ) {} return rc; } } cwal_size_t cwal_string_length_utf8( cwal_string const * str ){ return str ? cwal_cstr_length_utf8( (char unsigned const *)cwal_string_cstr(str), cwal_string_length_bytes(str) ) : 0; } int cwal_utf8_char_to_cstr(unsigned int c, unsigned char *output, cwal_size_t length){ /* Stolen from the internet, adapted from several variations which all _seem_ to have derived from librdf. */ int size=0; /* check for illegal code positions: * U+D800 to U+DFFF (UTF-16 surrogates) * U+FFFE and U+FFFF */ if((c > 0xD7FF && c < 0xE000) || c == 0xFFFE || c == 0xFFFF) return -1; /* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */ if(c > 0x10ffff) return -1; if(!c) size = 0; else if (c < 0x00000080) size = 1; else if (c < 0x00000800) size = 2; else if (c < 0x00010000) size = 3; else size = 4; if(!output) return size; else if(size > length) return -1; else switch(size) { case 0: output[0] = 0; return 0; case 4: output[3] = 0x80 | (c & 0x3F); c = c >> 6; c |= 0x10000; /* fall through */ case 3: output[2] = 0x80 | (c & 0x3F); c = c >> 6; c |= 0x800; /* fall through */ case 2: output[1] = 0x80 | (c & 0x3F); c = c >> 6; c |= 0xc0; /* fall through */ case 1: output[0] = (unsigned char)c; /* keep falling... */ default: return size; } } int cwal_utf8_char_tolower( int ch ){ /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt */ switch(ch){ case 0x0041: return 0x0061; case 0x0042: return 0x0062; case 0x0043: return 0x0063; case 0x0044: return 0x0064; case 0x0045: return 0x0065; case 0x0046: return 0x0066; case 0x0047: return 0x0067; case 0x0048: return 0x0068; case 0x0049: return 0x0069; case 0x004A: return 0x006A; case 0x004B: return 0x006B; case 0x004C: return 0x006C; case 0x004D: return 0x006D; case 0x004E: return 0x006E; case 0x004F: return 0x006F; case 0x0050: return 0x0070; case 0x0051: return 0x0071; case 0x0052: return 0x0072; case 0x0053: return 0x0073; case 0x0054: return 0x0074; case 0x0055: return 0x0075; case 0x0056: return 0x0076; case 0x0057: return 0x0077; case 0x0058: return 0x0078; case 0x0059: return 0x0079; case 0x005A: return 0x007A; case 0x00C0: return 0x00E0; case 0x00C1: return 0x00E1; case 0x00C2: return 0x00E2; case 0x00C3: return 0x00E3; case 0x00C4: return 0x00E4; case 0x00C5: return 0x00E5; case 0x00C6: return 0x00E6; case 0x00C7: return 0x00E7; case 0x00C8: return 0x00E8; case 0x00C9: return 0x00E9; case 0x00CA: return 0x00EA; case 0x00CB: return 0x00EB; case 0x00CC: return 0x00EC; case 0x00CD: return 0x00ED; case 0x00CE: return 0x00EE; case 0x00CF: return 0x00EF; case 0x00D0: return 0x00F0; case 0x00D1: return 0x00F1; case 0x00D2: return 0x00F2; case 0x00D3: return 0x00F3; case 0x00D4: return 0x00F4; case 0x00D5: return 0x00F5; case 0x00D6: return 0x00F6; case 0x00D8: return 0x00F8; case 0x00D9: return 0x00F9; case 0x00DA: return 0x00FA; case 0x00DB: return 0x00FB; case 0x00DC: return 0x00FC; case 0x00DD: return 0x00FD; case 0x00DE: return 0x00FE; case 0x0100: return 0x0101; case 0x0102: return 0x0103; case 0x0104: return 0x0105; case 0x0106: return 0x0107; case 0x0108: return 0x0109; case 0x010A: return 0x010B; case 0x010C: return 0x010D; case 0x010E: return 0x010F; case 0x0110: return 0x0111; case 0x0112: return 0x0113; case 0x0114: return 0x0115; case 0x0116: return 0x0117; case 0x0118: return 0x0119; case 0x011A: return 0x011B; case 0x011C: return 0x011D; case 0x011E: return 0x011F; case 0x0120: return 0x0121; case 0x0122: return 0x0123; case 0x0124: return 0x0125; case 0x0126: return 0x0127; case 0x0128: return 0x0129; case 0x012A: return 0x012B; case 0x012C: return 0x012D; case 0x012E: return 0x012F; case 0x0130: return 0x0069; case 0x0132: return 0x0133; case 0x0134: return 0x0135; case 0x0136: return 0x0137; case 0x0139: return 0x013A; case 0x013B: return 0x013C; case 0x013D: return 0x013E; case 0x013F: return 0x0140; case 0x0141: return 0x0142; case 0x0143: return 0x0144; case 0x0145: return 0x0146; case 0x0147: return 0x0148; case 0x014A: return 0x014B; case 0x014C: return 0x014D; case 0x014E: return 0x014F; case 0x0150: return 0x0151; case 0x0152: return 0x0153; case 0x0154: return 0x0155; case 0x0156: return 0x0157; case 0x0158: return 0x0159; case 0x015A: return 0x015B; case 0x015C: return 0x015D; case 0x015E: return 0x015F; case 0x0160: return 0x0161; case 0x0162: return 0x0163; case 0x0164: return 0x0165; case 0x0166: return 0x0167; case 0x0168: return 0x0169; case 0x016A: return 0x016B; case 0x016C: return 0x016D; case 0x016E: return 0x016F; case 0x0170: return 0x0171; case 0x0172: return 0x0173; case 0x0174: return 0x0175; case 0x0176: return 0x0177; case 0x0178: return 0x00FF; case 0x0179: return 0x017A; case 0x017B: return 0x017C; case 0x017D: return 0x017E; case 0x0181: return 0x0253; case 0x0182: return 0x0183; case 0x0184: return 0x0185; case 0x0186: return 0x0254; case 0x0187: return 0x0188; case 0x0189: return 0x0256; case 0x018A: return 0x0257; case 0x018B: return 0x018C; case 0x018E: return 0x01DD; case 0x018F: return 0x0259; case 0x0190: return 0x025B; case 0x0191: return 0x0192; case 0x0193: return 0x0260; case 0x0194: return 0x0263; case 0x0196: return 0x0269; case 0x0197: return 0x0268; case 0x0198: return 0x0199; case 0x019C: return 0x026F; case 0x019D: return 0x0272; case 0x019F: return 0x0275; case 0x01A0: return 0x01A1; case 0x01A2: return 0x01A3; case 0x01A4: return 0x01A5; case 0x01A6: return 0x0280; case 0x01A7: return 0x01A8; case 0x01A9: return 0x0283; case 0x01AC: return 0x01AD; case 0x01AE: return 0x0288; case 0x01AF: return 0x01B0; case 0x01B1: return 0x028A; case 0x01B2: return 0x028B; case 0x01B3: return 0x01B4; case 0x01B5: return 0x01B6; case 0x01B7: return 0x0292; case 0x01B8: return 0x01B9; case 0x01BC: return 0x01BD; case 0x01C4: return 0x01C6; case 0x01C5: return 0x01C6; case 0x01C7: return 0x01C9; case 0x01C8: return 0x01C9; case 0x01CA: return 0x01CC; case 0x01CB: return 0x01CC; case 0x01CD: return 0x01CE; case 0x01CF: return 0x01D0; case 0x01D1: return 0x01D2; case 0x01D3: return 0x01D4; case 0x01D5: return 0x01D6; case 0x01D7: return 0x01D8; case 0x01D9: return 0x01DA; case 0x01DB: return 0x01DC; case 0x01DE: return 0x01DF; case 0x01E0: return 0x01E1; case 0x01E2: return 0x01E3; case 0x01E4: return 0x01E5; case 0x01E6: return 0x01E7; case 0x01E8: return 0x01E9; case 0x01EA: return 0x01EB; case 0x01EC: return 0x01ED; case 0x01EE: return 0x01EF; case 0x01F1: return 0x01F3; case 0x01F2: return 0x01F3; case 0x01F4: return 0x01F5; case 0x01F6: return 0x0195; case 0x01F7: return 0x01BF; case 0x01F8: return 0x01F9; case 0x01FA: return 0x01FB; case 0x01FC: return 0x01FD; case 0x01FE: return 0x01FF; case 0x0200: return 0x0201; case 0x0202: return 0x0203; case 0x0204: return 0x0205; case 0x0206: return 0x0207; case 0x0208: return 0x0209; case 0x020A: return 0x020B; case 0x020C: return 0x020D; case 0x020E: return 0x020F; case 0x0210: return 0x0211; case 0x0212: return 0x0213; case 0x0214: return 0x0215; case 0x0216: return 0x0217; case 0x0218: return 0x0219; case 0x021A: return 0x021B; case 0x021C: return 0x021D; case 0x021E: return 0x021F; case 0x0220: return 0x019E; case 0x0222: return 0x0223; case 0x0224: return 0x0225; case 0x0226: return 0x0227; case 0x0228: return 0x0229; case 0x022A: return 0x022B; case 0x022C: return 0x022D; case 0x022E: return 0x022F; case 0x0230: return 0x0231; case 0x0232: return 0x0233; case 0x023A: return 0x2C65; case 0x023B: return 0x023C; case 0x023D: return 0x019A; case 0x023E: return 0x2C66; case 0x0241: return 0x0242; case 0x0243: return 0x0180; case 0x0244: return 0x0289; case 0x0245: return 0x028C; case 0x0246: return 0x0247; case 0x0248: return 0x0249; case 0x024A: return 0x024B; case 0x024C: return 0x024D; case 0x024E: return 0x024F; case 0x0370: return 0x0371; case 0x0372: return 0x0373; case 0x0376: return 0x0377; case 0x0386: return 0x03AC; case 0x0388: return 0x03AD; case 0x0389: return 0x03AE; case 0x038A: return 0x03AF; case 0x038C: return 0x03CC; case 0x038E: return 0x03CD; case 0x038F: return 0x03CE; case 0x0391: return 0x03B1; case 0x0392: return 0x03B2; case 0x0393: return 0x03B3; case 0x0394: return 0x03B4; case 0x0395: return 0x03B5; case 0x0396: return 0x03B6; case 0x0397: return 0x03B7; case 0x0398: return 0x03B8; case 0x0399: return 0x03B9; case 0x039A: return 0x03BA; case 0x039B: return 0x03BB; case 0x039C: return 0x03BC; case 0x039D: return 0x03BD; case 0x039E: return 0x03BE; case 0x039F: return 0x03BF; case 0x03A0: return 0x03C0; case 0x03A1: return 0x03C1; case 0x03A3: return 0x03C3; case 0x03A4: return 0x03C4; case 0x03A5: return 0x03C5; case 0x03A6: return 0x03C6; case 0x03A7: return 0x03C7; case 0x03A8: return 0x03C8; case 0x03A9: return 0x03C9; case 0x03AA: return 0x03CA; case 0x03AB: return 0x03CB; case 0x03CF: return 0x03D7; case 0x03D8: return 0x03D9; case 0x03DA: return 0x03DB; case 0x03DC: return 0x03DD; case 0x03DE: return 0x03DF; case 0x03E0: return 0x03E1; case 0x03E2: return 0x03E3; case 0x03E4: return 0x03E5; case 0x03E6: return 0x03E7; case 0x03E8: return 0x03E9; case 0x03EA: return 0x03EB; case 0x03EC: return 0x03ED; case 0x03EE: return 0x03EF; case 0x03F4: return 0x03B8; case 0x03F7: return 0x03F8; case 0x03F9: return 0x03F2; case 0x03FA: return 0x03FB; case 0x03FD: return 0x037B; case 0x03FE: return 0x037C; case 0x03FF: return 0x037D; case 0x0400: return 0x0450; case 0x0401: return 0x0451; case 0x0402: return 0x0452; case 0x0403: return 0x0453; case 0x0404: return 0x0454; case 0x0405: return 0x0455; case 0x0406: return 0x0456; case 0x0407: return 0x0457; case 0x0408: return 0x0458; case 0x0409: return 0x0459; case 0x040A: return 0x045A; case 0x040B: return 0x045B; case 0x040C: return 0x045C; case 0x040D: return 0x045D; case 0x040E: return 0x045E; case 0x040F: return 0x045F; case 0x0410: return 0x0430; case 0x0411: return 0x0431; case 0x0412: return 0x0432; case 0x0413: return 0x0433; case 0x0414: return 0x0434; case 0x0415: return 0x0435; case 0x0416: return 0x0436; case 0x0417: return 0x0437; case 0x0418: return 0x0438; case 0x0419: return 0x0439; case 0x041A: return 0x043A; case 0x041B: return 0x043B; case 0x041C: return 0x043C; case 0x041D: return 0x043D; case 0x041E: return 0x043E; case 0x041F: return 0x043F; case 0x0420: return 0x0440; case 0x0421: return 0x0441; case 0x0422: return 0x0442; case 0x0423: return 0x0443; case 0x0424: return 0x0444; case 0x0425: return 0x0445; case 0x0426: return 0x0446; case 0x0427: return 0x0447; case 0x0428: return 0x0448; case 0x0429: return 0x0449; case 0x042A: return 0x044A; case 0x042B: return 0x044B; case 0x042C: return 0x044C; case 0x042D: return 0x044D; case 0x042E: return 0x044E; case 0x042F: return 0x044F; case 0x0460: return 0x0461; case 0x0462: return 0x0463; case 0x0464: return 0x0465; case 0x0466: return 0x0467; case 0x0468: return 0x0469; case 0x046A: return 0x046B; case 0x046C: return 0x046D; case 0x046E: return 0x046F; case 0x0470: return 0x0471; case 0x0472: return 0x0473; case 0x0474: return 0x0475; case 0x0476: return 0x0477; case 0x0478: return 0x0479; case 0x047A: return 0x047B; case 0x047C: return 0x047D; case 0x047E: return 0x047F; case 0x0480: return 0x0481; case 0x048A: return 0x048B; case 0x048C: return 0x048D; case 0x048E: return 0x048F; case 0x0490: return 0x0491; case 0x0492: return 0x0493; case 0x0494: return 0x0495; case 0x0496: return 0x0497; case 0x0498: return 0x0499; case 0x049A: return 0x049B; case 0x049C: return 0x049D; case 0x049E: return 0x049F; case 0x04A0: return 0x04A1; case 0x04A2: return 0x04A3; case 0x04A4: return 0x04A5; case 0x04A6: return 0x04A7; case 0x04A8: return 0x04A9; case 0x04AA: return 0x04AB; case 0x04AC: return 0x04AD; case 0x04AE: return 0x04AF; case 0x04B0: return 0x04B1; case 0x04B2: return 0x04B3; case 0x04B4: return 0x04B5; case 0x04B6: return 0x04B7; case 0x04B8: return 0x04B9; case 0x04BA: return 0x04BB; case 0x04BC: return 0x04BD; case 0x04BE: return 0x04BF; case 0x04C0: return 0x04CF; case 0x04C1: return 0x04C2; case 0x04C3: return 0x04C4; case 0x04C5: return 0x04C6; case 0x04C7: return 0x04C8; case 0x04C9: return 0x04CA; case 0x04CB: return 0x04CC; case 0x04CD: return 0x04CE; case 0x04D0: return 0x04D1; case 0x04D2: return 0x04D3; case 0x04D4: return 0x04D5; case 0x04D6: return 0x04D7; case 0x04D8: return 0x04D9; case 0x04DA: return 0x04DB; case 0x04DC: return 0x04DD; case 0x04DE: return 0x04DF; case 0x04E0: return 0x04E1; case 0x04E2: return 0x04E3; case 0x04E4: return 0x04E5; case 0x04E6: return 0x04E7; case 0x04E8: return 0x04E9; case 0x04EA: return 0x04EB; case 0x04EC: return 0x04ED; case 0x04EE: return 0x04EF; case 0x04F0: return 0x04F1; case 0x04F2: return 0x04F3; case 0x04F4: return 0x04F5; case 0x04F6: return 0x04F7; case 0x04F8: return 0x04F9; case 0x04FA: return 0x04FB; case 0x04FC: return 0x04FD; case 0x04FE: return 0x04FF; case 0x0500: return 0x0501; case 0x0502: return 0x0503; case 0x0504: return 0x0505; case 0x0506: return 0x0507; case 0x0508: return 0x0509; case 0x050A: return 0x050B; case 0x050C: return 0x050D; case 0x050E: return 0x050F; case 0x0510: return 0x0511; case 0x0512: return 0x0513; case 0x0514: return 0x0515; case 0x0516: return 0x0517; case 0x0518: return 0x0519; case 0x051A: return 0x051B; case 0x051C: return 0x051D; case 0x051E: return 0x051F; case 0x0520: return 0x0521; case 0x0522: return 0x0523; case 0x0524: return 0x0525; case 0x0526: return 0x0527; case 0x0531: return 0x0561; case 0x0532: return 0x0562; case 0x0533: return 0x0563; case 0x0534: return 0x0564; case 0x0535: return 0x0565; case 0x0536: return 0x0566; case 0x0537: return 0x0567; case 0x0538: return 0x0568; case 0x0539: return 0x0569; case 0x053A: return 0x056A; case 0x053B: return 0x056B; case 0x053C: return 0x056C; case 0x053D: return 0x056D; case 0x053E: return 0x056E; case 0x053F: return 0x056F; case 0x0540: return 0x0570; case 0x0541: return 0x0571; case 0x0542: return 0x0572; case 0x0543: return 0x0573; case 0x0544: return 0x0574; case 0x0545: return 0x0575; case 0x0546: return 0x0576; case 0x0547: return 0x0577; case 0x0548: return 0x0578; case 0x0549: return 0x0579; case 0x054A: return 0x057A; case 0x054B: return 0x057B; case 0x054C: return 0x057C; case 0x054D: return 0x057D; case 0x054E: return 0x057E; case 0x054F: return 0x057F; case 0x0550: return 0x0580; case 0x0551: return 0x0581; case 0x0552: return 0x0582; case 0x0553: return 0x0583; case 0x0554: return 0x0584; case 0x0555: return 0x0585; case 0x0556: return 0x0586; case 0x10A0: return 0x2D00; case 0x10A1: return 0x2D01; case 0x10A2: return 0x2D02; case 0x10A3: return 0x2D03; case 0x10A4: return 0x2D04; case 0x10A5: return 0x2D05; case 0x10A6: return 0x2D06; case 0x10A7: return 0x2D07; case 0x10A8: return 0x2D08; case 0x10A9: return 0x2D09; case 0x10AA: return 0x2D0A; case 0x10AB: return 0x2D0B; case 0x10AC: return 0x2D0C; case 0x10AD: return 0x2D0D; case 0x10AE: return 0x2D0E; case 0x10AF: return 0x2D0F; case 0x10B0: return 0x2D10; case 0x10B1: return 0x2D11; case 0x10B2: return 0x2D12; case 0x10B3: return 0x2D13; case 0x10B4: return 0x2D14; case 0x10B5: return 0x2D15; case 0x10B6: return 0x2D16; case 0x10B7: return 0x2D17; case 0x10B8: return 0x2D18; case 0x10B9: return 0x2D19; case 0x10BA: return 0x2D1A; case 0x10BB: return 0x2D1B; case 0x10BC: return 0x2D1C; case 0x10BD: return 0x2D1D; case 0x10BE: return 0x2D1E; case 0x10BF: return 0x2D1F; case 0x10C0: return 0x2D20; case 0x10C1: return 0x2D21; case 0x10C2: return 0x2D22; case 0x10C3: return 0x2D23; case 0x10C4: return 0x2D24; case 0x10C5: return 0x2D25; case 0x10C7: return 0x2D27; case 0x10CD: return 0x2D2D; case 0x1E00: return 0x1E01; case 0x1E02: return 0x1E03; case 0x1E04: return 0x1E05; case 0x1E06: return 0x1E07; case 0x1E08: return 0x1E09; case 0x1E0A: return 0x1E0B; case 0x1E0C: return 0x1E0D; case 0x1E0E: return 0x1E0F; case 0x1E10: return 0x1E11; case 0x1E12: return 0x1E13; case 0x1E14: return 0x1E15; case 0x1E16: return 0x1E17; case 0x1E18: return 0x1E19; case 0x1E1A: return 0x1E1B; case 0x1E1C: return 0x1E1D; case 0x1E1E: return 0x1E1F; case 0x1E20: return 0x1E21; case 0x1E22: return 0x1E23; case 0x1E24: return 0x1E25; case 0x1E26: return 0x1E27; case 0x1E28: return 0x1E29; case 0x1E2A: return 0x1E2B; case 0x1E2C: return 0x1E2D; case 0x1E2E: return 0x1E2F; case 0x1E30: return 0x1E31; case 0x1E32: return 0x1E33; case 0x1E34: return 0x1E35; case 0x1E36: return 0x1E37; case 0x1E38: return 0x1E39; case 0x1E3A: return 0x1E3B; case 0x1E3C: return 0x1E3D; case 0x1E3E: return 0x1E3F; case 0x1E40: return 0x1E41; case 0x1E42: return 0x1E43; case 0x1E44: return 0x1E45; case 0x1E46: return 0x1E47; case 0x1E48: return 0x1E49; case 0x1E4A: return 0x1E4B; case 0x1E4C: return 0x1E4D; case 0x1E4E: return 0x1E4F; case 0x1E50: return 0x1E51; case 0x1E52: return 0x1E53; case 0x1E54: return 0x1E55; case 0x1E56: return 0x1E57; case 0x1E58: return 0x1E59; case 0x1E5A: return 0x1E5B; case 0x1E5C: return 0x1E5D; case 0x1E5E: return 0x1E5F; case 0x1E60: return 0x1E61; case 0x1E62: return 0x1E63; case 0x1E64: return 0x1E65; case 0x1E66: return 0x1E67; case 0x1E68: return 0x1E69; case 0x1E6A: return 0x1E6B; case 0x1E6C: return 0x1E6D; case 0x1E6E: return 0x1E6F; case 0x1E70: return 0x1E71; case 0x1E72: return 0x1E73; case 0x1E74: return 0x1E75; case 0x1E76: return 0x1E77; case 0x1E78: return 0x1E79; case 0x1E7A: return 0x1E7B; case 0x1E7C: return 0x1E7D; case 0x1E7E: return 0x1E7F; case 0x1E80: return 0x1E81; case 0x1E82: return 0x1E83; case 0x1E84: return 0x1E85; case 0x1E86: return 0x1E87; case 0x1E88: return 0x1E89; case 0x1E8A: return 0x1E8B; case 0x1E8C: return 0x1E8D; case 0x1E8E: return 0x1E8F; case 0x1E90: return 0x1E91; case 0x1E92: return 0x1E93; case 0x1E94: return 0x1E95; case 0x1E9E: return 0x00DF; case 0x1EA0: return 0x1EA1; case 0x1EA2: return 0x1EA3; case 0x1EA4: return 0x1EA5; case 0x1EA6: return 0x1EA7; case 0x1EA8: return 0x1EA9; case 0x1EAA: return 0x1EAB; case 0x1EAC: return 0x1EAD; case 0x1EAE: return 0x1EAF; case 0x1EB0: return 0x1EB1; case 0x1EB2: return 0x1EB3; case 0x1EB4: return 0x1EB5; case 0x1EB6: return 0x1EB7; case 0x1EB8: return 0x1EB9; case 0x1EBA: return 0x1EBB; case 0x1EBC: return 0x1EBD; case 0x1EBE: return 0x1EBF; case 0x1EC0: return 0x1EC1; case 0x1EC2: return 0x1EC3; case 0x1EC4: return 0x1EC5; case 0x1EC6: return 0x1EC7; case 0x1EC8: return 0x1EC9; case 0x1ECA: return 0x1ECB; case 0x1ECC: return 0x1ECD; case 0x1ECE: return 0x1ECF; case 0x1ED0: return 0x1ED1; case 0x1ED2: return 0x1ED3; case 0x1ED4: return 0x1ED5; case 0x1ED6: return 0x1ED7; case 0x1ED8: return 0x1ED9; case 0x1EDA: return 0x1EDB; case 0x1EDC: return 0x1EDD; case 0x1EDE: return 0x1EDF; case 0x1EE0: return 0x1EE1; case 0x1EE2: return 0x1EE3; case 0x1EE4: return 0x1EE5; case 0x1EE6: return 0x1EE7; case 0x1EE8: return 0x1EE9; case 0x1EEA: return 0x1EEB; case 0x1EEC: return 0x1EED; case 0x1EEE: return 0x1EEF; case 0x1EF0: return 0x1EF1; case 0x1EF2: return 0x1EF3; case 0x1EF4: return 0x1EF5; case 0x1EF6: return 0x1EF7; case 0x1EF8: return 0x1EF9; case 0x1EFA: return 0x1EFB; case 0x1EFC: return 0x1EFD; case 0x1EFE: return 0x1EFF; case 0x1F08: return 0x1F00; case 0x1F09: return 0x1F01; case 0x1F0A: return 0x1F02; case 0x1F0B: return 0x1F03; case 0x1F0C: return 0x1F04; case 0x1F0D: return 0x1F05; case 0x1F0E: return 0x1F06; case 0x1F0F: return 0x1F07; case 0x1F18: return 0x1F10; case 0x1F19: return 0x1F11; case 0x1F1A: return 0x1F12; case 0x1F1B: return 0x1F13; case 0x1F1C: return 0x1F14; case 0x1F1D: return 0x1F15; case 0x1F28: return 0x1F20; case 0x1F29: return 0x1F21; case 0x1F2A: return 0x1F22; case 0x1F2B: return 0x1F23; case 0x1F2C: return 0x1F24; case 0x1F2D: return 0x1F25; case 0x1F2E: return 0x1F26; case 0x1F2F: return 0x1F27; case 0x1F38: return 0x1F30; case 0x1F39: return 0x1F31; case 0x1F3A: return 0x1F32; case 0x1F3B: return 0x1F33; case 0x1F3C: return 0x1F34; case 0x1F3D: return 0x1F35; case 0x1F3E: return 0x1F36; case 0x1F3F: return 0x1F37; case 0x1F48: return 0x1F40; case 0x1F49: return 0x1F41; case 0x1F4A: return 0x1F42; case 0x1F4B: return 0x1F43; case 0x1F4C: return 0x1F44; case 0x1F4D: return 0x1F45; case 0x1F59: return 0x1F51; case 0x1F5B: return 0x1F53; case 0x1F5D: return 0x1F55; case 0x1F5F: return 0x1F57; case 0x1F68: return 0x1F60; case 0x1F69: return 0x1F61; case 0x1F6A: return 0x1F62; case 0x1F6B: return 0x1F63; case 0x1F6C: return 0x1F64; case 0x1F6D: return 0x1F65; case 0x1F6E: return 0x1F66; case 0x1F6F: return 0x1F67; case 0x1F88: return 0x1F80; case 0x1F89: return 0x1F81; case 0x1F8A: return 0x1F82; case 0x1F8B: return 0x1F83; case 0x1F8C: return 0x1F84; case 0x1F8D: return 0x1F85; case 0x1F8E: return 0x1F86; case 0x1F8F: return 0x1F87; case 0x1F98: return 0x1F90; case 0x1F99: return 0x1F91; case 0x1F9A: return 0x1F92; case 0x1F9B: return 0x1F93; case 0x1F9C: return 0x1F94; case 0x1F9D: return 0x1F95; case 0x1F9E: return 0x1F96; case 0x1F9F: return 0x1F97; case 0x1FA8: return 0x1FA0; case 0x1FA9: return 0x1FA1; case 0x1FAA: return 0x1FA2; case 0x1FAB: return 0x1FA3; case 0x1FAC: return 0x1FA4; case 0x1FAD: return 0x1FA5; case 0x1FAE: return 0x1FA6; case 0x1FAF: return 0x1FA7; case 0x1FB8: return 0x1FB0; case 0x1FB9: return 0x1FB1; case 0x1FBA: return 0x1F70; case 0x1FBB: return 0x1F71; case 0x1FBC: return 0x1FB3; case 0x1FC8: return 0x1F72; case 0x1FC9: return 0x1F73; case 0x1FCA: return 0x1F74; case 0x1FCB: return 0x1F75; case 0x1FCC: return 0x1FC3; case 0x1FD8: return 0x1FD0; case 0x1FD9: return 0x1FD1; case 0x1FDA: return 0x1F76; case 0x1FDB: return 0x1F77; case 0x1FE8: return 0x1FE0; case 0x1FE9: return 0x1FE1; case 0x1FEA: return 0x1F7A; case 0x1FEB: return 0x1F7B; case 0x1FEC: return 0x1FE5; case 0x1FF8: return 0x1F78; case 0x1FF9: return 0x1F79; case 0x1FFA: return 0x1F7C; case 0x1FFB: return 0x1F7D; case 0x1FFC: return 0x1FF3; case 0x2126: return 0x03C9; case 0x212A: return 0x006B; case 0x212B: return 0x00E5; case 0x2132: return 0x214E; case 0x2160: return 0x2170; case 0x2161: return 0x2171; case 0x2162: return 0x2172; case 0x2163: return 0x2173; case 0x2164: return 0x2174; case 0x2165: return 0x2175; case 0x2166: return 0x2176; case 0x2167: return 0x2177; case 0x2168: return 0x2178; case 0x2169: return 0x2179; case 0x216A: return 0x217A; case 0x216B: return 0x217B; case 0x216C: return 0x217C; case 0x216D: return 0x217D; case 0x216E: return 0x217E; case 0x216F: return 0x217F; case 0x2183: return 0x2184; case 0x24B6: return 0x24D0; case 0x24B7: return 0x24D1; case 0x24B8: return 0x24D2; case 0x24B9: return 0x24D3; case 0x24BA: return 0x24D4; case 0x24BB: return 0x24D5; case 0x24BC: return 0x24D6; case 0x24BD: return 0x24D7; case 0x24BE: return 0x24D8; case 0x24BF: return 0x24D9; case 0x24C0: return 0x24DA; case 0x24C1: return 0x24DB; case 0x24C2: return 0x24DC; case 0x24C3: return 0x24DD; case 0x24C4: return 0x24DE; case 0x24C5: return 0x24DF; case 0x24C6: return 0x24E0; case 0x24C7: return 0x24E1; case 0x24C8: return 0x24E2; case 0x24C9: return 0x24E3; case 0x24CA: return 0x24E4; case 0x24CB: return 0x24E5; case 0x24CC: return 0x24E6; case 0x24CD: return 0x24E7; case 0x24CE: return 0x24E8; case 0x24CF: return 0x24E9; case 0x2C00: return 0x2C30; case 0x2C01: return 0x2C31; case 0x2C02: return 0x2C32; case 0x2C03: return 0x2C33; case 0x2C04: return 0x2C34; case 0x2C05: return 0x2C35; case 0x2C06: return 0x2C36; case 0x2C07: return 0x2C37; case 0x2C08: return 0x2C38; case 0x2C09: return 0x2C39; case 0x2C0A: return 0x2C3A; case 0x2C0B: return 0x2C3B; case 0x2C0C: return 0x2C3C; case 0x2C0D: return 0x2C3D; case 0x2C0E: return 0x2C3E; case 0x2C0F: return 0x2C3F; case 0x2C10: return 0x2C40; case 0x2C11: return 0x2C41; case 0x2C12: return 0x2C42; case 0x2C13: return 0x2C43; case 0x2C14: return 0x2C44; case 0x2C15: return 0x2C45; case 0x2C16: return 0x2C46; case 0x2C17: return 0x2C47; case 0x2C18: return 0x2C48; case 0x2C19: return 0x2C49; case 0x2C1A: return 0x2C4A; case 0x2C1B: return 0x2C4B; case 0x2C1C: return 0x2C4C; case 0x2C1D: return 0x2C4D; case 0x2C1E: return 0x2C4E; case 0x2C1F: return 0x2C4F; case 0x2C20: return 0x2C50; case 0x2C21: return 0x2C51; case 0x2C22: return 0x2C52; case 0x2C23: return 0x2C53; case 0x2C24: return 0x2C54; case 0x2C25: return 0x2C55; case 0x2C26: return 0x2C56; case 0x2C27: return 0x2C57; case 0x2C28: return 0x2C58; case 0x2C29: return 0x2C59; case 0x2C2A: return 0x2C5A; case 0x2C2B: return 0x2C5B; case 0x2C2C: return 0x2C5C; case 0x2C2D: return 0x2C5D; case 0x2C2E: return 0x2C5E; case 0x2C60: return 0x2C61; case 0x2C62: return 0x026B; case 0x2C63: return 0x1D7D; case 0x2C64: return 0x027D; case 0x2C67: return 0x2C68; case 0x2C69: return 0x2C6A; case 0x2C6B: return 0x2C6C; case 0x2C6D: return 0x0251; case 0x2C6E: return 0x0271; case 0x2C6F: return 0x0250; case 0x2C70: return 0x0252; case 0x2C72: return 0x2C73; case 0x2C75: return 0x2C76; case 0x2C7E: return 0x023F; case 0x2C7F: return 0x0240; case 0x2C80: return 0x2C81; case 0x2C82: return 0x2C83; case 0x2C84: return 0x2C85; case 0x2C86: return 0x2C87; case 0x2C88: return 0x2C89; case 0x2C8A: return 0x2C8B; case 0x2C8C: return 0x2C8D; case 0x2C8E: return 0x2C8F; case 0x2C90: return 0x2C91; case 0x2C92: return 0x2C93; case 0x2C94: return 0x2C95; case 0x2C96: return 0x2C97; case 0x2C98: return 0x2C99; case 0x2C9A: return 0x2C9B; case 0x2C9C: return 0x2C9D; case 0x2C9E: return 0x2C9F; case 0x2CA0: return 0x2CA1; case 0x2CA2: return 0x2CA3; case 0x2CA4: return 0x2CA5; case 0x2CA6: return 0x2CA7; case 0x2CA8: return 0x2CA9; case 0x2CAA: return 0x2CAB; case 0x2CAC: return 0x2CAD; case 0x2CAE: return 0x2CAF; case 0x2CB0: return 0x2CB1; case 0x2CB2: return 0x2CB3; case 0x2CB4: return 0x2CB5; case 0x2CB6: return 0x2CB7; case 0x2CB8: return 0x2CB9; case 0x2CBA: return 0x2CBB; case 0x2CBC: return 0x2CBD; case 0x2CBE: return 0x2CBF; case 0x2CC0: return 0x2CC1; case 0x2CC2: return 0x2CC3; case 0x2CC4: return 0x2CC5; case 0x2CC6: return 0x2CC7; case 0x2CC8: return 0x2CC9; case 0x2CCA: return 0x2CCB; case 0x2CCC: return 0x2CCD; case 0x2CCE: return 0x2CCF; case 0x2CD0: return 0x2CD1; case 0x2CD2: return 0x2CD3; case 0x2CD4: return 0x2CD5; case 0x2CD6: return 0x2CD7; case 0x2CD8: return 0x2CD9; case 0x2CDA: return 0x2CDB; case 0x2CDC: return 0x2CDD; case 0x2CDE: return 0x2CDF; case 0x2CE0: return 0x2CE1; case 0x2CE2: return 0x2CE3; case 0x2CEB: return 0x2CEC; case 0x2CED: return 0x2CEE; case 0x2CF2: return 0x2CF3; case 0xA640: return 0xA641; case 0xA642: return 0xA643; case 0xA644: return 0xA645; case 0xA646: return 0xA647; case 0xA648: return 0xA649; case 0xA64A: return 0xA64B; case 0xA64C: return 0xA64D; case 0xA64E: return 0xA64F; case 0xA650: return 0xA651; case 0xA652: return 0xA653; case 0xA654: return 0xA655; case 0xA656: return 0xA657; case 0xA658: return 0xA659; case 0xA65A: return 0xA65B; case 0xA65C: return 0xA65D; case 0xA65E: return 0xA65F; case 0xA660: return 0xA661; case 0xA662: return 0xA663; case 0xA664: return 0xA665; case 0xA666: return 0xA667; case 0xA668: return 0xA669; case 0xA66A: return 0xA66B; case 0xA66C: return 0xA66D; case 0xA680: return 0xA681; case 0xA682: return 0xA683; case 0xA684: return 0xA685; case 0xA686: return 0xA687; case 0xA688: return 0xA689; case 0xA68A: return 0xA68B; case 0xA68C: return 0xA68D; case 0xA68E: return 0xA68F; case 0xA690: return 0xA691; case 0xA692: return 0xA693; case 0xA694: return 0xA695; case 0xA696: return 0xA697; case 0xA722: return 0xA723; case 0xA724: return 0xA725; case 0xA726: return 0xA727; case 0xA728: return 0xA729; case 0xA72A: return 0xA72B; case 0xA72C: return 0xA72D; case 0xA72E: return 0xA72F; case 0xA732: return 0xA733; case 0xA734: return 0xA735; case 0xA736: return 0xA737; case 0xA738: return 0xA739; case 0xA73A: return 0xA73B; case 0xA73C: return 0xA73D; case 0xA73E: return 0xA73F; case 0xA740: return 0xA741; case 0xA742: return 0xA743; case 0xA744: return 0xA745; case 0xA746: return 0xA747; case 0xA748: return 0xA749; case 0xA74A: return 0xA74B; case 0xA74C: return 0xA74D; case 0xA74E: return 0xA74F; case 0xA750: return 0xA751; case 0xA752: return 0xA753; case 0xA754: return 0xA755; case 0xA756: return 0xA757; case 0xA758: return 0xA759; case 0xA75A: return 0xA75B; case 0xA75C: return 0xA75D; case 0xA75E: return 0xA75F; case 0xA760: return 0xA761; case 0xA762: return 0xA763; case 0xA764: return 0xA765; case 0xA766: return 0xA767; case 0xA768: return 0xA769; case 0xA76A: return 0xA76B; case 0xA76C: return 0xA76D; case 0xA76E: return 0xA76F; case 0xA779: return 0xA77A; case 0xA77B: return 0xA77C; case 0xA77D: return 0x1D79; case 0xA77E: return 0xA77F; case 0xA780: return 0xA781; case 0xA782: return 0xA783; case 0xA784: return 0xA785; case 0xA786: return 0xA787; case 0xA78B: return 0xA78C; case 0xA78D: return 0x0265; case 0xA790: return 0xA791; case 0xA792: return 0xA793; case 0xA7A0: return 0xA7A1; case 0xA7A2: return 0xA7A3; case 0xA7A4: return 0xA7A5; case 0xA7A6: return 0xA7A7; case 0xA7A8: return 0xA7A9; case 0xA7AA: return 0x0266; case 0xFF21: return 0xFF41; case 0xFF22: return 0xFF42; case 0xFF23: return 0xFF43; case 0xFF24: return 0xFF44; case 0xFF25: return 0xFF45; case 0xFF26: return 0xFF46; case 0xFF27: return 0xFF47; case 0xFF28: return 0xFF48; case 0xFF29: return 0xFF49; case 0xFF2A: return 0xFF4A; case 0xFF2B: return 0xFF4B; case 0xFF2C: return 0xFF4C; case 0xFF2D: return 0xFF4D; case 0xFF2E: return 0xFF4E; case 0xFF2F: return 0xFF4F; case 0xFF30: return 0xFF50; case 0xFF31: return 0xFF51; case 0xFF32: return 0xFF52; case 0xFF33: return 0xFF53; case 0xFF34: return 0xFF54; case 0xFF35: return 0xFF55; case 0xFF36: return 0xFF56; case 0xFF37: return 0xFF57; case 0xFF38: return 0xFF58; case 0xFF39: return 0xFF59; case 0xFF3A: return 0xFF5A; case 0x10400: return 0x10428; case 0x10401: return 0x10429; case 0x10402: return 0x1042A; case 0x10403: return 0x1042B; case 0x10404: return 0x1042C; case 0x10405: return 0x1042D; case 0x10406: return 0x1042E; case 0x10407: return 0x1042F; case 0x10408: return 0x10430; case 0x10409: return 0x10431; case 0x1040A: return 0x10432; case 0x1040B: return 0x10433; case 0x1040C: return 0x10434; case 0x1040D: return 0x10435; case 0x1040E: return 0x10436; case 0x1040F: return 0x10437; case 0x10410: return 0x10438; case 0x10411: return 0x10439; case 0x10412: return 0x1043A; case 0x10413: return 0x1043B; case 0x10414: return 0x1043C; case 0x10415: return 0x1043D; case 0x10416: return 0x1043E; case 0x10417: return 0x1043F; case 0x10418: return 0x10440; case 0x10419: return 0x10441; case 0x1041A: return 0x10442; case 0x1041B: return 0x10443; case 0x1041C: return 0x10444; case 0x1041D: return 0x10445; case 0x1041E: return 0x10446; case 0x1041F: return 0x10447; case 0x10420: return 0x10448; case 0x10421: return 0x10449; case 0x10422: return 0x1044A; case 0x10423: return 0x1044B; case 0x10424: return 0x1044C; case 0x10425: return 0x1044D; case 0x10426: return 0x1044E; case 0x10427: return 0x1044F; default: return ch; } } int cwal_utf8_char_toupper( int ch ){ /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt */ switch(ch){ case 0x0061: return 0x0041; case 0x0062: return 0x0042; case 0x0063: return 0x0043; case 0x0064: return 0x0044; case 0x0065: return 0x0045; case 0x0066: return 0x0046; case 0x0067: return 0x0047; case 0x0068: return 0x0048; case 0x0069: return 0x0049; case 0x006A: return 0x004A; case 0x006B: return 0x004B; case 0x006C: return 0x004C; case 0x006D: return 0x004D; case 0x006E: return 0x004E; case 0x006F: return 0x004F; case 0x0070: return 0x0050; case 0x0071: return 0x0051; case 0x0072: return 0x0052; case 0x0073: return 0x0053; case 0x0074: return 0x0054; case 0x0075: return 0x0055; case 0x0076: return 0x0056; case 0x0077: return 0x0057; case 0x0078: return 0x0058; case 0x0079: return 0x0059; case 0x007A: return 0x005A; case 0x00B5: return 0x039C; case 0x00E0: return 0x00C0; case 0x00E1: return 0x00C1; case 0x00E2: return 0x00C2; case 0x00E3: return 0x00C3; case 0x00E4: return 0x00C4; case 0x00E5: return 0x00C5; case 0x00E6: return 0x00C6; case 0x00E7: return 0x00C7; case 0x00E8: return 0x00C8; case 0x00E9: return 0x00C9; case 0x00EA: return 0x00CA; case 0x00EB: return 0x00CB; case 0x00EC: return 0x00CC; case 0x00ED: return 0x00CD; case 0x00EE: return 0x00CE; case 0x00EF: return 0x00CF; case 0x00F0: return 0x00D0; case 0x00F1: return 0x00D1; case 0x00F2: return 0x00D2; case 0x00F3: return 0x00D3; case 0x00F4: return 0x00D4; case 0x00F5: return 0x00D5; case 0x00F6: return 0x00D6; case 0x00F8: return 0x00D8; case 0x00F9: return 0x00D9; case 0x00FA: return 0x00DA; case 0x00FB: return 0x00DB; case 0x00FC: return 0x00DC; case 0x00FD: return 0x00DD; case 0x00FE: return 0x00DE; case 0x00FF: return 0x0178; case 0x0101: return 0x0100; case 0x0103: return 0x0102; case 0x0105: return 0x0104; case 0x0107: return 0x0106; case 0x0109: return 0x0108; case 0x010B: return 0x010A; case 0x010D: return 0x010C; case 0x010F: return 0x010E; case 0x0111: return 0x0110; case 0x0113: return 0x0112; case 0x0115: return 0x0114; case 0x0117: return 0x0116; case 0x0119: return 0x0118; case 0x011B: return 0x011A; case 0x011D: return 0x011C; case 0x011F: return 0x011E; case 0x0121: return 0x0120; case 0x0123: return 0x0122; case 0x0125: return 0x0124; case 0x0127: return 0x0126; case 0x0129: return 0x0128; case 0x012B: return 0x012A; case 0x012D: return 0x012C; case 0x012F: return 0x012E; case 0x0131: return 0x0049; case 0x0133: return 0x0132; case 0x0135: return 0x0134; case 0x0137: return 0x0136; case 0x013A: return 0x0139; case 0x013C: return 0x013B; case 0x013E: return 0x013D; case 0x0140: return 0x013F; case 0x0142: return 0x0141; case 0x0144: return 0x0143; case 0x0146: return 0x0145; case 0x0148: return 0x0147; case 0x014B: return 0x014A; case 0x014D: return 0x014C; case 0x014F: return 0x014E; case 0x0151: return 0x0150; case 0x0153: return 0x0152; case 0x0155: return 0x0154; case 0x0157: return 0x0156; case 0x0159: return 0x0158; case 0x015B: return 0x015A; case 0x015D: return 0x015C; case 0x015F: return 0x015E; case 0x0161: return 0x0160; case 0x0163: return 0x0162; case 0x0165: return 0x0164; case 0x0167: return 0x0166; case 0x0169: return 0x0168; case 0x016B: return 0x016A; case 0x016D: return 0x016C; case 0x016F: return 0x016E; case 0x0171: return 0x0170; case 0x0173: return 0x0172; case 0x0175: return 0x0174; case 0x0177: return 0x0176; case 0x017A: return 0x0179; case 0x017C: return 0x017B; case 0x017E: return 0x017D; case 0x017F: return 0x0053; case 0x0180: return 0x0243; case 0x0183: return 0x0182; case 0x0185: return 0x0184; case 0x0188: return 0x0187; case 0x018C: return 0x018B; case 0x0192: return 0x0191; case 0x0195: return 0x01F6; case 0x0199: return 0x0198; case 0x019A: return 0x023D; case 0x019E: return 0x0220; case 0x01A1: return 0x01A0; case 0x01A3: return 0x01A2; case 0x01A5: return 0x01A4; case 0x01A8: return 0x01A7; case 0x01AD: return 0x01AC; case 0x01B0: return 0x01AF; case 0x01B4: return 0x01B3; case 0x01B6: return 0x01B5; case 0x01B9: return 0x01B8; case 0x01BD: return 0x01BC; case 0x01BF: return 0x01F7; case 0x01C5: return 0x01C4; case 0x01C6: return 0x01C4; case 0x01C8: return 0x01C7; case 0x01C9: return 0x01C7; case 0x01CB: return 0x01CA; case 0x01CC: return 0x01CA; case 0x01CE: return 0x01CD; case 0x01D0: return 0x01CF; case 0x01D2: return 0x01D1; case 0x01D4: return 0x01D3; case 0x01D6: return 0x01D5; case 0x01D8: return 0x01D7; case 0x01DA: return 0x01D9; case 0x01DC: return 0x01DB; case 0x01DD: return 0x018E; case 0x01DF: return 0x01DE; case 0x01E1: return 0x01E0; case 0x01E3: return 0x01E2; case 0x01E5: return 0x01E4; case 0x01E7: return 0x01E6; case 0x01E9: return 0x01E8; case 0x01EB: return 0x01EA; case 0x01ED: return 0x01EC; case 0x01EF: return 0x01EE; case 0x01F2: return 0x01F1; case 0x01F3: return 0x01F1; case 0x01F5: return 0x01F4; case 0x01F9: return 0x01F8; case 0x01FB: return 0x01FA; case 0x01FD: return 0x01FC; case 0x01FF: return 0x01FE; case 0x0201: return 0x0200; case 0x0203: return 0x0202; case 0x0205: return 0x0204; case 0x0207: return 0x0206; case 0x0209: return 0x0208; case 0x020B: return 0x020A; case 0x020D: return 0x020C; case 0x020F: return 0x020E; case 0x0211: return 0x0210; case 0x0213: return 0x0212; case 0x0215: return 0x0214; case 0x0217: return 0x0216; case 0x0219: return 0x0218; case 0x021B: return 0x021A; case 0x021D: return 0x021C; case 0x021F: return 0x021E; case 0x0223: return 0x0222; case 0x0225: return 0x0224; case 0x0227: return 0x0226; case 0x0229: return 0x0228; case 0x022B: return 0x022A; case 0x022D: return 0x022C; case 0x022F: return 0x022E; case 0x0231: return 0x0230; case 0x0233: return 0x0232; case 0x023C: return 0x023B; case 0x023F: return 0x2C7E; case 0x0240: return 0x2C7F; case 0x0242: return 0x0241; case 0x0247: return 0x0246; case 0x0249: return 0x0248; case 0x024B: return 0x024A; case 0x024D: return 0x024C; case 0x024F: return 0x024E; case 0x0250: return 0x2C6F; case 0x0251: return 0x2C6D; case 0x0252: return 0x2C70; case 0x0253: return 0x0181; case 0x0254: return 0x0186; case 0x0256: return 0x0189; case 0x0257: return 0x018A; case 0x0259: return 0x018F; case 0x025B: return 0x0190; case 0x0260: return 0x0193; case 0x0263: return 0x0194; case 0x0265: return 0xA78D; case 0x0266: return 0xA7AA; case 0x0268: return 0x0197; case 0x0269: return 0x0196; case 0x026B: return 0x2C62; case 0x026F: return 0x019C; case 0x0271: return 0x2C6E; case 0x0272: return 0x019D; case 0x0275: return 0x019F; case 0x027D: return 0x2C64; case 0x0280: return 0x01A6; case 0x0283: return 0x01A9; case 0x0288: return 0x01AE; case 0x0289: return 0x0244; case 0x028A: return 0x01B1; case 0x028B: return 0x01B2; case 0x028C: return 0x0245; case 0x0292: return 0x01B7; case 0x0345: return 0x0399; case 0x0371: return 0x0370; case 0x0373: return 0x0372; case 0x0377: return 0x0376; case 0x037B: return 0x03FD; case 0x037C: return 0x03FE; case 0x037D: return 0x03FF; case 0x03AC: return 0x0386; case 0x03AD: return 0x0388; case 0x03AE: return 0x0389; case 0x03AF: return 0x038A; case 0x03B1: return 0x0391; case 0x03B2: return 0x0392; case 0x03B3: return 0x0393; case 0x03B4: return 0x0394; case 0x03B5: return 0x0395; case 0x03B6: return 0x0396; case 0x03B7: return 0x0397; case 0x03B8: return 0x0398; case 0x03B9: return 0x0399; case 0x03BA: return 0x039A; case 0x03BB: return 0x039B; case 0x03BC: return 0x039C; case 0x03BD: return 0x039D; case 0x03BE: return 0x039E; case 0x03BF: return 0x039F; case 0x03C0: return 0x03A0; case 0x03C1: return 0x03A1; case 0x03C2: return 0x03A3; case 0x03C3: return 0x03A3; case 0x03C4: return 0x03A4; case 0x03C5: return 0x03A5; case 0x03C6: return 0x03A6; case 0x03C7: return 0x03A7; case 0x03C8: return 0x03A8; case 0x03C9: return 0x03A9; case 0x03CA: return 0x03AA; case 0x03CB: return 0x03AB; case 0x03CC: return 0x038C; case 0x03CD: return 0x038E; case 0x03CE: return 0x038F; case 0x03D0: return 0x0392; case 0x03D1: return 0x0398; case 0x03D5: return 0x03A6; case 0x03D6: return 0x03A0; case 0x03D7: return 0x03CF; case 0x03D9: return 0x03D8; case 0x03DB: return 0x03DA; case 0x03DD: return 0x03DC; case 0x03DF: return 0x03DE; case 0x03E1: return 0x03E0; case 0x03E3: return 0x03E2; case 0x03E5: return 0x03E4; case 0x03E7: return 0x03E6; case 0x03E9: return 0x03E8; case 0x03EB: return 0x03EA; case 0x03ED: return 0x03EC; case 0x03EF: return 0x03EE; case 0x03F0: return 0x039A; case 0x03F1: return 0x03A1; case 0x03F2: return 0x03F9; case 0x03F5: return 0x0395; case 0x03F8: return 0x03F7; case 0x03FB: return 0x03FA; case 0x0430: return 0x0410; case 0x0431: return 0x0411; case 0x0432: return 0x0412; case 0x0433: return 0x0413; case 0x0434: return 0x0414; case 0x0435: return 0x0415; case 0x0436: return 0x0416; case 0x0437: return 0x0417; case 0x0438: return 0x0418; case 0x0439: return 0x0419; case 0x043A: return 0x041A; case 0x043B: return 0x041B; case 0x043C: return 0x041C; case 0x043D: return 0x041D; case 0x043E: return 0x041E; case 0x043F: return 0x041F; case 0x0440: return 0x0420; case 0x0441: return 0x0421; case 0x0442: return 0x0422; case 0x0443: return 0x0423; case 0x0444: return 0x0424; case 0x0445: return 0x0425; case 0x0446: return 0x0426; case 0x0447: return 0x0427; case 0x0448: return 0x0428; case 0x0449: return 0x0429; case 0x044A: return 0x042A; case 0x044B: return 0x042B; case 0x044C: return 0x042C; case 0x044D: return 0x042D; case 0x044E: return 0x042E; case 0x044F: return 0x042F; case 0x0450: return 0x0400; case 0x0451: return 0x0401; case 0x0452: return 0x0402; case 0x0453: return 0x0403; case 0x0454: return 0x0404; case 0x0455: return 0x0405; case 0x0456: return 0x0406; case 0x0457: return 0x0407; case 0x0458: return 0x0408; case 0x0459: return 0x0409; case 0x045A: return 0x040A; case 0x045B: return 0x040B; case 0x045C: return 0x040C; case 0x045D: return 0x040D; case 0x045E: return 0x040E; case 0x045F: return 0x040F; case 0x0461: return 0x0460; case 0x0463: return 0x0462; case 0x0465: return 0x0464; case 0x0467: return 0x0466; case 0x0469: return 0x0468; case 0x046B: return 0x046A; case 0x046D: return 0x046C; case 0x046F: return 0x046E; case 0x0471: return 0x0470; case 0x0473: return 0x0472; case 0x0475: return 0x0474; case 0x0477: return 0x0476; case 0x0479: return 0x0478; case 0x047B: return 0x047A; case 0x047D: return 0x047C; case 0x047F: return 0x047E; case 0x0481: return 0x0480; case 0x048B: return 0x048A; case 0x048D: return 0x048C; case 0x048F: return 0x048E; case 0x0491: return 0x0490; case 0x0493: return 0x0492; case 0x0495: return 0x0494; case 0x0497: return 0x0496; case 0x0499: return 0x0498; case 0x049B: return 0x049A; case 0x049D: return 0x049C; case 0x049F: return 0x049E; case 0x04A1: return 0x04A0; case 0x04A3: return 0x04A2; case 0x04A5: return 0x04A4; case 0x04A7: return 0x04A6; case 0x04A9: return 0x04A8; case 0x04AB: return 0x04AA; case 0x04AD: return 0x04AC; case 0x04AF: return 0x04AE; case 0x04B1: return 0x04B0; case 0x04B3: return 0x04B2; case 0x04B5: return 0x04B4; case 0x04B7: return 0x04B6; case 0x04B9: return 0x04B8; case 0x04BB: return 0x04BA; case 0x04BD: return 0x04BC; case 0x04BF: return 0x04BE; case 0x04C2: return 0x04C1; case 0x04C4: return 0x04C3; case 0x04C6: return 0x04C5; case 0x04C8: return 0x04C7; case 0x04CA: return 0x04C9; case 0x04CC: return 0x04CB; case 0x04CE: return 0x04CD; case 0x04CF: return 0x04C0; case 0x04D1: return 0x04D0; case 0x04D3: return 0x04D2; case 0x04D5: return 0x04D4; case 0x04D7: return 0x04D6; case 0x04D9: return 0x04D8; case 0x04DB: return 0x04DA; case 0x04DD: return 0x04DC; case 0x04DF: return 0x04DE; case 0x04E1: return 0x04E0; case 0x04E3: return 0x04E2; case 0x04E5: return 0x04E4; case 0x04E7: return 0x04E6; case 0x04E9: return 0x04E8; case 0x04EB: return 0x04EA; case 0x04ED: return 0x04EC; case 0x04EF: return 0x04EE; case 0x04F1: return 0x04F0; case 0x04F3: return 0x04F2; case 0x04F5: return 0x04F4; case 0x04F7: return 0x04F6; case 0x04F9: return 0x04F8; case 0x04FB: return 0x04FA; case 0x04FD: return 0x04FC; case 0x04FF: return 0x04FE; case 0x0501: return 0x0500; case 0x0503: return 0x0502; case 0x0505: return 0x0504; case 0x0507: return 0x0506; case 0x0509: return 0x0508; case 0x050B: return 0x050A; case 0x050D: return 0x050C; case 0x050F: return 0x050E; case 0x0511: return 0x0510; case 0x0513: return 0x0512; case 0x0515: return 0x0514; case 0x0517: return 0x0516; case 0x0519: return 0x0518; case 0x051B: return 0x051A; case 0x051D: return 0x051C; case 0x051F: return 0x051E; case 0x0521: return 0x0520; case 0x0523: return 0x0522; case 0x0525: return 0x0524; case 0x0527: return 0x0526; case 0x0561: return 0x0531; case 0x0562: return 0x0532; case 0x0563: return 0x0533; case 0x0564: return 0x0534; case 0x0565: return 0x0535; case 0x0566: return 0x0536; case 0x0567: return 0x0537; case 0x0568: return 0x0538; case 0x0569: return 0x0539; case 0x056A: return 0x053A; case 0x056B: return 0x053B; case 0x056C: return 0x053C; case 0x056D: return 0x053D; case 0x056E: return 0x053E; case 0x056F: return 0x053F; case 0x0570: return 0x0540; case 0x0571: return 0x0541; case 0x0572: return 0x0542; case 0x0573: return 0x0543; case 0x0574: return 0x0544; case 0x0575: return 0x0545; case 0x0576: return 0x0546; case 0x0577: return 0x0547; case 0x0578: return 0x0548; case 0x0579: return 0x0549; case 0x057A: return 0x054A; case 0x057B: return 0x054B; case 0x057C: return 0x054C; case 0x057D: return 0x054D; case 0x057E: return 0x054E; case 0x057F: return 0x054F; case 0x0580: return 0x0550; case 0x0581: return 0x0551; case 0x0582: return 0x0552; case 0x0583: return 0x0553; case 0x0584: return 0x0554; case 0x0585: return 0x0555; case 0x0586: return 0x0556; case 0x1D79: return 0xA77D; case 0x1D7D: return 0x2C63; case 0x1E01: return 0x1E00; case 0x1E03: return 0x1E02; case 0x1E05: return 0x1E04; case 0x1E07: return 0x1E06; case 0x1E09: return 0x1E08; case 0x1E0B: return 0x1E0A; case 0x1E0D: return 0x1E0C; case 0x1E0F: return 0x1E0E; case 0x1E11: return 0x1E10; case 0x1E13: return 0x1E12; case 0x1E15: return 0x1E14; case 0x1E17: return 0x1E16; case 0x1E19: return 0x1E18; case 0x1E1B: return 0x1E1A; case 0x1E1D: return 0x1E1C; case 0x1E1F: return 0x1E1E; case 0x1E21: return 0x1E20; case 0x1E23: return 0x1E22; case 0x1E25: return 0x1E24; case 0x1E27: return 0x1E26; case 0x1E29: return 0x1E28; case 0x1E2B: return 0x1E2A; case 0x1E2D: return 0x1E2C; case 0x1E2F: return 0x1E2E; case 0x1E31: return 0x1E30; case 0x1E33: return 0x1E32; case 0x1E35: return 0x1E34; case 0x1E37: return 0x1E36; case 0x1E39: return 0x1E38; case 0x1E3B: return 0x1E3A; case 0x1E3D: return 0x1E3C; case 0x1E3F: return 0x1E3E; case 0x1E41: return 0x1E40; case 0x1E43: return 0x1E42; case 0x1E45: return 0x1E44; case 0x1E47: return 0x1E46; case 0x1E49: return 0x1E48; case 0x1E4B: return 0x1E4A; case 0x1E4D: return 0x1E4C; case 0x1E4F: return 0x1E4E; case 0x1E51: return 0x1E50; case 0x1E53: return 0x1E52; case 0x1E55: return 0x1E54; case 0x1E57: return 0x1E56; case 0x1E59: return 0x1E58; case 0x1E5B: return 0x1E5A; case 0x1E5D: return 0x1E5C; case 0x1E5F: return 0x1E5E; case 0x1E61: return 0x1E60; case 0x1E63: return 0x1E62; case 0x1E65: return 0x1E64; case 0x1E67: return 0x1E66; case 0x1E69: return 0x1E68; case 0x1E6B: return 0x1E6A; case 0x1E6D: return 0x1E6C; case 0x1E6F: return 0x1E6E; case 0x1E71: return 0x1E70; case 0x1E73: return 0x1E72; case 0x1E75: return 0x1E74; case 0x1E77: return 0x1E76; case 0x1E79: return 0x1E78; case 0x1E7B: return 0x1E7A; case 0x1E7D: return 0x1E7C; case 0x1E7F: return 0x1E7E; case 0x1E81: return 0x1E80; case 0x1E83: return 0x1E82; case 0x1E85: return 0x1E84; case 0x1E87: return 0x1E86; case 0x1E89: return 0x1E88; case 0x1E8B: return 0x1E8A; case 0x1E8D: return 0x1E8C; case 0x1E8F: return 0x1E8E; case 0x1E91: return 0x1E90; case 0x1E93: return 0x1E92; case 0x1E95: return 0x1E94; case 0x1E9B: return 0x1E60; case 0x1EA1: return 0x1EA0; case 0x1EA3: return 0x1EA2; case 0x1EA5: return 0x1EA4; case 0x1EA7: return 0x1EA6; case 0x1EA9: return 0x1EA8; case 0x1EAB: return 0x1EAA; case 0x1EAD: return 0x1EAC; case 0x1EAF: return 0x1EAE; case 0x1EB1: return 0x1EB0; case 0x1EB3: return 0x1EB2; case 0x1EB5: return 0x1EB4; case 0x1EB7: return 0x1EB6; case 0x1EB9: return 0x1EB8; case 0x1EBB: return 0x1EBA; case 0x1EBD: return 0x1EBC; case 0x1EBF: return 0x1EBE; case 0x1EC1: return 0x1EC0; case 0x1EC3: return 0x1EC2; case 0x1EC5: return 0x1EC4; case 0x1EC7: return 0x1EC6; case 0x1EC9: return 0x1EC8; case 0x1ECB: return 0x1ECA; case 0x1ECD: return 0x1ECC; case 0x1ECF: return 0x1ECE; case 0x1ED1: return 0x1ED0; case 0x1ED3: return 0x1ED2; case 0x1ED5: return 0x1ED4; case 0x1ED7: return 0x1ED6; case 0x1ED9: return 0x1ED8; case 0x1EDB: return 0x1EDA; case 0x1EDD: return 0x1EDC; case 0x1EDF: return 0x1EDE; case 0x1EE1: return 0x1EE0; case 0x1EE3: return 0x1EE2; case 0x1EE5: return 0x1EE4; case 0x1EE7: return 0x1EE6; case 0x1EE9: return 0x1EE8; case 0x1EEB: return 0x1EEA; case 0x1EED: return 0x1EEC; case 0x1EEF: return 0x1EEE; case 0x1EF1: return 0x1EF0; case 0x1EF3: return 0x1EF2; case 0x1EF5: return 0x1EF4; case 0x1EF7: return 0x1EF6; case 0x1EF9: return 0x1EF8; case 0x1EFB: return 0x1EFA; case 0x1EFD: return 0x1EFC; case 0x1EFF: return 0x1EFE; case 0x1F00: return 0x1F08; case 0x1F01: return 0x1F09; case 0x1F02: return 0x1F0A; case 0x1F03: return 0x1F0B; case 0x1F04: return 0x1F0C; case 0x1F05: return 0x1F0D; case 0x1F06: return 0x1F0E; case 0x1F07: return 0x1F0F; case 0x1F10: return 0x1F18; case 0x1F11: return 0x1F19; case 0x1F12: return 0x1F1A; case 0x1F13: return 0x1F1B; case 0x1F14: return 0x1F1C; case 0x1F15: return 0x1F1D; case 0x1F20: return 0x1F28; case 0x1F21: return 0x1F29; case 0x1F22: return 0x1F2A; case 0x1F23: return 0x1F2B; case 0x1F24: return 0x1F2C; case 0x1F25: return 0x1F2D; case 0x1F26: return 0x1F2E; case 0x1F27: return 0x1F2F; case 0x1F30: return 0x1F38; case 0x1F31: return 0x1F39; case 0x1F32: return 0x1F3A; case 0x1F33: return 0x1F3B; case 0x1F34: return 0x1F3C; case 0x1F35: return 0x1F3D; case 0x1F36: return 0x1F3E; case 0x1F37: return 0x1F3F; case 0x1F40: return 0x1F48; case 0x1F41: return 0x1F49; case 0x1F42: return 0x1F4A; case 0x1F43: return 0x1F4B; case 0x1F44: return 0x1F4C; case 0x1F45: return 0x1F4D; case 0x1F51: return 0x1F59; case 0x1F53: return 0x1F5B; case 0x1F55: return 0x1F5D; case 0x1F57: return 0x1F5F; case 0x1F60: return 0x1F68; case 0x1F61: return 0x1F69; case 0x1F62: return 0x1F6A; case 0x1F63: return 0x1F6B; case 0x1F64: return 0x1F6C; case 0x1F65: return 0x1F6D; case 0x1F66: return 0x1F6E; case 0x1F67: return 0x1F6F; case 0x1F70: return 0x1FBA; case 0x1F71: return 0x1FBB; case 0x1F72: return 0x1FC8; case 0x1F73: return 0x1FC9; case 0x1F74: return 0x1FCA; case 0x1F75: return 0x1FCB; case 0x1F76: return 0x1FDA; case 0x1F77: return 0x1FDB; case 0x1F78: return 0x1FF8; case 0x1F79: return 0x1FF9; case 0x1F7A: return 0x1FEA; case 0x1F7B: return 0x1FEB; case 0x1F7C: return 0x1FFA; case 0x1F7D: return 0x1FFB; case 0x1F80: return 0x1F88; case 0x1F81: return 0x1F89; case 0x1F82: return 0x1F8A; case 0x1F83: return 0x1F8B; case 0x1F84: return 0x1F8C; case 0x1F85: return 0x1F8D; case 0x1F86: return 0x1F8E; case 0x1F87: return 0x1F8F; case 0x1F90: return 0x1F98; case 0x1F91: return 0x1F99; case 0x1F92: return 0x1F9A; case 0x1F93: return 0x1F9B; case 0x1F94: return 0x1F9C; case 0x1F95: return 0x1F9D; case 0x1F96: return 0x1F9E; case 0x1F97: return 0x1F9F; case 0x1FA0: return 0x1FA8; case 0x1FA1: return 0x1FA9; case 0x1FA2: return 0x1FAA; case 0x1FA3: return 0x1FAB; case 0x1FA4: return 0x1FAC; case 0x1FA5: return 0x1FAD; case 0x1FA6: return 0x1FAE; case 0x1FA7: return 0x1FAF; case 0x1FB0: return 0x1FB8; case 0x1FB1: return 0x1FB9; case 0x1FB3: return 0x1FBC; case 0x1FBE: return 0x0399; case 0x1FC3: return 0x1FCC; case 0x1FD0: return 0x1FD8; case 0x1FD1: return 0x1FD9; case 0x1FE0: return 0x1FE8; case 0x1FE1: return 0x1FE9; case 0x1FE5: return 0x1FEC; case 0x1FF3: return 0x1FFC; case 0x214E: return 0x2132; case 0x2170: return 0x2160; case 0x2171: return 0x2161; case 0x2172: return 0x2162; case 0x2173: return 0x2163; case 0x2174: return 0x2164; case 0x2175: return 0x2165; case 0x2176: return 0x2166; case 0x2177: return 0x2167; case 0x2178: return 0x2168; case 0x2179: return 0x2169; case 0x217A: return 0x216A; case 0x217B: return 0x216B; case 0x217C: return 0x216C; case 0x217D: return 0x216D; case 0x217E: return 0x216E; case 0x217F: return 0x216F; case 0x2184: return 0x2183; case 0x24D0: return 0x24B6; case 0x24D1: return 0x24B7; case 0x24D2: return 0x24B8; case 0x24D3: return 0x24B9; case 0x24D4: return 0x24BA; case 0x24D5: return 0x24BB; case 0x24D6: return 0x24BC; case 0x24D7: return 0x24BD; case 0x24D8: return 0x24BE; case 0x24D9: return 0x24BF; case 0x24DA: return 0x24C0; case 0x24DB: return 0x24C1; case 0x24DC: return 0x24C2; case 0x24DD: return 0x24C3; case 0x24DE: return 0x24C4; case 0x24DF: return 0x24C5; case 0x24E0: return 0x24C6; case 0x24E1: return 0x24C7; case 0x24E2: return 0x24C8; case 0x24E3: return 0x24C9; case 0x24E4: return 0x24CA; case 0x24E5: return 0x24CB; case 0x24E6: return 0x24CC; case 0x24E7: return 0x24CD; case 0x24E8: return 0x24CE; case 0x24E9: return 0x24CF; case 0x2C30: return 0x2C00; case 0x2C31: return 0x2C01; case 0x2C32: return 0x2C02; case 0x2C33: return 0x2C03; case 0x2C34: return 0x2C04; case 0x2C35: return 0x2C05; case 0x2C36: return 0x2C06; case 0x2C37: return 0x2C07; case 0x2C38: return 0x2C08; case 0x2C39: return 0x2C09; case 0x2C3A: return 0x2C0A; case 0x2C3B: return 0x2C0B; case 0x2C3C: return 0x2C0C; case 0x2C3D: return 0x2C0D; case 0x2C3E: return 0x2C0E; case 0x2C3F: return 0x2C0F; case 0x2C40: return 0x2C10; case 0x2C41: return 0x2C11; case 0x2C42: return 0x2C12; case 0x2C43: return 0x2C13; case 0x2C44: return 0x2C14; case 0x2C45: return 0x2C15; case 0x2C46: return 0x2C16; case 0x2C47: return 0x2C17; case 0x2C48: return 0x2C18; case 0x2C49: return 0x2C19; case 0x2C4A: return 0x2C1A; case 0x2C4B: return 0x2C1B; case 0x2C4C: return 0x2C1C; case 0x2C4D: return 0x2C1D; case 0x2C4E: return 0x2C1E; case 0x2C4F: return 0x2C1F; case 0x2C50: return 0x2C20; case 0x2C51: return 0x2C21; case 0x2C52: return 0x2C22; case 0x2C53: return 0x2C23; case 0x2C54: return 0x2C24; case 0x2C55: return 0x2C25; case 0x2C56: return 0x2C26; case 0x2C57: return 0x2C27; case 0x2C58: return 0x2C28; case 0x2C59: return 0x2C29; case 0x2C5A: return 0x2C2A; case 0x2C5B: return 0x2C2B; case 0x2C5C: return 0x2C2C; case 0x2C5D: return 0x2C2D; case 0x2C5E: return 0x2C2E; case 0x2C61: return 0x2C60; case 0x2C65: return 0x023A; case 0x2C66: return 0x023E; case 0x2C68: return 0x2C67; case 0x2C6A: return 0x2C69; case 0x2C6C: return 0x2C6B; case 0x2C73: return 0x2C72; case 0x2C76: return 0x2C75; case 0x2C81: return 0x2C80; case 0x2C83: return 0x2C82; case 0x2C85: return 0x2C84; case 0x2C87: return 0x2C86; case 0x2C89: return 0x2C88; case 0x2C8B: return 0x2C8A; case 0x2C8D: return 0x2C8C; case 0x2C8F: return 0x2C8E; case 0x2C91: return 0x2C90; case 0x2C93: return 0x2C92; case 0x2C95: return 0x2C94; case 0x2C97: return 0x2C96; case 0x2C99: return 0x2C98; case 0x2C9B: return 0x2C9A; case 0x2C9D: return 0x2C9C; case 0x2C9F: return 0x2C9E; case 0x2CA1: return 0x2CA0; case 0x2CA3: return 0x2CA2; case 0x2CA5: return 0x2CA4; case 0x2CA7: return 0x2CA6; case 0x2CA9: return 0x2CA8; case 0x2CAB: return 0x2CAA; case 0x2CAD: return 0x2CAC; case 0x2CAF: return 0x2CAE; case 0x2CB1: return 0x2CB0; case 0x2CB3: return 0x2CB2; case 0x2CB5: return 0x2CB4; case 0x2CB7: return 0x2CB6; case 0x2CB9: return 0x2CB8; case 0x2CBB: return 0x2CBA; case 0x2CBD: return 0x2CBC; case 0x2CBF: return 0x2CBE; case 0x2CC1: return 0x2CC0; case 0x2CC3: return 0x2CC2; case 0x2CC5: return 0x2CC4; case 0x2CC7: return 0x2CC6; case 0x2CC9: return 0x2CC8; case 0x2CCB: return 0x2CCA; case 0x2CCD: return 0x2CCC; case 0x2CCF: return 0x2CCE; case 0x2CD1: return 0x2CD0; case 0x2CD3: return 0x2CD2; case 0x2CD5: return 0x2CD4; case 0x2CD7: return 0x2CD6; case 0x2CD9: return 0x2CD8; case 0x2CDB: return 0x2CDA; case 0x2CDD: return 0x2CDC; case 0x2CDF: return 0x2CDE; case 0x2CE1: return 0x2CE0; case 0x2CE3: return 0x2CE2; case 0x2CEC: return 0x2CEB; case 0x2CEE: return 0x2CED; case 0x2CF3: return 0x2CF2; case 0x2D00: return 0x10A0; case 0x2D01: return 0x10A1; case 0x2D02: return 0x10A2; case 0x2D03: return 0x10A3; case 0x2D04: return 0x10A4; case 0x2D05: return 0x10A5; case 0x2D06: return 0x10A6; case 0x2D07: return 0x10A7; case 0x2D08: return 0x10A8; case 0x2D09: return 0x10A9; case 0x2D0A: return 0x10AA; case 0x2D0B: return 0x10AB; case 0x2D0C: return 0x10AC; case 0x2D0D: return 0x10AD; case 0x2D0E: return 0x10AE; case 0x2D0F: return 0x10AF; case 0x2D10: return 0x10B0; case 0x2D11: return 0x10B1; case 0x2D12: return 0x10B2; case 0x2D13: return 0x10B3; case 0x2D14: return 0x10B4; case 0x2D15: return 0x10B5; case 0x2D16: return 0x10B6; case 0x2D17: return 0x10B7; case 0x2D18: return 0x10B8; case 0x2D19: return 0x10B9; case 0x2D1A: return 0x10BA; case 0x2D1B: return 0x10BB; case 0x2D1C: return 0x10BC; case 0x2D1D: return 0x10BD; case 0x2D1E: return 0x10BE; case 0x2D1F: return 0x10BF; case 0x2D20: return 0x10C0; case 0x2D21: return 0x10C1; case 0x2D22: return 0x10C2; case 0x2D23: return 0x10C3; case 0x2D24: return 0x10C4; case 0x2D25: return 0x10C5; case 0x2D27: return 0x10C7; case 0x2D2D: return 0x10CD; case 0xA641: return 0xA640; case 0xA643: return 0xA642; case 0xA645: return 0xA644; case 0xA647: return 0xA646; case 0xA649: return 0xA648; case 0xA64B: return 0xA64A; case 0xA64D: return 0xA64C; case 0xA64F: return 0xA64E; case 0xA651: return 0xA650; case 0xA653: return 0xA652; case 0xA655: return 0xA654; case 0xA657: return 0xA656; case 0xA659: return 0xA658; case 0xA65B: return 0xA65A; case 0xA65D: return 0xA65C; case 0xA65F: return 0xA65E; case 0xA661: return 0xA660; case 0xA663: return 0xA662; case 0xA665: return 0xA664; case 0xA667: return 0xA666; case 0xA669: return 0xA668; case 0xA66B: return 0xA66A; case 0xA66D: return 0xA66C; case 0xA681: return 0xA680; case 0xA683: return 0xA682; case 0xA685: return 0xA684; case 0xA687: return 0xA686; case 0xA689: return 0xA688; case 0xA68B: return 0xA68A; case 0xA68D: return 0xA68C; case 0xA68F: return 0xA68E; case 0xA691: return 0xA690; case 0xA693: return 0xA692; case 0xA695: return 0xA694; case 0xA697: return 0xA696; case 0xA723: return 0xA722; case 0xA725: return 0xA724; case 0xA727: return 0xA726; case 0xA729: return 0xA728; case 0xA72B: return 0xA72A; case 0xA72D: return 0xA72C; case 0xA72F: return 0xA72E; case 0xA733: return 0xA732; case 0xA735: return 0xA734; case 0xA737: return 0xA736; case 0xA739: return 0xA738; case 0xA73B: return 0xA73A; case 0xA73D: return 0xA73C; case 0xA73F: return 0xA73E; case 0xA741: return 0xA740; case 0xA743: return 0xA742; case 0xA745: return 0xA744; case 0xA747: return 0xA746; case 0xA749: return 0xA748; case 0xA74B: return 0xA74A; case 0xA74D: return 0xA74C; case 0xA74F: return 0xA74E; case 0xA751: return 0xA750; case 0xA753: return 0xA752; case 0xA755: return 0xA754; case 0xA757: return 0xA756; case 0xA759: return 0xA758; case 0xA75B: return 0xA75A; case 0xA75D: return 0xA75C; case 0xA75F: return 0xA75E; case 0xA761: return 0xA760; case 0xA763: return 0xA762; case 0xA765: return 0xA764; case 0xA767: return 0xA766; case 0xA769: return 0xA768; case 0xA76B: return 0xA76A; case 0xA76D: return 0xA76C; case 0xA76F: return 0xA76E; case 0xA77A: return 0xA779; case 0xA77C: return 0xA77B; case 0xA77F: return 0xA77E; case 0xA781: return 0xA780; case 0xA783: return 0xA782; case 0xA785: return 0xA784; case 0xA787: return 0xA786; case 0xA78C: return 0xA78B; case 0xA791: return 0xA790; case 0xA793: return 0xA792; case 0xA7A1: return 0xA7A0; case 0xA7A3: return 0xA7A2; case 0xA7A5: return 0xA7A4; case 0xA7A7: return 0xA7A6; case 0xA7A9: return 0xA7A8; case 0xFF41: return 0xFF21; case 0xFF42: return 0xFF22; case 0xFF43: return 0xFF23; case 0xFF44: return 0xFF24; case 0xFF45: return 0xFF25; case 0xFF46: return 0xFF26; case 0xFF47: return 0xFF27; case 0xFF48: return 0xFF28; case 0xFF49: return 0xFF29; case 0xFF4A: return 0xFF2A; case 0xFF4B: return 0xFF2B; case 0xFF4C: return 0xFF2C; case 0xFF4D: return 0xFF2D; case 0xFF4E: return 0xFF2E; case 0xFF4F: return 0xFF2F; case 0xFF50: return 0xFF30; case 0xFF51: return 0xFF31; case 0xFF52: return 0xFF32; case 0xFF53: return 0xFF33; case 0xFF54: return 0xFF34; case 0xFF55: return 0xFF35; case 0xFF56: return 0xFF36; case 0xFF57: return 0xFF37; case 0xFF58: return 0xFF38; case 0xFF59: return 0xFF39; case 0xFF5A: return 0xFF3A; case 0x10428: return 0x10400; case 0x10429: return 0x10401; case 0x1042A: return 0x10402; case 0x1042B: return 0x10403; case 0x1042C: return 0x10404; case 0x1042D: return 0x10405; case 0x1042E: return 0x10406; case 0x1042F: return 0x10407; case 0x10430: return 0x10408; case 0x10431: return 0x10409; case 0x10432: return 0x1040A; case 0x10433: return 0x1040B; case 0x10434: return 0x1040C; case 0x10435: return 0x1040D; case 0x10436: return 0x1040E; case 0x10437: return 0x1040F; case 0x10438: return 0x10410; case 0x10439: return 0x10411; case 0x1043A: return 0x10412; case 0x1043B: return 0x10413; case 0x1043C: return 0x10414; case 0x1043D: return 0x10415; case 0x1043E: return 0x10416; case 0x1043F: return 0x10417; case 0x10440: return 0x10418; case 0x10441: return 0x10419; case 0x10442: return 0x1041A; case 0x10443: return 0x1041B; case 0x10444: return 0x1041C; case 0x10445: return 0x1041D; case 0x10446: return 0x1041E; case 0x10447: return 0x1041F; case 0x10448: return 0x10420; case 0x10449: return 0x10421; case 0x1044A: return 0x10422; case 0x1044B: return 0x10423; case 0x1044C: return 0x10424; case 0x1044D: return 0x10425; case 0x1044E: return 0x10426; case 0x1044F: return 0x10427; default: return ch; } } #ifdef PUSHED_MSC_WARNING # pragma warning( pop ) # undef PUSHED_MSC_WARNING #endif /* end of file cwal_utf.c */ /* start of file JSON_parser/JSON_parser.c */ /* Copyright (c) 2005 JSON.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Callbacks, comments, Unicode handling by Jean Gressmann (jean@0x42.de), 2007-2010. Changelog: 2010-11-25 Support for custom memory allocation (sgbeal@googlemail.com). 2010-05-07 Added error handling for memory allocation failure (sgbeal@googlemail.com). Added diagnosis errors for invalid JSON. 2010-03-25 Fixed buffer overrun in grow_parse_buffer & cleaned up code. 2009-10-19 Replaced long double in JSON_value_struct with double after reports of strtold being broken on some platforms (charles@transmissionbt.com). 2009-05-17 Incorporated benrudiak@googlemail.com fix for UTF16 decoding. 2009-05-14 Fixed float parsing bug related to a locale being set that didn't use '.' as decimal point character (charles@transmissionbt.com). 2008-10-14 Renamed states.IN to states.IT to avoid name clash which IN macro defined in windef.h (alexey.pelykh@gmail.com) 2008-07-19 Removed some duplicate code & debugging variable (charles@transmissionbt.com) 2008-05-28 Made JSON_value structure ansi C compliant. This bug was report by trisk@acm.jhu.edu 2008-05-20 Fixed bug reported by charles@transmissionbt.com where the switching from static to dynamic parse buffer did not copy the static parse buffer's content. */ #include #include #include #include #include #include #include #include #ifdef _MSC_VER # if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ # pragma warning(disable:4996) /* unsecure sscanf */ # pragma warning(disable:4127) /* conditional expression is constant */ # endif #endif #define true 1 #define false 0 #define __ -1 /* the universal error code */ /* values chosen so that the object size is approx equal to one page (4K) */ #ifndef JSON_PARSER_STACK_SIZE # define JSON_PARSER_STACK_SIZE 128 #endif #ifndef JSON_PARSER_PARSE_BUFFER_SIZE # define JSON_PARSER_PARSE_BUFFER_SIZE 3500 #endif typedef void* (*JSON_debug_malloc_t)(size_t bytes, const char* reason); #ifdef JSON_PARSER_DEBUG_MALLOC # define JSON_parser_malloc(func, bytes, reason) ((JSON_debug_malloc_t)func)(bytes, reason) #else # define JSON_parser_malloc(func, bytes, reason) func(bytes) #endif typedef unsigned short UTF16; struct JSON_parser_struct { JSON_parser_callback callback; void* ctx; signed char state, before_comment_state, type, escaped, comment, allow_comments, handle_floats_manually, error; char decimal_point; UTF16 utf16_high_surrogate; int current_char; int depth; int top; int stack_capacity; signed char* stack; char* parse_buffer; size_t parse_buffer_capacity; size_t parse_buffer_count; signed char static_stack[JSON_PARSER_STACK_SIZE]; char static_parse_buffer[JSON_PARSER_PARSE_BUFFER_SIZE]; JSON_malloc_t malloc; JSON_free_t free; }; #define COUNTOF(x) (sizeof(x)/sizeof(x[0])) /* Characters are mapped into these character classes. This allows for a significant reduction in the size of the state transition table. */ enum classes { C_SPACE, /* space */ C_WHITE, /* other whitespace */ C_LCURB, /* { */ C_RCURB, /* } */ C_LSQRB, /* [ */ C_RSQRB, /* ] */ C_COLON, /* : */ C_COMMA, /* , */ C_QUOTE, /* " */ C_BACKS, /* \ */ C_SLASH, /* / */ C_PLUS, /* + */ C_MINUS, /* - */ C_POINT, /* . */ C_ZERO , /* 0 */ C_DIGIT, /* 123456789 */ C_LOW_A, /* a */ C_LOW_B, /* b */ C_LOW_C, /* c */ C_LOW_D, /* d */ C_LOW_E, /* e */ C_LOW_F, /* f */ C_LOW_L, /* l */ C_LOW_N, /* n */ C_LOW_R, /* r */ C_LOW_S, /* s */ C_LOW_T, /* t */ C_LOW_U, /* u */ C_ABCDF, /* ABCDF */ C_E, /* E */ C_ETC, /* everything else */ C_STAR, /* * */ NR_CLASSES }; static const signed char ascii_class[128] = { /* This array maps the 128 ASCII characters into character classes. The remaining Unicode characters should be mapped to C_ETC. Non-whitespace control characters are errors. */ __, __, __, __, __, __, __, __, __, C_WHITE, C_WHITE, __, __, C_WHITE, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, C_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_STAR, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH, C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC, C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC, C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC }; /* The state codes. */ enum states { GO, /* start */ OK, /* ok */ OB, /* object */ KE, /* key */ CO, /* colon */ VA, /* value */ AR, /* array */ ST, /* string */ ES, /* escape */ U1, /* u1 */ U2, /* u2 */ U3, /* u3 */ U4, /* u4 */ MI, /* minus */ ZE, /* zero */ IT, /* integer */ FR, /* fraction */ E1, /* e */ E2, /* ex */ E3, /* exp */ T1, /* tr */ T2, /* tru */ T3, /* true */ F1, /* fa */ F2, /* fal */ F3, /* fals */ F4, /* false */ N1, /* nu */ N2, /* nul */ N3, /* null */ C1, /* / */ C2, /* / * */ C3, /* * */ FX, /* *.* *eE* */ D1, /* second UTF-16 character decoding started by \ */ D2, /* second UTF-16 character proceeded by u */ NR_STATES }; enum actions { CB = -10, /* comment begin */ CE = -11, /* comment end */ FA = -12, /* false */ TR = -13, /* false */ NU = -14, /* null */ DE = -15, /* double detected by exponent e E */ DF = -16, /* double detected by fraction . */ SB = -17, /* string begin */ MX = -18, /* integer detected by minus */ ZX = -19, /* integer detected by zero */ IX = -20, /* integer detected by 1-9 */ EX = -21, /* next char is escaped */ UC = -22 /* Unicode character read */ }; static const signed char state_transition_table[NR_STATES][NR_CLASSES] = { /* The state transition table takes the current state and the current symbol, and returns either a new state or an action. An action is represented as a negative number. A JSON text is accepted if at the end of the text the state is OK and if the mode is MODE_DONE. white 1-9 ABCDF etc space | { } [ ] : , " \ / + - . 0 | a b c d e f l n r s t u | E | * */ /*start GO*/ {GO,GO,-6,__,-5,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*ok OK*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*object OB*/ {OB,OB,__,-9,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*key KE*/ {KE,KE,__,__,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*colon CO*/ {CO,CO,__,__,__,__,-2,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*value VA*/ {VA,VA,-6,__,-5,__,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__}, /*array AR*/ {AR,AR,-6,__,-5,-7,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__}, /*string ST*/ {ST,__,ST,ST,ST,ST,ST,ST,-4,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST}, /*escape ES*/ {__,__,__,__,__,__,__,__,ST,ST,ST,__,__,__,__,__,__,ST,__,__,__,ST,__,ST,ST,__,ST,U1,__,__,__,__}, /*u1 U1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U2,U2,U2,U2,U2,U2,U2,U2,__,__,__,__,__,__,U2,U2,__,__}, /*u2 U2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U3,U3,U3,U3,U3,U3,U3,U3,__,__,__,__,__,__,U3,U3,__,__}, /*u3 U3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U4,U4,U4,U4,U4,U4,U4,U4,__,__,__,__,__,__,U4,U4,__,__}, /*u4 U4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,UC,UC,UC,UC,UC,UC,UC,UC,__,__,__,__,__,__,UC,UC,__,__}, /*minus MI*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,ZE,IT,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*zero ZE*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*int IT*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,IT,IT,__,__,__,__,DE,__,__,__,__,__,__,__,__,DE,__,__}, /*frac FR*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__}, /*e E1*/ {__,__,__,__,__,__,__,__,__,__,__,E2,E2,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*ex E2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*exp E3*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*tr T1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T2,__,__,__,__,__,__,__}, /*tru T2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T3,__,__,__,__}, /*true T3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__}, /*fa F1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*fal F2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F3,__,__,__,__,__,__,__,__,__}, /*fals F3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F4,__,__,__,__,__,__}, /*false F4*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__}, /*nu N1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N2,__,__,__,__}, /*nul N2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N3,__,__,__,__,__,__,__,__,__}, /*null N3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__}, /*/ C1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,C2}, /*/* C2*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, /** C3*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,CE,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, /*_. FX*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__}, /*\ D1*/ {__,__,__,__,__,__,__,__,__,D2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__}, /*\ D2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,U1,__,__,__,__}, }; /* These modes can be pushed on the stack. */ enum modes { MODE_ARRAY = 1, MODE_DONE = 2, MODE_KEY = 3, MODE_OBJECT = 4 }; static void set_error(JSON_parser jc) { switch (jc->state) { case GO: switch (jc->current_char) { case '{': case '}': case '[': case ']': jc->error = JSON_E_UNBALANCED_COLLECTION; break; default: jc->error = JSON_E_INVALID_CHAR; break; } break; case OB: jc->error = JSON_E_EXPECTED_KEY; break; case AR: jc->error = JSON_E_UNBALANCED_COLLECTION; break; case CO: jc->error = JSON_E_EXPECTED_COLON; break; case KE: jc->error = JSON_E_EXPECTED_KEY; break; /* \uXXXX\uYYYY */ case U1: case U2: case U3: case U4: case D1: case D2: jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; break; /* true, false, null */ case T1: case T2: case T3: case F1: case F2: case F3: case F4: case N1: case N2: case N3: jc->error = JSON_E_INVALID_KEYWORD; break; /* minus, integer, fraction, exponent */ case MI: case ZE: case IT: case FR: case E1: case E2: case E3: jc->error = JSON_E_INVALID_NUMBER; break; default: jc->error = JSON_E_INVALID_CHAR; break; } } static int push(JSON_parser jc, int mode) { /* Push a mode onto the stack. Return false if there is overflow. */ assert(jc->top <= jc->stack_capacity); if (jc->depth < 0) { if (jc->top == jc->stack_capacity) { const size_t bytes_to_copy = jc->stack_capacity * sizeof(jc->stack[0]); const size_t new_capacity = jc->stack_capacity * 2; const size_t bytes_to_allocate = new_capacity * sizeof(jc->stack[0]); void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "stack"); if (!mem) { jc->error = JSON_E_OUT_OF_MEMORY; return false; } jc->stack_capacity = (int)new_capacity; memcpy(mem, jc->stack, bytes_to_copy); if (jc->stack != &jc->static_stack[0]) { jc->free(jc->stack); } jc->stack = (signed char*)mem; } } else { if (jc->top == jc->depth) { jc->error = JSON_E_NESTING_DEPTH_REACHED; return false; } } jc->stack[++jc->top] = (signed char)mode; return true; } static int pop(JSON_parser jc, int mode) { /* Pop the stack, assuring that the current mode matches the expectation. Return false if there is underflow or if the modes mismatch. */ if (jc->top < 0 || jc->stack[jc->top] != mode) { return false; } jc->top -= 1; return true; } #define parse_buffer_clear(jc) \ do {\ jc->parse_buffer_count = 0;\ jc->parse_buffer[0] = 0;\ } while (0) #define parse_buffer_pop_back_char(jc)\ do {\ assert(jc->parse_buffer_count >= 1);\ --jc->parse_buffer_count;\ jc->parse_buffer[jc->parse_buffer_count] = 0;\ } while (0) void delete_JSON_parser(JSON_parser jc) { if (jc) { if (jc->stack != &jc->static_stack[0]) { jc->free((void*)jc->stack); } if (jc->parse_buffer != &jc->static_parse_buffer[0]) { jc->free((void*)jc->parse_buffer); } jc->free((void*)jc); } } int JSON_parser_reset(JSON_parser jc) { if (NULL == jc) { return false; } jc->state = GO; jc->top = -1; /* parser has been used previously? */ if (NULL == jc->parse_buffer) { /* Do we want non-bound stack? */ if (jc->depth > 0) { jc->stack_capacity = jc->depth; if (jc->depth <= (int)COUNTOF(jc->static_stack)) { jc->stack = &jc->static_stack[0]; } else { const size_t bytes_to_alloc = jc->stack_capacity * sizeof(jc->stack[0]); jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_to_alloc, "stack"); if (jc->stack == NULL) { return false; } } } else { jc->stack_capacity = (int)COUNTOF(jc->static_stack); jc->depth = -1; jc->stack = &jc->static_stack[0]; } /* set up the parse buffer */ jc->parse_buffer = &jc->static_parse_buffer[0]; jc->parse_buffer_capacity = COUNTOF(jc->static_parse_buffer); } /* set parser to start */ push(jc, MODE_DONE); parse_buffer_clear(jc); return true; } JSON_parser new_JSON_parser(JSON_config const * config) { /* new_JSON_parser starts the checking process by constructing a JSON_parser object. It takes a depth parameter that restricts the level of maximum nesting. To continue the process, call JSON_parser_char for each character in the JSON text, and then call JSON_parser_done to obtain the final result. These functions are fully reentrant. */ int use_std_malloc = false; JSON_config default_config; JSON_parser jc; JSON_malloc_t alloc; /* set to default configuration if none was provided */ if (NULL == config) { /* initialize configuration */ init_JSON_config(&default_config); config = &default_config; } /* use std malloc if either the allocator or deallocator function isn't set */ use_std_malloc = NULL == config->malloc || NULL == config->free; alloc = use_std_malloc ? malloc : config->malloc; jc = JSON_parser_malloc(alloc, sizeof(*jc), "parser"); if (NULL == jc) { return NULL; } /* configure the parser */ memset(jc, 0, sizeof(*jc)); jc->malloc = alloc; jc->free = use_std_malloc ? free : config->free; jc->callback = config->callback; jc->ctx = config->callback_ctx; jc->allow_comments = (signed char)(config->allow_comments != 0); jc->handle_floats_manually = (signed char)(config->handle_floats_manually != 0); jc->decimal_point = *localeconv()->decimal_point; /* We need to be able to push at least one object */ jc->depth = config->depth == 0 ? 1 : config->depth; /* reset the parser */ if (!JSON_parser_reset(jc)) { jc->free(jc); return NULL; } return jc; } static int parse_buffer_grow(JSON_parser jc) { const size_t bytes_to_copy = jc->parse_buffer_count * sizeof(jc->parse_buffer[0]); const size_t new_capacity = jc->parse_buffer_capacity * 2; const size_t bytes_to_allocate = new_capacity * sizeof(jc->parse_buffer[0]); void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "parse buffer"); if (mem == NULL) { jc->error = JSON_E_OUT_OF_MEMORY; return false; } assert(new_capacity > 0); memcpy(mem, jc->parse_buffer, bytes_to_copy); if (jc->parse_buffer != &jc->static_parse_buffer[0]) { jc->free(jc->parse_buffer); } jc->parse_buffer = (char*)mem; jc->parse_buffer_capacity = new_capacity; return true; } static int parse_buffer_reserve_for(JSON_parser jc, unsigned chars) { while (jc->parse_buffer_count + chars + 1 > jc->parse_buffer_capacity) { if (!parse_buffer_grow(jc)) { assert(jc->error == JSON_E_OUT_OF_MEMORY); return false; } } return true; } #define parse_buffer_has_space_for(jc, count) \ (jc->parse_buffer_count + (count) + 1 <= jc->parse_buffer_capacity) #define parse_buffer_push_back_char(jc, c)\ do {\ assert(parse_buffer_has_space_for(jc, 1)); \ jc->parse_buffer[jc->parse_buffer_count++] = c;\ jc->parse_buffer[jc->parse_buffer_count] = 0;\ } while (0) #define assert_is_non_container_type(jc) \ assert( \ jc->type == JSON_T_NULL || \ jc->type == JSON_T_FALSE || \ jc->type == JSON_T_TRUE || \ jc->type == JSON_T_FLOAT || \ jc->type == JSON_T_INTEGER || \ jc->type == JSON_T_STRING) static int parse_parse_buffer(JSON_parser jc) { if (jc->callback) { JSON_value value, *arg = NULL; if (jc->type != JSON_T_NONE) { assert_is_non_container_type(jc); switch(jc->type) { case JSON_T_FLOAT: arg = &value; if (jc->handle_floats_manually) { value.vu.str.value = jc->parse_buffer; value.vu.str.length = jc->parse_buffer_count; } else { /* not checking with end pointer b/c there may be trailing ws */ value.vu.float_value = strtod(jc->parse_buffer, NULL); } break; case JSON_T_INTEGER: arg = &value; sscanf(jc->parse_buffer, JSON_PARSER_INTEGER_SSCANF_TOKEN, &value.vu.integer_value); break; case JSON_T_STRING: arg = &value; value.vu.str.value = jc->parse_buffer; value.vu.str.length = jc->parse_buffer_count; break; } if (!(*jc->callback)(jc->ctx, jc->type, arg)) { return false; } } } parse_buffer_clear(jc); return true; } #define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800) #define IS_LOW_SURROGATE(uc) (((uc) & 0xFC00) == 0xDC00) #define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000) static const unsigned char utf8_lead_bits[4] = { 0x00, 0xC0, 0xE0, 0xF0 }; static int decode_unicode_char(JSON_parser jc) { int i; unsigned uc = 0; char* p; int trail_bytes; assert(jc->parse_buffer_count >= 6); p = &jc->parse_buffer[jc->parse_buffer_count - 4]; for (i = 12; i >= 0; i -= 4, ++p) { unsigned x = *p; if (x >= 'a') { x -= ('a' - 10); } else if (x >= 'A') { x -= ('A' - 10); } else { x &= ~0x30u; } assert(x < 16); uc |= x << i; } /* clear UTF-16 char from buffer */ jc->parse_buffer_count -= 6; jc->parse_buffer[jc->parse_buffer_count] = 0; /* attempt decoding ... */ if (jc->utf16_high_surrogate) { if (IS_LOW_SURROGATE(uc)) { uc = DECODE_SURROGATE_PAIR(jc->utf16_high_surrogate, uc); trail_bytes = 3; jc->utf16_high_surrogate = 0; } else { /* high surrogate without a following low surrogate */ return false; } } else { if (uc < 0x80) { trail_bytes = 0; } else if (uc < 0x800) { trail_bytes = 1; } else if (IS_HIGH_SURROGATE(uc)) { /* save the high surrogate and wait for the low surrogate */ jc->utf16_high_surrogate = (UTF16)uc; return true; } else if (IS_LOW_SURROGATE(uc)) { /* low surrogate without a preceding high surrogate */ return false; } else { trail_bytes = 2; } } jc->parse_buffer[jc->parse_buffer_count++] = (char) ((uc >> (trail_bytes * 6)) | utf8_lead_bits[trail_bytes]); for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) { jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) | 0x80); } jc->parse_buffer[jc->parse_buffer_count] = 0; return true; } static int add_escaped_char_to_parse_buffer(JSON_parser jc, int next_char) { assert(parse_buffer_has_space_for(jc, 1)); jc->escaped = 0; /* remove the backslash */ parse_buffer_pop_back_char(jc); switch(next_char) { case 'b': parse_buffer_push_back_char(jc, '\b'); break; case 'f': parse_buffer_push_back_char(jc, '\f'); break; case 'n': parse_buffer_push_back_char(jc, '\n'); break; case 'r': parse_buffer_push_back_char(jc, '\r'); break; case 't': parse_buffer_push_back_char(jc, '\t'); break; case '"': parse_buffer_push_back_char(jc, '"'); break; case '\\': parse_buffer_push_back_char(jc, '\\'); break; case '/': parse_buffer_push_back_char(jc, '/'); break; case 'u': parse_buffer_push_back_char(jc, '\\'); parse_buffer_push_back_char(jc, 'u'); break; default: return false; } return true; } static int add_char_to_parse_buffer(JSON_parser jc, int next_char, int next_class) { if (!parse_buffer_reserve_for(jc, 1)) { assert(JSON_E_OUT_OF_MEMORY == jc->error); return false; } if (jc->escaped) { if (!add_escaped_char_to_parse_buffer(jc, next_char)) { jc->error = JSON_E_INVALID_ESCAPE_SEQUENCE; return false; } } else if (!jc->comment) { if ((jc->type != JSON_T_NONE) | !((next_class == C_SPACE) | (next_class == C_WHITE)) /* non-white-space */) { parse_buffer_push_back_char(jc, (char)next_char); } } return true; } #define assert_type_isnt_string_null_or_bool(jc) \ assert(jc->type != JSON_T_FALSE); \ assert(jc->type != JSON_T_TRUE); \ assert(jc->type != JSON_T_NULL); \ assert(jc->type != JSON_T_STRING) int JSON_parser_char(JSON_parser jc, int next_char) { /* After calling new_JSON_parser, call this function for each character (or partial character) in your JSON text. It can accept UTF-8, UTF-16, or UTF-32. It returns true if things are looking ok so far. If it rejects the text, it returns false. */ int next_class, next_state; /* Store the current char for error handling */ jc->current_char = next_char; /* Determine the character's class. */ if (next_char < 0) { jc->error = JSON_E_INVALID_CHAR; return false; } if (next_char >= 128) { next_class = C_ETC; } else { next_class = ascii_class[next_char]; if (next_class <= __) { set_error(jc); return false; } } if (!add_char_to_parse_buffer(jc, next_char, next_class)) { return false; } /* Get the next state from the state transition table. */ next_state = state_transition_table[jc->state][next_class]; if (next_state >= 0) { /* Change the state. */ jc->state = (signed char)next_state; } else { /* Or perform one of the actions. */ switch (next_state) { /* Unicode character */ case UC: if(!decode_unicode_char(jc)) { jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; return false; } /* check if we need to read a second UTF-16 char */ if (jc->utf16_high_surrogate) { jc->state = D1; } else { jc->state = ST; } break; /* escaped char */ case EX: jc->escaped = 1; jc->state = ES; break; /* integer detected by minus */ case MX: jc->type = JSON_T_INTEGER; jc->state = MI; break; /* integer detected by zero */ case ZX: jc->type = JSON_T_INTEGER; jc->state = ZE; break; /* integer detected by 1-9 */ case IX: jc->type = JSON_T_INTEGER; jc->state = IT; break; /* floating point number detected by exponent*/ case DE: assert_type_isnt_string_null_or_bool(jc); jc->type = JSON_T_FLOAT; jc->state = E1; break; /* floating point number detected by fraction */ case DF: assert_type_isnt_string_null_or_bool(jc); if (!jc->handle_floats_manually) { /* Some versions of strtod (which underlies sscanf) don't support converting C-locale formated floating point values. */ assert(jc->parse_buffer[jc->parse_buffer_count-1] == '.'); jc->parse_buffer[jc->parse_buffer_count-1] = jc->decimal_point; } jc->type = JSON_T_FLOAT; jc->state = FX; break; /* string begin " */ case SB: parse_buffer_clear(jc); assert(jc->type == JSON_T_NONE); jc->type = JSON_T_STRING; jc->state = ST; break; /* n */ case NU: assert(jc->type == JSON_T_NONE); jc->type = JSON_T_NULL; jc->state = N1; break; /* f */ case FA: assert(jc->type == JSON_T_NONE); jc->type = JSON_T_FALSE; jc->state = F1; break; /* t */ case TR: assert(jc->type == JSON_T_NONE); jc->type = JSON_T_TRUE; jc->state = T1; break; /* closing comment */ case CE: jc->comment = 0; assert(jc->parse_buffer_count == 0); assert(jc->type == JSON_T_NONE); jc->state = jc->before_comment_state; break; /* opening comment */ case CB: if (!jc->allow_comments) { return false; } parse_buffer_pop_back_char(jc); if (!parse_parse_buffer(jc)) { return false; } assert(jc->parse_buffer_count == 0); assert(jc->type != JSON_T_STRING); switch (jc->stack[jc->top]) { case MODE_ARRAY: case MODE_OBJECT: switch(jc->state) { case VA: case AR: jc->before_comment_state = jc->state; break; default: jc->before_comment_state = OK; break; } break; default: jc->before_comment_state = jc->state; break; } jc->type = JSON_T_NONE; jc->state = C1; jc->comment = 1; break; /* empty } */ case -9: parse_buffer_clear(jc); if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { return false; } if (!pop(jc, MODE_KEY)) { return false; } jc->state = OK; break; /* } */ case -8: parse_buffer_pop_back_char(jc); if (!parse_parse_buffer(jc)) { return false; } if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { return false; } if (!pop(jc, MODE_OBJECT)) { jc->error = JSON_E_UNBALANCED_COLLECTION; return false; } jc->type = JSON_T_NONE; jc->state = OK; break; /* ] */ case -7: parse_buffer_pop_back_char(jc); if (!parse_parse_buffer(jc)) { return false; } if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_END, NULL)) { return false; } if (!pop(jc, MODE_ARRAY)) { jc->error = JSON_E_UNBALANCED_COLLECTION; return false; } jc->type = JSON_T_NONE; jc->state = OK; break; /* { */ case -6: parse_buffer_pop_back_char(jc); if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_BEGIN, NULL)) { return false; } if (!push(jc, MODE_KEY)) { return false; } assert(jc->type == JSON_T_NONE); jc->state = OB; break; /* [ */ case -5: parse_buffer_pop_back_char(jc); if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_BEGIN, NULL)) { return false; } if (!push(jc, MODE_ARRAY)) { return false; } assert(jc->type == JSON_T_NONE); jc->state = AR; break; /* string end " */ case -4: parse_buffer_pop_back_char(jc); switch (jc->stack[jc->top]) { case MODE_KEY: assert(jc->type == JSON_T_STRING); jc->type = JSON_T_NONE; jc->state = CO; if (jc->callback) { JSON_value value; value.vu.str.value = jc->parse_buffer; value.vu.str.length = jc->parse_buffer_count; if (!(*jc->callback)(jc->ctx, JSON_T_KEY, &value)) { return false; } } parse_buffer_clear(jc); break; case MODE_ARRAY: case MODE_OBJECT: assert(jc->type == JSON_T_STRING); if (!parse_parse_buffer(jc)) { return false; } jc->type = JSON_T_NONE; jc->state = OK; break; default: return false; } break; /* , */ case -3: parse_buffer_pop_back_char(jc); if (!parse_parse_buffer(jc)) { return false; } switch (jc->stack[jc->top]) { case MODE_OBJECT: /* A comma causes a flip from object mode to key mode. */ if (!pop(jc, MODE_OBJECT) || !push(jc, MODE_KEY)) { return false; } assert(jc->type != JSON_T_STRING); jc->type = JSON_T_NONE; jc->state = KE; break; case MODE_ARRAY: assert(jc->type != JSON_T_STRING); jc->type = JSON_T_NONE; jc->state = VA; break; default: return false; } break; /* : */ case -2: /* A colon causes a flip from key mode to object mode. */ parse_buffer_pop_back_char(jc); if (!pop(jc, MODE_KEY) || !push(jc, MODE_OBJECT)) { return false; } assert(jc->type == JSON_T_NONE); jc->state = VA; break; /* Bad action. */ default: set_error(jc); return false; } } return true; } int JSON_parser_done(JSON_parser jc) { if ((jc->state == OK || jc->state == GO) && pop(jc, MODE_DONE)) { return true; } jc->error = JSON_E_UNBALANCED_COLLECTION; return false; } int JSON_parser_is_legal_white_space_string(const char* s) { int c, char_class; if (s == NULL) { return false; } for (; *s; ++s) { c = *s; if (c < 0 || c >= 128) { return false; } char_class = ascii_class[c]; if (char_class != C_SPACE && char_class != C_WHITE) { return false; } } return true; } int JSON_parser_get_last_error(JSON_parser jc) { return jc->error; } void init_JSON_config(JSON_config* config) { if (config) { memset(config, 0, sizeof(*config)); config->depth = JSON_PARSER_STACK_SIZE - 1; config->malloc = malloc; config->free = free; } } /* end of file JSON_parser/JSON_parser.c */ /* end of file ../cwal_amalgamation.c */ /* start of file th1ish_io.c */ #include #include /* strlen() */ #if 1 #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #else #define MARKER(pfexp) if(0) printf pfexp #endif #define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args); \ assert(ie) int th1ish_io_cb_flush( cwal_callback_args const * args, cwal_value **rv ){ int rc = cwal_output_flush(args->engine); ARGS_IE; if(rc) rc = th1ish_toss(ie, rc, "Flushing output failed."); /* Weird: if i output from here, my error messages caught via handling linenoise input are flushed immediately, else they are not. */ /* MARKER(("Flushed %d\n",rc)); */ return rc; } /** Script usage: $output args... each arg is output with no separator and no newline at the end. If args->callee.separator is set then that value is output between each item. If args->callee.eol is set then that value is output after all arguments. */ int th1ish_io_cb_output( cwal_callback_args const * args, cwal_value **rv ){ uint16_t i; int rc = 0; static cwal_json_output_opt JsonOutOpt = cwal_json_output_opt_empty_m; cwal_value * vsep = cwal_prop_get(cwal_function_value(args->callee), "separator", 9); cwal_value * veol = cwal_prop_get(cwal_function_value(args->callee), "eol", 3); th1ish_interp * ie = (th1ish_interp *)args->state; cwal_engine * e = args->engine; cwal_buffer * buf = &ie->buffer; cwal_size_t sepLen = 0; cwal_size_t eolLen = 0; char const * eolStr = 0; buf->used = 0; if(vsep){ rc = th1ish_value_to_buffer( ie, buf, vsep ); sepLen = buf->used; if(rc) goto end; } if(veol){ rc = cwal_buffer_append( ie->e, buf, "\0", 1 ); if(rc) goto end; rc = th1ish_value_to_buffer( ie, buf, veol ); if(rc) goto end; eolStr = (char const *)(buf->mem + sepLen + 1); eolLen = buf->used - sepLen - 1; if(rc) goto end; } /* dump_val(args->self, "'this' for print()"); */ /* MARKER(("th1ish_f_print() called with %"PRIu16" arg(s).\n", args->argc)); */ for(i = 0; !rc && (i < args->argc); ++i ){ cwal_value * v = args->argv[i]; if(i && sepLen){ rc = cwal_output(e, buf->mem, sepLen); } if(rc) break; switch(cwal_value_type_id(v)){ case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_BOOL: case CWAL_TYPE_OBJECT: case CWAL_TYPE_ARRAY: case CWAL_TYPE_NULL: case CWAL_TYPE_EXCEPTION: rc = cwal_json_output_engine( e, v, &JsonOutOpt ); break; case CWAL_TYPE_UNDEF: rc = cwal_output(e, "undefined", 9); break; case CWAL_TYPE_STRING:{ cwal_string const * str = cwal_value_get_string(v); char const * cstr = cwal_string_cstr(str); cwal_size_t const slen = cwal_string_length_bytes(str); rc = slen ? cwal_output(e, cstr, slen) : 0; break; } case CWAL_TYPE_FUNCTION: case CWAL_TYPE_NATIVE: case CWAL_TYPE_BUFFER:{ rc = cwal_outputf(e, "%s@%p", cwal_value_type_name(v), (void const*)v); break; } default: break; } } end: if(!rc && eolLen){ rc = cwal_output(e, eolStr, eolLen); } if(rc){ rc = cwal_exception_setf(args->engine, rc, "Output error #%d (%s).", rc, cwal_rc_cstr(rc)); } /*cwal_result_set(args->engine, cwal_value_undefined());*/ /* cwal_output_flush(args->engine); */ *rv = NULL; return rc; } int th1ish_setup_io( th1ish_interp * ie, cwal_value * ns ){ int rc; cwal_value * v; cwal_value * outer; if(!cwal_props_can(ns)) return CWAL_RC_MISUSE; outer = th1ish_new_object(ie); rc = cwal_prop_set( ns, "io", 2, outer ); if(rc) goto end; #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ if(!rc) rc = cwal_prop_set( outer, NAME, strlen(NAME), v ); \ assert(!rc); \ if(rc) goto end FUNC2("flush", th1ish_io_cb_flush); FUNC2("output", th1ish_io_cb_output); FUNC2("slurpFile", th1ish_f_slurp_file); #undef FUNC2 #undef CHECKV end: return rc; } #undef MARKER #undef ARGS_IE /* end of file th1ish_io.c */ /* start of file th1ish_mod.c */ #include #include /* strlen() */ #if 1 /* debugging only */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #endif /** @def TH1ISH_HAVE_DLOPEN If set to true, use dlopen() and friends. Requires linking to -ldl on most platforms. Only one of TH1ISH_HAVE_DLOPEN and TH1ISH_HAVE_LTDLOPEN may be true. */ /** @def TH1ISH_HAVE_LTDLOPEN If set to true, use lt_dlopen() and friends. Requires linking to -lltdl on most platforms. Only one of TH1ISH_HAVE_DLOPEN and TH1ISH_HAVE_LTDLOPEN may be true. */ /* #define TH1ISH_HAVE_DLOPEN 1 #define TH1ISH_HAVE_LTDLOPEN 0 */ #if !defined(TH1ISH_HAVE_DLOPEN) #define TH1ISH_HAVE_DLOPEN 0 #endif #if !defined(TH1ISH_HAVE_LTDLOPEN) #define TH1ISH_HAVE_LTDLOPEN 0 #endif #if !defined(TH1ISH_ENABLE_MODULES) # define TH1ISH_ENABLE_MODULES (TH1ISH_HAVE_LTDLOPEN || TH1ISH_HAVE_DLOPEN) #endif /** For access() resp. _access(). FIXME: move the platform determination somewhere more global. */ #if defined(_WIN32) # define SMELLS_LIKE_UNIX 0 #else # define SMELLS_LIKE_UNIX 1 #endif #if SMELLS_LIKE_UNIX # include /* access() */ #else # include #endif #if TH1ISH_HAVE_LTDLOPEN # include typedef lt_dlhandle dl_handle_t; #elif TH1ISH_HAVE_DLOPEN typedef void * dl_handle_t; # include /* this actually has a different name on some platforms! */ #elif !TH1ISH_ENABLE_MODULES typedef int dl_handle_t; #else # error "We have no dlopen() impl for this configuration." #endif #if TH1ISH_ENABLE_MODULES static void dl_init(){ static char once = 0; if(!once){ once = 1; # if TH1ISH_HAVE_LTDLOPEN lt_dlinit(); lt_dlopen( 0 ); # elif TH1ISH_HAVE_DLOPEN dlopen( 0, RTLD_NOW | RTLD_GLOBAL ); # endif } } #endif #if TH1ISH_ENABLE_MODULES static dl_handle_t dl_open( char const * fname, char const **errMsg ){ dl_handle_t soh; #if TH1ISH_HAVE_LTDLOPEN soh = lt_dlopen( fname ); #elif TH1ISH_HAVE_DLOPEN soh = dlopen( fname, RTLD_NOW | RTLD_GLOBAL ); #endif if(!soh && errMsg){ #if TH1ISH_HAVE_LTDLOPEN *errMsg = lt_dlerror(); #elif TH1ISH_HAVE_DLOPEN *errMsg = dlerror(); #endif } return soh; } #endif #if TH1ISH_ENABLE_MODULES static th1ish_loadable_module const * dl_sym( dl_handle_t soh, char const * mname ){ dl_handle_t sym = #if TH1ISH_HAVE_LTDLOPEN lt_dlsym( soh, mname ) #elif TH1ISH_HAVE_DLOPEN dlsym( soh, mname ) #else NULL #endif ; return sym ? *((th1ish_loadable_module const **)sym) : NULL; } #endif #if TH1ISH_ENABLE_MODULES static void dl_close( dl_handle_t soh ){ /* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */ #if TH1ISH_HAVE_LTDLOPEN lt_dlclose( soh ); #elif TH1ISH_HAVE_DLOPEN dlclose( soh ); #endif } #endif void th1ish_modules_close( th1ish_interp * ie ){ if(ie){ #if !TH1ISH_ENABLE_MODULES assert(!ie->modules.list); #else if(ie->modules.list){ cwal_size_t i; assert(ie->modules.count); i = ie->modules.count-1; for( ; i < ie->modules.count; --i ){ void * soh = ie->modules.list[i]; assert(soh); dl_close(soh); } cwal_list_reserve( ie->e, &ie->modules, 0 ); assert(!ie->modules.list); assert(!ie->modules.count); assert(!ie->modules.alloced); } #endif } } int th1ish_module_load( th1ish_interp * ie, char const * fname, char const * symName, cwal_value * injectPoint, char const ** errMsg ){ #if !TH1ISH_ENABLE_MODULES if(errMsg) *errMsg = "No dlopen() equivalent is installed for this build configuration."; return CWAL_RC_UNSUPPORTED; #else #if !TH1ISH_HAVE_LTDLOPEN && !TH1ISH_HAVE_DLOPEN # error "We have no dlopen() and friends impl for this configuration." #endif if(!ie || !fname || !*fname || !injectPoint) return CWAL_RC_MISUSE; else if(!cwal_props_can(injectPoint)) return CWAL_RC_TYPE; else { static char once = 0; dl_handle_t soh; if(!once){ once = 1; dl_init(); } soh = dl_open( fname, errMsg ); if(!soh) return CWAL_RC_ERROR; else { enum { MaxLen = 60 }; char buf[MaxLen] = {0}; cwal_size_t const slen = symName ? strlen(symName) : 0; th1ish_loadable_module const * mod; if(slen > (MaxLen-20)) return CWAL_RC_RANGE; if(!slen){ memcpy(buf, "th1ish_module", 13); buf[14] = 0; }else{ sprintf(buf,"th1ish_module_%s", symName); } mod = dl_sym( soh, buf ); /* MARKER(("module=%s ==> %p\n",buf, (void const *)mod)); */ if(mod){ cwal_size_t i = 0; char found = 0; for( ; i < ie->modules.count; ++i ){ if(soh == ie->modules.list[i]){ found = 1; break; } } if(!found){ int const rc = cwal_list_append(ie->e, &ie->modules, soh); if(rc){ dl_close(soh); return rc; } } } return mod ? mod->init( ie, injectPoint ) : CWAL_RC_NOT_FOUND; } } #endif } int th1ish_f_module_load( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = (th1ish_interp *)args->state; #if !TH1ISH_ENABLE_MODULES assert(ie); return th1ish_toss(ie, CWAL_RC_UNSUPPORTED, "Module loading is not supported " "in this build configuration."); #else int rc; char const * fn; char const * sym = NULL; cwal_value * injectPoint; char const * errMsg = NULL; assert(ie); if(args->argc < 2){ goto misuse; } fn = cwal_string_cstr(cwal_value_get_string(args->argv[0])); sym = fn ? cwal_string_cstr(cwal_value_get_string(args->argv[1])) : NULL; if(args->argc>2){ injectPoint = args->argv[2]; }else{ injectPoint = args->argv[1]; sym = NULL; } if(!injectPoint || !cwal_props_can(injectPoint)){ goto misuse; } rc = th1ish_module_load(ie, fn, sym, injectPoint, &errMsg); /* MARKER(("load_module(%s, %s) rc=%d\n", fn, sym, rc)); */ if(rc){ if(errMsg){ rc = th1ish_toss(ie, rc, "Opening DLL failed with code " "%d (%s): %s", rc, cwal_rc_cstr(rc), errMsg); }else{ rc = th1ish_toss(ie, rc, "Loading module failed with code " "%d (%s).", rc, cwal_rc_cstr(rc)); } }else{ *rv = injectPoint; } return rc; misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a (String,String,Object) " "or (String,Object) arguments."); #endif } char th1ish_file_is_accessible( char const * fn, char checkWrite ){ #ifdef _WIN32 # define CHECKACCESS _access # define CHECKRIGHTS (checkWrite ? 2 : 4) /* 2==writeable 4==readable 6==r/w */ #elif SMELLS_LIKE_UNIX # define CHECKACCESS access # define CHECKRIGHTS (checkWrite ? W_OK : R_OK) #else # error "Don't know how to check file readability in this configuration!" #endif return (0 == CHECKACCESS( fn, CHECKRIGHTS )); #undef CHECKACCESS #undef CHECKRIGHTS } int th1ish_f_file_accessible( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = (th1ish_interp *)args->state; assert(ie); if(!args->argc || !cwal_value_is_string(args->argv[0])){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a string argument."); } else{ char const * fn = cwal_string_cstr(cwal_value_get_string(args->argv[0])); char const checkWrite = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0; assert(fn); *rv = th1ish_file_is_accessible(fn, checkWrite) ? cwal_value_true() : cwal_value_false() ; return 0; } } #undef MARKER #undef SMELLS_LIKE_UNIX /* end of file th1ish_mod.c */ /* start of file th1ish_num.c */ #include #include /* strlen() */ #include #define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf int th1ish_number_cb_parseInt( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "parseInt requires one argument."); }else{ cwal_value * v = args->argv[0]; switch(cwal_value_type_id(v)){ case CWAL_TYPE_INTEGER: *rv = v; return 0; case CWAL_TYPE_DOUBLE: case CWAL_TYPE_BOOL: *rv = cwal_new_integer( args->engine, cwal_value_get_integer(v) ); return *rv ? 0 : CWAL_RC_OOM; case CWAL_TYPE_STRING:{ cwal_int_t i = 0; int rc = cwal_string_to_int( cwal_value_get_string(v), &i ); if(rc){ *rv = cwal_value_undefined(); rc = 0; }else{ *rv = cwal_new_integer(args->engine, i); } return rc; } default: return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "parseInt does not know how to handle " "arguments of type '%s'.", cwal_value_type_name(v)); } } } int th1ish_number_cb_parseDouble( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "parseDouble requires one argument."); }else{ cwal_value * v = args->argv[0]; switch(cwal_value_type_id(v)){ case CWAL_TYPE_DOUBLE: *rv = v; return 0; case CWAL_TYPE_INTEGER: case CWAL_TYPE_BOOL: *rv = cwal_new_double( args->engine, cwal_value_get_integer(v) ); return *rv ? 0 : CWAL_RC_OOM; case CWAL_TYPE_STRING:{ cwal_double_t d = 0; int rc = cwal_string_to_double( cwal_value_get_string(v), &d ); if(rc){ *rv = cwal_value_undefined(); rc = 0; }else{ *rv = cwal_new_double(args->engine, d); } return rc; } default: return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "parseDouble does not know how to handle " "arguments of type '%s'.", cwal_value_type_name(v)); } } } int th1ish_setup_number( th1ish_interp * ie, cwal_value * ns ){ int rc; cwal_value * v; cwal_value * outer; if(!cwal_props_can(ns)) return CWAL_RC_MISUSE; outer = th1ish_new_object(ie); rc = cwal_prop_set( ns, "numeric", 7, outer ); if(rc) goto end; #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ if(!rc) rc = cwal_prop_set( outer, NAME, strlen(NAME), v ); \ assert(!rc); \ if(rc) goto end /* FUNC2("output", th1ish_io_cb_output); */ #define SET(KEY) rc = cwal_prop_set( outer, KEY, strlen(KEY), v ); \ assert(!rc); if(rc) goto end FUNC2("parseInt", th1ish_number_cb_parseInt); FUNC2("parseDouble", th1ish_number_cb_parseDouble); v = cwal_new_integer( ie->e, CWAL_INT_T_MIN ); CHECKV; SET("INT_MIN"); v = cwal_new_integer( ie->e, CWAL_INT_T_MAX ); CHECKV; SET("INT_MAX"); #undef SET #undef FUNC2 #undef CHECKV end: return rc; } #undef MARKER /* end of file th1ish_num.c */ /* start of file th1ish_protos.c */ #include #include /* strlen() */ #include /* tolower() and friends */ #include /* floor() and friends */ /* This file includes the code related to the various value prototypes which th1ish sets up. */ #if 0 /* Only for debuggering */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #endif #define PROTOTYPE_NAME_BUF(K) \ enum { BufLen = 64 }; \ char buf[BufLen]; \ int const rc = sprintf(buf, "prototype.%s", K); \ assert(rce, id ); } cwal_value * th1ish_prototype_get( th1ish_interp * ie, cwal_value const * v ){ return cwal_value_prototype_get(ie->e, v); } cwal_value * th1ish_prototype_get_by_name( th1ish_interp * ie, char const * typeName ){ PROTOTYPE_NAME_BUF(typeName); return th1ish_stash_get( ie, buf ); } #undef PROTOTYPE_NAME_BUF static int th1ish_cb_value_may_iterate( cwal_callback_args const * args, cwal_value **rv ){ *rv = cwal_value_may_iterate(args->self) ? cwal_value_true() : cwal_value_false(); return 0; } #define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args); \ assert(ie) static int th1ish_cb_prop_has_own( cwal_callback_args const * args, cwal_value **rv ){ ARGS_IE; if(!args->argc) return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a property key."); else { *rv = cwal_new_bool(cwal_prop_has_v(args->self, 0, args->argv[0])); return 0; } } static int th1ish_cb_prop_clear( cwal_callback_args const * args, cwal_value **rv ){ int rc; ARGS_IE; rc = cwal_props_clear( args->self ); if(rc) rc = th1ish_toss(ie, rc, "Cannot clear properties for this value."); return rc; } static int th1ish_value_visit_append_to_array( cwal_value * v, void * state ){ return cwal_array_append((cwal_array *)state, v); } /** Script usage: var keys = [obj.propertyKeys] Returns an array containing all key/value pairs for obj. */ static int th1ish_cb_prop_keys( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_array * ar; ARGS_IE; if(!cwal_props_can(args->self)){ return th1ish_toss(ie, CWAL_RC_TYPE, "This value (of type %s) cannot hold properties.", cwal_value_type_name(args->self)); } ar = th1ish_new_array(ie); if(!ar) return CWAL_RC_OOM; rc = cwal_props_visit_keys( args->self, th1ish_value_visit_append_to_array, ar ); if(!rc) *rv = cwal_array_value(ar); else { cwal_array_unref(ar); } return rc; } /** Script usage: obj.set(KEY,VAL) If obj is-a Array and KEY is-a Integer then the key is interpreted as an array index. Sets *rv to the VAL argument. If KEY is an integer and obj is-a Array then this function treats the key as an array index. Potential TODO: obj.set OBJ2 to copy all keys from OBJ2 to obj. */ static int th1ish_cb_prop_set( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; ARGS_IE; if(1 == args->argc){ /** [obj.set OBJ2] copies all properties of OBJ2. */ if(!cwal_props_can(args->argv[0])){ rc = CWAL_RC_TYPE; goto misuse; }else{ rc = cwal_props_copy( args->argv[0], args->self ); if(!rc) *rv = cwal_value_undefined(); return rc; } } else if(2 == args->argc){ assert(args->self); rc = th1ish_set_v( ie, NULL, args->self, args->argv[0], args->argv[1] ); if(!rc) *rv = args->argv[1]; return rc; } /* Else fall through */ misuse: assert(rc); return th1ish_toss(ie, rc, "'set' requires (INDEX, VALUE) " "or (OBJECT) arguments."); } /** Script usage: obj.unset(KEY [, ... KEY]) If obj is-a Array and KEY is-a Integer then the key is interpreted as an array index. Sets *rv to true on success. */ static int th1ish_cb_prop_unset( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; uint16_t i = 0; char b = -1; ARGS_IE; if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "'unset' requires (INDEX|KEY,...) arguments."); } for( ; !rc && (i < args->argc); ++i ){ rc = th1ish_set_v( ie, NULL, args->self, args->argv[i], NULL ); if(CWAL_RC_NOT_FOUND==rc){ rc = 0; b = 0; } else if(!rc && (-1==b)) b = 1; } if(!rc) *rv = cwal_new_bool( b>0 ); return rc; } /** Script usage: [obj.get KEY] Returns the given key for the given obj, or undefined if not found. If KEY is an integer and obj is-a Array then this function treats the key as an array index. Sets *rv to the fetch value if found, else sets it to the undefined value. Potential TODO: obj.get [list of keys] returns [list of values] */ int th1ish_cb_prop_get( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_value * v = 0; ARGS_IE; assert(ie); if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "'get' requires (INDEX|KEY) argument."); } rc = th1ish_get_v(ie, args->self, args->argv[0], &v); if(!rc) *rv = v ? v : cwal_value_undefined(); return rc; } /** Internal state for Object.eachProperty(). */ struct ObjectEach { cwal_value * self; cwal_function * callback; }; typedef struct ObjectEach ObjectEach; /** Script usage: [obj.eachProperty Function] the given function is called once per property, passed the key and value. *rv will be set to undefined. Propagates any exception thrown from the callback. */ static int th1ish_kvp_visitor_prop_each( cwal_kvp const * kvp, void * state_ ){ cwal_value * av[2] = {NULL,NULL} /* Value, Index arguments for callback */; ObjectEach * state = (ObjectEach*)state_; av[0] = cwal_kvp_key(kvp); av[1] = cwal_kvp_value(kvp); assert(av[0] && av[1]); #if 0 cwal_dump_v(av[0],"visiting key"); cwal_dump_v(av[1],"visiting value"); #endif assert(state->callback && state->self); return cwal_function_call( state->callback, state->self, NULL, 2, av ); } int th1ish_cb_prop_each( cwal_callback_args const * args, cwal_value **rv ){ cwal_function * f; ARGS_IE; f = args->argc ? cwal_value_get_function(args->argv[0]) : NULL; if(!f){ return th1ish_toss(ie, CWAL_RC_MISUSE, "'each' expects a Function argument."); }else{ ObjectEach state; int rc; state.callback = f; state.self = args->self /*better option???*/; rc = cwal_props_visit_kvp( args->self, th1ish_kvp_visitor_prop_each, &state ); if(!rc) *rv = cwal_value_undefined(); return rc; } } cwal_value * th1ish_prototype_object( th1ish_interp * ie ){ int rc = 0; cwal_value * proto; cwal_value * v; proto = cwal_prototype_base_get( ie->e, CWAL_TYPE_OBJECT ); if(proto) return proto; proto = cwal_new_object_value(ie->e); if(!proto){ rc = CWAL_RC_OOM; goto end; } assert(!cwal_value_prototype_get(ie->e, proto)); rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_OBJECT, proto ); if(rc) goto end; th1ish_prototype_store(ie, "Object", proto); assert(proto == cwal_prototype_base_get(ie->e, CWAL_TYPE_OBJECT)); /** TODOs: - toJSON() - toString() ... */ #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ if(rc) goto end FUNC2("clearProperties", th1ish_cb_prop_clear); FUNC2("compare", th1ish_f_value_compare); FUNC2("eachProperty", th1ish_cb_prop_each); FUNC2("get", th1ish_cb_prop_get); FUNC2("hasOwnProperty", th1ish_cb_prop_has_own); FUNC2("mayIterate", th1ish_cb_value_may_iterate); FUNC2("propertyKeys", th1ish_cb_prop_keys); FUNC2("set", th1ish_cb_prop_set); FUNC2("toJSONString", th1ish_f_this_to_json_token); FUNC2("unset", th1ish_cb_prop_unset); #undef FUNC2 #undef CHECKV end: return rc ? NULL : proto; } cwal_value * th1ish_new_object(th1ish_interp * ie){ cwal_value * rc; cwal_value * ap = th1ish_prototype_object( ie ) /* Must be called once to initialize the automatic prototype injection. */; if(!ap){ th1ish_toss(ie, CWAL_RC_ERROR, "Object prototype initialization failed."); return NULL; } rc = cwal_new_object_value(ie->e); if(!rc) return NULL; assert(cwal_value_prototype_get(ie->e, rc)==ap); return rc; } /** Temporary helper for Array member functions to set up their arguments. */ #define THIS_ARRAY \ cwal_array * self = 0; \ ARGS_IE; \ self = cwal_value_array_part(ie->e, args->self); \ if(!self){ \ return th1ish_toss( ie, CWAL_RC_TYPE, \ "'this' is-not-an Array." ); \ } (void)0 static int th1ish_cb_array_length( cwal_callback_args const * args, cwal_value **rv ){ THIS_ARRAY; if(args->argc){ /* SET length */ cwal_value * const index = args->argv[0]; cwal_int_t len = cwal_value_get_integer(index); int rc; if((len<0) || !cwal_value_is_number(index)){ return th1ish_toss(ie, CWAL_RC_MISUSE, "length argument must be " "a non-negative integer."); } rc = cwal_array_length_set( self, (cwal_size_t)len ); if(rc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Setting array length to %"CWAL_INT_T_PFMT " failed with code %d (%s).", len, rc, cwal_rc_cstr(rc)); } } *rv = cwal_new_integer( args->engine, (cwal_int_t)cwal_array_length_get(self) ); /* dump_val(*rv,"array.length()"); */ return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_array_reserve( cwal_callback_args const * args, cwal_value **rv ){ static char const * usage = "Expecting a non-negative integer argument."; THIS_ARRAY; if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "%s", usage ); } else{ cwal_value * const index = args->argv[0]; cwal_int_t len = cwal_value_get_integer(index); int rc; if((len<0) || !cwal_value_is_number(index)){ return th1ish_toss(ie, CWAL_RC_MISUSE, "%s", usage); } rc = cwal_array_reserve( self, (cwal_size_t)len ); if(rc){ return th1ish_toss(ie, rc, "cwal_array_reserve() failed!"); } *rv = args->self; } return 0; } static int th1ish_cb_array_push( cwal_callback_args const * args, cwal_value **rv ){ THIS_ARRAY; if(!args->argc){ *rv = cwal_value_undefined(); return 0; } else { int rc; cwal_size_t i = 0; for( i = 0; i < args->argc; ++i ){ rc = cwal_array_append( self, args->argv[i] ); if(!rc) *rv = args->argv[i]; else break; } if(rc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Appending to array failed with " "code %d (%s).", rc, cwal_rc_cstr(rc)); } } return 0; } static int th1ish_cb_array_slice( cwal_callback_args const * args, cwal_value **rv ){ cwal_int_t offset = 0, count = 0; cwal_array * acp = NULL; int rc; THIS_ARRAY; if(args->argc>0){ offset = cwal_value_get_integer(args->argv[0]); if(args->argc>1){ count = cwal_value_get_integer(args->argv[1]); } } if((offset<0) || (count<0)){ return th1ish_toss(ie, CWAL_RC_RANGE, "Slice offset and count may not be negative."); } rc = cwal_array_copy_range( self, (cwal_size_t)offset, (cwal_size_t)count, &acp ); if(!rc){ assert(acp); *rv = cwal_array_value(acp); } return rc; } static int th1ish_cb_array_unshift( cwal_callback_args const * args, cwal_value **rv ){ THIS_ARRAY; if(!args->argc){ *rv = cwal_value_undefined(); return 0; } else { int rc; cwal_size_t i = 0; if(args->argc>1){ rc = cwal_array_reserve(self, args->argc + cwal_array_length_get(self)); if(rc) return rc; } for( i = args->argc-1; i < args->argc; --i ){ rc = cwal_array_prepend( self, args->argv[i] ); if(!rc) *rv = args->argv[i]; else break; } if(rc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Prepending to array failed with " "code %d (%s).", rc, cwal_rc_cstr(rc)); } } return 0; } static int th1ish_cb_array_shift( cwal_callback_args const * args, cwal_value **rv ){ int rc; THIS_ARRAY; rc = cwal_array_shift( self, rv ); if(CWAL_RC_RANGE==rc){ rc = 0; *rv = cwal_value_undefined(); } assert(*rv); return rc; } static int th1ish_cb_array_sort( cwal_callback_args const * args, cwal_value **rv ){ int rc; THIS_ARRAY; if(!args->argc || !cwal_value_is_function(args->argv[0])){ rc = cwal_array_sort(self, cwal_compare_value_void); }else{ cwal_function * cmp = cwal_value_get_function(args->argv[0]); rc = cwal_array_sort_func( self, args->self, cmp); } if(!rc){ *rv = args->self; } return rc; } static int th1ish_cb_array_reverse( cwal_callback_args const * args, cwal_value **rv ){ int rc; THIS_ARRAY; rc = cwal_array_reverse(self); if(!rc) *rv = args->self; return rc; } static int th1ish_cb_array_each( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; cwal_function * f; cwal_size_t i = 0; cwal_size_t alen; cwal_value * vnull = cwal_value_undefined(); cwal_value * av[2] = {NULL,NULL} /* Value, Index arguments for callback */; cwal_value * ndx; THIS_ARRAY; f = args->argc ? cwal_value_get_function(args->argv[0]) : NULL; if(!f){ return th1ish_toss(ie, CWAL_RC_MISUSE, "'each' expects a Function argument."); } alen = cwal_array_length_get(self); for( i = 0; !rc && i < alen; ++i ){ av[0] = cwal_array_get( self, i ); if(!av[0]) av[0] = vnull; ndx = cwal_new_integer( args->engine, (cwal_int_t)i ); if(!ndx){ rc = CWAL_RC_OOM; break; } /* cwal_value_ref(ndx); */ av[1] = ndx; rc = cwal_function_call( f, args->self /*better option???*/, NULL, 2, av ); av[0] = av[1] = NULL; /* Reminder ndx will always end up in the argv array, which will clean it up before the call returns. */ /* cwal_value_unref(ndx) */ /* If we unref without explicitly ref()'ing we will likely pull the arguments out from the underlying argv array. If we don't ref it we don't know if it can be cleaned up safely. So we ref/unref it manually to force cleanup _unless_ the argv array adds a reference. */; } if(!rc) *rv = args->self; return rc; } /** Internal helper which looks for an integer argument at args->argv[atIndex] and checks if it is negative. Returns non-0 on error. Assigns the fetched integer (if any) to *rv, even if it is negative (which also results in a CWAL_RC_RANGE exception). */ static int th1ish_array_get_index( cwal_callback_args const * args, uint16_t atIndex, cwal_int_t * rv ){ ARGS_IE; if(args->argcargv[atIndex]); return (*rv<0) ? th1ish_toss(ie, CWAL_RC_RANGE, "Array indexes may not be be negative.") : 0; } int th1ish_cb_array_get_index( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_int_t i = 0; THIS_ARRAY; assert(ie); rc = th1ish_array_get_index(args, 0, &i); if(rc) return rc; else{ cwal_value * v; assert(i>=0); v = cwal_array_get(self, (cwal_size_t)i); *rv = v ? v : cwal_value_undefined(); return 0; } } int th1ish_cb_array_set_index( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_int_t i; THIS_ARRAY; rc = th1ish_array_get_index(args, 0, &i); if(rc) return rc; else{ cwal_value * v = (args->argc>1) ? args->argv[1] : NULL; assert(i>=0); rc = cwal_array_set(self, (cwal_size_t)i, v); if(!rc) *rv = v ? v : cwal_value_undefined(); return rc; } } static int th1ish_cb_array_join( cwal_callback_args const * args, cwal_value **rv ){ char const * joiner = NULL; cwal_size_t jLen = 0; cwal_size_t i, aLen, oldUsed; int rc = 0; cwal_buffer * buf; THIS_ARRAY; buf = &ie->buffer; oldUsed = buf->used; if(!args->argc){ misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting string argument."); } joiner = cwal_value_get_cstr(args->argv[0], &jLen); if(!joiner) goto misuse; aLen = cwal_array_length_get(self); for( i = 0; !rc && i < aLen; ++i ){ cwal_value * v = cwal_array_get(self, i); if(v){ rc = th1ish_value_to_buffer(ie, buf, v); } if(!rc && i!=(aLen-1)){ rc = cwal_buffer_append(args->engine, buf, joiner, jLen); } } if(!rc){ cwal_size_t const ln = buf->used - oldUsed; assert(oldUsed <= buf->used); *rv = cwal_new_string_value(args->engine, ln ? (char const *)(buf->mem+oldUsed) : NULL, ln); if(!*rv) rc = CWAL_RC_OOM; } buf->used = oldUsed; return rc; } cwal_value * th1ish_prototype_array( th1ish_interp * ie ){ int rc; cwal_value * proto; cwal_value * v; assert(ie && ie->e); proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_ARRAY); if(proto) return proto; proto = cwal_new_object_value(ie->e); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_ARRAY, proto); if(rc) goto end; th1ish_prototype_store(ie, "Array", proto); assert(proto == cwal_prototype_base_get(ie->e, CWAL_TYPE_ARRAY)); assert(cwal_value_prototype_get(ie->e,proto) == th1ish_prototype_object(ie)); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ if(rc) goto end FUNC("eachIndex", th1ish_cb_array_each); FUNC("getIndex", th1ish_cb_array_get_index); FUNC("join",th1ish_cb_array_join); FUNC("length",th1ish_cb_array_length); FUNC("push", th1ish_cb_array_push); FUNC("reserve", th1ish_cb_array_reserve); FUNC("reverse", th1ish_cb_array_reverse); FUNC("setIndex", th1ish_cb_array_set_index); FUNC("shift", th1ish_cb_array_shift); FUNC("slice", th1ish_cb_array_slice); FUNC("sort", th1ish_cb_array_sort); FUNC("unshift", th1ish_cb_array_unshift); #undef FUNC #undef CHECKV end: return rc ? 0 : proto; } cwal_array * th1ish_new_array(th1ish_interp * ie){ cwal_value * rc; cwal_value * ap = th1ish_prototype_array( ie ); if(!ap){ th1ish_toss(ie, CWAL_RC_ERROR, "Array prototype initialization failed."); return NULL; } rc = cwal_new_array_value(ie->e); if(!rc) return NULL; else { assert(ap == cwal_value_prototype_get(ie->e,rc)); return cwal_value_get_array(rc); } } cwal_value * th1ish_new_function2( th1ish_interp * ie, cwal_callback_f callback){ return th1ish_new_function(ie, callback, ie, NULL, &th1ish_interp_empty); } cwal_value * th1ish_new_function( th1ish_interp * ie, cwal_callback_f callback, void * state, cwal_finalizer_f stateDtor, void const * stateTypeID){ cwal_value * rc; cwal_value * proto = th1ish_prototype_function( ie ); if(!proto){ th1ish_toss(ie, CWAL_RC_ERROR, "Function prototype initialization failed."); return NULL; } rc = cwal_new_function_value(ie->e, callback, state, stateDtor, stateTypeID); if(!rc) return NULL; else { assert(cwal_value_prototype_get(ie->e,rc) == proto); return rc; } } /** Internal property key for storing "imported symbols" in a Function instance. ACHTUNG: duplicated in th1ish.c!!! */ #define FUNC_SYM_PROP "$imported$" #define FUNC_SYM_PROP_LEN (sizeof(FUNC_SYM_PROP)-1) #define THIS_FUNCTION \ cwal_function * self = 0; \ ARGS_IE; \ self = cwal_value_function_part(ie->e, args->self); \ if(!self){ \ return th1ish_toss( ie, CWAL_RC_TYPE, \ "'this' is-not-a Function." ); \ } (void)0 /** Implements Function.importSymbols(). */ int th1ish_cb_func_import_symbols( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; cwal_value * props /* where we store the symbols */; cwal_value * ce = args->self; uint16_t i; THIS_FUNCTION; if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting at least one symbol name to import."); } props = cwal_prop_get(ce, FUNC_SYM_PROP, FUNC_SYM_PROP_LEN); if(!props){ props = cwal_new_object_value(ie->e); if(!props) return CWAL_RC_OOM; rc = cwal_prop_set(ce, FUNC_SYM_PROP, FUNC_SYM_PROP_LEN, props); if(rc){ cwal_value_unref(props); return rc; } }else{ cwal_props_clear( props ); } for( i = 0; !rc && (i < args->argc); ++i ){ cwal_value * key; cwal_value * v; key = args->argv[i]; v = th1ish_var_get_v(ie, -1, key); if(!v){ cwal_string const * str = cwal_value_get_string(key); char const * cstr = str ? cwal_string_cstr(str) : NULL; return th1ish_toss(ie, CWAL_RC_NOT_FOUND, "Could not resolve symbol '%.*s' " "in the current scope stack.", str ? (int)cwal_string_length_bytes(str) : 0, cstr ? cstr : "" /* Reminder: %.*s is necessary for the case of non-NUL-terminated x-strings. */ ); } rc = cwal_prop_set_v( props, key, v ); } if(!rc){ /* *rv = cwal_function_value(args->callee); */ *rv = /*args->self*/ cwal_function_value(self); } return rc; } /** Script usage: [Function.apply thisObject array[arg1...argN]] */ static int th1ish_cb_func_apply( cwal_callback_args const * args, cwal_value **rv ){ /* FIXME: consolidate with th1ish_cb_func_call(). 90% the same code. */ int rc; cwal_value * fwdSelf /* args->argv[0], the 'this' object for the next call() */; cwal_array * theList /* args->argv[1], the list of arguments to apply() */; cwal_value * origArgv = 0 /* scope.argv, set by args collection process */; cwal_array * origArgvA = 0; cwal_scope * sc; uint16_t argc = 0; cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS] = {0}; THIS_FUNCTION; assert(ie); memset( argv, 0, sizeof(argv)); if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "'apply' expects (OBJ[, ARRAY]) argument(s)."); } origArgvA = th1ish_new_array(ie); if(!origArgvA) return CWAL_RC_OOM; rc = cwal_array_reserve(origArgvA, args->argc-1); if(rc) return rc; origArgv = cwal_array_value(origArgvA); fwdSelf = args->argv[0]; if(!th1ish_props_can(ie, fwdSelf)){ return th1ish_toss(ie, CWAL_RC_TYPE, "Invalid first argument to 'apply' - " "expecting an Object or at least " "something with a prototype."); } theList = (args->argc>1) ? cwal_value_array_part(ie->e, args->argv[1]) : NULL; if((args->argc>1) && !theList){ return th1ish_toss(ie, CWAL_RC_TYPE, "Second argument to 'apply' must be an array."); } assert(origArgvA); /* assert(0 == cwal_array_length_get(origArgvA)); */ if(theList){ /** Copy the arguments into argv and origArgv... */ cwal_size_t const alen = cwal_array_length_get(theList); /* th1ish_dump_val(args->argv[1],"theList"); */ if(alen>CWAL_OPT_MAX_FUNC_CALL_ARGS){ return th1ish_toss(ie, CWAL_RC_TYPE, "Too many arguments in parameter list. " "Limit is %d.", CWAL_OPT_MAX_FUNC_CALL_ARGS); } for(; argc < alen; ++argc ){ argv[argc] = cwal_array_get(theList,argc); /* dump_val(argv[argc],"argv[argc]"); */ if(!argv[argc]) argv[argc] = cwal_value_undefined(); cwal_array_set( origArgvA, argc, argv[argc] ); } } else { theList = th1ish_new_array(ie); } sc = cwal_scope_current_get(ie->e); #define RC if(rc) return rc /* rc = cwal_array_length_set( origArgvA, argc ) */ /* Make sure we don't bleed old arguments into the function. Been there, debugged there. */ /* We have to duplicate some of the bits done in th1ish_eval_func_collect_call() or else our up-coming call will use the value that function set when it called THIS function... */ rc = cwal_scope_chain_set( sc, 0, "this", 4, fwdSelf ); RC; rc = cwal_prop_set( origArgv, "callee", 6, cwal_function_value(self) /*argv->self?*/ ); RC; rc = cwal_scope_chain_set( sc, 0, "argv", 4, origArgv ); RC; rc = cwal_function_call_in_scope( sc, self, fwdSelf, rv, argc, argc ? argv : NULL ); #if 0 /* Reminder: unsetting callee causes valgrind warnings. Not sure why but it is highly unexpected. */ cwal_prop_set( origArgv, "callee", 6, NULL ); cwal_scope_chain_set( sc, 0, "this", 4, NULL ); /* This breaks if caller returns argv. */ cwal_scope_chain_set( sc, 0, "argv", 4, NULL ); if(argc) cwal_array_length_set( origArgvA, 0 ) /* to avoid holding on to temporaries or to strings held by higher scopes. */ ; #endif #undef RC return rc; } /** Script usage: [Function.call thisObject arg1...argN] */ static int th1ish_cb_func_call( cwal_callback_args const * args, cwal_value **rv ){ /* FIXME: consolidate with th1ish_cb_func_apply(). 90% the same code. */ int rc; cwal_value * fwdSelf /* args->argv[0], the 'this' object for the next call() */; cwal_value * origArgv = 0 /* scope.argv, set by args collection process */; cwal_array * origArgvA = 0; cwal_scope * sc; uint16_t i; THIS_FUNCTION; assert(ie); if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting (OBJ[, params...]) argument(s)."); } origArgvA = th1ish_new_array(ie); if(!origArgvA) return CWAL_RC_OOM; rc = cwal_array_reserve(origArgvA, args->argc-1); if(rc) return rc; origArgv = cwal_array_value(origArgvA); assert(origArgv && "Expecting origArgv to be set from the func args processing process."); fwdSelf = args->argv[0]; if(!th1ish_props_can(ie, fwdSelf)){ return th1ish_toss(ie, CWAL_RC_TYPE, "Invalid first argument - " "expecting an Object."); } /* assert(0 == cwal_array_length_get(origArgvA)); */ for( i = 1; i < args->argc; ++i ){ /** Copy the arguments into argv and origArgv... */ rc = cwal_array_set(origArgvA, i-1, args->argv[i]); if(rc) return rc; } sc = cwal_scope_current_get(ie->e); cwal_array_length_set( origArgvA, args->argc-1 ) /* Make sure we don't bleed old arguments into the function. Been there, debugged there. */ ; #define RC if(rc) return rc /* We have to duplicate some of the bits done in th1ish_eval_func_collect_call() or else our up-coming call will use the value that function set when it called THIS function... */ rc = cwal_scope_chain_set( sc, 0, "this", 4, fwdSelf ); RC; rc = cwal_prop_set( origArgv, "callee", 6, cwal_function_value(self) /*argv->self?*/ ); RC; rc = cwal_scope_chain_set( sc, 0, "argv", 4, origArgv ); RC; rc = cwal_function_call_in_scope( sc, self, fwdSelf, rv, args->argc-1, args->argv+1 ); #if 0 cwal_prop_set( origArgv, "callee", 6, NULL ); cwal_scope_chain_set( sc, 0, "this", 4, NULL ); if(1argc) cwal_array_length_set( origArgvA, 0 ) /* to avoid holding on to temporaries or values held by higher scopes. */ ; #endif #undef RC return rc; } /* Defined in th1ish.c for type visibility reasons. */ int th1ish_f_source_info( cwal_callback_args const * args, cwal_value **rv ); cwal_value * th1ish_prototype_function( th1ish_interp * ie ){ int rc; cwal_value * proto; cwal_value * v; assert(ie && ie->e); proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_FUNCTION); if(proto) return proto; th1ish_prototype_object(ie) /* Workaround for a timing issue in the creation of functions before Object's prototype is registered. */ ; proto = cwal_new_object_value(ie->e); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_FUNCTION, proto); if(rc) goto end; th1ish_prototype_store(ie, "Function", proto); assert(proto == cwal_prototype_base_get(ie->e, CWAL_TYPE_FUNCTION)); assert(cwal_value_prototype_get(ie->e,proto) == th1ish_prototype_object(ie)); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ assert(!rc); \ if(rc) goto end /** Side-note: Function.apply's prototype is Function, so we can do this bit of sillyness: [anyFunc.apply.apply.apply.apply foo [bar]] it won't have the same effect as [func.apply...], of course (it will apply() to apply()). */ FUNC2("apply", th1ish_cb_func_apply); FUNC2("call", th1ish_cb_func_call); FUNC2("importSymbols", th1ish_cb_func_import_symbols); FUNC2("functionInfo", th1ish_f_source_info); #undef FUNC2 #undef CHECKV end: if(rc){ proto = 0 /* let ie->stash deal with it */; } return rc ? 0 : proto; } /* Implemented in th1ish.c */ int th1ish_strace_copy( th1ish_interp * ie, cwal_value * ex ); static int th1ish_cb_exception_isa( cwal_callback_args const * args, cwal_value **rv ){ ARGS_IE; *rv = args->argc ? (cwal_value_exception_part(ie->e,args->argv[0]) ? cwal_value_true() : cwal_value_false() ) : cwal_value_false(); return 0; } /** The Exception class constructor. Script-side usage: var ex = api.Exception("Message") var ex = api.Exception(1234, "Message") If stack tracing is enabled, it captures the line/column info from the construction point */ static int th1ish_cb_exception_ctor( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; int code = CWAL_RC_EXCEPTION; cwal_value * exv = NULL; cwal_value * arg0 = NULL; cwal_value * arg1 = NULL; cwal_engine * e = args->engine; ARGS_IE; if(args->argc>0){ arg0 = args->argv[0]; if(args->argc>1) arg1 = args->argv[1]; if(arg1){ code = (int)cwal_value_get_integer(arg0); }else{ if(cwal_value_is_integer(arg0)){ code = (int)cwal_value_get_integer(arg0); }else{ arg1 = arg0; } } } exv = th1ish_new_exception(ie, code, arg1); if(!exv) return CWAL_RC_OOM; if(!rc && ie->stack.tail){ #if 1 /* if stack trace is enabled, we glean the source info from it. */ th1ish_stack_entry se = th1ish_stack_entry_empty; if(th1ish_stacktrace_current(ie, &se)){ rc = cwal_prop_set( exv, "line", 4, cwal_new_integer(e, (cwal_int_t)se.line)); if(!rc) rc = cwal_prop_set( exv, "column", 6, cwal_new_integer(e, (cwal_int_t)se.col)); if(!rc && se.scriptName){ char const * s = (char const *)(se.scriptNameLen ? se.scriptName : NULL); rc = cwal_prop_set( exv, "script", 6, cwal_new_string_value(e, s, se.scriptNameLen) ); } if(!rc) rc = th1ish_strace_copy(ie, exv); } #else rc = th1ish_strace_copy(ie, exv); #endif } if(!rc) *rv = exv; else cwal_value_unref(exv); return rc; } cwal_value * th1ish_prototype_exception( th1ish_interp * ie ){ int rc; cwal_value * proto; cwal_value * v; assert(ie && ie->e); proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_EXCEPTION); if(proto) return proto; proto = th1ish_new_function2(ie, th1ish_cb_exception_ctor ); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_EXCEPTION, proto); if(rc) goto end; th1ish_prototype_store(ie, "Exception", proto); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ assert(!rc); \ if(rc) goto end FUNC2("isA",th1ish_cb_exception_isa); #undef FUNC2 #undef CHECKV end: if(rc){ proto = 0 /* let ie->stash deal with it */; } return rc ? 0 : proto; } cwal_value * th1ish_new_exception(th1ish_interp * ie, int code, cwal_value * msg ){ cwal_value * rv; cwal_value * proto = th1ish_prototype_exception( ie ); if(!proto){ th1ish_toss(ie, CWAL_RC_ERROR, "Exception prototype initialization failed."); return NULL; } rv = cwal_new_exception_value(ie->e, code, msg); if(!rv) return NULL; else { assert(proto == cwal_value_prototype_get(ie->e,rv)); cwal_prop_set( rv, "code", 4, cwal_new_integer(ie->e, code)); return rv; } } #define THIS_DOUBLE \ cwal_double_t self; \ ARGS_IE; \ assert(ie); \ if(!cwal_value_is_number(args->self)){ \ return th1ish_toss( ie, CWAL_RC_TYPE, \ "'this' is-not-a Number." ); \ }\ self = cwal_value_get_double(args->self) #define THIS_INT \ cwal_int_t self; \ ARGS_IE; \ assert(ie); \ if(!cwal_value_is_number(args->self)){ \ return th1ish_toss( ie, CWAL_RC_TYPE, \ "'this' is-not-a Number." ); \ }\ self = cwal_value_get_integer(args->self) static int th1ish_cb_int_to_dbl( cwal_callback_args const * args, cwal_value **rv ){ THIS_INT; *rv = cwal_value_is_double(args->self) ? args->self : cwal_new_double( args->engine, (cwal_int_t)self ) ; return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_int_to_utf8_char( cwal_callback_args const * args, cwal_value **rv ){ unsigned char buf[5] = {0,0,0,0,0}; int sz; THIS_INT; sz = cwal_utf8_char_to_cstr( (unsigned int)self, buf, 5U ); if(sz<0){ return th1ish_toss(ie, CWAL_RC_RANGE, "Integer value %"CWAL_INT_T_PFMT" is not " "a valid UTF8 character.", self); } *rv = cwal_new_string_value(args->engine, (char const *)buf, (cwal_size_t)sz); return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_dbl_to_int( cwal_callback_args const * args, cwal_value **rv ){ THIS_DOUBLE; *rv = cwal_value_is_integer(args->self) ? args->self : cwal_new_integer( args->engine, (cwal_int_t)self ) ; return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_num_to_string( cwal_callback_args const * args, cwal_value **rv ){ enum { BufLen = 100 }; char buf[BufLen] = {0}; cwal_size_t bLen = (cwal_size_t)BufLen; int rc; THIS_DOUBLE; if(cwal_value_is_double(args->self)){ rc = cwal_double_to_cstr( self, buf, &bLen ); }else{ rc = cwal_int_to_cstr( (cwal_int_t)self, buf, &bLen ); } if(rc){ return th1ish_toss(ie, rc, "Conversion from number to string failed."); } *rv = cwal_new_string_value(args->engine, bLen ? buf : NULL, bLen); return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_dbl_floor( cwal_callback_args const * args, cwal_value **rv ){ THIS_DOUBLE; *rv = cwal_new_double( args->engine, floor((double)self)); return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_dbl_ceil( cwal_callback_args const * args, cwal_value **rv ){ THIS_DOUBLE; *rv = cwal_new_double( args->engine, ceil((double)self)); return *rv ? 0 : CWAL_RC_OOM; } #if 0 /* Requires C99 :/ */ static int th1ish_cb_dbl_round( cwal_callback_args const * args, cwal_value **rv ){ THIS_DOUBLE; *rv = cwal_new_double( args->engine, round((double)self)); return *rv ? 0 : CWAL_RC_OOM; } #endif static int th1ish_cb_dbl_format( cwal_callback_args const * args, cwal_value **rv ){ int left = 0, right = 0; enum { BufLen = 100 }; char buf[BufLen] = {0}; THIS_DOUBLE; if(args->argc>0){ left = (int)cwal_value_get_integer(args->argv[0]); if(args->argc>1) right = (int)cwal_value_get_integer(args->argv[1]); else{ right = left; left = 0; } } sprintf( buf, "%%%d.%d"CWAL_DOUBLE_T_PFMT, left, right ); *rv = cwal_string_value( cwal_new_stringf( ie->e, buf, self ) ); return *rv ? 0 : CWAL_RC_OOM; } /* TODO: add th1ish_prototype_number() and make it the prototype of th1ish_prototype_double/integer(). */ cwal_value * th1ish_prototype_double( th1ish_interp * ie ){ int rc = 0; cwal_value * proto; cwal_value * v; proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_DOUBLE); if(proto) return proto; /* cwal_value * fv; */ assert(ie && ie->e); proto = cwal_new_object_value(ie->e); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_DOUBLE, proto); if(rc) goto end; #if 0 cwal_value_prototype_set(proto, NULL) /* So that (1.0 instanceof object{}.prototype) fails. */; #endif /* TODO: add Number prototype. */ th1ish_prototype_store(ie, "Double", proto); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ if(rc) goto end /* FUNC2("round", th1ish_cb_dbl_round); */ FUNC2("ceil", th1ish_cb_dbl_ceil); FUNC2("compare", th1ish_f_value_compare); FUNC2("floor", th1ish_cb_dbl_floor); FUNC2("format", th1ish_cb_dbl_format); FUNC2("toDouble", th1ish_cb_int_to_dbl); FUNC2("toInt", th1ish_cb_dbl_to_int); FUNC2("toJSONString", th1ish_f_this_to_json_token); FUNC2("toString", th1ish_cb_num_to_string); #undef FUNC2 #undef CHECKV end: return rc ? NULL : proto; } cwal_value * th1ish_prototype_integer( th1ish_interp * ie ){ int rc = 0; cwal_value * proto; cwal_value * v; proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_INTEGER); if(proto) return proto; /* cwal_value * fv; */ assert(ie && ie->e); proto = cwal_new_object_value(ie->e); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_INTEGER, proto); if(rc) goto end; #if 0 cwal_value_prototype_set(proto, NULL) /* So that (1 instanceof object{}.prototype) fails. */; #endif /* TODO: add Number prototype. */ th1ish_prototype_store(ie, "Integer", proto); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ if(rc) goto end FUNC2("toDouble", th1ish_cb_int_to_dbl); FUNC2("toInt", th1ish_cb_dbl_to_int); FUNC2("toJSONString", th1ish_f_this_to_json_token); FUNC2("toString", th1ish_cb_num_to_string); FUNC2("toUtf8Char", th1ish_cb_int_to_utf8_char); FUNC2("compare", th1ish_f_value_compare); #undef FUNC2 #undef CHECKV end: return rc ? NULL : proto; } #define THIS_HASH \ cwal_hash * h; \ ARGS_IE; \ h = cwal_value_hash_part(ie->e, args->self); \ if(!h) return cwal_exception_setf(args->engine, CWAL_RC_TYPE, \ "'this' is not a hash table.") int th1ish_cb_hash_insert( cwal_callback_args const * args, cwal_value **rv ){ THIS_HASH; if(2!=args->argc) return th1ish_toss(ie, CWAL_RC_MISUSE, "insert() requires (KEY,VALUE) " "arguments."); else{ int rc = 0; rc = cwal_hash_insert_v( h, args->argv[0], args->argv[1], 1 ); if(!rc) *rv = args->argv[1]; else switch(rc){ case CWAL_RC_ACCESS: rc = th1ish_toss(ie, rc, "May not modify a hash while it " "is being iterated over."); break; default: break; } return rc; } } int th1ish_cb_hash_get( cwal_callback_args const * args, cwal_value **rv ){ cwal_value * v; THIS_HASH; if(1!=args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "get() requires (KEY) argument."); } v = cwal_hash_search_v( h, args->argv[0] ); *rv = v ? v : cwal_value_undefined(); return 0; } int th1ish_cb_hash_has( cwal_callback_args const * args, cwal_value **rv ){ THIS_HASH; if(!args->argc) return th1ish_toss(ie, CWAL_RC_MISUSE, "has() expects 1 argument."); else { *rv = cwal_hash_search_v( h, args->argv[0] ) ? cwal_value_true() : cwal_value_false(); return 0; } } static int th1ish_cb_hash_clear( cwal_callback_args const * args, cwal_value **rv ){ char clearProps; THIS_HASH; clearProps = (args->argc>0) ? cwal_value_get_bool(args->argv[0]) : 0; cwal_hash_clear(h, clearProps); return 0; } int th1ish_cb_hash_remove( cwal_callback_args const * args, cwal_value **rv ){ int rc; THIS_HASH; if(1!=args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "remove() requires (KEY) argument."); } rc = cwal_hash_remove( h, args->argv[0] ); if(!rc) *rv = cwal_value_true(); else switch(rc){ case CWAL_RC_ACCESS: rc = th1ish_toss(ie, rc, "May not modify a hash while it " "is being iterated over."); break; case CWAL_RC_NOT_FOUND: *rv = cwal_value_false(); rc = 0; break; default: break; } return rc; } int th1ish_cb_hash_size( cwal_callback_args const * args, cwal_value **rv ){ THIS_HASH; *rv = cwal_new_integer(args->engine, (cwal_int_t)h->hashSize); return *rv ? 0 : CWAL_RC_OOM; } /** Returns the "next higher" prime number starting at (n+1), where "next" is really the next one in a list of rather arbitrary prime numbers. If n is larger than some value which we internally (and almost arbitrarily) define in this code, a value smaller than n will be returned. */ static cwal_hash_t cwal_hash_next_prime( cwal_hash_t n ){ /** This list was more or less arbitrarily chosen by starting at some relatively small prime and roughly doubling it for each increment, then filling out some numbers in the middle. */ #define N(P) if(n < P) return P N(11); else N(19); else N(27); else N(59); else N(71); else N(109); else N(137); else N(167); else N(199); else N(239); else N(373); else N(457); else N(613); else N(983); else N(1319); else N(2617); else N(5801); else N(7919); else N(9973); else N(20011); #if CWAL_INT_T_BITS > 16 else N(49999); else N(100003); else return 199999; #else else return 49999; #endif #undef N } cwal_value * th1ish_prototype_hash( th1ish_interp * ie ); int th1ish_new_hash( th1ish_interp * ie, cwal_int_t hashSize, /* char strictKeyTypes, */cwal_value ** rv ){ cwal_hash * h; cwal_value * hv; cwal_value * proto = th1ish_prototype_hash( ie ); if(!proto){ return th1ish_toss(ie, CWAL_RC_ERROR, "Hash table prototype initialization failed."); } if(hashSize<=0) hashSize = 17; else if(hashSize>7919){ hashSize = 7919; } else { hashSize = cwal_hash_next_prime(hashSize); } h = cwal_new_hash(ie->e, hashSize/* , strictKeyTypes */); hv = h ? cwal_hash_value(h) : NULL; if(!hv){ assert(!h); return CWAL_RC_OOM; } else { assert(proto == cwal_value_prototype_get(ie->e,hv)); *rv = hv; return 0; } } /** Internal impl of th1ish_cp_hash_keys/values(). mode==0 means keys, anything else means values. */ static int th1ish_cb_hash_kv( cwal_callback_args const * args, char mode, cwal_value **rv ){ int rc; cwal_array * ar; THIS_HASH; ar = th1ish_new_array(ie); if(!ar) return CWAL_RC_OOM; if(!mode){ rc = cwal_hash_visit_keys( h, th1ish_value_visit_append_to_array, ar ); }else{ rc = cwal_hash_visit_values( h, th1ish_value_visit_append_to_array, ar ); } if(!rc) *rv = cwal_array_value(ar); else { cwal_array_unref(ar); } return rc; } static int th1ish_cb_hash_keys( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_hash_kv(args, 0, rv); } static int th1ish_cb_hash_values( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_hash_kv(args, 1, rv); } static int th1ish_cb_hash_entry_count( cwal_callback_args const * args, cwal_value **rv ){ THIS_HASH; *rv = cwal_new_integer(args->engine, (cwal_int_t)cwal_hash_entry_count(h)); return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_hash_new( cwal_callback_args const * args, cwal_value **rv ){ cwal_int_t hsz = 0; ARGS_IE; hsz = (args->argc>0) ? cwal_value_get_integer(args->argv[0]) : 0; #if 0 if(args->argc>1) strictKeyTypes = cwal_value_get_bool(args->argv[1]); #endif return th1ish_new_hash(ie, hsz/* , strictKeyTypes */, rv ); } /** Script usage: [obj.eachEntry FunctionValue] the given function is called once per property, passed the key and value. *rv will be set to undefined. */ static int th1ish_cb_hash_each_entry( cwal_callback_args const * args, cwal_value **rv ){ cwal_function * f; THIS_HASH; f = args->argc ? cwal_value_get_function(args->argv[0]) : NULL; if(!f){ return th1ish_toss(ie, CWAL_RC_MISUSE, "'each' expects a Function argument."); }else{ cwal_kvp * kvp; ObjectEach state; int rc = 0; cwal_size_t i; state.callback = f; state.self = args->self /*better option???*/; cwal_value_set_visiting(args->self, 1); for( i = 0; !rc && (i < h->hashSize); ++i ){ kvp = h->list[i]; if(kvp){ cwal_kvp * next = 0; for( ; !rc && kvp; kvp = next ){ next = kvp->right; #if 0 cwal_dump_v(kvp->key,"about to visit key"); cwal_dump_v(kvp->value,"about to visit value"); #endif rc = th1ish_kvp_visitor_prop_each(kvp, &state); } } } cwal_value_set_visiting(args->self, 0); if(!rc) *rv = cwal_value_undefined(); return rc; } } cwal_value * th1ish_prototype_hash( th1ish_interp * ie ){ int rc; cwal_value * proto; cwal_value * v; assert(ie && ie->e); proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_HASH); if(proto) return proto; proto = th1ish_new_function2(ie, th1ish_cb_hash_new ); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_HASH, proto); if(rc) goto end; th1ish_prototype_store(ie, "Hash", proto); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ assert(!rc); \ if(rc) goto end FUNC2("clearEntries", th1ish_cb_hash_clear); FUNC2("containsEntry", th1ish_cb_hash_has); FUNC2("eachEntry", th1ish_cb_hash_each_entry); FUNC2("entryKeys", th1ish_cb_hash_keys); FUNC2("entryValues", th1ish_cb_hash_values); FUNC2("hashSize", th1ish_cb_hash_size); FUNC2("insert", th1ish_cb_hash_insert); FUNC2("entryCount", th1ish_cb_hash_entry_count); FUNC2("remove", th1ish_cb_hash_remove); FUNC2("search", th1ish_cb_hash_get); #undef FUNC2 #undef CHECKV end: if(rc){ proto = 0 /* let ie->gc deal with it */; } return rc ? 0 : proto; } int th1ish_setup_hash( th1ish_interp * ie, cwal_value * ns ){ int rc; cwal_value * v; #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_prop_set( ns, NAME, strlen(NAME), v ); \ if(rc) goto end FUNC2("Hash", th1ish_cb_hash_new); #undef FUNC2 #undef CHECKV end: return rc; } struct th1ish_pf { th1ish_interp * ie; cwal_value * self; cwal_buffer buf; /* char (*predicate)( char const * fn ); */ }; static const th1ish_pf th1ish_pf_empty = { NULL/*ie*/, NULL/*self*/, cwal_buffer_empty_m/*buf*/ }; cwal_array * th1ish_pf_exts(th1ish_pf *pf){ cwal_value * ar = cwal_prop_get(pf->self,"suffix", 6); if(!ar || !cwal_value_is_array(ar)){ ar = cwal_new_array_value(pf->ie->e); if(ar && (0!=cwal_prop_set(pf->self, "suffix",6, ar))){ cwal_value_unref(ar); ar = NULL; } } return cwal_value_get_array(ar); } cwal_array * th1ish_pf_dirs(th1ish_pf *pf){ cwal_value * ar = cwal_prop_get(pf->self,"prefix", 6); if(!ar || !cwal_value_is_array(ar)){ ar = cwal_new_array_value(pf->ie->e); if(ar && (0!=cwal_prop_set(pf->self, "prefix", 6, ar))){ cwal_value_unref(ar); ar = NULL; } } return cwal_value_get_array(ar); } static void th1ish_pf_finalizer(cwal_engine * e, void * m){ th1ish_pf * pf = (th1ish_pf*)m; cwal_buffer_reserve(e, &pf->buf, 0); cwal_free( e, m ); } int th1ish_pf_dirs_set( th1ish_pf * pf, cwal_array * ar ){ if(!pf || !pf->self || !ar) return CWAL_RC_MISUSE; else{ return cwal_prop_set(pf->self, "prefix", 6, cwal_array_value(ar)); } } int th1ish_pf_exts_set( th1ish_pf * pf, cwal_array * ar ){ if(!pf || !pf->self || !ar) return CWAL_RC_MISUSE; else{ return cwal_prop_set(pf->self, "suffix", 6, cwal_array_value(ar)); } } int th1ish_pf_dir_add_v( th1ish_pf * pf, cwal_value * v ){ if(!pf || !pf->self || !v) return CWAL_RC_MISUSE; else{ cwal_array * ar = th1ish_pf_dirs(pf); return ar ? cwal_array_append(ar, v) : CWAL_RC_OOM; } } int th1ish_pf_dir_add( th1ish_pf * pf, char const * dir, cwal_size_t dirLen){ if(!pf || !pf->self || !dir) return CWAL_RC_MISUSE; else{ int rc; cwal_value * v = cwal_new_string_value(pf->ie->e, dirLen ? dir : NULL, dirLen); if(!v) rc = CWAL_RC_OOM; else { rc = th1ish_pf_dir_add_v( pf, v); if(rc) cwal_value_unref(v); } return rc; } } int th1ish_pf_ext_add_v( th1ish_pf * pf, cwal_value * v ){ if(!pf || !pf->self || !v) return CWAL_RC_MISUSE; else{ cwal_array * ar = th1ish_pf_exts(pf); return ar ? cwal_array_append(ar, v) : CWAL_RC_OOM; } } int th1ish_pf_ext_add( th1ish_pf * pf, char const * dir, cwal_size_t dirLen){ if(!pf || !pf->self || !dir) return CWAL_RC_MISUSE; else{ int rc; cwal_value * v = cwal_new_string_value(pf->ie->e, dirLen ? dir : NULL, dirLen); if(!v) rc = CWAL_RC_OOM; else { rc = th1ish_pf_ext_add_v( pf, v); if(rc) cwal_value_unref(v); } return rc; } } /** If v is-a PathFinder or derives from it, this function returns the th1ish_pf part of v or one of its prototypes. */ static th1ish_pf * th1ish_value_pf_part(cwal_value *v){ cwal_native * n; th1ish_pf * pf = NULL; while(v){ n = cwal_value_get_native(v); if(n){ pf = (th1ish_pf *)cwal_native_get(n, &th1ish_pf_empty); if(pf) break; } v = cwal_value_prototype_get(NULL,v); } return pf; } #define THIS_PF \ th1ish_pf * pf = th1ish_value_pf_part(args->self); \ th1ish_interp * ie = pf ? pf->ie : NULL; \ if(!pf) return \ cwal_exception_setf(args->engine, CWAL_RC_TYPE, \ "'this' is not a PathFinder instance."); \ else assert(ie) static int th1ish_cb_pf_add( cwal_callback_args const * args, cwal_value **rv, cwal_array * dest ){ THIS_PF; if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting string arguments."); } else if(!dest) return CWAL_RC_OOM; else { cwal_size_t i = 0; int rc = 0; for( ; !rc && (i < args->argc); ++i ){ rc = cwal_array_append( dest, args->argv[i]); } if(!rc) *rv = args->self; return rc; } } static int th1ish_cb_pf_dir_add( cwal_callback_args const * args, cwal_value **rv ){ THIS_PF; return th1ish_cb_pf_add( args, rv, th1ish_pf_dirs(pf) ); } static int th1ish_cb_pf_ext_add( cwal_callback_args const * args, cwal_value **rv ){ THIS_PF; return th1ish_cb_pf_add( args, rv, th1ish_pf_exts(pf) ); } static void th1ish_pf_separator( th1ish_pf * pf, char const ** sep, cwal_size_t * sepLen ){ cwal_value const * vs = cwal_prop_get(pf->self,"separator",9); char const * rc = vs ? cwal_value_get_cstr(vs, sepLen) : NULL; assert(sep && sepLen); if(!rc){ if(vs){ /* Non-string separator value. FIXME: we _could_ use a non-string with just a little more work but there's currently no use case for it. If PF is abstracted to do other types of searches (where the user supplies a predicate function we call for each path combination we try) then it will make sense to allow non-string separators. For now we're only dealing with file paths, which means strings. */ *sep = ""; *sepLen = 0; } else{ #ifdef _WIN32 rc = "\\"; *sepLen = 1; #else rc = "/"; *sepLen = 1; #endif } } *sep = rc; } char const * th1ish_pf_search( th1ish_pf * pf, char const * base, cwal_size_t baseLen, cwal_size_t * rcLen ){ char const * pathSep = NULL; cwal_size_t sepLen; cwal_buffer * buf; cwal_engine * e; cwal_size_t d, x, nD, nX, resetLen = 0; cwal_array * ad; cwal_array * ax; int rc = 0; if(!pf || !base) return NULL; else if(!*base || !baseLen) return NULL; th1ish_pf_separator(pf, &pathSep, &sepLen); assert(pathSep); buf = &pf->buf; e = pf->ie->e; ad = th1ish_pf_dirs(pf); ax = th1ish_pf_exts(pf); nD = cwal_array_length_get(ad); nX = cwal_array_length_get(ax); buf->used = 0; for( d = 0; !rc && (d < nD); ++d ){ cwal_value * vD = cwal_array_get(ad, d); buf->used = 0; if(vD){ cwal_size_t const used = buf->used; rc = th1ish_value_to_buffer(pf->ie, buf, vD); if(rc) break; if(sepLen && (used != buf->used)){ /* Only append separator if vD is non-empty. */ rc = cwal_buffer_append(e, buf, pathSep, sepLen); if(rc) break; } } rc = cwal_buffer_append(e, buf, base, baseLen); if(rc) break; if(th1ish_file_is_accessible( (char const *)buf->mem, 0 )) goto gotone; resetLen = buf->used; for( x = 0; !rc && (x < nX); ++x ){ cwal_value * vX = cwal_array_get(ax, x); if(vX){ buf->used = resetLen; rc = th1ish_value_to_buffer(pf->ie, buf, vX); if(rc) break; } assert(buf->used < buf->capacity); buf->mem[buf->used] = 0; if(th1ish_file_is_accessible( (char const *)buf->mem, 0 )){ goto gotone; } } } return NULL; gotone: if(rcLen) *rcLen = buf->used; return (char const *)buf->mem; } static int th1ish_cb_pf_search( cwal_callback_args const * args, cwal_value **rv ){ cwal_size_t baseLen = 0; char const * base; char const * rc; cwal_size_t rcLen = 0; THIS_PF; if(!args->argc) goto misuse; base = cwal_value_get_cstr(args->argv[0], &baseLen); if(!base || !baseLen) goto misuse; rc = th1ish_pf_search( pf, base, baseLen, &rcLen ); if(!rc) *rv = cwal_value_undefined(); else { *rv = cwal_new_string_value(args->engine, rc, rcLen); } return *rv ? 0 : CWAL_RC_OOM; misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a non-empty string argument."); } int th1ish_cb_pf_new( cwal_callback_args const * args, cwal_value **rv ){ if(th1ish_value_pf_part(args->self)){ /* A PF instance call()ed via its prototype Function. */ return th1ish_cb_pf_search(args, rv); }else{ /* Constructor... */ int rc = 0; th1ish_pf * pf; ARGS_IE; pf = th1ish_pf_new(ie); if(!pf) return CWAL_RC_OOM; *rv = pf->self; if(args->argc){ cwal_array * ar = cwal_value_get_array(args->argv[0]); if(ar){ rc = th1ish_pf_dirs_set(pf, ar); ar = (!rc && (args->argc > 1)) ? cwal_value_get_array(args->argv[1]) : NULL; if(ar) th1ish_pf_exts_set(pf, ar); } } return rc; } } cwal_value * th1ish_prototype_pf(th1ish_interp *ie){ int rc = 0; cwal_value * v; cwal_value * proto; char const * pKey = "class.PathFinder"; proto = th1ish_prototype_get_by_name(ie, pKey); if(proto) return proto; assert(ie && ie->e); proto = cwal_new_function_value(ie->e, th1ish_cb_pf_new, ie, NULL, &th1ish_interp_empty); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = th1ish_prototype_store( ie, pKey, proto ); if(rc) goto end; #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define SET(NAME) \ CHECKV; \ rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ if(rc) goto end #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ SET(NAME) v = cwal_new_string_value(ie->e, "PathFinder", 10); SET("__typename"); FUNC2("addDir", th1ish_cb_pf_dir_add); FUNC2("addExt", th1ish_cb_pf_ext_add); FUNC2("search", th1ish_cb_pf_search); #undef SET #undef FUNC2 #undef CHECKV end: return rc ? NULL : proto; } th1ish_pf * th1ish_pf_new(th1ish_interp *ie){ th1ish_pf * pf; cwal_value * vSelf; if(!ie || !ie->e) return NULL; pf = (th1ish_pf*)cwal_malloc(ie->e, sizeof(th1ish_pf)); if(!pf) return NULL; *pf = th1ish_pf_empty; vSelf = cwal_new_native_value(ie->e, pf, th1ish_pf_finalizer, &th1ish_pf_empty); if(!vSelf){ cwal_free(ie->e, pf); pf = NULL; }else{ pf->self = vSelf; pf->ie = ie; assert(cwal_props_can(vSelf)); #if 0 th1ish_pf_dirs(pf); th1ish_pf_exts(pf); assert(cwal_prop_get(vSelf,"suffix",6)); assert(cwal_prop_get(vSelf,"prefix",6)); #endif cwal_value_prototype_set( vSelf, th1ish_prototype_pf(ie) ); } return pf; } int th1ish_install_pf( th1ish_interp * ie, cwal_value * ns ){ return cwal_props_can(ns) ? cwal_prop_set(ns, "PathFinder", 10, th1ish_prototype_pf(ie)) : CWAL_RC_MISUSE; } #undef THIS_ARRAY #undef THIS_STRING #undef THIS_DOUBLE #undef THIS_INT #undef THIS_HASH #undef THIS_FUNCTION #undef THIS_PF #undef ARGS_IE #undef FUNC_SYM_PROP #undef FUNC_SYM_PROP_LEN #undef PROTOTYPE_NAME_BUF #undef MARKER /* end of file th1ish_protos.c */ /* start of file th1ish_str.c */ #include #include /* strlen() */ #include /* tolower() and friends */ #include #if 1 #endif /* This file includes the script-side string-related functions. They are encapsulated script-side under (api.string...). */ #if 1 /* Only for debuggering */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #endif #define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args) #define THIS_STRING \ ARGS_IE; \ cwal_string * self = cwal_value_string_part(args->engine, args->self); \ if(!self && args->argc) self = cwal_value_string_part(args->engine, args->argv[0]); \ if(!self){ \ return th1ish_toss( ie, CWAL_RC_TYPE, \ "Expecting a string 'this' or first argument." ); \ } (void)0 static int th1ish_cb_str_length( cwal_callback_args const * args, cwal_value **rv ){ THIS_STRING; *rv = cwal_new_integer( args->engine, (cwal_int_t)cwal_string_length_bytes(self) ); return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_str_length_utf8( cwal_callback_args const * args, cwal_value **rv ){ THIS_STRING; *rv = cwal_new_integer(args->engine, (cwal_int_t) cwal_string_length_utf8(self) ); return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_str_char_at( cwal_callback_args const * args, cwal_value **rv ){ cwal_int_t pos; THIS_STRING; pos = (args->argc>0) ? (cwal_value_is_number(args->argv[0]) ? cwal_value_get_integer(args->argv[0]) : -1) : -1; if(pos<0){ return th1ish_toss( ie, CWAL_RC_MISUSE, "charAt expects an integer argument with a value of 0 or more."); }else{ unsigned char const * cstr = (unsigned char const *)cwal_string_cstr(self); cwal_size_t const slen = cwal_string_length_bytes(self); if(pos >= (cwal_int_t)slen){ *rv = cwal_value_undefined(); }else{ unsigned char const * at = cstr; unsigned char const * end = cstr + slen; cwal_int_t i = 0; for( ; i < pos; ++i ){ cwal_utf8_read_char( at, end, &at ); } assert(at!=end); cstr = at; i = cwal_utf8_read_char( cstr, end, &at ); assert(at>cstr); if((args->argc>1) && cwal_value_get_bool(args->argv[1])){ *rv = cwal_new_integer(args->engine, i); } else { *rv = cwal_new_string_value( args->engine, (char const *)cstr, at - cstr); } } return *rv ? 0 : CWAL_RC_OOM; } } static int th1ish_cb_str_byte_at( cwal_callback_args const * args, cwal_value **rv ){ cwal_int_t pos; THIS_STRING; pos = (args->argc>0) ? (cwal_value_is_number(args->argv[0]) ? cwal_value_get_integer(args->argv[0]) : -1) : -1; if(pos<0){ return th1ish_toss( ie, CWAL_RC_MISUSE, "byteAt expects an integer argument with a value of 0 or more."); }else{ unsigned char const * cstr = (unsigned char const *)cwal_string_cstr(self); cwal_size_t const slen = cwal_string_length_bytes(self); if(pos >= (cwal_int_t)slen){ *rv = cwal_value_undefined(); }else{ *rv = cwal_new_integer(args->engine, cstr[pos]); } return *rv ? 0 : CWAL_RC_OOM; } } static int th1ish_cb_str_toupperlower( cwal_callback_args const * args, cwal_value **rv, char doUpper ){ THIS_STRING; { int rc = 0; unsigned char const * cs = (unsigned char const *)cwal_string_cstr(self); cwal_size_t const len = cwal_string_length_bytes(self); unsigned char const * csEnd = cs+len; unsigned char * out; int ch; th1ish_interp * ie = (th1ish_interp *)args->state; cwal_buffer buf = cwal_buffer_empty; if(!len){ *rv = cwal_string_value(self); return 0; } rc = cwal_buffer_reserve( ie->e, &buf, len + 5 ); if(rc) return rc; out = buf.mem; for( ; cs < csEnd; ){ unsigned char const * pos = cs; ch = cwal_utf8_read_char( cs, csEnd, &cs ); ch = doUpper ? cwal_utf8_char_toupper(ch) : cwal_utf8_char_tolower(ch); cwal_utf8_char_to_cstr( (unsigned int)ch, out, 5 /*(buf.capacity - (out-buf.mem))*/); out += cs-pos; } assert(out >= buf.mem); buf.used = out - buf.mem; buf.mem[buf.used] = 0; *rv = cwal_new_string_value(ie->e, buf.used ? (char const *)buf.mem : NULL, buf.used); if(!*rv) rc = CWAL_RC_OOM; cwal_buffer_reserve(ie->e, &buf, 0); return rc; } } static int th1ish_cb_str_tolower( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_str_toupperlower( args, rv, 0 ); } static int th1ish_cb_str_toupper( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_str_toupperlower( args, rv, 1 ); } static int th1ish_cb_str_split( cwal_callback_args const * args, cwal_value **rv ){ cwal_size_t sepLen = 0; unsigned char const * sep; THIS_STRING; sep = args->argc ? (unsigned char const *)cwal_value_get_cstr(args->argv[0], &sepLen) : NULL; if(!sep){ return cwal_exception_setf( args->engine, CWAL_RC_MISUSE, "Expecting a string argument."); }else{ int rc = 0; /* cwal_int_t count = 0; */ /* cwal_int_t limit; */ cwal_array * ar = NULL; unsigned char const * pos = (unsigned char const *)cwal_string_cstr(self); cwal_size_t const slen = cwal_string_length_bytes(self); unsigned char const * eof = pos + slen; unsigned char const * start = pos; cwal_value * v; /*TODO?: limit = (args->argc>1) ? cwal_value_get_integer(args->argv[1]) : 0 ;*/ ar = cwal_new_array(args->engine); if(!ar) return CWAL_RC_OOM; else if(!slen || (sepLen>slen)){ rc = cwal_array_append(ar, args->self); } else while( 1 ){ if( (pos>=eof) || (0==memcmp(sep, pos, sepLen)) ){ cwal_size_t sz; char last = (pos>=eof); if(pos>eof) pos=eof; sz = pos-start; v = sz ? cwal_new_string_value(args->engine, (char const *)start, sz) : NULL; if(!v && sz){ rc = CWAL_RC_OOM; goto end; } else rc = cwal_array_append(ar, v); if(rc){ cwal_value_unref(v); goto end; } if(last) goto end; /* ++count; */ pos += sepLen; start = pos; }else{ cwal_utf8_read_char( pos, eof, &pos ); } } end: if(rc && ar){ cwal_array_unref(ar); }else if(!rc){ *rv = cwal_array_value(ar); if(!*rv) rc = CWAL_RC_OOM; } return rc; } } /** */ static int th1ish_cb_str_substr( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "Expecting 1 or 2 integer arguments."); }else{ cwal_int_t offset, len; cwal_size_t sLen; char const * cstr; THIS_STRING; offset = cwal_value_get_integer(args->argv[0]); len = (args->argc>1) ? cwal_value_get_integer(args->argv[1]) : 0; if(len<0){ return cwal_exception_setf(args->engine, CWAL_RC_RANGE, "Length argument may not be negative."); } sLen = cwal_string_length_bytes(self); cstr = cwal_string_cstr(self); if(offset<0){ offset = sLen + offset; if(offset < 0) offset = 0; } if(offset>=sLen){ *rv = cwal_new_string_value(args->engine, NULL, 0); return 0; } else if((offset+len) > sLen){ len = sLen - offset; /* assert(len < sLen); */ if(len > sLen) len = sLen; } { /* Calculate the range/length based on the UTF8 length. */ unsigned char const * at = (unsigned char const *)cstr; unsigned char const * eof = at + sLen; unsigned char const * start; unsigned char const * end; cwal_int_t i = 0; for( ; i < offset; ++i ){ cwal_utf8_read_char( at, eof, &at ); } start = at; if(len){ end = start; for( i = 0; (end= start); len = end - start; *rv = cwal_new_string_value(args->engine, len ? (char const *)start : NULL, len); } return *rv ? 0 : CWAL_RC_OOM; } } /** Script usage: var x = "haystack".indexOf("needle" [, startOffset=0]) if(undefined===x) { ...no match found... } else { x === index of "needle" in "haystack" } If the startOffset is negative, it is counted as the number of characters from the end of the haystack, but does not change the search direction (because counting utf8 lengths backwards sounds really hard ;). */ static int th1ish_cb_str_indexof( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc){ misuse: return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "Expecting 1 string argument."); }else{ char const * arg; cwal_size_t aLen = 0; THIS_STRING; arg = cwal_value_get_cstr(args->argv[0], &aLen); if(!arg) goto misuse; else if(!aLen){ *rv = cwal_new_integer(args->engine, -1); return 0; } else{ /* Calculate the range/length based on the UTF8 length. */ cwal_size_t sLen = cwal_string_length_bytes(self); unsigned char const * myStr = (unsigned char const *)cwal_string_cstr(self); unsigned char const * eof = myStr + sLen; cwal_int_t i = 0; cwal_int_t offset; if(aLen>sLen){ *rv = cwal_value_undefined(); return 0; } offset = (args->argc>1) ? cwal_value_get_integer(args->argv[1]) : 0; if(offset<0){ cwal_size_t const ulen = cwal_string_length_utf8(self); if(ulen < ((cwal_size_t)-offset)){ offset = 0; }else{ offset = ulen + offset; } assert(offset >= 0); } while(1){ unsigned char const * start = myStr; if((start==eof) || (aLen > (eof-start))) break; cwal_utf8_read_char( start, eof, &myStr ); if(iengine, i); return *rv ? 0 : CWAL_RC_OOM; } else ++i; } *rv = cwal_new_integer(args->engine, -1); return 0; } } } static int th1ish_cb_str_concat( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "'concat' requires at lest one argument."); }else{ int rc = 0; cwal_size_t i = 0, argc = args->argc; cwal_buffer buf = cwal_buffer_empty; th1ish_interp * ie = (th1ish_interp *)args->state; if(cwal_value_is_string(args->self)){ rc = th1ish_value_to_buffer( ie, &buf, args->self ); } for( ; !rc && (i < argc); ++i ){ rc = th1ish_value_to_buffer( ie, &buf, args->argv[i] ); } if(!rc){ *rv = cwal_new_string_value(ie->e, buf.used ? (char const *)buf.mem : NULL, buf.used); if(!*rv) rc = CWAL_RC_OOM; } cwal_buffer_reserve( ie->e, &buf, 0 ); return rc; } } /** Impl of (STRING+VALUE) operator. */ static int th1ish_cb_str_op_concat( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "'concat' requires at lest one argument."); }else{ int rc = 0; cwal_size_t i = 0, argc = args->argc; cwal_buffer buf = cwal_buffer_empty; th1ish_interp * ie = (th1ish_interp *)args->state; char checkForSelf = 0; if(cwal_value_is_string(args->self)){ checkForSelf = 1; rc = th1ish_value_to_buffer( ie, &buf, args->self ); } for( ; !rc && (i < argc); ++i ){ if((0==i) && checkForSelf && (args->argv[i]==args->self)){ checkForSelf = 0; continue; } rc = th1ish_value_to_buffer( ie, &buf, args->argv[i] ); } if(!rc){ *rv = cwal_new_string_value(ie->e, buf.used ? (char const *)buf.mem : NULL, buf.used); if(!*rv) rc = CWAL_RC_OOM; } cwal_buffer_reserve( ie->e, &buf, 0 ); return rc; } } static int th1ish_cb_str_unescape_c( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc && !cwal_value_is_string(args->self)){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "'' requires at lest one argument."); }else{ int rc = 0; unsigned char const * begin = 0; cwal_size_t sLen = 0; cwal_string * sv; cwal_value * theV; th1ish_interp * ie = (th1ish_interp *)args->state; cwal_buffer * buf = &ie->buffer; cwal_size_t const oldUsed = buf->used; if(cwal_value_is_string(args->self)){ theV = args->self; }else{ theV = args->argv[0]; } sv = cwal_value_get_string(theV); if(!sv){ return th1ish_toss(ie, CWAL_RC_TYPE, "Expecting a STRING as 'this' or first argument."); } begin = (unsigned char const *)cwal_string_cstr(sv); sLen = cwal_string_length_bytes(sv); if(!sLen){ *rv = cwal_string_value(sv); return 0; } rc = cwal_st_unescape_string(args->engine, begin, begin + sLen, buf ); assert(buf->used >= oldUsed); if(rc){ return th1ish_toss(ie, rc, "Unescaping failed!?"); } if((sLen == (buf->used-oldUsed)) && (0==cwal_compare_cstr((char const*)buf->mem, buf->used, (char const*)begin, sLen))){ /* Same byte sequence - re-use it. */ *rv = theV; }else{ *rv = cwal_new_string_value( args->engine, (buf->used!=oldUsed) ? ((char const *)buf->mem+oldUsed) : NULL, buf->used-oldUsed ); } buf->used = oldUsed; return *rv ? 0 : CWAL_RC_OOM; } } /** Impl for trim/trimLeft/trimRight(). mode determines which: <0 == left 0 == both >0 == right */ static int th1ish_cb_str_trim_impl( cwal_callback_args const * args, cwal_value **rv, char mode ){ THIS_STRING; { int rc = 0; char const * cs = cwal_string_cstr(self); cwal_size_t const len = cwal_string_length_bytes(self); char const * end = cs + len; if(!len){ *rv = args->self; return rc; } if(mode <= 0) for( ; *cs && isspace(*cs); ++cs){} if(mode>=0){ for( --end; (end>cs) && isspace(*end); --end){} ++end; } *rv = ((end-cs) == len) ? args->self : cwal_new_string_value(args->engine, cs, end-cs) ; return *rv ? 0 : CWAL_RC_OOM; } } static int th1ish_cb_str_trim_left( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_str_trim_impl( args, rv, -1 ); } static int th1ish_cb_str_trim_right( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_str_trim_impl( args, rv, 1 ); } static int th1ish_cb_str_trim_both( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_str_trim_impl( args, rv, 0 ); } static int th1ish_cb_str_apply_format( cwal_callback_args const * args, cwal_value **rv ){ char const * fmt; cwal_size_t fmtLen; cwal_size_t oldUsed; int rc; THIS_STRING; fmt = cwal_string_cstr(self); fmtLen = cwal_string_length_bytes(self); oldUsed = ie->buffer.used; rc = cwal_buffer_format( args->engine, &ie->buffer, fmt, fmtLen, args->argc, args->argv); if(!rc){ cwal_size_t const n = ie->buffer.used - oldUsed; *rv = cwal_new_string_value(args->engine, n ? ((char const *)ie->buffer.mem+oldUsed) : NULL, n); rc = *rv ? 0 : CWAL_RC_OOM; } ie->buffer.used = oldUsed; return rc; } static int th1ish_cb_str_to_string( cwal_callback_args const * args, cwal_value **rv ){ cwal_string * str; ARGS_IE; assert(ie); str = cwal_value_string_part( args->engine, args->self ); if(!str){ return th1ish_toss(ie, CWAL_RC_TYPE, "FIXME: check for a different toString() impl in this case."); }else{ *rv = cwal_string_value(str); return 0; } } cwal_value * th1ish_prototype_string( th1ish_interp * ie ){ int rc = 0; cwal_value * proto; cwal_value * v; proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_STRING); if(proto) return proto; /* cwal_value * fv; */ assert(ie && ie->e); proto = cwal_new_object_value(ie->e); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_STRING, proto); if(rc) goto end; #if 0 cwal_value_prototype_set(proto, NULL) /* So that ("" instanceof object{}.prototype) fails. */; #endif th1ish_prototype_store(ie, "String", proto); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ if(rc) goto end FUNC2("applyFormat", th1ish_cb_str_apply_format); FUNC2("byteAt", th1ish_cb_str_byte_at); FUNC2("charAt", th1ish_cb_str_char_at); FUNC2("concat", th1ish_cb_str_concat); FUNC2("indexOf", th1ish_cb_str_indexof); FUNC2("lengthBytes", th1ish_cb_str_length); FUNC2("lengthUtf8", th1ish_cb_str_length_utf8); FUNC2("length", th1ish_cb_str_length_utf8); FUNC2("operator+", th1ish_cb_str_op_concat); FUNC2("split", th1ish_cb_str_split); FUNC2("substr", th1ish_cb_str_substr); FUNC2("toLower", th1ish_cb_str_tolower); FUNC2("toString", th1ish_cb_str_to_string); FUNC2("toUpper", th1ish_cb_str_toupper); FUNC2("trimLeft", th1ish_cb_str_trim_left); FUNC2("trimRight", th1ish_cb_str_trim_right); FUNC2("trim", th1ish_cb_str_trim_both); FUNC2("unescape", th1ish_cb_str_unescape_c); FUNC2("compare", th1ish_f_value_compare); #undef FUNC2 #undef CHECKV end: return rc ? NULL : proto; } #define THIS_BUFFER \ ARGS_IE; \ cwal_buffer * self = cwal_value_get_buffer(args->self); \ assert(ie); \ if(!self){ \ return cwal_exception_setf( args->engine, CWAL_RC_TYPE, \ "'this' is-not-a Buffer." ); \ } (void)0 /** Callback handler for buffer.length() (if isCapacity is false) and buffer.capacity (if isCapacity is true). */ static int th1ish_cb_buffer_length_uc( cwal_callback_args const * args, cwal_value **rv, char isCapacity){ THIS_BUFFER; if(args->argc>0){ /* SET length */ cwal_value * const index = args->argv[0]; cwal_int_t len = cwal_value_get_integer(index); int rc; if((len<0) || !cwal_value_is_number(index)){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "length argument must be " "non-negative integer."); } if(isCapacity || ((cwal_size_t)len > self->capacity)){ rc = cwal_buffer_reserve(args->engine, self, (cwal_size_t)len ); if(rc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "Setting buffer length to %"CWAL_INT_T_PFMT " failed with code %d (%s).", len, rc, cwal_rc_cstr(rc)); } } self->used = (cwal_size_t)len; assert(self->used <= self->capacity); *rv = args->self; }else{ *rv = cwal_new_integer( args->engine, (cwal_int_t) (isCapacity ? self->capacity : self->used) ); } return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_cb_buffer_length_u( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_buffer_length_uc( args, rv, 0 ); } static int th1ish_cb_buffer_length_c( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_cb_buffer_length_uc( args, rv, 1 ); } static int th1ish_cb_buffer_append( cwal_callback_args const * args, cwal_value **rv ){ uint16_t i; int rc = 0; THIS_BUFFER; for( i = 0; !rc && (i < args->argc); ++i ){ rc = th1ish_value_to_buffer(ie, self, args->argv[i]); } return rc; } static int th1ish_cb_buffer_appendf( cwal_callback_args const * args, cwal_value **rv ){ uint16_t i; int rc = 0; enum { AC = CWAL_OPT_MAX_FUNC_CALL_ARGS }; cwal_value * argv[AC]; cwal_size_t fmtlen; char const * fmt; cwal_size_t oldUsed; THIS_BUFFER; fmt = (args->argc>0) ? cwal_value_get_cstr(args->argv[0], &fmtlen) : NULL; if((!args->argc) || !fmt){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting (String,...) arguments."); } oldUsed = self->used; memset(argv, 0, sizeof(argv)); for( i = 1; !rc && (i < args->argc); ++i ){ argv[i-1] = args->argv[i]; } for( ; i <= (sizeof(argv)/sizeof(argv[0])); ++i){ argv[i-1] = NULL; } rc = cwal_buffer_format(args->engine, self, fmt, fmtlen, args->argc-1, argv); if(rc && (CWAL_RC_OOM != rc)){ if(self->used > oldUsed){ rc = th1ish_toss(ie, rc, "%s", (char const *)(self->mem + oldUsed)); self->used = oldUsed; }else{ rc = th1ish_toss(ie, rc, "String formatting failed for " "unknown reason!"); } }else{ cwal_size_t const newLen = self->used - oldUsed; *rv = cwal_new_integer(args->engine, (int)(newLen)); rc = *rv ? 0 : CWAL_RC_OOM; } return rc; } static int th1ish_cb_buffer_reset( cwal_callback_args const * args, cwal_value **rv ){ THIS_BUFFER; self->used = 0; memset(self->mem, 0, self->capacity); *rv = args->self; return 0; } static int th1ish_cb_buffer_to_string( cwal_callback_args const * args, cwal_value **rv ){ char const * begin = 0; char const * end = 0; THIS_BUFFER; if(args->argc){ cwal_int_t off, len; if(!cwal_value_is_number(args->argv[0])) goto misuse; off = cwal_value_get_integer(args->argv[0]); if(args->argc>1){ if(!cwal_value_is_number(args->argv[1])) goto misuse; len = cwal_value_get_integer(args->argv[1]); }else{ len = off; off = 0; } if(off<0 || len<0) goto misuse; begin = (char const *)self->mem + off; end = begin + len; if(end>((char const *)self->mem+self->used)){ end = (char const *)self->mem + self->used; } } else { begin = (char const *)self->mem; end = begin + self->used; } *rv = cwal_new_string_value(args->engine, (end-begin) ? begin : NULL, end - begin ); return *rv ? 0 : CWAL_RC_OOM; misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Buffer.toString() arguments must 0 or " "positive integers."); } static cwal_int_t th1ish_get_byte(cwal_value const *v){ char const * cstr = cwal_value_get_cstr(v, NULL); return cstr ? *cstr : cwal_value_get_integer(v); } int th1ish_cb_buffer_byte_at( cwal_callback_args const * args, cwal_value **rv ){ THIS_BUFFER; if(!args->argc || !cwal_value_is_number(args->argv[0])){ return cwal_exception_setf( args->engine, CWAL_RC_MISUSE, "Expecting one integer argument."); } else { cwal_value * const index = args->argv[0]; cwal_int_t pos = cwal_value_get_integer(index); if(pos<0){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "Byte position argument must be " "non-negative integer."); } if(args->argc<2){ *rv = (pos>=(cwal_int_t)self->used) ? cwal_value_undefined() : cwal_new_integer(args->engine, self->mem[pos]) ; return *rv ? 0 : CWAL_RC_OOM; }else{ int const rc = cwal_buffer_reserve( args->engine, self, (cwal_size_t)pos+1); if(rc) return rc; self->mem[pos] = 0xFF & th1ish_get_byte(args->argv[1]); *rv = cwal_value_true(); if(self->used <= (cwal_size_t)pos) self->used = (cwal_size_t)pos+1; return 0; } } } int th1ish_cb_buffer_file_read( cwal_callback_args const * args, cwal_value **rv ){ int rc; char const * fname; THIS_BUFFER; fname = args->argc ? cwal_string_cstr(cwal_value_get_string(args->argv[0])) : NULL; if(!fname){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a filename argument."); } rc = cwal_buffer_fill_from_filename( args->engine, self, fname ); if(rc){ assert(CWAL_RC_EXCEPTION!=rc); rc = th1ish_toss(ie, rc, "Reading file [%s] failed.", fname); } else { *rv = args->self; } return rc; } int th1ish_cb_buffer_file_write( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; char const * fname; cwal_size_t nameLen = 0; THIS_BUFFER; fname = args->argc ? cwal_value_get_cstr(args->argv[0], &nameLen) : NULL; if(!fname){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a filename argument."); } else{ FILE * f; char append = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0; f = fopen( fname, append ? "a" : "w" ); if(!f){ if(errno){ rc = th1ish_toss(ie, CWAL_RC_IO, "Could not open file [%.*s] " "in %s mode. " "errno=%d: %s", (int)nameLen, fname, append ? "append" : "truncate/write", errno, strerror(errno)); }else{ rc = th1ish_toss(ie, CWAL_RC_IO, "Could not open file [%.*s] " "in %s mode.", (int)nameLen, fname, append ? "append" : "truncate/write"); } }else{ if(1 != fwrite(self->mem, self->used, 1, f)){ rc = th1ish_toss(ie, CWAL_RC_IO, "Error writing %"CWAL_SIZE_T_PFMT" byte(s) " "to file [%.*s].", self->used, (int)nameLen, fname); } } fclose(f); return rc; } } /** Script usage: buffer.fill( Byte [offset=0 [,length=0]]) Byte may be an integer or string (in which case the first byte is used, even if it is a NUL byte). By default it fills the whole buffer with the byte, but the offset and length by be specified. It _will_not_ expand the buffer. If the offset/length are out of range this is a no-op. */ static int th1ish_cb_buffer_fill( cwal_callback_args const * args, cwal_value **rv ){ cwal_int_t byte; THIS_BUFFER; if(!args->argc){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a byte value argument."); } byte = 0xFF & th1ish_get_byte(args->argv[0]); if(1 == args->argc){ cwal_buffer_fill(self, (unsigned char)byte); *rv = cwal_new_integer(args->engine, (cwal_int_t)self->capacity); return 0; }else{ cwal_int_t offset; cwal_int_t len; offset = cwal_value_get_integer(args->argv[1]); len = (args->argc>2) ? cwal_value_get_integer(args->argv[2]) : 0; if((offset<0) || (len<0)){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Neither offset nor length may be negative."); } if(0==len){ len = ((cwal_int_t)self->capacity <= offset) ? 0 : ((cwal_int_t)self->capacity - offset); } if( (len>0) && ((cwal_size_t)offset < self->capacity) ) { if((size_t)(offset+len) > self->capacity) len = (cwal_int_t)self->capacity - offset; /*MARKER(("Filling %d byte(s) at offset %d to 0x%02x\n", (int)len, (int)offset, (int)byte));*/ memset( self->mem+offset, byte, (size_t)len ); } *rv = cwal_new_integer( args->engine, len ); return *rv ? 0 : CWAL_RC_OOM; } } #undef THIS_BUFFER static int th1ish_cb_buffer_new( cwal_callback_args const * args, cwal_value **rv ); cwal_value * th1ish_prototype_buffer( th1ish_interp * ie ){ int rc; cwal_value * proto; cwal_value * v; assert(ie && ie->e); proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_BUFFER); if(proto) return proto; proto = th1ish_new_function2(ie, th1ish_cb_buffer_new); if(!proto){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_BUFFER, proto); if(rc) goto end; th1ish_prototype_store(ie, "Buffer", proto); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_value_prototype_set(v, proto); \ assert(!rc); \ if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v ); \ assert(!rc); \ if(rc) goto end FUNC2("append", th1ish_cb_buffer_append); FUNC2("appendf", th1ish_cb_buffer_appendf); FUNC2("byteAt", th1ish_cb_buffer_byte_at); FUNC2("capacity", th1ish_cb_buffer_length_c); FUNC2("fill", th1ish_cb_buffer_fill); FUNC2("length", th1ish_cb_buffer_length_u); FUNC2("readFile", th1ish_cb_buffer_file_read); FUNC2("reserve", th1ish_cb_buffer_length_c); FUNC2("reset", th1ish_cb_buffer_reset); FUNC2("toString", th1ish_cb_buffer_to_string); FUNC2("writeToFile", th1ish_cb_buffer_file_write); #undef FUNC2 #undef CHECKV end: if(rc){ proto = 0 /* let ie->gc deal with it */; } return rc ? 0 : proto; } cwal_value * th1ish_new_buffer(th1ish_interp * ie, cwal_size_t initialSize){ cwal_buffer * buf; cwal_value * bp = th1ish_prototype_buffer( ie ); if(!bp){ cwal_exception_setf(ie->e, CWAL_RC_ERROR, "Buffer prototype initialization failed."); return NULL; } buf = cwal_new_buffer(ie->e, initialSize); return buf ? cwal_buffer_value(buf) : NULL; } int th1ish_cb_buffer_new( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = (th1ish_interp *)args->state; cwal_value * b; cwal_int_t len; assert(ie); if(cwal_value_buffer_part(ie->e, args->self)){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Do not call a buffer as a function."); } len = args->argc ? cwal_value_get_integer(args->argv[0]) : 0; if(len<0) len = 0; b = th1ish_new_buffer(ie, (cwal_size_t) len); if(!b) return CWAL_RC_OOM; else { *rv = b; return 0; } } static int th1ish_cb_str_length_bytes( cwal_callback_args const * args, cwal_value **rv ){ if(1 != args->argc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "'strlen' requires a string argument."); }else{ cwal_string * s = cwal_value_get_string(args->argv[0]); if(!s){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "'strlen' requires a string argument."); } *rv = cwal_new_integer(args->engine, (cwal_int_t) cwal_string_length_bytes(s) ); return 0; } } int th1ish_value_to_buffer( th1ish_interp *ie, cwal_buffer * buf, cwal_value * arg ){ int rc = 0; cwal_engine * e = ie->e; switch(cwal_value_type_id(arg)){ case CWAL_TYPE_STRING:{ cwal_string const * s = cwal_value_get_string(arg); rc = cwal_buffer_append( e, buf, cwal_string_cstr(s), cwal_string_length_bytes(s) ); break; } case CWAL_TYPE_BOOL: { char const b = cwal_value_get_bool(arg); rc = cwal_buffer_append( e, buf, b ? "true" : "false", b ? 4 : 5); break; } case CWAL_TYPE_UNDEF: rc = cwal_buffer_append( e, buf, "undefined", 9); break; case CWAL_TYPE_NULL: rc = cwal_buffer_append( e, buf, "null", 4); break; case CWAL_TYPE_INTEGER: rc = cwal_buffer_printf( e, buf, "%"CWAL_INT_T_PFMT, cwal_value_get_integer(arg)); break; case CWAL_TYPE_DOUBLE: rc = cwal_buffer_printf( e, buf, "%"CWAL_DOUBLE_T_PFMT, cwal_value_get_double(arg)); if(!rc){ /* Trim trailing zeroes... */ unsigned char * pos = buf->mem + buf->used - 1; while('0' == *pos) { *pos = 0; --pos; --buf->used; } } break; case CWAL_TYPE_BUFFER:{ cwal_buffer * vb = cwal_value_get_buffer(arg); assert(vb); rc = vb->used ? cwal_buffer_append( e, buf, vb->mem, vb->used ) : 0; } case CWAL_TYPE_OBJECT: case CWAL_TYPE_ARRAY:{ cwal_output_buffer_state job = cwal_output_buffer_state_empty; job.e = ie->e; job.b = buf; rc = cwal_json_output( arg, cwal_output_f_buffer, &job, NULL ); break; } default: rc = cwal_exception_setf( e, CWAL_RC_TYPE, "Don't know how to to-string arguments " "of type '%s'.", cwal_value_type_name(arg)); break; } return rc; } #if 0 /** Huge kludge: push a new scope onto the stack, set the 'this' to self for that scope, calls f, and pops the scope. Returns 0 on success. */ static int th1ish_function_call_this_kludge( th1ish_interp *ie, cwal_function * f, cwal_value * self, cwal_value ** rv, uint16_t argc, cwal_value **argv ){ int rc; #if 1 cwal_scope SC = cwal_scope_empty; cwal_scope * sc = &SC; cwal_scope * oldScope = cwal_scope_current_get(ie->e); assert(self); rc = cwal_scope_push(ie->e, &sc); if(rc) return rc; cwal_scope_chain_set( sc, 0, "this", 4, self ); rc = cwal_function_call_in_scope( sc, f, self, rv, argc, argv ); if(*rv){ cwal_value_rescope( oldScope, *rv ); } cwal_scope_pop(ie->e); #else cwal_value * oldThis = 0; assert(self); th1ish_get(ie, NULL, "this", 4, &oldThis); cwal_dump_v(self,"'this' for toString call"); th1ish_set( ie, NULL, NULL, "this", 4, /* HUGE KLUDGE! */ self ); rc = cwal_function_call( f, self, rv, argc, argv ); th1ish_set( ie, NULL, NULL, "this", 4, /* HUGE KLUDGE! */ oldThis ); #endif return rc; } #endif static char th1ish_value_try_toString(th1ish_interp *ie, cwal_value * arg, cwal_value **rv, int * rc ){ cwal_value * fv = 0; cwal_function * f = 0; cwal_value * origRv; char result = 0; start: origRv = *rv; *rc = th1ish_get( ie, arg, "toString", 8, &fv ); if(*rc) return *rc; f = fv ? cwal_value_get_function(fv) : NULL; if(!f){ *rc = 0; return result; } #if 0 *rc = th1ish_function_call_this_kludge(ie, f, arg, rv, 0, NULL); #else *rc = cwal_function_call(f, arg, rv, 0, NULL); #endif result = 1; #if 1 if(!*rc && (origRv!=*rv) && !cwal_value_is_string(*rv)){ arg = *rv; goto start; } #endif return result; } int th1ish_value_to_string( th1ish_interp *ie, cwal_value * arg, cwal_value **rv ){ int rc = 0; cwal_value * v = 0; cwal_buffer buf = cwal_buffer_empty; cwal_engine * e = ie->e; switch(cwal_value_type_id(arg)){ case CWAL_TYPE_STRING: v = arg; break; default: if(!th1ish_value_try_toString(ie, arg, &v, &rc)){ rc = th1ish_value_to_buffer(ie, &buf, arg); }else if(!rc){ if(!v) v = cwal_value_undefined(); if(!cwal_value_is_string(v)){ rc = th1ish_value_to_buffer(ie, &buf, v); v = 0; } } break; } if(!rc && !v){ v = cwal_new_string_value(e, buf.used ? (char const *)buf.mem : NULL, buf.used); if(!v) rc = CWAL_RC_OOM; } if(!rc){ assert(v); *rv = v; assert(cwal_value_type_id(*rv) == CWAL_TYPE_STRING); } cwal_buffer_reserve(ie->e, &buf, 0); return rc; } int th1ish_cb_str_tostring( cwal_callback_args const * args, cwal_value **rv ){ if(1 != args->argc){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "'toString' requires one argument."); }else{ int rc = 0; cwal_value * v = 0; th1ish_interp * ie = (th1ish_interp *)args->state; rc = th1ish_value_to_string( ie, args->argv[0], &v ); if(!rc){ assert(v); *rv = v; assert(cwal_value_type_id(*rv) == CWAL_TYPE_STRING); } return rc; } } /** */ int th1ish_setup_strings( th1ish_interp * ie, cwal_value * ns ){ int rc; cwal_value * v; cwal_value * outer; if(!cwal_props_can(ns)) return CWAL_RC_MISUSE; outer = th1ish_new_object(ie); rc = cwal_prop_set( ns, "string", 6, outer ); if(rc) goto end; #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define SET(NAME) \ CHECKV; \ rc = cwal_prop_set( outer, NAME, strlen(NAME), v ); \ if(rc) goto end #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ SET(NAME) FUNC2("concat", th1ish_cb_str_concat); FUNC2("length", th1ish_cb_str_length_bytes); FUNC2("lengthUtf8", th1ish_cb_str_length_utf8); FUNC2("toJSONString", th1ish_f_this_to_json_token); FUNC2("toLower", th1ish_cb_str_tolower); FUNC2("toString", th1ish_cb_str_tostring); FUNC2("toUpper", th1ish_cb_str_toupper); FUNC2("Buffer", th1ish_cb_buffer_new); /* Also add Buffer to the api namespace ... */ rc = cwal_prop_set( ns, "Buffer", 6, v ); if(rc) goto end; #undef SET #undef FUNC2 #undef CHECKV end: return rc; } #undef MARKER #undef ARGS_IE /* end of file th1ish_str.c */ /* start of file cwal_cish_ttypes.h */ /* Automatically generated. Edit at your own risk. License: Public Domain. */ #ifndef WANDERINGHORSE_NET_CWAL_TOKER_TYPES_H_INCLUDED #define WANDERINGHORSE_NET_CWAL_TOKER_TYPES_H_INCLUDED 1 #ifdef __cplusplus extern "C" { #endif enum cwal_cish_token_types { TT_UNDEF= -1, /* Ideally a token's initial type. */ TT_EOF= 0, /* NULL char or end of source */ TT_Bell= 7, /* \\a */ TT_Backspace= 8, /* \\b */ TT_HTab= 9, /* \\t */ TT_NL= 10, /* \\n */ TT_VTab= 11, /* \\v */ TT_FormFeed= 12, /* \\f */ TT_CR= 13, /* \\r */ TT_ChSpace= 32, TT_ChExcl= 33, TT_ChQuote= 34, TT_ChHash= 35, TT_ChDollar= 36, TT_ChPercent= 37, TT_ChAmp= 38, TT_ChApos= 39, TT_ChParenOpen= 40, TT_ChParenClose= 41, TT_ChAsterisk= 42, TT_ChPlus= 43, TT_ChComma= 44, TT_ChMinus= 45, TT_ChPeriod= 46, TT_ChFSlash= 47, TT_Ch0= 48, TT_Ch1= 49, TT_Ch2= 50, TT_Ch3= 51, TT_Ch4= 52, TT_Ch5= 53, TT_Ch6= 54, TT_Ch7= 55, TT_Ch8= 56, TT_Ch9= 57, TT_ChColon= 58, TT_ChSemicolon= 59, TT_ChLt= 60, TT_ChEq= 61, TT_ChGt= 62, TT_ChQuestion= 63, TT_ChAt= 64, TT_ChA= 65, TT_ChB= 66, TT_ChC= 67, TT_ChD= 68, TT_ChE= 69, TT_ChF= 70, TT_ChN= 78, TT_ChO= 79, TT_ChP= 80, TT_ChQ= 81, TT_ChR= 82, TT_ChS= 83, TT_ChT= 84, TT_ChU= 85, TT_ChV= 86, TT_ChW= 87, TT_ChX= 88, TT_ChY= 89, TT_ChZ= 90, TT_ChBraceOpen= 91, TT_ChBSlash= 92, TT_ChBraceClose= 93, /* it is curious that ] is not directly after [ in the ASCII chart */ TT_ChCaret= 94, TT_ChUnderscore= 95, TT_ChBacktick= 96, TT_Cha= 97, TT_Chb= 98, TT_Chc= 99, TT_Chd= 100, TT_Che= 101, TT_Chf= 102, TT_Chg= 103, TT_Chh= 104, TT_Chi= 105, TT_Chj= 106, TT_Chk= 107, TT_Chl= 108, TT_Chm= 109, TT_Chn= 110, TT_Cho= 111, TT_Chp= 112, TT_Chq= 113, TT_Chr= 114, TT_Chs= 115, TT_Cht= 116, TT_Chu= 117, TT_Chv= 118, TT_Chw= 119, TT_Chx= 120, TT_Chy= 121, TT_Chz= 122, TT_ChScopeOpen= 123, TT_ChPipe= 124, TT_ChScopeClose= 125, TT_ChTilde= 126, TT_Literal= 3000, /* some literal constant type: int, double, string */ TT_LiteralIntDec= 3001, /* a decimal int number */ TT_LiteralDouble= 3002, /* a double number */ TT_LiteralStringDQ= 3003, /* a double-quoted string */ TT_LiteralStringSQ= 3004, /* a single-quoted string */ TT_LiteralChar= 3005, TT_LiteralIntHex= 3006, /* a hexadecimal number */ TT_LiteralIntOctal= 3007, /* an octal number */ TT_Whitespace= 3030, /* Runs of whitespace. */ TT_CppComment= 3031, TT_CComment= 3032, TT_EOL= 3033, /* intended for use in generically replacing TT_NL and TT_CR. */ TT_CRNL= 3034, /* Windows-style newline. */ TT_Blank= 3035, /* Runs of blanks, not including TT_NL/TT_CR. */ TT_CommentHash= 3036, /* hash-style comment */ TT_LiteralObjOpen= 3100, /* {{ */ TT_LiteralObjClose= 3101, /* }} */ TT_LiteralArrayOpen= TT_ChBraceOpen, TT_LiteralArrayClose= TT_ChBraceClose, TT_OpMinusEq= 4000, /* -= */ TT_OpDecr= 4001, /* -- */ TT_OpPlusEq= 4002, /* += */ TT_OpIncr= 4003, /* ++ */ TT_OpLogNot= TT_ChExcl, /* ! */ TT_OpLogAnd= 4004, /* && */ TT_OpLogOr= 4005, /* || */ TT_OpShiftLeft= 4006, /* << */ TT_OpShiftRight= 4007, /* >> */ TT_OpMultEq= 4008, /* *= */ TT_OpDivEq= 4009, /* /= */ TT_OpShiftLeftEq= 4010, /* <<= */ TT_OpShiftRightEq= 4011, /* >>= */ TT_OpTildeEq= 4012, /* ~= */ TT_OpAndEq= 4013, /* &= */ TT_OpOrEq= 4014, /* |= */ TT_OpModEq= 4015, /* %= */ TT_OpHeredocStart= 4016, /* <<< */ TT_OpEqHeredocStart= 4017, /* =<<< */ TT_OpXorEq= 4018, /* =^ */ TT_OpArrow= 4019, /* -> */ TT_OpNamespace= 4020, /* :: */ TT_OpMultiply= TT_ChAsterisk, TT_OpDivide= TT_ChFSlash, TT_OpModulo= TT_ChPercent, TT_OpAdd= TT_ChPlus, TT_OpSubtract= TT_ChMinus, TT_OpAssign= TT_ChEq, /* = */ TT_CmpEq= 4100, /* == */ TT_CmpGe= 4101, /* >= */ TT_CmpGt= TT_ChGt, /* > */ TT_CmpLe= 4102, /* <= */ TT_CmpLt= TT_ChLt, /* < */ TT_CmpNe= 4103, /* != */ TT_CmpEqStrict= 4104, /* === */ TT_CmpNeStrict= 4105, /* !== */ TT_Identifier= 5000, /* function name, keyword, typename or var name */ TT_IdFuncName= 5001, /* name of a script-callable function */ TT_IdNamespaceName= 5002, /* name of a namespace or scope */ TT_IdVarName= 5003, /* name of a variable */ TT_IdOstream= 5004, /* name of a built-in ostream, e.g. cout or cerr */ TT_IdIstream= 5005, /* name of a built-in istream, e.g. cin */ TT_IdEllipsis= 5006, /* '...' TODO: re-implement this (was removed during C++-to-C port) */ TT_Keyword= 5100, TT_KeywordCase= 5101, TT_KeywordChar= 5102, TT_KeywordDo= 5103, TT_KeywordDouble= 5104, TT_KeywordElse= 5105, TT_KeywordBreak= 5106, /* break */ TT_KeywordExit= 5107, /* exit */ TT_KeywordFor= 5108, TT_KeywordIf= 5109, TT_KeywordInt= 5110, TT_KeywordNamespace= 5111, TT_KeywordContinue= 5112, /* continue */ TT_KeywordReturn= 5113, /* return */ TT_KeywordString= 5114, TT_KeywordSwitch= 5115, TT_KeywordVariant= 5116, TT_KeywordWhile= 5117, TT_KeywordCatch= 5118, TT_KeywordThrow= 5119, TT_KeywordTry= 5120, TT_KeywordAssert= 5121, TT_KeywordCast= 5122, TT_KeywordConst= 5123, TT_KeywordUndef= 5124, TT_KeywordIsA= 5125, TT_KeywordForEach= 5126, /* foreach */ TT_KeywordIn= 5127, /* in */ TT_KeywordTypeof= 5128, /* typeof */ TT_KeywordTypename= 5129, /* typename */ TT_KeywordRefcount= 5130, /* Internal ref-count of values. */ TT_KeywordUnref= 5131, /* "unref" keyword */ TT_KeywordUnset= 5132, /* Unsets a var declared in the current scope. */ TT_KeywordSweep= 5133, TT_KeywordClosure= 5134, TT_KeywordEval= 5135, /* eval */ TT_KeywordScope= 5136, /* scope */ TT_KeywordSet= 5137, /* set */ TT_KeywordGet= 5138, /* get */ TT_KeywordNull= 5139, /* null */ TT_KeywordTrue= 5140, /* true */ TT_KeywordFalse= 5141, /* false */ TT_KeywordFunction= 5142, /* "function", "proc", etc. */ TT_KeywordArray= 5143, /* "array" */ TT_KeywordObject= 5144, /* "object" */ TT_KeywordUse= 5145, /* use */ TT_KeywordPrototype= 5146, /* prototype */ TT_KeywordNameof= 5147, /* nameof */ TT_Keyword__FILE= 5148, /* __FILE */ TT_Keyword__LINE= 5149, /* __LINE */ TT_Keyword__COLUMN= 5150, /* __COLUMN */ TT_KeywordFatal= 5151, /* fatal */ TT_Keyword__BREAKPOINT= 5152, /* __BREAKPOINT */ TT_Keyword__SRC= 5153, /* __SRC */ TT_Keyword__SCOPE= 5154, /* __SCOPE */ TT_KeywordToss= 5155, /* toss */ TT_KeywordInstanceOf= 5156, /* instanceof */ TT_KeywordVarDecl= 5200, /* var */ TT_KeywordVarDeclConst= 5201, /* const */ TT_ExpandoEntry= 5300, /* @array expansion entries */ TT_ElemOpen= 7000, TT_ElemClose= 7001, TT_ElemName= 7002, TT_ElemValue= 7003, TT_AttrName= 7004, TT_AttrValue= 7005, TT_HeredocBody= 7100, /* Contents of a heredoc */ TT_Undef= 9000, /* Used by new Toekins */ TT_TokErr= 9001, /* Used to tag toekinization errors */ TT_VEND= 9002, /* Used as a list terminator for ... funcs. */ cwal_cish_token_types_AUTOMATION_KLUDGE }; /*enum cwal_cish_token_types*/ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* WANDERINGHORSE_NET_CWAL_TOKER_TYPES_H_INCLUDED */ /* end of file cwal_cish_ttypes.h */ /* start of file cwal_t10n.c */ #include #include /* strtol() and friends. */ #include /* memcpy() */ #if 1 #include #define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf #else #define MARKER if(0) printf #endif #if defined(__cplusplus) extern "C" { #endif const cwal_simple_tokenizer cwal_simple_tokenizer_empty = cwal_simple_tokenizer_empty_m; const cwal_simple_token cwal_simple_token_empty = cwal_simple_token_empty_m; const cwal_st_src cwal_st_src_empty = cwal_st_src_empty_m; char cwal_is_whitespace( int ch ){ switch(ch){ case ' ': case '\n': case '\r': case '\t': case '\v': case '\f': return 1; default: return 0; } } char cwal_is_blank( int ch ){ switch(ch){ case ' ': case '\t': case '\v': case '\f': return 1; default: return 0; } } char cwal_is_digit( int ch ){ return '0'<=ch && '9'>=ch; } char cwal_is_xdigit( int ch ){ return ('a'<=ch && 'f'>=ch) ? 1 : (('A'<=ch && 'F'>=ch) ? 1 :cwal_is_digit(ch)); } char cwal_is_alpha( int ch ){ return ('a'<=ch && 'z'>=ch) ? 1 : ('A'<=ch && 'Z'>=ch); } char cwal_is_alnum( int ch ){ return cwal_is_alpha(ch) ? 1 : cwal_is_digit(ch); } char cwal_is_identifier_char( int ch, char isStart ) { switch(ch){ case '_': /* case '@': */ return 1; default: return isStart ? cwal_is_alpha(ch) : cwal_is_alnum(ch); } } cwal_simple_token * cwal_st_skip_ws( cwal_simple_token * t ){ for( ; t && (TT_Blank==t->ttype || TT_Whitespace==t->ttype || cwal_is_whitespace(t->ttype)); t = t->right ){ } return t; } cwal_simple_token * cwal_st_skip_blank( cwal_simple_token * t ){ for( ; t && (TT_Blank==t->ttype || cwal_is_blank(t->ttype)); t = t->right ){ } return t; } char cwal_st_is_noise( cwal_simple_token const * t ){ switch(t->ttype){ case ' ': case '\t': case '\v': case '\f': /*??? case TT_Whitespace:*/ case TT_Blank: case TT_CComment: case TT_CppComment: return 1; default: return 0; } } cwal_simple_token * cwal_st_skip_noise( cwal_simple_token * t ){ for( ; t && !cwal_st_eof(t); ){ if( cwal_st_is_noise(t) ) t = t->right; else break; } return t; } char cwal_st_eof( cwal_simple_token const * t ){ return !t || (TT_EOF==t->ttype); } int cwal_cish_next_token( cwal_simple_tokenizer * t ){ int rc = 0; char const * curpos = t->current.end ? t->current.end : t->begin; t->errMsg = 0; #define BUMP(X) curpos+=(X); #define RETURN_ERR(RC,MSG) t->ttype = TT_TokErr; t->errMsg = MSG; return (RC) #define NEXT_LINE (void)0 t->ttype = TT_UNDEF; t->current.begin = curpos; /* FIXME: convert this to switch(*curpos){}, and move only those which handle long ranges and such to if/else. */ switch(*curpos){ case 0: t->ttype = TT_EOF; /*This is a necessary exception to the bump-on-consume rule.*/ break; case '\r': if('\n'==*curpos){ t->ttype = TT_CRNL; BUMP(2); NEXT_LINE; } else{ t->ttype = TT_CR; BUMP(1); } break; case '\n': t->ttype = TT_NL; BUMP(1); NEXT_LINE; break; case ' ': case '\t': case '\v': case '\f': t->ttype = *curpos /*TT_Blank*/; BUMP(1); #if 1 /* TODO once i figure out if we should handle EOL and EOL/blank runs consistently. */ while( *curpos && cwal_is_blank(*curpos) ){ t->ttype = TT_Blank; BUMP(1); } #endif break; case ':': /* colon or namespace */ if( ':' == *(curpos+1) ){ t->ttype = TT_OpNamespace; BUMP(2); }else{ t->ttype = TT_ChColon; BUMP(1); } break; case '~': /* ~ or ~= */ t->ttype = *curpos; BUMP(1); #if 0 if('=' == *curpos){ t->ttype = TT_OpInverseEq; BUMP(1); } #endif break; case '/': /* numeric division or C-style comment block */ t->ttype = *curpos; BUMP(1); if('=' == *curpos){ t->ttype = TT_OpDivEq; BUMP(1); } else if( '*' == *curpos) /* C-style comment block */{ t->ttype = TT_CComment; BUMP(1); do{ while( *curpos && ('*' != *curpos) ){ if('\n'==*curpos){ NEXT_LINE; } BUMP(1); } if( ! *curpos ) break; BUMP(1); /* skip '*' */ } while( *curpos && ('/' != *curpos)); if( *curpos != '/' ){ RETURN_ERR(CWAL_RC_RANGE,"End of C++-style comment not found."); } BUMP(1); /* get that last slash */ } else if( '/' == *curpos ) /* C++-style comment line */{ BUMP(1); t->ttype = TT_CppComment; while( *curpos && ('\n' != *curpos) ){ BUMP(1); } } break; case '"': case '\'': /* read string literal */{ char const quote = *curpos; t->ttype = ('"' == *curpos) ? TT_LiteralStringDQ : TT_LiteralStringSQ; BUMP(1)/*leading quote*/; while(*curpos && (*curpos != quote)){ if( (*curpos == '\\') ){ /* consider next char to be escaped, but keep escape char */ BUMP(1); if(*curpos == 0){ RETURN_ERR(CWAL_RC_RANGE, "Unexpected EOF while toekinizing backslash-escaped char in string literal."); } BUMP(1); continue; } else if('\n'==*curpos){ BUMP(1); NEXT_LINE; } else { BUMP(1); } } if(*curpos != quote){ RETURN_ERR(CWAL_RC_RANGE, "Unexpected end of string literal."); } BUMP(1)/*trailing quote*/; break; } /* end literal string */ case '&': /* & or && */ t->ttype = TT_ChAmp; BUMP(1); if( '&' == *curpos ){ t->ttype = TT_OpLogAnd; BUMP(1); } else if( '=' == *curpos ){ t->ttype = TT_OpAndEq; BUMP(1); } break; case '|': /* | or || */ t->ttype = TT_ChPipe; BUMP(1); if( '|' == *curpos ){ t->ttype = TT_OpLogOr; BUMP(1); } else if( '=' == *curpos ){ t->ttype = TT_OpOrEq; BUMP(1); } break; case '+': /* + or ++ or += */{ t->ttype = TT_ChPlus; BUMP(1); switch(*curpos){ case '+': /* ++ */ t->ttype = TT_OpIncr; BUMP(1); break; case '=': /* += */ t->ttype = TT_OpPlusEq; BUMP(1); break; default: break; } break; } case '*': /* multiply operator */ t->ttype = TT_ChAsterisk; BUMP(1); if('=' == *curpos){ t->ttype = TT_OpMultEq; BUMP(1); } break; case '-': /* - or -- or -= */{ t->ttype = TT_ChMinus; BUMP(1); switch( *curpos ){ case '-': /* -- */ t->ttype = TT_OpDecr; BUMP(1); break; case '=': t->ttype = TT_OpMinusEq; BUMP(1); break; case '>': t->ttype = TT_OpArrow; BUMP(1); break; default: break; } break; } case '<': /* LT or << or <= or <<= or <<< */{ t->ttype = TT_CmpLt; BUMP(1); switch( *curpos ){ case '<': /* tok= << */ { t->ttype = TT_OpShiftLeft; BUMP(1); switch(*curpos){ case '=': /* <<= */ t->ttype = TT_OpShiftLeftEq; BUMP(1); break; #if 0 case '<': /* <<< */ t->ttype = TT_OpHeredocStart; BUMP(1); break; #endif default: break; } break; } case '=': /* tok= <= */ t->ttype = TT_CmpLe; BUMP(1); break; default: break; } break; } case '>': /* GT or >> or >= */{ t->ttype = TT_CmpGt; BUMP(1); switch(*curpos){ case '>': /* tok= >> */ t->ttype = TT_OpShiftRight; BUMP(1); if( '=' == *curpos ){/* >>= */ t->ttype = TT_OpShiftRightEq; BUMP(1); } break; case '=': /* tok= >= */ t->ttype = TT_CmpGe; BUMP(1); break; default: break; } break; } case '=': /* = or == or === */ t->ttype = TT_ChEq; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpEq; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpEqStrict; BUMP(1); } } break; case '%': /* % or %= */ t->ttype = *curpos; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_OpModEq; BUMP(1); } break; case '!': /* ! or != or !== */ t->ttype = TT_ChExcl; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpNe; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpNeStrict; BUMP(1); } } break; case '{': /* { or {{ */ t->ttype = TT_ChScopeOpen; BUMP(1); #if 0 if('{' == *curpos){ t->ttype = TT_LiteralObjOpen; BUMP(1); } #endif break; case '}': /* } or }} */ t->ttype = TT_ChScopeClose; BUMP(1); #if 0 if('}' == *curpos){ t->ttype = TT_LiteralObjClose; BUMP(1); } #endif break; case '0': /* hex or octal */ BUMP(1); if( (*curpos == 'x') || (*curpos == 'X') ){ /** hex digit. Convert to decimal to save pain later. */ /* TODO: reimplement to use strtol(). */ BUMP(1); while( *curpos && cwal_is_xdigit(*curpos) ){ BUMP(1); } t->ttype = TT_LiteralIntHex; if( *curpos && ( cwal_is_alnum(*curpos) || (*curpos == '_')) ){ BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed hexidecimal int constant."); } } else if((*curpos>='0') && (*curpos<='7')){ /* try octal... */ BUMP(1); while( *curpos && (*curpos>='0') && (*curpos<='7') ){ BUMP(1); } t->ttype = TT_LiteralIntOctal; if( *curpos && ( cwal_is_alnum(*curpos) || (*curpos == '_')) ){ BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed octal int constant."); } } else if( *curpos && ( cwal_is_alnum(*curpos) || (*curpos == '_')) ) { /* reject 12334x where x is alphanum or _ */ BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed numeric constant starts " "with '0' but is neither octal nor hex.."); } else if('.'==*curpos){ BUMP(1); while( cwal_is_digit(*curpos) ){ BUMP(1); } if('.'==*(curpos-1)){ RETURN_ERR(CWAL_SCR_UNEXPECTED_TOKEN, "Mis-terminated floating point value."); } t->ttype = TT_LiteralDouble; } else { t->ttype = TT_LiteralIntDec; /* A literal 0. This is okay. */ } break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* integer or double literal. */ while( *curpos && cwal_is_digit(*curpos) ){ /* integer or first part of a double. */ BUMP(1); } if( ('.' == *curpos) && (cwal_is_digit(*(curpos+1))) ){ /* double number */ t->ttype = TT_LiteralDouble; BUMP(1); while( *curpos && cwal_is_digit(*curpos) ){ BUMP(1); } } else { t->ttype = TT_LiteralIntDec; } if( *curpos && (cwal_is_alnum(*curpos) || (*curpos == '_'))) { BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed numeric constant."); } break; }/* end of switch(*curpos) */ /** TODO: UTF8 multibyte. The necessary code is in cwal_json.c. */ if(0==rc && (curpos == t->current.begin) && (TT_EOF != t->ttype) ){ /* keep trying... */ if( cwal_is_identifier_char(*curpos,1) ) /* identifier string */{ BUMP(1); while( cwal_is_identifier_char(*curpos,0) ){ BUMP(1); } t->ttype = TT_Identifier; } else if( (*curpos >= 32) && (*curpos < 127) ) { t->ttype = *curpos; BUMP(1); } else { assert(curpos == t->current.begin); assert(TT_UNDEF==t->ttype); } } t->current.end = curpos; if(TT_UNDEF == t->ttype){ t->ttype = TT_TokErr; t->errMsg = "Don't know how to tokenize this."; rc = CWAL_RC_ERROR; } #undef BUMP #undef RETURN_ERR #undef NEXT_LINE return rc; } cwal_simple_token * cwal_st_alloc( cwal_engine * e, cwal_simple_token * left ){ cwal_simple_token * t = (cwal_simple_token*)cwal_malloc(e,sizeof(cwal_simple_token)); if(t) *t = cwal_simple_token_empty; if(left){ assert(0==left->right); cwal_st_insert_after( left, t ); } return t; } void cwal_st_free( cwal_engine * e, cwal_simple_token * t ){ assert(0 == t->right); /*??? if(t->value) cwal_value_unref(t->value);*/ *t = cwal_simple_token_empty; cwal_free( e, t ); } void cwal_st_snip( cwal_simple_token * t ){ t->right = 0; } void cwal_st_insert_after( cwal_simple_token * after, cwal_simple_token * t ){ cwal_st_snip( t ); if( after->right ){ t->right = after->right; } after->right = t; } cwal_size_t cwal_st_count( cwal_simple_token const * t ){ cwal_size_t i = 0; for( ; t; t = t->right ){ ++i; } return i; } void cwal_st_free_chain( cwal_engine * e, cwal_simple_token * head ){ if(!e || !head) return; else { cwal_simple_token * t, * next; for( t = head; t; t = next ){ next = t->right; t->right = 0; cwal_st_free( e, t ); } } } char cwal_st_predicate_ttype( cwal_simple_token const * t, void * state ){ return t->ttype == *(int*)state; } void cwal_st_snip_matching( cwal_simple_token ** head, cwal_simple_token ** snippedHead, cwal_st_predicate_f pred, void * predState ){ cwal_simple_token * tail = 0; cwal_simple_token * next; cwal_simple_token * t = *head; cwal_simple_token * snipped = 0; for( ; t && TT_EOF!=t->ttype; t = next ){ next = t->right; if( pred( t, predState ) ){ cwal_st_snip(t); if(!snipped) snipped = t; if(tail) cwal_st_insert_after( tail, t ); tail = t; if(*head == t) *head = next; } } *snippedHead = snipped; } void cwal_st_snip_ttype( cwal_simple_token ** head, cwal_simple_token ** snippedHead, int ttype ){ cwal_st_snip_matching( head, snippedHead, cwal_st_predicate_ttype, &ttype ); } void cwal_st_snip_junk( cwal_engine * e, cwal_simple_token ** head ){ cwal_simple_token * cut = 0; #define SNIP(T) \ cwal_st_snip_ttype( head, &cut, T); \ if(cut) {cwal_st_free_chain(e,cut); cut = 0; } \ if(!*head) return SNIP(TT_CComment); SNIP(TT_CppComment); /*SNIP(TT_Blank);*/ #undef SNIP } char cwal_st_isa( cwal_simple_token const * t, int type ){ return t ? ((t->ttype == type) ? 1 : 0) : ((type==TT_EOF) ? 1 : 0); } cwal_size_t cwal_st_tag_functions( cwal_simple_token * head ){ cwal_size_t count = 0; cwal_simple_token * n; for( ; head; head = n ){ n = cwal_st_skip_noise(head->right); if( !cwal_st_isa(head,TT_Identifier) ) continue; if( cwal_st_isa(n,TT_ChParenOpen) ){ head->ttype = TT_IdFuncName; head = head->right /* skip this one */; } } return count; } char cwal_st_is_literal( cwal_simple_token const * tok ){ switch( tok->ttype ){ case TT_LiteralIntDec: case TT_LiteralIntOctal: case TT_LiteralIntHex: case TT_LiteralStringSQ: case TT_LiteralStringDQ: case TT_LiteralDouble: return 1; default: return 0; }; } cwal_simple_token * cwal_st_find_matching_brace( cwal_simple_token * t ) { if( ! t ) return 0; else{ int matcher = 0; int bcount = 0; int const brace = t->ttype; switch(brace) { case TT_ChParenOpen: matcher = TT_ChParenClose; break; case TT_ChBraceOpen: matcher = TT_ChBraceClose; break; case TT_ChScopeOpen: matcher = TT_ChScopeClose; break; case TT_ChQuestion: matcher = TT_ChColon; break; case TT_ChLt: matcher = TT_ChGt; break; default: break; }; if( ! matcher ) return NULL; while( t ){ if( brace == t->ttype ) ++bcount; else if( matcher == t->ttype ) --bcount; if( 0 == bcount ) { /* found a match */ return t; } t = t->right; } return NULL; } } #if 0 int cwal_st_process_heredoc( cwal_engine * e, cwal_simple_token * t, cwal_simple_token ** next, char createValues, cwal_buffer * escapeBuf ){ if((t->ttype != TT_OpHeredocStart) && (t->ttype != TT_OpEqHeredocStart)) return CWAL_RC_TYPE; else { } } #endif static char cwal_hexbyte( int ch ){ if(ch>='0' && ch<='9') return ch - '0'; else if(ch>='a' && ch<='f') return ch - 'a' + 10; else if(ch>='A' && ch<='F') return ch - 'A' + 10; else return -1; } static unsigned char cwal_read_hex_bytes(unsigned char const *zPos, unsigned int * rv ){ unsigned int rc = 0; char ch1, ch2; unsigned char n = 0; assert(zPos && rv); while(n<8){ ch1 = *zPos ? cwal_hexbyte(*zPos++) : -1; ch2 = (ch1>=0) ? cwal_hexbyte(*zPos++) : -1; if(ch2<0) break; rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2); n += 2; } *rv = rc; return n; } int cwal_st_unescape_string( cwal_engine * e, unsigned char const * begin, unsigned char const * end, cwal_buffer * dest ){ cwal_size_t sz; unsigned char const * p = begin; unsigned char * out; int check; cwal_size_t oldUsed; unsigned char uCount; unsigned int uChar; if(!e || !begin || !end || !dest) return CWAL_RC_MISUSE; else if(endused; sz = end - begin + 1 /*NUL*/; check = cwal_buffer_reserve( e, dest, sz + oldUsed ); if(check) return check; out = dest->mem + oldUsed; for( ; p < end; ++p ){ switch(*p){ case '\\': switch(*++p){ case 't': *(out++) = '\t'; break; case 'n': *(out++) = '\n'; break; case 'u': uCount = cwal_read_hex_bytes(p+1, &uChar); if(!uCount) return CWAL_RC_RANGE; assert(0 == (uCount % 2)); check = cwal_utf8_char_to_cstr(uChar, out, uCount); if(check<0) return CWAL_RC_RANGE /* Invalid UTF */ ; out += check; p += uCount; break; default: *(out++) = *p; break; } break; default: *(out++) = *p; break; } } assert( (out - dest->mem + oldUsed) >= 0 ); *out = 0; dest->used = out - dest->mem; return 0; } int cwal_st_from_tokenizer( cwal_engine * e, cwal_simple_tokenizer * t, cwal_simple_token ** tgt, char createValues, cwal_buffer * escapeBuf ){ cwal_simple_token * rc = 0; cwal_simple_token * left = *tgt; rc = cwal_st_alloc(e, left); if(!rc) return CWAL_RC_OOM; else { rc->ttype = t->ttype; rc->src = t->current; *tgt = rc; return 0; } } int cwal_simple_tokenizer_init( cwal_simple_tokenizer * t, char const * src, cwal_size_t len ){ if(!t||!src) return CWAL_RC_MISUSE; else{ if(!len) len = strlen(src); t->begin = src; t->end = src+len; t->errMsg = t->current.begin = t->current.end = 0; t->ttype = TT_UNDEF; return 0; } } cwal_simple_token * cwal_st_skip( cwal_simple_token * t ){ char cont = 1; for( ; cont && t; ){ switch(t->ttype){ case ' ': case '\t': case '\v': case '\f': case TT_Blank: case TT_Whitespace: case TT_CR: case TT_NL: case TT_CRNL: case TT_EOL: case TT_CComment: case TT_CppComment: t = t->right; continue; default: cont = 0; break; } } return t; } #if 0 static int cwal_st_count_lines( char const * src, cwal_size_t srcLen, char const * pos, int *line, int *col ){ int l = 1, c = 0; if((pos(src+srcLen))) return CWAL_RC_RANGE; for( ; (pos < (src+srcLen)) && *pos; ++pos ){ if('\n'==*pos){ ++l; c = 0; } else { ++c; } } if(line) *line = l; if(col) *col = c; return 0; } #endif int cwal_st_toss_val( cwal_engine * e, cwal_simple_token * t, int code, cwal_value * v){ int rc = 0; cwal_exception * x; char iAllocedEx = 0; x = v ? cwal_value_get_exception(v) : 0; if(!x){ x = cwal_new_exception( e, code, v ); if(!x) return CWAL_RC_OOM; iAllocedEx = 1; v = cwal_exception_value(x); } #if 0 if( !cwal_prop_get( v, "line", 4 ) ){ rc = cwal_prop_set( v, "line", 4, cwal_new_integer(e, t->src.line) ); if(!rc) rc = cwal_prop_set( v, "column", 6, cwal_new_integer(e, t->src.column) ); } #elif 0 rc = cwal_prop_set( v, "column", 6, cwal_new_integer(e, (cwal_int_t)t->src.column) ); if(!rc) rc = cwal_prop_set( v, "line", 4, cwal_new_integer(e, (cwal_int_t)t->src.line) ); if(!rc) rc = cwal_prop_set( v, "code", 4, cwal_new_integer(e, (cwal_int_t)code ) ); #endif if(!rc) rc = cwal_exception_set( e, v ); if(rc && (CWAL_RC_EXCEPTION!=rc) && iAllocedEx){ cwal_value_unref( cwal_exception_value(x) ); } return rc ? rc : CWAL_RC_EXCEPTION; } int cwal_st_tossv( cwal_engine * e, cwal_simple_token * t, int code, char const * fmt, va_list args ){ int rc; #if 1 rc = cwal_exception_setfv( e, code, fmt, args ); #else cwal_value * xV; cwal_exception * x; rc = cwal_exception_setfv( e, code, fmt, args ); if(t && (CWAL_RC_EXCEPTION==rc)){ xV = cwal_exception_get(e); assert(xV); x = cwal_value_get_exception(xV); assert(x); rc = cwal_prop_set( xV, "column", 6, cwal_new_integer(e, t->src.column) ); if(!rc) rc = cwal_prop_set( xV, "line", 4, cwal_new_integer(e, t->src.line) ); if(!rc) rc = cwal_prop_set( xV, "code", 4, cwal_new_integer(e, (cwal_int_t)code) ); if(!rc) rc = CWAL_RC_EXCEPTION; } #endif return rc; } int cwal_st_toss( cwal_engine * e, cwal_simple_token * t, int code, char const * fmt, ... ){ int rc; va_list args; va_start(args,fmt); rc = cwal_st_tossv( e, t, code, fmt, args ); va_end(args); return rc; } #if defined(__cplusplus) } /*extern "C"*/ #endif #undef MARKER /* end of file cwal_t10n.c */ /* start of file th1ish_hwtime.h */ /* ** 2008 May 27 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** This file contains inline asm code for retrieving "high-performance" ** counters for x86 class CPUs. */ #ifndef _MY_HWTIME_H_INCLUDED_ #define _MY_HWTIME_H_INCLUDED_ /* ** The following routine only works on pentium-class (or newer) processors. ** It uses the RDTSC opcode to read the cycle count value out of the ** processor and returns that value. This can be used for high-res ** profiling. */ #if (defined(__GNUC__) || defined(_MSC_VER)) && \ (defined(i386) || defined(__i386__) || defined(_M_IX86)) #if defined(__GNUC__) __inline__ uint64_t sqlite3Hwtime(void){ unsigned int lo, hi; __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return (uint64_t)hi << 32 | lo; } #elif defined(_MSC_VER) __declspec(naked) __inline __uint64 __cdecl sqlite3Hwtime(void){ __asm { rdtsc ret ; return value at EDX:EAX } } #endif #elif (defined(__GNUC__) && defined(__x86_64__)) __inline__ uint64_t sqlite3Hwtime(void){ unsigned long val; __asm__ __volatile__ ("rdtsc" : "=A" (val)); return val; } #elif (defined(__GNUC__) && defined(__ppc__)) __inline__ uint64_t sqlite3Hwtime(void){ unsigned long long retval; unsigned long junk; __asm__ __volatile__ ("\n\ 1: mftbu %1\n\ mftb %L0\n\ mftbu %0\n\ cmpw %0,%1\n\ bne 1b" : "=r" (retval), "=r" (junk)); return retval; } #else #if 0 #error Need implementation of sqlite3Hwtime() for your platform. #endif /* ** To compile without implementing sqlite3Hwtime() for your platform, ** you can remove the above #error and use the following ** stub function. You will lose timing support for many ** of the debugging and testing utilities, but it should at ** least compile and run. */ uint64_t sqlite3Hwtime(void){ return ((uint64_t)0); } #endif #endif /* !defined(_MY_HWTIME_H_INCLUDED_) */ /* end of file th1ish_hwtime.h */ /* start of file th1ish.c */ /** Main implementation of the th1ish scripting engine. */ #if defined(NDEBUG) /* force assert() to always work */ # undef NDEBUG #endif #include #include #include /* strtol() and friends, getenv(). */ #include /* time() */ #include /* isspace() */ #if 0 #endif #if 1 #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #else #define MARKER(exp) if(0) printf #endif typedef int (*th1ish_eval_f)(th1ish_interp *, cwal_simple_token *, cwal_simple_token **, cwal_value ** ); enum th1ish_interp_flags { /** Used by th1ish_eval_dot_deref() to communicate that the next '.' should not be consumed by the higher-level parsers because th1ish_eval_dot_deref() is busy expanding an object.property chain. */ TH1ISH_F_EVAL_NO_DOT = 0x0001, /** A flag which means that encountering another +=, -=, *=, /=, or %= is an error (they may not be chained). */ TH1ISH_F_EVAL_NO_ASSIGN = 0x0002, /** If this th1ish_interp flag is set then conventionalFuncCallSyntax() is enabled. */ TH1ISH_F_CONVENTIONAL_CALL_OP = 0x0004, /** If this th1ish_interp flag is set then for/while loops get a new scope for each iteration. Costs memory and performance (about 15% in my simple tests) but enables var declarations inside such loops. */ TH1ISH_F_LOOPS_EXTRA_SCOPES = 0x0008, /** If this th1ish_interp flag is set then assertion checks which pass are output via cwal_output(). */ TH1ISH_F_TRACE_ASSERTIONS = 0x0100, /** Stack tracing is only done if this th1ish_interp flag is set. @see th1ish_enable_stacktrace() */ TH1ISH_F_TRACE_STACK = 0x0200, /** If this interpreter flag is set, the 'proc' handling will cache token changes for parameter lists and bodies. Strangely, though, this costs us more total overall mallocs/memory than not caching in my tests. Presumably this is because the caching empties out the recycler, causing more mallocs? Maybe caching only becomes useful for often-called functions? Wider tests show that this normally saves a number of allocs and maybe a few bytes of memory, but normally costs a tick more memory. In some tests it notably cuts the number of allocs (about 1/3rd) but increases both the peak and total memory usage. It does run a tiny tick faster with caching, though. */ TH1ISH_F_CACHE_PROCS = 0x0400, TH1ISH_F_LOG_AUTO_SWEEP = 0x0800, /** The default values for th1ish_interp::flags. */ TH1ISH_F_INTERP_DEFAULTS = TH1ISH_F_TRACE_STACK | TH1ISH_F_LOG_AUTO_SWEEP /* | TH1ISH_F_CACHE_PROCS */ /* | TH1ISH_F_CONVENTIONAL_CALL_OP */ }; const th1ish_stack_entry th1ish_stack_entry_empty = th1ish_stack_entry_empty_m; const th1ish_interp th1ish_interp_empty = th1ish_interp_empty_m; /** In some experimental cases, strings longer than this value will be created via cwal_new_xstring() instead of cwal_new_string(). This actually ends up costing us more if string interning is enabled because xstrings don't currently participate in string interning. */ static cwal_size_t const th1ish_xstr_threshold = sizeof(char const *) + 32 ; /* Internal helper. */ #define IE_SKIP_MODE (ie->skipLevel) /** Pushes ent entry into the stack trace stack (ie->stack) and sets ent->scriptPos to srcPos. We delay evaluation of ent's other members until an exception is thrown to avoid unduly slowing down execution while we count line/column positions. ent is expected to be a stack-allocated instance and its memory MUST survive until the matching call to th1ish_strace_pop() or all sorts of bad stuff will happen. The only error conditions are if !ie or !ent, but we're hoping you're not going to do that. TODO: delay calculation of the line/column until we need it. */ static void th1ish_strace_push_pos( th1ish_interp * ie, unsigned char const * srcPos, th1ish_stack_entry * ent ); /** Moves ie->stack.tail to ie->stack.reuse and decrements ie->stack.count. Asserts that there is a stack entry. Returns non-0 on error, and the only error condition is that ie->stack.count is 0. It might assert in that case, anyway, so ignore the result code. */ static int th1ish_strace_pop( th1ish_interp * ie ); char th1ish_stacktrace_current( th1ish_interp * ie, th1ish_stack_entry * dest ){ if(!ie || !ie->stack.tail) return 0; else{ if(dest) *dest = *ie->stack.tail; return 1; } } /* typedef int (*th1ish_op_f)( th1ish_interp *, cwal_value * lhs, cwal_value * rhs ); */ /** Moves ie->returnVal up a scope, if possible. */ static void th1ish_result_up( th1ish_interp * ie ){ if(ie && ie->returnVal){ cwal_scope * p = cwal_scope_parent( cwal_scope_current_get(ie->e) ); if(p){ cwal_value_rescope( p, ie->returnVal ); } } } /** Evaluates to true if RC==CWAL_RC_EXIT or RC==CWAL_RC_FATAL, else false. */ #define RC_IS_EXIT(RC) ((CWAL_RC_EXIT==(RC))||(CWAL_RC_FATAL==(RC))) /** If RC_IS_EXIT(RC) then this macro advances *(NEXT) to the TT_EOF token of (TOK)'s chain, else it has no side effects. */ #define th1ish_eval_check_exit(IE,RC,TOK,NEXT) if(RC_IS_EXIT(RC)){ \ for( ; (NEXT) && (TOK) && (TT_EOF!=(TOK)->ttype); (TOK) = (TOK)->right ){} \ *(NEXT) = (TOK);\ } (void)0 enum th1ish_rc { TH1ISH_RC_EOC = TT_VEND + 1, TH1ISH_RC_TOSS /* TH1ISH_RC_EOEXP */ }; typedef enum th1ish_rc th1ish_rc; static int th1ish_possibly_bubble(th1ish_interp * ie, int rc, cwal_scope * to, cwal_value * v ){ if(!v) return 0; else switch(rc){ case TH1ISH_RC_TOSS: case CWAL_RC_RETURN: case CWAL_RC_BREAK: /* cwal_value_rescope( to, v ); */ /* fall through */ default: return rc; } } /** Sets the current "return value" (triggered by the CWAL_RC_RETURN evaluation result). The return value needs some special handling to ensure that it survives longer than its originating scope. */ static int th1ish_result_set( th1ish_interp * ie, cwal_value * v ){ if(v == ie->returnVal) return 0; else { ie->returnVal = v; if(v){ #if 0 cwal_value_ref(v) /* shouldn't be needed! And yet it is! It's evil and orphans values in an unsweepable manner.*/; #endif th1ish_result_up( ie ); } return 0; } } #if 0 /* To try out someday: http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/ */ typedef struct th1ish_op th1ish_op; struct th1ish_op{ /* token type */ int ttype; /* Precedence */ int prec; /* -1==left, +1===right */ char assoc; }; static th1ish_op th1ish_ops[] = { /* More or less adapted from: http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence */ {TT_ChParenOpen,-10,0}, /*{TT_ChParenClose,-10,0},*/ {TT_OpIncr,-10,-1} /* postfix */, {TT_OpDecr,-10,-1} /* postfix */, /* Missing: . [] */ {TT_ChExcl,0,1}, /* Logical NOT */ {TT_ChTilde,0,1}, /* bitwise NOT */ {TT_ChPlus,0,1}, /* Unary + */ {TT_ChMinus,0,1}, /* Unary - */ {TT_OpIncr,0,1} /* ++prefix */, {TT_OpDecr,0,1} /* --prefix */, {TT_OpMultiply,5,-1}, {TT_OpDivide,5,-1}, {TT_OpModulo,5,-1}, {TT_ChPlus,10,-1}, /* Binary + */ {TT_ChMinus,10,-1}, /* Binary - */ /* Bitshift operators */ {TT_OpShiftLeft,15,-1}, {TT_OpShiftRight,15,-1}, /* Comparison ops */ {TT_CmpLt,20,-1}, {TT_CmpLe,20,-1}, {TT_CmpGt,20,-1}, {TT_CmpGe,20,-1}, {TT_CmpEq,30,-1}, {TT_CmpNe,30,-1}, {TT_CmpEqStrict,30,-1}, {TT_CmpNeStrict,30,-1}, /* Bitwise ops */ {TT_ChAmp,40,-1}, {TT_ChCaret,40,-1}, {TT_ChPipe,40,-1}, /* Logical ops */ {TT_OpLogAnd,50,-1}, {TT_OpLogOr,50,-1}, /* Ternary IF */ {TT_ChQuestion,60,1}, /* Assignments */ {TT_OpAssign,70,1}, {TT_OpPlusEq,70,1}, {TT_OpMinusEq,70,1}, {TT_OpMultEq,70,1}, {TT_OpDivEq,70,1}, {TT_OpModEq,70,1}, {TT_OpOrEq,70,1}, {TT_OpAndEq,70,1}, {TT_OpShiftLeftEq,70,1}, {TT_OpShiftRightEq,70,1}, {TT_ChComma,100,-1}, #if 0 /* Token id collisions with Unary +/- */ {TT_OpAdd,10,0}, /* Binary + */ {TT_OpSubtract,10,-1}, /* Binary - */ #endif {0,0,0} }; typedef struct th1ish_expr_state th1ish_expr_state; struct th1ish_expr_state { int opcode; int skipLevel; cwal_value * rv; }; const struct th1ish_expr_state th1ish_expr_state_empty = {0,0,0}; typedef int (*th1ish_expr_f)( th1ish_interp * ie, th1ish_expr_state * st ); typedef struct th1ish_expr th1ish_expr; struct th1ish_expr { th1ish_op const * op; th1ish_expr * parent; th1ish_expr * left; th1ish_expr * right; }; th1ish_op const * th1ish_op_by_type( int ttype ){ int i = 0; for(; i < sizeof(th1ish_ops)/sizeof(th1ish_ops[0]); ++i ){ th1ish_op const * b = &th1ish_ops[i]; if(b->ttype==ttype) return b; } return NULL; } /* End th1ish_op bits. */ #endif /** Internal help macro which evaluates to the next non-space/blank/junk token to the right of (cwal_simple_token*) T, or 0 if !T. */ #define TNEXT(T) ((T) ? cwal_st_skip((T)->right) : 0) /** Returns the first non-blank/space/comment token starting in the chain t, moving to the right. Returns 0 if the input contains only "blank" tokens. Returns t if t is-not-a skipable token type. */ static cwal_simple_token * th1ish_skip( th1ish_interp *ie, cwal_simple_token * t ){ char cont = 1; while( t && cont ){ switch(t->ttype){ case '\t': case ' ': case TT_Blank: /* case TT_CommentHash: */ case TT_CComment: case TT_CppComment: t = t->right; continue; case TT_EOL: #if 0 /* 20130706: had to disable this because it causes so very broken behaviour in perfectly valid if() conditions. Have not yet determined which expression evaluator(s) is/are the culprits. _Might_ be related to $func..., which does its own EOL handling and would be indirectly affected by this. */ if(ie->skipNLLevel > 0){ t = t->right; continue; } #endif /*else fall through*/ default: cont = 0; break; } } return t; } void th1ish_dump_val2( cwal_value * v, char const * msg, char const * func, int line ){ cwal_scope const * sc = cwal_value_scope(v); MARKER(("%s:%d: %s%stype=%s@%p[scope=#%d@%p] refcount=%d: ", func, line, msg ? msg : "", msg?": ":"", cwal_value_type_name(v), (void const *)v, (int)(sc ? sc->level : 0), (void const *)sc, (int)cwal_value_refcount(v))); switch( cwal_value_type_id(v) ){ case CWAL_TYPE_FUNCTION: case CWAL_TYPE_BUFFER: case CWAL_TYPE_NATIVE: cwal_outputf(cwal_value_engine(v), "%s@%p\n", cwal_value_type_name(v), (void const*)v); break; default: cwal_json_output_FILE( v, stdout, 0 ); break; } } /* static cwal_json_output_opt JsonOutOpt = cwal_json_output_opt_empty_m; */ #if 0 static void dump_json( cwal_value * v ){ assert(v); cwal_json_output_FILE( v, stdout, 0 ); } #endif void dump_token2( cwal_simple_token const * t, char const * msg, int fileLine ){ if(!t) return; printf("%s:%d: %s%sToken type [%d] len=%u string=<<<%.*s>>>", __FILE__, fileLine, msg?msg:"", msg?": ":"", t->ttype, (unsigned)(t->src.end - t->src.begin), (int)(t->src.end - t->src.begin), t->src.begin ); #if 0 if(t->value){ printf(" JSON value: "); dump_json(t->value); } else #endif putchar('\n'); } #define dump_token(TOK,MSG) dump_token2((TOK), (MSG), __LINE__) static struct { int stAllocs; int stFrees; int stRequests; } Th1ishMetrics = { 0/*stAllocs*/, 0/*stFrees*/ }; /** Tries to allocate a cwal_simple_token from the given recycler. li->list must contain (if anything) a chain of (cwal_simple_token*). Returns NULL if there is nothing to recycle, otherwise it removes the first entry from li->list and updates the list accordingly. */ static cwal_simple_token * th1ish_st_from_recycler( cwal_recycler * li ){ if(!li || !li->count) return NULL; else { cwal_simple_token * t = (cwal_simple_token *) li->list; assert(t); li->list = t->right; t->right = 0; --li->count; /* MARKER(("Pulled token@%p from recycler.\n", (void const *)t)); */ return t; } } /** If ie has recycling enabled and there is space left in the recycle bin then st is moved to ie's recycler, else st and all tokens to st->right are freed. */ static int th1ish_st_to_recycler( th1ish_interp * ie, cwal_simple_token * st ){ if(!ie || !ie->e) return CWAL_RC_MISUSE; else { cwal_recycler * li = &ie->recycler.tokens; if(li->count >= li->maxLength) { ++Th1ishMetrics.stFrees; cwal_free( ie->e, st ); return CWAL_RC_RANGE; } else { cwal_simple_token * h = li->count ? (cwal_simple_token *) li->list : 0; /* dump_token(st,"Adding to recycler"); */ cwal_st_snip(st); if(h){ st->right = h; } st->ttype = 0; st->src = cwal_st_src_empty; li->list = st; ++li->count; return 0; } } } /** Allocates a new cwal_simple_token instance, from the recycling bin if possible. Returns NULL if !ie or on OOM. */ static cwal_simple_token * th1ish_st_alloc( th1ish_interp * ie ){ cwal_simple_token * rc = NULL; if(ie){ rc = th1ish_st_from_recycler(&ie->recycler.tokens); if(!rc){ rc = (cwal_simple_token *)cwal_malloc( ie->e, sizeof(cwal_simple_token) ); ++Th1ishMetrics.stAllocs; } if(rc) *rc = cwal_simple_token_empty; ++Th1ishMetrics.stRequests; } #if 0 if(rc){ MARKER(("token allocation counters: total " "requests: %d, actually allocated=%d, freed=%d, " "in recycle bin=%"CWAL_SIZE_T_PFMT"\n", Th1ishMetrics.stRequests, Th1ishMetrics.stAllocs, Th1ishMetrics.stFrees, ie->recycler.tokens.count )); } #endif return rc; } /** Frees all memory held by ie->recycler. */ static void th1ish_recycler_clean( th1ish_interp * ie ){ if(ie && ie->e){ cwal_recycler * re = &ie->recycler.tokens; cwal_simple_token * tli = (cwal_simple_token *)re->list; cwal_simple_token * next; for( ; tli; tli = next ){ /* dump_token( tli, "Cleaning up recycler"); */ next = tli->right; tli->right = 0; cwal_free( ie->e, tli ); } re->list = 0; re->count = 0; } } void th1ish_st_free_chain( th1ish_interp * ie, cwal_simple_token * st, char allowRecycle ){ assert(ie && ie->e); if(!st) return; else{ cwal_simple_token * n; for( ; st; st = n ) { n = st->right; st->right = 0; if(allowRecycle){ th1ish_st_to_recycler( ie, st ); }else{ cwal_st_free( ie->e, st ); } } } } /* Implemented in th1ish_mod.c */ void th1ish_modules_close( th1ish_interp * ie ); static void th1ish_script_finalize2(th1ish_script *scr); /** Internal impl of th1ish_script_finalize(). */ void th1ish_interp_finalize( th1ish_interp * ie ){ if(!ie) return; else if(!ie->e){ assert(!ie->recycler.tokens.count); assert(!ie->recycler.tokens.list); assert(!ie->buffer.mem); return; } else{ cwal_engine * e = ie->e; void const * allocStamp = e->allocStamp; cwal_buffer_reserve(e, &ie->buffer, 0); if(ie->stash){ cwal_value_unref(ie->stash); ie->stash = 0; } while(ie->ob.count){ th1ish_ob_pop(ie); } cwal_list_reserve(e, &ie->ob, 0); ie->recycler.tokens.maxLength = 0 /* so that engine-time cleanup does not leak entries to the recycle list. */ ; th1ish_recycler_clean( ie ) /* cannot call after the engine is cleaned up */ ; assert(0 == ie->recycler.tokens.count); /* Reminder: we have a chicken/egg scenario here: Destroying the engine will free up any resources left in the global scope, so we have to leave ie's memory intact until the engine is destroyed. Case in point: th1ish_proc_state_finalize() may free a cached token chain during cleanup and it needs both ie and ie->e. Now we have another potential ordering problem... th1ish_scripts need to be cleaned up by e, but it is theoretically possible that a finalizing value during e's cleanup might trigger script code living in one of e->scripts. We have to clean up the scripts before cleaning up e because we simply have no other choice (we need e in order to clean them up). Two non-mutually-exclusive workarounds: a) It is, as of now, illegal to trigger script code from value finalizers. b) Pop all scopes from e before cleaning it to ensure destruction of all values. We set the exception state to 0 to keep it from bubbling up the scope stack. Why didn't i think of this before? */ cwal_exception_set( e, NULL ); while( !cwal_scope_pop(e) ){ /* see notes above. */ } while( ie->scripts.head ) { th1ish_script * sc = ie->scripts.head; ie->scripts.head = sc->chain; sc->chain = 0; th1ish_script_finalize2( sc ); } ie->stack.head = NULL; ie->stack.count = 0; th1ish_modules_close(ie); cwal_engine_destroy( e ); assert(0 == ie->recycler.tokens.count); assert(!ie->buffer.mem); *ie = th1ish_interp_empty; if(&th1ish_interp_empty == allocStamp){ cwal_free( e, ie ); }else{ ie->allocStamp = allocStamp; } return; } } cwal_engine * th1ish_interp_engine( th1ish_interp * ie ){ return ie ? ie->e : NULL; } int th1ish_stash_set( th1ish_interp * ie, char const * key, cwal_value * v ){ int rc; if(!ie || !key || !v || !ie->topScope) return CWAL_RC_MISUSE; else if(!ie->stash){ ie->stash = cwal_new_hash_value(ie->e, 27); if(!ie->stash) return CWAL_RC_OOM; assert(ie->topScope); cwal_value_rescope(ie->topScope, ie->stash); cwal_value_ref(ie->stash) /* Make sure it doesn't get swept up! */ ; #if 0 cwal_scope_chain_set( ie->topScope, 0, "$th1ish_stash$", 14, ie->stash ) /* Only as an experiment with cwal_engine_sweep_non_vars(), to keep it from nuking the stash. */ ; #endif } rc = cwal_hash_insert( cwal_value_get_hash(ie->stash), key, 0, v, 1 ); return rc; } cwal_value * th1ish_stash_get( th1ish_interp * ie, char const * key ){ if(!ie || !key || !*key) return NULL; else return ie->stash ? cwal_hash_search( cwal_value_get_hash(ie->stash), key, strlen(key) ) : 0; } /** cwal_value_type_name_proxy_f() implementation which checks v for a property named __typename. */ static char const * th1ish_type_name_proxy( cwal_value const * v, cwal_size_t * len ){ cwal_value const * tn = cwal_prop_get(v, "__typename", 10); return tn ? cwal_value_get_cstr(tn, len) : NULL; } int th1ish_interp_init( th1ish_interp * ie, cwal_engine * e ){ int rc; if(!ie || !e) return CWAL_RC_MISUSE; assert(!ie->e); *ie = th1ish_interp_empty; ie->flags = TH1ISH_F_INTERP_DEFAULTS; ie->e = e; rc = cwal_buffer_reserve(e, &ie->buffer, 512); if(rc) goto end; ie->topScope = cwal_scope_current_get(e); if(!ie->topScope){ rc = CWAL_RC_RANGE; goto end; } cwal_engine_type_name_proxy( e, th1ish_type_name_proxy ); ie->recycler.tokens.maxLength = #if 1 (1024*(CWAL_SIZE_T_BITS/2)) / sizeof(cwal_simple_token) #elif 0 100 #else 0 #endif ; #if 0 /* Remind me again why i wanted this? */ { cwal_value * gp = th1ish_global_props(ie); if(!gp) rc = CWAL_RC_OOM; else { assert(!cwal_value_prototype_get(gp)); rc = cwal_value_prototype_set(gp, th1ish_prototype_object(ie)); assert(0==rc); } } #endif /* The following we do so that the default prototypes get set up. */ { cwal_value * v; #define DO(T) v = th1ish_prototype_##T(ie); \ if(!v) { rc = CWAL_RC_OOM; goto end; } (void)0 DO(object); DO(function); DO(integer); DO(double); DO(array); DO(hash); DO(buffer); DO(exception); DO(string); #undef DO } end: return rc; } int th1ish_interp_init2( th1ish_interp ** ie, cwal_engine * e ){ if(!ie || !e) return CWAL_RC_MISUSE; else { int rc; th1ish_interp * i = ie ? *ie : 0; if(!i){ i = cwal_malloc(e, sizeof(th1ish_interp)); if(!i) return CWAL_RC_OOM; *i = th1ish_interp_empty; i->allocStamp = &th1ish_interp_empty; } rc = th1ish_interp_init( i, e ); if(rc && (&th1ish_interp_empty == i->allocStamp)){ cwal_free( e, i ); } return rc; } } /** If pos is in the range [src,end] then this function calculates the line (1-based) and column (0-based) of pos within [src,end] and sets line/col to those values. If pos is out of range CWAL_RC_RANGE is returned and this function has no side-effects. Returns 0 on success. */ static int th1ish_count_lines( char const * src, char const * end, char const * pos, unsigned int *line, unsigned int *col ){ unsigned int ln = 1, c = 0; char const * x = src; if((posend)) { /* This happens when processing sub-eval'd strings :/ */ /* MARKER(("BAD count_lines RANGE!\n")); */ return CWAL_RC_RANGE; } else for( ; (x < pos) && *x; ++x ){ if('\n'==*x){ ++ln; c = 0; } else { ++c; } } if(line) *line = ln; if(col) *col = c; /* MARKER(("Updated line/col: %u, %u\n", ln, c )); */ return 0; } static th1ish_script * th1ish_script_for_pos( th1ish_interp * ie, unsigned char const * pos ){ th1ish_script * sc = ie->scripts.current; if(sc && (pos >= sc->code) && (pos < (sc->code+sc->codeLength))) { return sc; } sc = ie->scripts.head; for( ; sc; sc = sc->chain ){ if((pos >= sc->code) && (pos < (sc->code+sc->codeLength))) { return sc; } } return NULL; } th1ish_script * th1ish_script_by_name( th1ish_interp const * ie, char const * name ){ th1ish_script * sc; cwal_size_t nLen; if(!ie || !ie->scripts.head || !name) return NULL; nLen = strlen(name); #if 0 sc = ie->scripts.current; if(sc && (sc->nameLen==nLen) && (0==cwal_compare_cstr(name, nLen, sc->name, sc->nameLen))){ return ie->scripts.current; } #endif sc = ie->scripts.head; for( ; sc; sc = sc->chain ){ if( (sc->nameLength==nLen) && (0==cwal_compare_cstr(name, nLen, (char const *)sc->name, sc->nameLength))){ return sc; } } return NULL; } static int th1ish_line_col( th1ish_interp * ie, char const * pos, unsigned int * line, unsigned int * col ){ int rc = CWAL_RC_RANGE; char const * begin = 0; char const * end = 0; #if 1 /* Would like to get rid of th1ish_interp::src but th1ish_eval_filename(), or one of the routines it uses, still relies on it. */ if( (pos >= ie->src.begin) && (pos < ie->src.end)) { begin = ie->src.begin; end = ie->src.end; } else #endif { th1ish_script * sc = th1ish_script_for_pos(ie, (unsigned char const *)pos ); if( !sc ) return CWAL_RC_RANGE; begin = (char const *)sc->code; end = (char const *)sc->code + sc->codeLength; } rc = th1ish_count_lines( begin, end, pos, line, col ); return rc; } static int th1ish_token_line_col( th1ish_interp * ie, cwal_simple_token const * t, unsigned int * line, unsigned int * col ){ return th1ish_line_col( ie, t->src.begin, line, col ); } /** Returns true if ch is a legal char for "identifier" strings (e.g. var/function names). Pass 1 as isStart for the first call and 0 for subsequent calls for the same string. If isStart is true it only allows alpha and underscore characters, otherwise is also allows numbers. To support unicode identifer, this function accepts any character with a value >0x7f (127d) as an identifier character. Any character value <=0 is not. */ static char th1ish_is_id_char( int ch, char isStart ) { if(ch<=0) return 0; else if(ch>0x7f) return 1 /* TODO: filter to specific ranges? Or at least remove any unicode whitespace/blanks (are there such things?). */; else switch(ch){ case '_': /* case '@': */ return 1; default: return isStart ? cwal_is_alpha(ch) : cwal_is_alnum(ch); } } /** Assumes that zPos is the start of an identifier and reads until the next non-identifier character. zMaxPos must be the logical EOF for zPos. On returning, *zIdEnd is set to the one-after-the-end position of the read character (which will be (*zIdEnd-pos) bytes long). Expects the input to be valid, else results are undefined. */ static void th1ish_read_identifier( char const * zPos, char const * zMaxPos, char const ** zIdEnd ){ unsigned char const * start = (unsigned char const *) zPos; unsigned char const * pos = start; unsigned char const * end = (unsigned char const *) zMaxPos; unsigned char const * endChar = pos; int ch; char isStart = 1; assert(zMaxPos>zPos); for( ; pos < end; isStart = 0){ ch = cwal_utf8_read_char( pos, end, &endChar ); if(!th1ish_is_id_char(ch, isStart)) break; pos = endChar; } *zIdEnd = zPos + (pos - start); } /** Tries to parse the next token from t, which must have been initalized with cwal_simple_tokenizer_init(). On success returns 0 and updates t's state to reflect the location and type of the curren token. On error returns non-0 and t->errMsg "might" contain a description of the problem. */ static int th1ish_next_token( cwal_simple_tokenizer * t ){ int rc = 0; char const * curpos = t->current.end ? t->current.end : t->begin; t->errMsg = 0; #if 0 /* i want something _like_ this, but it needs to behave properly with multi-byte tokens. e.g. hitting EOF after a '+', while checking for '++'. */ #define CHECKEND if(curpos>t->end) { t->type = TT_EOF; return 0; } #else #define CHECKEND (void)0 #endif #define BUMP(X) curpos+=(X); CHECKEND; #define RETURN_ERR(RC,MSG) t->ttype = TT_TokErr; t->errMsg = MSG; return (RC) #define NEXT_LINE (void)0 if((curpos == t->begin) && (('#'==*curpos) && ('!'==*(curpos+1)))){ /* Workaround: strip shebang line from start of scripts */ while(*(++curpos) && ('\n' != *curpos)) {} ++curpos; } if( curpos >= t->end ) { t->ttype = TT_EOF; t->current.begin = t->current.end = t->end; return 0; } t->ttype = TT_UNDEF; t->current.begin = curpos; switch(*curpos){ case 0: t->ttype = TT_EOF; /*This is a necessary exception to the bump-on-consume rule.*/ break; case '\\': /* Treat \ as a continuation of a line */ if(('\n'==*(curpos+1)) || (('\r'==*(curpos+1)) && ('\n'==*(curpos+2)))){ t->ttype = TT_Whitespace; BUMP(('\r'==*(curpos+1)) ? 3 : 2); NEXT_LINE; } break; case '\r': if('\n'==*curpos){ t->ttype = TT_CRNL; BUMP(2); NEXT_LINE; } else{ t->ttype = TT_CR; BUMP(1); } break; case '\n': t->ttype = TT_NL; BUMP(1); NEXT_LINE; break; case ' ': case '\t': case '\v': case '\f': t->ttype = *curpos /*TT_Blank*/; BUMP(1); #if 1 /* TODO once i figure out if we should handle EOL and EOL/blank runs consistently. */ while( *curpos && cwal_is_blank(*curpos) ){ t->ttype = TT_Blank; BUMP(1); } #endif break; case ':': /* colon or namespace */ if( ':' == *(curpos+1) ){ t->ttype = TT_OpNamespace; BUMP(2); }else{ t->ttype = TT_ChColon; BUMP(1); } break; case '~': /* ~ or ~= */ t->ttype = *curpos; BUMP(1); #if 1 if('=' == *curpos){ t->ttype = TT_OpTildeEq; BUMP(1); } #endif break; #if 0 /** #-style comments disabled because they break this use case: var str {item #1} b/c the brace bits use the tokenizer to find the closing brace. */ case '#': /* comment until EOL */ t->ttype = TT_CommentHash; while( *curpos && ('\n' != *curpos) ){ BUMP(1); } break; #endif case '/': /* numeric division or C-style comment block */ t->ttype = *curpos; BUMP(1); if('=' == *curpos){ t->ttype = TT_OpDivEq; BUMP(1); } else if( '*' == *curpos) /* C-style comment block */{ t->ttype = TT_CComment; BUMP(1); do{ while( *curpos && ('*' != *curpos) ){ if('\n'==*curpos){ NEXT_LINE; } BUMP(1); } if( ! *curpos ) break; BUMP(1); /* skip '*' */ } while( *curpos && ('/' != *curpos)); if( *curpos != '/' ){ RETURN_ERR(CWAL_RC_RANGE,"End of C++-style comment not found."); } BUMP(1); /* get that last slash */ } else if( '/' == *curpos ) /* C++-style comment line */{ BUMP(1); t->ttype = TT_CppComment; while( *curpos && ('\n' != *curpos) ){ BUMP(1); } } break; case '"': case '\'': /* read string literal */{ char const quote = *curpos; t->ttype = ('"' == *curpos) ? TT_LiteralStringDQ : TT_LiteralStringSQ; BUMP(1)/*leading quote*/; while(*curpos && (*curpos != quote)){ if( (*curpos == '\\') ){ /* consider next char to be escaped, but keep escape char */ BUMP(1); if(*curpos == 0){ RETURN_ERR(CWAL_RC_RANGE, "Unexpected EOF while toekinizing backslash-escaped char in string literal."); } BUMP(1); continue; } else if('\n'==*curpos){ BUMP(1); NEXT_LINE; } else { BUMP(1); } } if(*curpos != quote){ RETURN_ERR(CWAL_RC_RANGE, "Unexpected end of string literal."); } BUMP(1)/*trailing quote*/; break; } /* end literal string */ case '&': /* & or && */ t->ttype = TT_ChAmp; BUMP(1); if( '&' == *curpos ){ t->ttype = TT_OpLogAnd; BUMP(1); } else if( '=' == *curpos ){ t->ttype = TT_OpAndEq; BUMP(1); } break; case '|': /* | or || */ t->ttype = TT_ChPipe; BUMP(1); if( '|' == *curpos ){ t->ttype = TT_OpLogOr; BUMP(1); } else if( '=' == *curpos ){ t->ttype = TT_OpOrEq; BUMP(1); } break; case '^': /* ^ or ^= */ t->ttype = TT_ChPipe; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_OpXorEq; BUMP(1); } break; case '+': /* + or ++ or += */{ t->ttype = TT_ChPlus; BUMP(1); switch(*curpos){ case '+': /* ++ */ t->ttype = TT_OpIncr; BUMP(1); break; case '=': /* += */ t->ttype = TT_OpPlusEq; BUMP(1); break; default: break; } break; } case '*': /* multiply operator */ t->ttype = TT_ChAsterisk; BUMP(1); if('=' == *curpos){ t->ttype = TT_OpMultEq; BUMP(1); } else if('/' == *curpos){ t->ttype = TT_UNDEF; RETURN_ERR(CWAL_RC_RANGE, "Comment closer (*/) not inside a comment."); } break; case '-': /* - or -- or -= */{ t->ttype = TT_ChMinus; BUMP(1); switch( *curpos ){ case '-': /* -- */ t->ttype = TT_OpDecr; BUMP(1); break; case '=': t->ttype = TT_OpMinusEq; BUMP(1); break; case '>': t->ttype = TT_OpArrow; BUMP(1); break; default: break; } break; } case '<': /* LT or << or <= or <<= or <<< */{ t->ttype = TT_CmpLt; BUMP(1); switch( *curpos ){ case '<': /* tok= << */ { t->ttype = TT_OpShiftLeft; BUMP(1); switch(*curpos){ case '=': /* <<= */ t->ttype = TT_OpShiftLeftEq; BUMP(1); break; #if 1 case '<': /* <<< */ t->ttype = TT_OpHeredocStart; BUMP(1); break; #endif default: break; } break; } case '=': /* tok= <= */ t->ttype = TT_CmpLe; BUMP(1); break; default: break; } break; } case '>': /* GT or >> or >= */{ t->ttype = TT_CmpGt; BUMP(1); switch(*curpos){ case '>': /* tok= >> */ t->ttype = TT_OpShiftRight; BUMP(1); if( '=' == *curpos ){/* >>= */ t->ttype = TT_OpShiftRightEq; BUMP(1); } break; case '=': /* tok= >= */ t->ttype = TT_CmpGe; BUMP(1); break; default: break; } break; } case '=': /* = or == or === */ t->ttype = TT_ChEq; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpEq; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpEqStrict; BUMP(1); } } break; case '%': /* % or %= */ t->ttype = *curpos; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_OpModEq; BUMP(1); } break; case '!': /* ! or != or !== */ t->ttype = TT_ChExcl; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpNe; BUMP(1); if( '=' == *curpos ){ t->ttype = TT_CmpNeStrict; BUMP(1); } } break; case '{': /* { or {{ */ t->ttype = TT_ChScopeOpen; BUMP(1); #if 0 if('{' == *curpos){ t->ttype = TT_LiteralObjOpen; BUMP(1); } #endif break; case '}': /* } or }} */ t->ttype = TT_ChScopeClose; BUMP(1); #if 0 if('}' == *curpos){ t->ttype = TT_LiteralObjClose; BUMP(1); } #endif break; case '0': /* hex or octal */ BUMP(1); if( (*curpos == 'x') || (*curpos == 'X') ){ /** hex digit. Convert to decimal to save pain later. */ /* TODO: reimplement to use strtol(). */ BUMP(1); while( *curpos && cwal_is_xdigit(*curpos) ){ BUMP(1); } t->ttype = TT_LiteralIntHex; if( *curpos && ( cwal_is_alnum(*curpos) || (*curpos == '_')) ){ BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed hexidecimal int constant."); } } else if((*curpos>='0') && (*curpos<='7')){ /* try octal... */ BUMP(1); while( *curpos && (*curpos>='0') && (*curpos<='7') ){ BUMP(1); } t->ttype = TT_LiteralIntOctal; if( *curpos && ( cwal_is_alnum(*curpos) || (*curpos == '_')) ){ BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed octal int constant."); } } else if( *curpos && ( cwal_is_alnum(*curpos) || (*curpos == '_')) ) { /* reject 12334x where x is alphanum or _ */ BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed numeric constant starts " "with '0' but is neither octal nor hex.."); } else if('.'==*curpos){ BUMP(1); while( cwal_is_digit(*curpos) ){ BUMP(1); } if('.'==*(curpos-1)){ #if 0 #else RETURN_ERR(CWAL_SCR_UNEXPECTED_TOKEN, "Mis-terminated floating point value."); #endif } t->ttype = TT_LiteralDouble; } else { t->ttype = TT_LiteralIntDec; /* A literal 0. This is okay. */ } break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* integer or double literal. */ while( *curpos && cwal_is_digit(*curpos) ){ /* integer or first part of a double. */ BUMP(1); } if( ('.' == *curpos) && (cwal_is_digit(*(curpos+1))) ){ /* double number */ t->ttype = TT_LiteralDouble; BUMP(1); while( *curpos && cwal_is_digit(*curpos) ){ BUMP(1); } } else { t->ttype = TT_LiteralIntDec; } if( *curpos && (cwal_is_alnum(*curpos) || (*curpos == '_'))) { BUMP(1); /* make sure it shows up in the error string. */ RETURN_ERR(CWAL_RC_RANGE, "Malformed numeric constant."); } break; }/* end of switch(*curpos) */ /** TODO: UTF8 multibyte. The necessary code is in cwal_json.c. */ if(0==rc && (curpos == t->current.begin) && (TT_EOF != t->ttype) ){ /* keep trying... */ if( th1ish_is_id_char(*curpos,1) ) /* identifier string */{ #if 1 th1ish_read_identifier( curpos, t->end, &curpos ); #else BUMP(1); while( cwal_is_identifier_char(*curpos,0) ){ BUMP(1); } #endif t->ttype = TT_Identifier; } else if( (*curpos >= 32) && (*curpos < 127) ) { t->ttype = *curpos; BUMP(1); } else { assert(curpos == t->current.begin); assert(TT_UNDEF==t->ttype); } } if(TT_UNDEF == t->ttype){ unsigned char const cCh = (unsigned char)*curpos; if( cCh > 0x7F ) /* _hope_ for a UTF8 identifier string! */{ th1ish_read_identifier( curpos, t->end, &curpos ); t->ttype = TT_Identifier; }else{ t->ttype = TT_TokErr; /* MARKER(("byte=%02x\n", (unsigned char)*curpos)); */ t->errMsg = "Don't know how to tokenize this."; rc = CWAL_RC_ERROR; } } t->current.end = curpos; #undef CHECKEND #undef BUMP #undef RETURN_ERR #undef NEXT_LINE return rc; } static void dump_tokenizer2( cwal_simple_tokenizer const * t, char const * msg, int srcLine ){ cwal_size_t const plen = t->current.end - t->current.begin; unsigned int line = 0, col = 0; th1ish_count_lines( t->begin, t->end, t->current.begin, &line, &col ); printf("%s:%d: %s%sTokenizer: Token type %d line=%u " "col(@end)=%u length=%"CWAL_SIZE_T_PFMT", string=<<<%.*s>>>\n", __FILE__, srcLine, msg?msg:"", msg?": ":"", t->ttype, line, col, plen, (int)plen, t->current.begin ); } #define dump_tokenizer( T, MSG ) dump_tokenizer2( (T), (MSG), __LINE__ ) /** Creates a value for a token. This basically just takes the t->src and makes a string from it, but has different handling for certain token types. On success *rv contains the new string. Returns 0 on success, CWAL_RC_OOM on OOM, else it should not fail. */ static int th1ish_st_create_value( th1ish_interp * ie, cwal_simple_token * t, cwal_value ** rv ){ int rc = CWAL_RC_TYPE; cwal_value * v = NULL; cwal_engine * e = ie->e; /* FIXME: improve the string-to-numeric conversions. We're largely relying on the tokenizer having done a good job and the caller checking result codes. */ #define RC rc = v ? CWAL_RC_OK : CWAL_RC_OOM switch(t->ttype){ case TT_LiteralIntDec: v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin, NULL, 10)); RC; break; case TT_LiteralIntOctal: assert( (t->src.end-t->src.begin) > 2 /*"0" prefix */); v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+1/*0*/, NULL, 8)); /* t->ttype = TT_LiteralIntDec; */ RC; break; case TT_LiteralIntHex: assert( (t->src.end-t->src.begin) > 2 /*"0x" prefix */); v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+2/*0x*/, NULL, 16)); /* t->ttype = TT_LiteralIntDec; */ RC; break; case TT_LiteralDouble: v = cwal_new_double(e, (cwal_double_t)strtod(t->src.begin, NULL)); RC; break; case TT_LiteralStringSQ: case TT_LiteralStringDQ:{ #if 0 /* This variant does no unescaping of the string's content. Valgrind comparisons show that this _normally_ saves us just a few bytes per script (<200) and could also inconvenience print()-style routines a great deal :/. Re-using ie->buffer for escaping makes all the difference in the number of real allocations we make. */ cwal_size_t const len = t->src.end - t->src.begin - 2; assert((t->src.end - t->src.begin)>=2 /* for the quotes */); v = (0 && (len>=th1ish_xstr_threshold)) ? cwal_new_xstring_value(e, len ? (t->src.begin+1) : 0, len) : cwal_new_string_value(e, len ? (t->src.begin+1) : 0, len) /* Reminder: if we pass (t->src.begin,0) then it uses strlen() to determine the length. That bit me. */ ; #else cwal_buffer * escapeBuf = &ie->buffer; cwal_size_t const oldUsed = escapeBuf->used; int check; assert((t->src.end - t->src.begin)>=2 /* for the quotes */); check = cwal_st_unescape_string(e, (unsigned char const *)t->src.begin + 1 /*quote*/, (unsigned char const *)t->src.end - 1 /*quote*/, escapeBuf ); if(check) return check; assert(escapeBuf->used >= oldUsed); /*MARKER("STRING: [%.*s]\n", (int)(escapeBuf->used - oldUsed), (char const *)escapeBuf->mem+oldUsed);*/ assert(0 == escapeBuf->mem[escapeBuf->used]); v = cwal_new_string_value(e, (escapeBuf->used-oldUsed) ? (char const *)(escapeBuf->mem+oldUsed) : NULL, escapeBuf->used - oldUsed ); escapeBuf->used = oldUsed; #endif /* th1ish_dump_val(v,"string literal"); */ RC; break; } default:{ cwal_size_t len = t->src.end - t->src.begin; assert(t->src.end >= t->src.begin); /* MARKER("STRING: [%.*s]\n", (int)(t->src.end - t->src.begin), t->src.begin); */ /* MARKER("STRING: len=%ld\n", (long) (t->src.end - t->src.begin) ); */ /* Remember that tokenization post-processing changed t->src to point to the trimmed string and t->right to be the element after the closing brace. */ v = (0 && (len>=th1ish_xstr_threshold)) ? cwal_new_xstring_value(e, len ? t->src.begin : 0, len) : cwal_new_string_value(e, len ? t->src.begin : 0, len) /* Reminder: if we pass (t->src.begin,0) then it uses strlen() to determine the length. That bit me. */ ; /* th1ish_dump_val(v,"scope string"); */ RC; break; } } if(!rc) { assert(v); *rv = v; }else{ assert(!v); } return rc; #undef RC }/* th1ish_st_create_value() */ char const * th1ish_interp_script_name( th1ish_interp * ie, cwal_size_t * len ){ if(!ie) return NULL; else { th1ish_script * sc = ie->scripts.current; if(!sc){ if(len) *len = ie->srcName ? strlen(ie->srcName) : 0; return ie->srcName; } else { if(len) *len = sc->nameLength; return (char const *)sc->name; } } } /** For the given source code position, this function tries to find the name of the script in which that position is found. On success returns the name and if len is not NULL then *len is set to the length of the name (it "should" be NUL-terminated, though). */ static unsigned char const * th1ish_script_name_for_pos( th1ish_interp * ie, unsigned char const * srcPos, cwal_size_t * len ){ if(!ie) return NULL; else { th1ish_script * sc = th1ish_script_for_pos(ie, srcPos); if(sc){ if(len) *len = sc->nameLength; return sc->name; } else if((srcPos>=(unsigned char const *)ie->src.begin) && (srcPos<(unsigned char const *)ie->src.end)){ if(len) *len = ie->srcName ? strlen(ie->srcName) : 0; return (unsigned char const *)ie->srcName; } return NULL; } } /** Sets the following properties of tgt: line, column: the position of srcPos within its source script. script: the name of the current script code: if code is non-0 then this property is added, else it is not. Returns 0 on success. */ static int th1ish_set_script_props(th1ish_interp * ie, int code, char const * srcPos, cwal_value * tgt ){ int rc; unsigned int line = 0; unsigned int column = 0; cwal_size_t nameLen = 0; unsigned char const * name = th1ish_script_name_for_pos(ie, (unsigned char const *)srcPos, &nameLen); th1ish_line_col( ie, srcPos, &line, &column ); rc = cwal_prop_set( tgt, "column", 6, cwal_new_integer(ie->e, (cwal_int_t)column) ); if(!rc) rc = cwal_prop_set( tgt, "line", 4, cwal_new_integer(ie->e, (cwal_int_t)line) ); if(!rc && code) rc = cwal_prop_set( tgt, "code", 4, cwal_new_integer(ie->e, (cwal_int_t)code) ); if(!rc && name && *name){ rc = cwal_prop_set(tgt, "script", 6, cwal_new_string_value(ie->e, (char const *)name, nameLen)); } /* th1ish_dump_val(tgt,"th1ish_set_script_props"); */ return rc; } /** Populates ent->scriptName, ent->line, and ent->col based on the value of ent->scriptPos. */ static void th1ish_strace_populate_src_info( th1ish_interp * ie, th1ish_stack_entry *ent ){ if(ent->scriptPos){ unsigned int line = 0; unsigned int column = 0; ent->scriptName = th1ish_script_name_for_pos(ie, ent->scriptPos, &ent->scriptNameLen); th1ish_line_col( ie, (char const *)ent->scriptPos, &line, &column ); ent->line = (cwal_size_t)line; ent->col = (cwal_size_t)column; } } /** @internal Copies the line/column/script location infromation from the current stack entry to the given container value. Returns 0 on success or if no stack entries are available. */ int th1ish_strace_copy( th1ish_interp * ie, cwal_value * ex ){ if(!ie->stack.head || !ie->stack.count) return 0; else if(!(TH1ISH_F_TRACE_STACK & ie->flags)) return 0; else { int rc = 0; cwal_array * cp; th1ish_stack_entry * ent = ie->stack.tail; assert(ent); cp = th1ish_new_array(ie); if(!cp) return CWAL_RC_OOM; rc = cwal_array_reserve(cp, ie->stack.count); for( ; !rc && ent; ent = ent->up ){ cwal_value * c = th1ish_new_object(ie); if(!c){ rc = CWAL_RC_OOM; break; } if(!ent->scriptName){ th1ish_strace_populate_src_info(ie, ent); } rc = cwal_prop_set( c, "line", 4, cwal_new_integer(ie->e, (cwal_int_t)ent->line)); if(!rc) rc = cwal_prop_set( c, "column", 6, cwal_new_integer(ie->e, (cwal_int_t)ent->col)); if(!rc && ent->scriptName){ rc = cwal_prop_set(c, "script", 6, cwal_new_string_value(ie->e, (char const *)ent->scriptName, ent->scriptNameLen)); } if(rc){ cwal_value_unref(c); break; } if(!rc) rc = cwal_array_append( cp, c ); } if(!rc) rc = cwal_prop_set(ex, "callStack", 9, cwal_array_value(cp)); if(rc) cwal_array_unref(cp) /* Reminder: only safe because there's no way cp can have been injected in ex at this point */; /* th1ish_dump_val(ex,"callStack?"); */ return rc; } } int th1ish_toss_val( th1ish_interp * ie, int code, cwal_value * msg ){ int rc; cwal_value * ex = msg ? cwal_value_exception_part(ie->e, msg) : NULL; if(!ex){ ex = th1ish_new_exception( ie, code, msg ); if(!ex) return CWAL_RC_OOM; } rc = cwal_exception_set(ie->e, ex); if(CWAL_RC_EXCEPTION!=rc){ if(msg != ex) cwal_value_unref(ex); }else{ rc = th1ish_strace_copy( ie, ex ); if(!rc) rc = CWAL_RC_EXCEPTION; } return rc; } int th1ish_tossv( th1ish_interp * ie, int code, char const * fmt, va_list args ){ int rc; cwal_value * exp = th1ish_prototype_exception(ie)/*initialize prototype injection*/; rc = cwal_exception_setfv(ie->e, code, fmt, args); if(CWAL_RC_EXCEPTION==rc){ cwal_value * ex = cwal_exception_get(ie->e); assert(ex); assert(cwal_value_prototype_get(ie->e, ex)==exp); rc = th1ish_strace_copy( ie, ex ); if(!rc) rc = CWAL_RC_EXCEPTION; } return rc; } int th1ish_toss( th1ish_interp * ie, int code, char const * fmt, ... ){ int rc; va_list args; va_start(args,fmt); rc = th1ish_tossv(ie, code, fmt, args); va_end(args); return rc; } int th1ish_toss_token_val( th1ish_interp * ie, cwal_simple_token const * t, int code, cwal_value * v ){ int rc; cwal_value * xV = v ? cwal_value_exception_part(ie->e, v) : NULL; if(!xV){ xV = th1ish_new_exception( ie, code, v ); if(!xV) return CWAL_RC_OOM; } rc = cwal_exception_set( ie->e, xV ); if(CWAL_RC_EXCEPTION!=rc){ if(v != xV) cwal_value_unref(xV); } else { rc = th1ish_set_script_props( ie, #if 0 cwal_exception_code_get(cwal_value_get_exception(xV)), #else code, #endif t->src.begin, xV ); if(!rc) rc = th1ish_strace_copy( ie, xV ); if(!rc) rc = CWAL_RC_EXCEPTION; /* th1ish_dump_val(xV,"callStack?"); */ } return rc; } int th1ish_toss_posv( th1ish_interp * ie, unsigned char const * codePos, int code, char const * fmt, va_list args ){ int rc; rc = th1ish_tossv( ie, code, fmt, args ); if(CWAL_RC_EXCEPTION==rc){ cwal_value * xV; xV = cwal_exception_get(ie->e); assert(xV); rc = th1ish_set_script_props( ie, code, (char const *)codePos, xV ); if(!rc) rc = CWAL_RC_EXCEPTION; /* th1ish_dump_val(xV,"stack trace?"); */ } return rc; } int th1ish_toss_pos( th1ish_interp * ie, unsigned char const * codePos, int code, char const * fmt, ... ){ int rc; va_list args; va_start(args,fmt); rc = th1ish_toss_posv( ie, codePos, code, fmt, args ); va_end(args); return rc; } int th1ish_toss_token( th1ish_interp * ie, cwal_simple_token const * t, int code, char const * fmt, ... ){ int rc; va_list args; va_start(args,fmt); rc = th1ish_toss_posv( ie, (unsigned char const *)t->src.begin, code, fmt, args ); va_end(args); return rc; } /** Intended to be called when th1ish_next_token() fails, passing it the tokenizer and the code from the failed tokenization. This function tosses an exception with that information and returns CWAL_RC_EXCEPTION on success. It never returns 0. */ static int th1ish_toss_t10n(th1ish_interp * ie, cwal_simple_tokenizer const * cst, int code){ return th1ish_toss_pos(ie, (unsigned char const *)cst->current.begin, code, "Tokenizer returned code %d (%s) %s%s", code, cwal_rc_cstr(code), cst->errMsg ? ": " :"", cst->errMsg ? cst->errMsg : ""); } /** Requires st to be a TT_ChScopeOpen or TT_ChBraceOpen token. It "slurps" the content part (between the braces), trimming whitespace from beginning and ending. Sets out->begin and out->end to reflect the trimmed range (which may be empty, i.e. end==begin). Returns 0 on success. On error it may set ie->e's exception state. */ static int th1ish_slurp_braces( th1ish_interp *ie, cwal_simple_tokenizer * st, cwal_st_src * out ){ int const opener = st->ttype; int closer; int rc; int level = 1; cwal_st_src origSrc; char const * pos; switch(opener){ case TT_ChParenOpen: closer = TT_ChParenClose; break; case TT_ChBraceOpen: closer = TT_ChBraceClose; break; case TT_ChScopeOpen: closer = TT_ChScopeClose; break; default: return CWAL_RC_TYPE; } /* TODO? Tokenize this ourselves instead of going through st. The main advantage is we avoid a lot of tokenization. The end effect is the same, but the former is less expensive (many fewer function calls), but memory usage is the same either way. */ /* dump_tokenizer(st, "slurping..."); */ assert(st->end > st->current.begin); while((st->end > ++st->current.begin) && cwal_is_whitespace(*st->current.begin)){ /* Skip leading whitespaces */ } if(st->end == st->current.begin){ return CWAL_RC_RANGE; } st->current.end = st->current.begin; origSrc = st->current; #if 0 /** Use the tokenizer to find the closing brace. This has up-sides and down sides. It traverses string literals, so that any '}' in the string is not seen by the slurping code. But it breaks a desired use case: var x {item #3} the #-style comment will hide the closing brace if we use the tokenizer here. It also breaks simpler things like: { ... 1a ... } */ while(1){ rc = th1ish_next_token(st); if(rc) { rc = th1ish_toss_t10n( ie, &st, rc); break; } if(TT_EOF == st->ttype){ cwal_simple_token tok = cwal_simple_token_empty; tok.ttype = opener; tok.src = origSrc; rc = th1ish_toss_token(ie, &tok, CWAL_RC_RANGE, "Unexpected EOF while slurping '%c' block.", opener); break; } else if(opener == st->ttype){ ++level; } else if(closer == st->ttype){ assert(level>0); if(!--level){ #if 1 char const * end = st->current.end-1/*back to closing brace*/; /*dump_tokenizer( st, "At slurp closing");*/ assert( closer == *end ); while((--end>origSrc.begin) && cwal_is_whitespace(*end)){} ++end; #else char const * end = st->current.begin; assert( end >= origSrc.begin ); /*dump_tokenizer( st, "At slurp closing");*/ assert( closer == *end ); if(end>origSrc.begin) --end /* skip brace */; while((end>origSrc.begin) && cwal_is_whitespace(*end)){ /* Trim trailing whitespace (arguable!) */ --end; } #endif /*dump_tokenizer( st, "Again at slurp closing");*/ out->begin = origSrc.begin; assert( end >= origSrc.begin ); out->end = end; /* dump_tokenizer( st, "Again at slurp closing"); */ rc = 0; /* MARKER("slurped <<%.*s>>\n", (int)(out->end - out->begin), out->begin); */ break; } } } #else /** Alternate slurp strategy: traverse the raw bytes. This cannot protect us from a '}' inside a string unless we add the string-skipping code here. */ pos = origSrc.begin; for( ; pos < st->end ; ++pos){ /** FIXME: by slurping this way, instead of going through the tokenizer, we ignore any string literals and such which might "protect" what we will interpret here as a closing '}'. OTOH, slurping this way allows us to defer some forms of tokenization errors until this source string is tokenized. It also allows us to enable #-style comments in the tokenizer without breaking things here. */ if(!*pos){ cwal_simple_token tok = cwal_simple_token_empty; tok.ttype = opener; tok.src = origSrc; rc = th1ish_toss_token( ie, &tok, CWAL_RC_RANGE, "Unexpected EOF while slurping " "{SCOPE} block."); break; } else if(opener == *pos){ ++level; } else if(closer == *pos){ assert(level>0); if(!--level){ char const * end = pos /*st->current.end-1*/ /*back to closing brace*/; /*dump_tokenizer( st, "At slurp closing");*/ while((--end>origSrc.begin) && cwal_is_whitespace(*end)){} ++end; st->current.end = pos+1; /*dump_tokenizer( st, "Again at slurp closing");*/ out->begin = origSrc.begin; assert( end >= origSrc.begin ); out->end = end; /* dump_tokenizer( st, "Again at slurp closing"); */ rc = 0; /* MARKER("slurped <<%.*s>>\n", (int)(out->end - out->begin), out->begin); */ break; } } } #endif return rc; } /** Post-processor for certain token types. ie is the interpreter, t is the current token. target is where the results are written. If the token needs postprocessing then target's state will differ from t's when this function returns successfully. t->current will be continually modified by the post-processing to point to the current end of the tokenization process. After completion (on success), tokenization of the script may continue at t->current's location. On success target->ttype is set to the type of the original t->ttype value, but target->current's contents will reflect a different source range, a superset of t and some arbitrary number of tokens after t. Returns 0 on success. */ static int th1ish_tokenize_post( th1ish_interp * ie, cwal_simple_tokenizer * t, cwal_simple_tokenizer * target ){ int rc = 0; int const openerType = t ? t->ttype : 0; if(!target) target = t; else if(target != t) *target = *t; switch(openerType){ case TT_ChParenOpen: case TT_ChBraceOpen: case TT_ChScopeOpen: { rc = th1ish_slurp_braces( ie, t, &target->current ); /* dump_tokenizer( t, "After slurp-braces()"); */ if(!rc) { target->ttype = openerType; #if 0 MARKER("Slurped up a () or {}: <<<"); fwrite( target->current.begin, target->current.end - target->current.begin, 1, stdout ); puts(">>>"); /* MARKER("Char after slurp=%d (%c)\n", *t->current.end, *t->current.end); */ dump_tokenizer(target, "After slurping"); #endif } break; } default: break; } return rc; } /** Post-processor for TT_OpHeredocStart tokens. ie is the interpreter, tr is the current token state. tgt is where the results are written. If the token needs postprocessing then target's state will differ from t's when this function returns successfully. On success tgt->ttype is set to TT_ChScopeOpen and its contents behave more or less like a {...} block. That is, its start/end pointers reflect the content between the heredoc identifier begin/end markers. On succes tgt->current will reflect a different source range thant tr->current, a superset of t and some arbitrary number of tokens after tr. tr->current is adjusted so that after this returns successfully, th1ish_next_token(tr) will start tokenizing immediately after the heredoc's closing identifer. Returns 0 on success. */ static int th1ish_tokenize_heredoc( th1ish_interp * ie, cwal_simple_tokenizer * tr, cwal_simple_tokenizer * tgt ){ int rc = 0; char const * docBegin; char const * docEnd; char const * idBegin; char const * idEnd; char const * theEnd = NULL; cwal_size_t idLen; /* char const * idEnd; */ assert(tr && tgt); assert(TT_OpHeredocStart==tr->ttype); *tgt = *tr; idBegin = tgt->current.begin + 3; if(isspace(*idBegin)){ do{ ++idBegin; } while(isspace(*idBegin)); tgt->current.end = idBegin; } rc = th1ish_next_token(tgt); if(rc) goto tok_err; if(TT_Identifier != tgt->ttype){ return th1ish_toss_pos(ie, (unsigned char const *)tgt->current.begin, CWAL_SCR_SYNTAX, "Expecting identifier at start of HEREDOC."); } idBegin = tgt->current.begin; idEnd = tgt->current.end; idLen = idEnd - idBegin; docBegin = idEnd; if('\n'==*docBegin) ++docBegin; else while((' '==*docBegin) || ('\t'==*docBegin)) ++docBegin; rc = CWAL_SCR_SYNTAX; for( docEnd = docBegin; docEnd < tgt->end; ++docEnd ){ if(*docEnd != *idBegin) continue; else if(0 == memcmp( docEnd, idBegin, idLen)){ #if 1 char const * back = docEnd-1; theEnd = docEnd + idLen; if('\n'==*back) --docEnd; else for( ; (' '==*back) || ('\t'==*back); --back) --docEnd; rc = 0; break; #else char const check = *(docEnd + idLen); /* We can't reliably check to see if the trailing identifier has something after it because (A) we sometimes see trailing bits from outer (), {}, [] constructs and (B) X-strings needn't be NUL terminated. */ if(!check || (';'==check) || (','==check) || isspace(check)){ char const * back = docEnd-1; theEnd = docEnd + idLen; if('\n'==*back) --docEnd; else for( ; (' '==*back) || ('\t'==*back); --back) --docEnd; rc = 0; break; }else{ docEnd += idLen; } #endif } } if(rc){ rc = th1ish_toss_pos(ie, (unsigned char const *)docBegin, rc, "Did not find end of HEREDOC."); }else{ tr->current.end = theEnd; tgt->ttype = TT_ChScopeOpen; tgt->current.begin = docBegin; tgt->current.end = docEnd; #if 0 MARKER(("HEREDOC<%.*s> body: %.*s\n", (int)(idEnd-idBegin), idBegin, (int)(docEnd-docBegin), docBegin)); #endif } return rc; tok_err: { return th1ish_toss_t10n(ie, tgt, rc); } } /* th1ish_eval_atom() and th1ish_eval_0..5() are named so as a tip of the hat to Herbert Schildt. (And because i relied very much on his sample code for the overall structure.) Atoms include: - Literal values (strings, numbers) - Identifiers - Keywords - Ternay if expressions */ static int th1ish_eval_atom(th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); /** Evaluator for var decls, var assignments. */ static int th1ish_eval_0(th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); /** Process relational ops. */ static int th1ish_eval_1(th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); /** Process binary add/subtract and assignment */ static int th1ish_eval_2(th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); /** Process multiply/divide/modulo. */ static int th1ish_eval_3(th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); /** Unary +/-/~, prefix ++/-- */ static int th1ish_eval_4(th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); /** Interprets (lists,of,expressions), a single atom, or $functionCallsWithDollarPrefix ([func calls in braces] are handled as atoms). */ static int th1ish_eval_5(th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); /** Evalutator for for keyword tokens. */ static int th1ish_eval_keywords( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ); static int th1ish_eval_ternary_if(th1ish_interp * ie, cwal_value * lhs, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ); /** The entry-point for evaluating a single expression. It handles some common token cases like newlines, semicolons, and EOF, falling back to th1ish_eval_0(). */ static int th1ish_eval_entry( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ); /** Similar to th1ish_eval_chain() but pushes a scope before executing the code. If rv is not NULL then the result value will (on success) be written there and it will be scoped so that it will live at least as long as the currently running scope (the one active when this function is called). */ int th1ish_eval_chain_push_scope( th1ish_interp * ie, cwal_simple_token * head, cwal_simple_token ** next, cwal_value **rv ); /** Evaluates t and subsequent expressions as arguments to be passed to the given function by iteratively using th1ish_eval_0(). On success func is call()ed with selfObj as the "this" context (or the function itself as the context is selfObj is NULL). The selfObj value will be available the args->self passed to the cwal_callback_f() implementation wrapped by func. This processes calls for two different forms: $func arg0 ... argN func(args...) Returns 0 on success. */ static int th1ish_eval_func_collect_call(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv, cwal_value * selfObj, cwal_function * func, char ignoreEOL); /** Expects t to be a TT_ChScopeOpen, and evaluates its code via th1ish_eval_string(ie, addScope, ...code..., rv). Returns 0 on success. If addScope is greater than 0 and the eval returns CWAL_RC_RETURN, then the RETURN code is translated to 0. FIXME: don't do that translation here, or add a flag for it. FIXME: ensure that rv is arranged to survive after popping the scope. */ static int th1ish_eval_scope_string(th1ish_interp * ie, char addScope, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ); /** Typedef for "evaluator" callbacks. These are the recursive-descent parsers for an interpreter. ie is the interpreter instance. tok is the current token which we wish to evaluate it. If the callback does not know how to handle tok it must retun CWAL_SCR_CANNOT_CONSUME and not modify any of its arguments. On success *next must be assigned to the token after the last-consumed token and *rv must be set to the result value (it may be set to NULL). On error non-0 must be returned. Some parsers (looping/recursive ones) may have already set *rv and *next by the time they fail, and that is ok (it can help narrow down the position of the error). Upon returning if *next==tok and the function returns 0 then EOF should be assumed. All non-EOF parsers must consume input if they return success. It is illegal to pass a NULL for any argument. Implementations must keep in mind that "filler" tokens often litter the input chain (comments, whitespace, extra newlines) and they may need to account for those. */ typedef int (*th1ish_evaluator_f)( th1ish_interp * ie, cwal_simple_token * tok, cwal_simple_token ** next, cwal_value ** rv ); typedef struct th1ish_keyword th1ish_keyword; struct th1ish_keyword { int ttype; char const * word; unsigned char wlen; th1ish_evaluator_f parser; }; #define KW(W) static int th1ish_keyword_##W( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ) KW(array); KW(assert); KW(catch); KW(continue); KW(eval); KW(for); KW(get); KW(if); KW(nameof); KW(object); KW(proc); KW(refcount); KW(return); KW(set); KW(sweep); KW(throw); KW(typename); KW(var); KW(while); KW(builtin_value); KW(FILE_LINE); KW(SCOPE); KW(SRC); #if 0 KW(closure); KW(unref); #endif #undef KW static int th1ish_keyword_breakpoint( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ /** The sole purpose of this breakpoint is to act as way to drop debugger breakpoints into a script, such that one can relatively quickly jump straight to a particular part of the script. */ unsigned int line = 0; unsigned int col = 0; cwal_size_t nameLen = 0; unsigned char const * script = th1ish_script_name_for_pos(ie, (unsigned char const *)t->src.begin, &nameLen ); th1ish_line_col(ie, t->src.begin, &line, &col); if(!script){/*unused var warning*/} /* Place your debugging point around here and you'll have the script location information populated. This makes it easier to figure out which instance of a breakpoint you're running at. */ *next = t->right; return 0; } static th1ish_keyword th1ish_keywords[] = { /* Keep these ASCII-sorted by name - we rely on that in other places as a search optimization. */ /* { TOKEN_TYPE, NAME, NAME_LEN, th1ish_evaluator_f } */ {TT_Keyword__BREAKPOINT, "__BREAKPOINT",12, th1ish_keyword_breakpoint}, {TT_Keyword__COLUMN, "__COLUMN",8, th1ish_keyword_FILE_LINE }, {TT_Keyword__FILE, "__FILE",6, th1ish_keyword_FILE_LINE }, {TT_Keyword__LINE, "__LINE",6, th1ish_keyword_FILE_LINE }, {TT_Keyword__SCOPE, "__SCOPE",7, th1ish_keyword_SCOPE}, {TT_Keyword__SRC, "__SRC",5, th1ish_keyword_SRC }, {TT_KeywordSweep, "__sweep",7, th1ish_keyword_sweep}, {TT_KeywordArray, "array", 5, th1ish_keyword_array}, {TT_KeywordAssert, "assert",6, th1ish_keyword_assert}, {TT_KeywordBreak, "break",5, th1ish_keyword_return/*yes, return*/}, {TT_KeywordCatch, "catch",5, th1ish_keyword_catch}, {TT_KeywordVarDeclConst, "const", 5, th1ish_keyword_var}, {TT_KeywordContinue, "continue",8, th1ish_keyword_continue}, {TT_KeywordElse, "else", 4, 0 /* handled internally by 'if' */}, {TT_KeywordEval, "eval",4, th1ish_keyword_eval}, {TT_KeywordExit, "exit",4, th1ish_keyword_return}, {TT_KeywordFalse, "false", 5, th1ish_keyword_builtin_value}, {TT_KeywordFatal, "fatal",5, th1ish_keyword_return}, {TT_KeywordFor, "for",3, th1ish_keyword_for}, {TT_KeywordFunction, "function", 8, th1ish_keyword_proc}, {TT_KeywordGet, "get",3, th1ish_keyword_get}, {TT_KeywordIf, "if", 2, th1ish_keyword_if}, {TT_KeywordNameof, "nameof", 6, th1ish_keyword_nameof}, {TT_KeywordNull, "null", 4, th1ish_keyword_builtin_value}, {TT_KeywordObject, "object", 6, th1ish_keyword_object}, {TT_KeywordFunction, "proc",4, th1ish_keyword_proc}, {TT_KeywordPrototype, "prototype", 9, 0}, {TT_KeywordRefcount, "refcount", 8, th1ish_keyword_refcount}, {TT_KeywordReturn, "return",6, th1ish_keyword_return}, {TT_KeywordSet, "set",3, th1ish_keyword_set}, {TT_KeywordScope, "scope",5, th1ish_keyword_eval}, {TT_KeywordThrow, "throw",5, th1ish_keyword_throw}, {TT_KeywordToss, "toss",4, th1ish_keyword_return}, {TT_KeywordTrue, "true", 4, th1ish_keyword_builtin_value}, {TT_KeywordTypename, "typename",8, th1ish_keyword_typename}, {TT_KeywordUndef, "undefined", 9, th1ish_keyword_builtin_value}, {TT_KeywordUnset, "unset", 5, th1ish_keyword_set}, {TT_KeywordVarDecl, "var", 3, th1ish_keyword_var}, {TT_KeywordWhile, "while",5, th1ish_keyword_while}, #if 0 {TT_KeywordClosure, "closure",7, 0}, {TT_KeywordDo, "do",2, 0}, {TT_KeywordElse, "else",4, 0/*actually handled internally*/}, {TT_KeywordExit, "exit",4, 0}, {TT_KeywordForEach, "foreach",6, 0}, {TT_KeywordIn, "in",2, 0}, {TT_KeywordUnref, "unref", 5, 0}, #endif {0,0,0} }; /** An internal helper for th1ish_chain_postprocess() to work around this script-side problem: var a array[array [1,2,3]] a.0.1 That looks like (IDENTIFIER,DOT,DOUBLE) to the tokenizer. This function "undoes" that misunderstanding. Expects t->ttype to be TT_ChPeriod. If the following token is a TT_LiteralDouble then this code replaces that token with (INTEGER,DOT,INTEGER) tokens. (*next) is set to the the final token this function processes, _not_ the one-after-the-end like all of the rest of the API, because the 1 caller of this function requires it to be that way. The caller must continue processing at (*next)->right. Returns 0 on success, non-0 only on allocation error. */ static int th1ish_chain_fix_dot_double( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token **next){ int rc = 0; cwal_simple_token * dbl; cwal_simple_token * i1; cwal_simple_token * i2; cwal_simple_token * dot; char const * pos; start: assert(TT_ChPeriod==t->ttype); dbl = TNEXT(t); if(!dbl || (TT_LiteralDouble!=dbl->ttype)){ *next = t; return 0; } /* Split the double into INTEGER DOT INTEGER */ /* Create first INTEGER token */ i1 = th1ish_st_alloc( ie ); dot = i1 ? th1ish_st_alloc( ie ) : 0; i2 = dot ? dbl /*th1ish_st_alloc( ie )*/ : 0; if(!dot){ if(i1) th1ish_st_free_chain(ie, i1, 1); if(dot) th1ish_st_free_chain(ie, dot, 1); assert(!i2); rc = CWAL_RC_OOM; goto end; } t->right = i1; i1->right = dot; dot->right = i2; *next = i2 /*not dbl->right due to 'continue' behaviour in our caller */; /* The first integer token... */ i1->ttype = i2->ttype = TT_LiteralIntDec; pos = i1->src.begin = dbl->src.begin; for( ; '.' != *pos; ++pos ){} assert('.'==*pos); i1->src.end = pos; /* The DOT token */ dot->ttype = TT_ChPeriod; dot->src.begin = pos; dot->src.end = pos + 1; /* The second integer token... */ i2->src.begin = pos+1; assert(i2->src.end > i2->src.begin); /* dump_token(i1,"i1"); dump_token(dot,"dot"); dump_token(i2,"i2"); */ t = i2->right ? cwal_st_skip(i2->right) : 0; if(t && (TT_ChPeriod==t->ttype)){ goto start; } end: return rc; } /** Returns true if t is a TT_ChPeriod or TT_OpArrow, else false. */ static char th1ish_token_is_dot_op(cwal_simple_token const * t){ switch(t ? t->ttype : 0){ case TT_ChPeriod: case TT_OpArrow: return 1; default: return 0; } } /** Walks to the right through the given token chain and does some post-processing: - It converts Identifier tokens which contain keywords to Keyword tokens. - Tweaks "prototype" tokens to be of type TT_KeywordPrototype. Returns 0 on success. */ static int th1ish_chain_postprocess( th1ish_interp * ie, cwal_simple_token * t ){ int rc = 0; th1ish_keyword * k; cwal_size_t klen; cwal_simple_token * prev = 0; t = cwal_st_skip(t); for( ; !rc && t; t = TNEXT(t) ){ klen = t->src.end - t->src.begin; if(!klen) continue; switch(t->ttype){ case TT_Whitespace: case TT_EOF: case TT_EOL: /* Do not set (prev=t) so that the (x.y) check(s) below can work. */ continue; case TT_ChPeriod:{ cwal_simple_token * x = t; rc = th1ish_chain_fix_dot_double(ie, t, &x); /* dump_token(t,"t after fix-dot-double"); */ if(t==x) break; else continue; } case TT_LiteralDouble: if(prev && (TT_ChPeriod==prev->ttype)){ assert(!"FIXME: dot followed by float is " "not recognized how it needs to be."); /* fall through */ } default: break; } if(TT_Identifier != t->ttype){ prev = t; continue; } if((8==klen) && (0==cwal_compare_cstr("inherits", klen, t->src.begin, klen))){ /* We treat inherits as an operator, not keyword handler. */ t->ttype = TT_KeywordInstanceOf; prev = t; continue; } for( k = &th1ish_keywords[0]; k->word; ++k ){ if(k->wlen != klen) continue; else { int const cmp = /* memcmp(t->src.begin, k->word, klen) */ cwal_compare_cstr(k->word, klen, t->src.begin, klen); ; if(cmp>0) break /* Relies on th1ish_keywords being ASCII sorted by k->word. */ ; else if(0==cmp){ #if 1 if((k->ttype==TT_KeywordPrototype) && !th1ish_token_is_dot_op(prev)){ /* Not-strictly-necessary workaround: allow 'prototype' as an identifier outside the context of obj.prototype resolution (where we must treat it specially). */ t->ttype = TT_Identifier; } else #endif t->ttype = k->ttype; assert(1 || prev); break; } } } prev = t; } return rc; } static th1ish_keyword const * th1ish_st_keyword_get_for_type( int ttype ){ th1ish_keyword const * k; for( k = &th1ish_keywords[0]; k->word; ++k ){ if(k->ttype == ttype) return k; } return 0; } static th1ish_keyword const * th1ish_st_keyword_get( cwal_simple_token const * t ){ return t ? th1ish_st_keyword_get_for_type( t->ttype ) : NULL; } #if 0 static cwal_function * th1ish_check_function( cwal_value * v ){ return cwal_value_get_function(v); } #endif /** NULLs out IE->currentThis and IE->currentIdentKey. This is needed at the start of most top-level eval_N() routines to ensure that the dot-deref operation results do not polute down-stream processing. */ #define th1ish_reset_this(IE) (IE)->currentThis = (IE)->currentIdentKey = NULL char th1ish_props_can( th1ish_interp * ie, cwal_value const * v ){ while(v){ if(cwal_props_can(v)) return 1; v = th1ish_prototype_get(ie, v); } return 0; } cwal_value * th1ish_global_props(th1ish_interp * ie){ return ie ? cwal_scope_properties(ie->topScope) : NULL; } cwal_scope * th1ish_global_scope(th1ish_interp * ie){ return ie ? ie->topScope : NULL; } int th1ish_set_v( th1ish_interp * ie, unsigned char const * srcPos, cwal_value * self, cwal_value * key, cwal_value * v ){ int rc; char const *errMsg = 0; if(self){ if(th1ish_props_can(ie,self)){ cwal_array * ar; if(cwal_value_is_integer(key) && (ar=cwal_value_array_part(ie->e,self))){ /* Reminder: th1ish_value_array_part() ends up setting entries in arrays used as prototypes. e.g.: var a = array[], b = array[] b.3 = 'hi' assert b.3 !== a.3 // would fail but that is arguably expected. */ cwal_int_t const i = cwal_value_get_integer(key); if(i<0){ rc = CWAL_RC_RANGE; errMsg = "Array indexes may not be negative."; }else{ /*MARKER("Setting array index #%u\n",(unsigned)i); th1ish_dump_val(v,"array entry");*/ rc = cwal_array_set(ar, (cwal_size_t)i, v); if(rc) errMsg = "cwal_array_set() failed."; } } else if(!cwal_props_can(self)){ goto non_container; } else if(0==cwal_compare_str_cstr( cwal_value_get_string(key), "prototype", 9 )){ /* i don't like this "prototype" bit at all, but it's a necessary workaround... */ rc = cwal_value_prototype_set( self, v ); if(rc) errMsg = "cwal_value_prototype_set() failed"; } else if(cwal_props_can(self)){ rc = cwal_prop_set_v( self, key, v ); if(CWAL_RC_TYPE==rc) errMsg = "Invalid target type for assignment."; #if 0 else if(!v && (CWAL_RC_NOT_FOUND==rc)) rc = 0 /* for sanity's sake */; #endif } else { assert(!"Unhandled case"); } } else{ goto non_container; } }else{ #if 0 th1ish_dump_val(key,"KEY Setting scope var"); th1ish_dump_val(v,"VALUE Setting scope var"); th1ish_dump_val(self,"SELF Setting scope var"); #endif /** FIXME: disable a SET on a scope variable which is undeclared in all scopes, throw an error in that case. Requires a search+set (and set has to do its own search, again) or a flag to one of the cwal-level setters which tells it to fail if the var cannot be found. */ rc = th1ish_var_set_v( ie, v ? -1 : 0 /* don't allow 'unset' across scopes*/, key, v ); switch(rc){ case 0: case CWAL_RC_EXCEPTION: break; case CWAL_RC_ACCESS: errMsg = "Cannot assign to a const variable."; break; default: errMsg = "th1ish_var_set_v() failed."; break; } } if(errMsg && (CWAL_RC_EXCEPTION!=rc)){ rc = srcPos ? th1ish_toss_pos(ie, srcPos, rc, "%s", errMsg ) : th1ish_toss(ie, rc, "%s", errMsg ); } return rc; non_container: { cwal_size_t tlen = 0; char const * tn = cwal_value_type_name2(self, &tlen); return th1ish_toss_pos(ie, srcPos, CWAL_RC_TYPE, "Cannot set properties on " "non-container type '%.*s'.", (int)tlen, tn); } } int th1ish_set( th1ish_interp * ie, unsigned char const * srcPos, cwal_value * self, char const * key, cwal_size_t keyLen, cwal_value * v ){ int rc; char const *errMsg = 0; if(!key) return CWAL_RC_MISUSE; else if(!keyLen && *key) keyLen = strlen(key); if(self){ if(th1ish_props_can(ie,self)){ if(!cwal_props_can(self)){ goto non_container; } else if((9==keyLen) && 0==memcmp( key, "prototype", 9 )){ /* i don't like this "prototype" bit at all, but it's a necessary workaround... */ rc = cwal_value_prototype_set( self, v ); if(rc) errMsg = "cwal_value_prototype_set() failed."; } else{ rc = cwal_prop_set( self, key, keyLen, v ); if(CWAL_RC_TYPE==rc) errMsg = "Invalid target type for assignment."; #if 0 else if(!v && (CWAL_RC_NOT_FOUND==rc)) rc = 0 /* for sanity's sake */; #endif } } else{ goto non_container; } }else{ /** FIXME: disable a SET on a scope variable which is undeclared in all scopes, throw an error in that case. Requires a search+set (and set has to do its own search, again) or a flag to one of the cwal-level setters which tells it to fail if the var cannot be found. */ rc = th1ish_var_set( ie, v ? -1 : 0 /* don't allow 'unset' across scopes*/, key, keyLen, v ); if(rc && (CWAL_RC_EXCEPTION!=rc)){ errMsg = "th1ish_var_set_v() failed."; } } if(errMsg && (CWAL_RC_EXCEPTION!=rc)){ rc = srcPos ? th1ish_toss_pos(ie, srcPos, rc, "%s", errMsg ) : th1ish_toss(ie, rc, "%s", errMsg ); } return rc; non_container: { cwal_size_t tlen = 0; char const * tn = cwal_value_type_name2(self, &tlen); return th1ish_toss_pos(ie, srcPos, CWAL_RC_TYPE, "Cannot set properties on " "non-container type '%s'.", (int)tlen, tn); } } /** Internal th1ish_get_v() impl. Recursively looks up self's protototype chain until it finds the given property key. Reminder to self: this is what fixed the problem that in some callbacks, this.xyx was always resolving to undefined. */ static cwal_value * th1ish_get_v_proxy( th1ish_interp * ie, cwal_value * self, cwal_value * key ){ cwal_value * v = NULL; assert(ie && self && key); while(!v && self){ if(cwal_props_can(self)) v = cwal_prop_get_v(self, key); if(!v) self = th1ish_prototype_get(ie, self); } return v; } /** C-string equivalent of th1ish_get_v_proxy(). */ static cwal_value * th1ish_get_proxy( th1ish_interp * ie, cwal_value * self, char const * key, cwal_size_t keyLen ){ cwal_value * v = NULL; assert(ie && self && key); while(!v && self){ if(cwal_props_can(self)) v = cwal_prop_get(self, key, keyLen); if(!v) self = th1ish_prototype_get(ie, self); } return v; } int th1ish_get_v( th1ish_interp * ie, cwal_value * self, cwal_value * key, cwal_value ** rv ){ int rc; char const *errMsg = 0; if(self){ cwal_array * ar; if(cwal_value_is_integer(key) && (ar=cwal_value_array_part(ie->e,self))){ cwal_int_t const i = cwal_value_get_integer(key); if(i<0){ rc = CWAL_RC_RANGE; errMsg = "Array indexes may not be negative."; }else{ *rv = cwal_array_get(ar, (cwal_size_t)i); rc = 0; } } /* i don't like this "prototype" bit at all, but it's a necessary workaround... */ else if(0==cwal_compare_str_cstr( cwal_value_get_string(key), "prototype", 9 )){ *rv = th1ish_prototype_get( ie, self ); rc = 0; } else { *rv = th1ish_get_v_proxy( ie, self, key ); rc = 0; } }else{ *rv = th1ish_var_get_v( ie, -1, key ); rc = 0; } if(errMsg && (CWAL_RC_EXCEPTION!=rc)){ assert(rc); rc = th1ish_toss(ie, rc, "%s", errMsg ); } return rc; } int th1ish_get( th1ish_interp * ie, cwal_value * self, char const * key, cwal_size_t keyLen, cwal_value ** rv ){ int rc = 0; if(!key) return CWAL_RC_MISUSE; else if(!keyLen && *key) keyLen = strlen(key); if((keyLen==9) && (0==memcmp( key, "prototype", 9 ))){ *rv = th1ish_prototype_get( ie, self ); rc = 0; } else if(self){ *rv = th1ish_get_proxy( ie, self, key, keyLen ); rc = 0; }else{ *rv = th1ish_var_get( ie, -1, key, keyLen ); rc = 0; } return rc; } /** Adds or subtracts lhs and rhs, depending on the value of 'add' (true==addition, false==subtraction). Returns 0 on success. Result is written to *rv. srcPos is the source code position of the operation, for error reporting purposes. It may be NULL, but don't complain if there is no proper error location info. */ int th1ish_values_addsub( th1ish_interp * ie, unsigned char const * srcPos, cwal_value * lhs, char add, cwal_value * rhs, cwal_value **rv ); /** Multiplies, divides, or modulos lhs and rhs, depending on the value of mode: mode: <0==divide, >0==multiply, 0==modulo codePos should point to the script code position being evaluated, or NULL if evaluating outside of script evaluation. Returns 0 on success. Result is written to *rv. */ static int th1ish_values_multdivmod( th1ish_interp * ie, unsigned char const *codePos, cwal_value * lhs, char mode, cwal_value * rhs, cwal_value **rv ); /** Implements these assignment operators: =, +=, -=, /=, *=, %= Returns 0 on success. */ static int th1ish_eval_assignment(th1ish_interp * ie, cwal_value * self, cwal_value * key, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_value * vL = 0; cwal_value * vR = 0; unsigned char const * srcPos = (unsigned char const *)t->src.begin; int const op = t->ttype; /* cwal_value * nextThis = 0; */ /* cwal_value * nextKey = 0; */ /* cwal_value * prevThis = 0; */ /* cwal_value * prevKey = 0; */ /* start: */ switch(op){ case TT_OpAssign: break; case TT_OpShiftLeftEq: case TT_OpShiftRightEq: case TT_OpOrEq: case TT_OpXorEq: case TT_OpAndEq: case TT_OpPlusEq: case TT_OpMinusEq: case TT_OpMultEq: case TT_OpDivEq: case TT_OpModEq: if(TH1ISH_F_EVAL_NO_ASSIGN & ie->flags){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "The '%.*s' assignment operator " "may not be chained.", (int)(t->src.end-t->src.begin), t->src.begin); } ie->flags |= TH1ISH_F_EVAL_NO_ASSIGN; break; default: assert(!"cannot happen"); return CWAL_SCR_CANNOT_CONSUME; } *next = t = TNEXT(t) /* Reminders to self: When we encounter an operator we treat following newlines as blanks, skipping over them. */; th1ish_reset_this(ie); rc = th1ish_eval_0(ie, t, next, &vR) /* Reminder: we use eval_0 instead of eval_1 so that assignment chaining is processed recursively and so that we can better report errors when non-'=' assignment ops are chained. An iterative approach (like i'd prefer) does not mix well with ie->currentThis and friends out of the box. We */ ; if(rc || IE_SKIP_MODE) { goto end; } /* nextThis = ie->currentThis; */ /* nextKey = ie->currentIdentKey; */ assert(vR); switch(op){ case TT_OpAssign: break; #define GET \ rc = th1ish_get_v(ie, self, key, &vL); \ if(!vL) rc = th1ish_toss_pos(ie, srcPos, CWAL_RC_TYPE, \ "Invalid left-hand side (type %s) " \ "for '%.*s' assignment.",\ cwal_value_type_name(self), 2, (char const *)srcPos) #define DOOP(MODE) \ GET; \ else if(!rc) rc = th1ish_values_addsub(ie, srcPos, vL, (MODE), vR, &vR) case TT_OpPlusEq: DOOP(1); break; case TT_OpMinusEq: DOOP(0); break; #undef DOOP #define DOOP(MODE) \ GET; \ else if(!rc) rc = th1ish_values_multdivmod(ie, srcPos, vL, (MODE), vR, &vR) case TT_OpMultEq: DOOP(1); break; case TT_OpDivEq: DOOP(-1); break; case TT_OpModEq: DOOP(0); break; #undef DOOP /* FIXME: we're bypassing operator overloading here */ case TT_OpShiftRightEq: case TT_OpShiftLeftEq: case TT_OpOrEq: case TT_OpXorEq: case TT_OpAndEq:{ GET; else if(!rc){ cwal_int_t const iL = cwal_value_get_integer(vL); cwal_int_t const iR = cwal_value_get_integer(vR); cwal_int_t v; switch(op){ case TT_OpShiftRightEq: v = iL>>iR; break; case TT_OpShiftLeftEq: v = iL<e, v); if(!vR) rc = CWAL_RC_OOM; break; } break; } #undef GET default: assert(!"cannot happen"); rc = CWAL_SCR_CANNOT_CONSUME; goto end; } if(!rc){ rc = th1ish_set_v( ie, (unsigned char const *)t->src.begin, self, key, vR ); if(!rc) *rv = vR; } end: ie->flags &= ~TH1ISH_F_EVAL_NO_ASSIGN; return rc; } /** Returns non-0 (true) if t is a TT_Identifier token followed immediately (blanks do not count) by a TT_OpAssign token, else 0 (false). */ static char th1ish_is_assignment_to_ident(th1ish_interp * ie, cwal_simple_token * t){ if(!t || (TT_Identifier!=t->ttype)) return 0; t = th1ish_skip(ie, t->right); switch(t ? t->ttype : 0){ case TT_OpShiftLeftEq: case TT_OpShiftRightEq: case TT_OpOrEq: case TT_OpXorEq: case TT_OpAndEq: case TT_OpPlusEq: case TT_OpMinusEq: case TT_OpMultEq: case TT_OpDivEq: case TT_OpModEq: case TT_OpAssign: return 1; default: return 0; } } int th1ish_eval_0(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; char isIdentAssign; int const oldIdentPolicy = ie->identPolicy; int identPolicy; t = 1 ? cwal_st_skip(t) : th1ish_skip(ie, t) /* prefered but currently breaks stuff */ ; isIdentAssign = th1ish_is_assignment_to_ident(ie, t); identPolicy = isIdentAssign ? 1 /* Fetch this up-coming identifier in its string form */ : oldIdentPolicy; *next = t; th1ish_reset_this(ie); ie->identPolicy = identPolicy; ++ie->sweepGuard; rc = th1ish_eval_1(ie, t, next, rv); th1ish_eval_check_exit(ie,rc, t, next); ie->identPolicy = oldIdentPolicy; if(rc) goto end; /* Check for assignments: identifier = ... obj.prop = ... */ t = th1ish_skip(ie,*next); switch(t ? t->ttype : 0){ case TT_OpShiftLeftEq: case TT_OpShiftRightEq: case TT_OpOrEq: case TT_OpXorEq: case TT_OpAndEq: case TT_OpPlusEq: case TT_OpMinusEq: case TT_OpMultEq: case TT_OpDivEq: case TT_OpModEq: case TT_OpAssign: *next = t; if(ie->currentIdentKey){ assert(ie->currentThis); /* th1ish_dump_val(ie->currentThis,"assignment LHS ie->currentThis"); */ /* th1ish_dump_val(ie->currentIdentKey,"assignment LHS ie->currentIdentKey"); */ rc = th1ish_eval_assignment(ie, ie->currentThis, ie->currentIdentKey, t, next, rv ); }else if(cwal_value_is_string(*rv)){ /* th1ish_dump_val(*rv,"assignment LHS string"); */ rc = th1ish_eval_assignment(ie, NULL, *rv, t, next, rv ); }else{ rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE, "Invalid left-hand side for '%.*s' assignment.", \ (t->src.end-t->src.begin), t->src.begin); } default: break; } end: --ie->sweepGuard; return rc; } int th1ish_eval_1(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ cwal_simple_token * n; cwal_value * vL = 0; cwal_value * vR = 0; int op = 0; int rc; char bL = 0; char bR = -1; switch(t ? t->ttype : 0){ case TT_EOF: return 0/*CWAL_SCR_EOF*/; default: break; } rc = th1ish_eval_2(ie, t, next, rv); th1ish_eval_check_exit(ie,rc, t, next); if(rc || cwal_st_eof(*next)) return rc; assert((*next != t) || ie->expando.ar); the_beginning: vL = *rv; n = *next; /* Logical ops, comparisons, and bit-shifts. */ switch(n->ttype){ case TT_OpLogOr: case TT_OpLogAnd: bL = cwal_value_get_bool(vL); /* Fall through */ case TT_CmpLt: case TT_CmpLe: case TT_CmpGt: case TT_CmpGe: case TT_CmpEq: case TT_CmpEqStrict: case TT_CmpNe: case TT_CmpNeStrict:{ char doSkip = 0; /* get RHS */ op = n->ttype; /* If we got a true LHS with an OR op, or a false LHS with an AND op, turn on skip-mode to implement short-circuit logic. */ if(IE_SKIP_MODE || (bL && (op==TT_OpLogOr)) || (!bL && (op==TT_OpLogAnd))){ bR = 0; doSkip = 1; ++ie->skipLevel; } #if 1 rc = th1ish_eval_2( ie, TNEXT(n), &n, rv ); #else switch(op){ case TT_CmpLt: case TT_CmpLe: case TT_CmpGt: case TT_CmpGe: rc = th1ish_eval_1( ie, TNEXT(n), &n, rv ); break; default: rc = th1ish_eval_2( ie, TNEXT(n), &n, rv ) /* th1ish_eval_1( ie, TNEXT(n), &n, rv ) */ /** Experimentally using eval_1() so that: (1==0 || 2<3) evaluates properly. Hmm. But that breaks: 7 === (1==3 || 7) */ ; break; } #endif *next = n; if(doSkip){ --ie->skipLevel; } if(rc) return rc; vR = *rv; /*if( cwal_st_eof(n) ) return 0;*/ break /* deal with the op down below */; } default: /* there was no comparison */ return 0; } if(rc) return rc; else if(IE_SKIP_MODE) goto tail_check; switch(op){ case TT_OpLogAnd: case TT_OpLogOr:{ bL = cwal_value_get_bool(vL); if(-1==bR) bR = cwal_value_get_bool(vR); if(TT_OpLogOr == op){ if(bL) *rv = vL; #if 0 else if(bR) *rv = vR; else *rv = cwal_new_bool(0); #endif else *rv = vR; /*th1ish_dump_val(vL,"Logical || LHS"); th1ish_dump_val(vR,"Logical || RHS"); */ }else{ *rv = cwal_new_bool(bL && bR); } goto tail_check; break; } /** The strict === and !== operators must not invoke the value comparison if the types are not identical. */ case TT_CmpEqStrict: case TT_CmpNeStrict: if(vL == vR){ rc = -3; } else if(cwal_value_type_id(vL) != cwal_value_type_id(vR)){ rc = -2; } break; default: break; } if(-3 == rc){ /* a===a, a!==a */ rc = (TT_CmpEqStrict==op) ? 1 : 0/*invert for !==*/; /*fall through*/ } if(-2 == rc){ /* x===y or x!==y of differing types */ rc = (TT_CmpEqStrict==op) ? 0 : 1/*invert for !==*/; } else { rc = cwal_value_compare(vL, vR); #if 0 MARKER("compare rc=%d\n", rc); th1ish_dump_val(vL,"LHS"); th1ish_dump_val(vR,"RHS"); #endif switch(op){ case TT_CmpLt: rc = rc < 0; break; case TT_CmpLe: rc = rc <= 0; break; case TT_CmpGt: rc = rc > 0; break; case TT_CmpGe: rc = rc >= 0; break; case TT_CmpEqStrict: rc = 0==rc; break; case TT_CmpEq: /* Reminder: we now have the problem that: assert null != undefined assert !(null == undefined) Which is somewhat non-intuitive/non-JavaScripty. Not sure how to best handle that without special-casing null/undefined. */ rc = 0==rc; break; case TT_CmpNeStrict: rc = 0!=rc; break; case TT_CmpNe: rc = 0!=rc; break; default: assert(!"CANNOT HAPPEN"); return CWAL_RC_CANNOT_HAPPEN; } } *rv = /*(rc<0) ? vL : ((rc>0) ? vR : vL)*/ cwal_new_bool( rc ? 1 : 0 ) /* reminder: does not alloc (cannot fail) */ ; rc = 0; tail_check: if(!rc){ n = th1ish_skip(ie,*next); switch(n ? n->ttype : 0){ case TT_CmpLt: case TT_CmpLe: case TT_CmpGt: case TT_CmpGe: case TT_CmpEqStrict: case TT_CmpEq: case TT_CmpNeStrict: case TT_CmpNe: case TT_OpLogAnd: case TT_OpLogOr: /* MARKER("Continuing &&/||...\n"); */ vL = *rv; t = *next = n; goto the_beginning; default: break; } } return rc; } /** Operator overloading handling. Checks lhs to see if it has a Function property named opName (which is opLen bytes long). If so, it calls that function like: [f.apply lhs [lhs,rhs]] // SEE BELOW!!! If lhs has no such function it then checks rhs in same way IF tryRhsOp is true. If it finds a function, it calls that function and sets *rc to its return value, then returns true (non-0). On any error (running the function or fetching the operator) then *theRc is set to the error value (and this function may return either true or false in that case). Note that if the lhs does not contain the operator and rhs does then the call is made slightly differently: [f.call rhs lhs rhs] If rhs is NULL then it is treated like a nullary function: [f.call lhs] i.e. the argument order is the same but the 'this' is different. */ static char th1ish_operator_proxy( th1ish_interp * ie, unsigned char const * srcPos, char const * opName, cwal_size_t opLen, cwal_value * lhs, cwal_value * rhs, char tryRhsOp, cwal_value **rv, int * theRc ){ cwal_value * fCheck = lhs; cwal_value * fv = 0; int rc; assert(opLen); opCheck: /* Check for member function operator*, operator%, or operator/. */ rc = th1ish_get( ie, fCheck, opName, opLen, &fv ); if(rc){ if((CWAL_RC_NOT_FOUND!=rc) || (tryRhsOp && (fCheck==rhs))){ *theRc = rc; return 0; } /*else fall through and through and try with rhs...*/ } else if(fv && cwal_value_is_function(fv)){ th1ish_stack_entry sta = th1ish_stack_entry_empty; cwal_value * argv[2] = {0,0}; argv[0] = lhs; argv[1] = rhs; if(srcPos) th1ish_strace_push_pos( ie, srcPos, &sta ); *theRc = cwal_function_call( cwal_value_get_function(fv), fCheck, rv, rhs ? 2 : 0, argv ); if(srcPos) th1ish_strace_pop( ie ); return 1; } if(rhs && tryRhsOp && (lhs==fCheck) && (lhs!=rhs)){ fCheck = rhs; goto opCheck; } return 0; } int th1ish_values_addsub( th1ish_interp * ie, unsigned char const * srcPos, cwal_value * lhs, char doAdd, cwal_value * rhs, cwal_value **rv ){ switch(cwal_value_type_id(lhs)){ case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL: case CWAL_TYPE_BOOL: case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE: break; default:{ static cwal_size_t const opLen = 9; char const * opStr = doAdd ? "operator+" : "operator-"; int rc = 0; /* Check for operator overloads... */ if(th1ish_operator_proxy( ie, srcPos, opStr, opLen, lhs, rhs, 0 /* addition is not (necessarily) commutative for abitrary types (only numbers) */, rv, &rc ) || rc) return rc; /* else fall through... */ break; } } { const cwal_double_t iL = cwal_value_get_double( lhs ); if(doAdd && (iL==0.0)) *rv = rhs; else { cwal_double_t const iR = cwal_value_get_double( rhs ); if(doAdd && (iR==0.0)) *rv = lhs; else{ const cwal_double_t res = doAdd ? (iL+iR) : (iL-iR); *rv = cwal_value_is_double(lhs) ? cwal_new_double(ie->e, res ) : cwal_new_integer(ie->e, (cwal_int_t)res ) ; } } return *rv ? 0 : CWAL_RC_OOM; } } int th1ish_values_multdivmod( th1ish_interp * ie, unsigned char const *codePos, cwal_value * lhs, char mode, cwal_value * rhs, cwal_value **rv ){ int rc = 0; char buf[10] = {'o','p','e','r','a','t','o','r','X',0}; buf[8] = (mode<0) ? '/' : ((mode>0) ? '*' : '%'); switch(cwal_value_type_id(lhs)){ case CWAL_TYPE_NULL: case CWAL_TYPE_BOOL: case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_STRING: break; default: /* Check for operator overloads... */ if(th1ish_operator_proxy(ie, codePos, buf, 9, lhs, rhs, (mode>0) /* only multiplication is commutative here */, rv, &rc) || rc) return rc; /* else fall through... */ } if(mode<0){ /* Division */ cwal_double_t const iR = cwal_value_get_double( rhs ); if(0.0==iR){ char const * msg = "Divide by zero."; return codePos ? th1ish_toss_pos(ie, codePos, CWAL_SCR_DIV_BY_ZERO, msg) : th1ish_toss(ie, CWAL_SCR_DIV_BY_ZERO, msg); }else{ cwal_double_t const iL = cwal_value_get_double( lhs ); /* Optimizations: if 0==lhs, return lhs if 1==rhs, return lhs. */ if((0.0==iL) && cwal_value_is_number(lhs)) *rv = lhs; else if(1.0==iR) *rv = lhs; else { cwal_int_t const res = iL / iR; *rv = cwal_value_is_double(lhs) ? cwal_new_double(ie->e, res ) : cwal_new_integer(ie->e, (cwal_int_t)res) ; } } }else{ if(0==mode){ /* Modulo */ cwal_int_t const iR = cwal_value_get_integer( rhs ); if(0.0==iR){ char const * msg = "Modulo by zero."; return codePos ? th1ish_toss_pos(ie, codePos, CWAL_SCR_DIV_BY_ZERO, msg) : th1ish_toss(ie, CWAL_SCR_DIV_BY_ZERO, msg); }else{ cwal_int_t const iL = cwal_value_get_integer( lhs ); /* Optimization: if 1==lhs && 1!=rhs, return lhs. If the rhs is 1 it is already optimized b/c cwal doesn't allocate for value 0. */ *rv = ((1==iL) && (iR!=1)) ? lhs : cwal_new_integer(ie->e, iL % iR); } }else{ /* Multiplication */ cwal_double_t const iL = cwal_value_get_double( lhs ); cwal_double_t const iR = cwal_value_get_double( rhs ); /* Optimizations: if 1.0==lhs, return rhs, and vice versa. if either is 0.0, return that value. */ if(1.0==iR) *rv = lhs; else if((0.0==iL) && cwal_value_is_number(lhs)) *rv = lhs; else if(1.0==iL) *rv = rhs; else if((0.0==iR) && cwal_value_is_number(rhs)) *rv = rhs; else{ cwal_double_t const res = iL * iR; *rv = cwal_value_is_double(lhs) ? cwal_new_double(ie->e, res ) : cwal_new_integer(ie->e, (cwal_int_t)res) ; } } } return *rv ? 0 : CWAL_RC_OOM; } static int th1ish_eval_binary_addsub_shift(th1ish_interp * ie, cwal_value * vL, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_value * vR = 0; int ttype; unsigned char const * srcPos = (unsigned char const *)t->src.begin; start: ttype = t->ttype; switch(ttype){ case TT_OpShiftRight: case TT_OpShiftLeft: case TT_OpAdd: case TT_OpSubtract: break; default: assert(!"CANNOT HAPPEN"); return CWAL_SCR_CANNOT_CONSUME; } *next = t = TNEXT(t) /* Reminders to self: When we encounter an operator we treat following newlines as blanks, skipping over them. */; rc = th1ish_eval_3(ie, t, next, &vR); if(rc) return rc; if(!IE_SKIP_MODE){ switch(ttype){ case TT_OpShiftRight: case TT_OpShiftLeft:{ char const * opStr = (TT_OpShiftLeft==ttype) ? "operator<<" : "operator>>"; if(!th1ish_operator_proxy(ie, srcPos, opStr, 10 /*opStr len*/, vL, vR, 0, rv, &rc) && !rc){ cwal_int_t const iL = cwal_value_get_integer(vL); cwal_int_t const iR = cwal_value_get_integer(vR); cwal_int_t const v = (TT_OpShiftLeft==ttype) ? (iL<>iR); *rv = cwal_new_integer(ie->e, v); rc = *rv ? 0 : CWAL_RC_OOM; } break; } case TT_OpAdd: case TT_OpSubtract: rc = th1ish_values_addsub(ie, srcPos, vL, (TT_OpAdd==ttype), vR, rv); break; default: break; } } /* dump_token(t,"Checking for trailing + - << >>"); */ if(!rc){ t = *next = th1ish_skip(ie,*next); switch(t ? t->ttype : 0){ case TT_OpShiftRight: case TT_OpShiftLeft: case TT_OpAdd: case TT_OpSubtract: vL = *rv; goto start; break; default: break; } } return rc; } /** */ static int th1ish_eval_binary_multdivmod(th1ish_interp * ie, cwal_value * vL, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ); int th1ish_eval_2(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_value * vL = 0; cwal_simple_token * n = 0; th1ish_reset_this(ie); rc = th1ish_eval_3(ie, t, next, rv); th1ish_eval_check_exit(ie,rc, t, next); if(rc || *next==t) return rc; vL = *rv ? *rv : cwal_value_undefined(); n = *next = th1ish_skip(ie,*next); switch(n ? n->ttype : 0){ case TT_OpShiftLeft: case TT_OpShiftRight: case TT_OpAdd: case TT_OpSubtract: rc = th1ish_eval_binary_addsub_shift( ie, vL, n, next, rv ); break; default: break; } return rc; } static int th1ish_eval_binary_multdivmod(th1ish_interp * ie, cwal_value * vL, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_value * vR = 0; int ttype; start: ttype = t->ttype; switch(ttype){ case TT_OpMultiply: case TT_OpDivide: case TT_OpModulo: break; default: assert(!"CANNOT HAPPEN"); return CWAL_SCR_CANNOT_CONSUME; } *next = t = TNEXT(t) /* Reminder: we treat newlines in this run as blanks */ ; th1ish_reset_this(ie); rc = th1ish_eval_4(ie, t, next, &vR); if(rc) return rc; else if(!IE_SKIP_MODE){ rc = th1ish_values_multdivmod(ie, (unsigned char const *)t->src.begin, vL, ((TT_OpModulo==ttype) ? 0 : ((TT_OpMultiply==ttype) ? 1 : -1)), vR, rv); if(rc) return rc; } else if(!*rv) *rv = cwal_value_undefined(); t = th1ish_skip(ie,*next); if(!rc) switch(t ? t->ttype : 0){ case TT_OpDivide: case TT_OpModulo: case TT_OpMultiply: /*rc = th1ish_eval_binary_multdivmod( ie, *rv, t, next, rv );*/ vL = *rv; goto start; break; default: break; } return rc; } /** Implements operators (|, &, <<, >>). vL is the left hand side value, t is the operator. Result is saved in *rv. *next will be set to the next token to evaluate. Returns 0 on success. */ static int th1ish_eval_binary_and_or(th1ish_interp * ie, cwal_value * vL, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_value * vR = 0; cwal_int_t iL, iR, res; int ttype; start: ttype = t->ttype; switch(ttype){ case TT_ChCaret: case TT_ChPipe: case TT_ChAmp: break; default: assert(!"CANNOT HAPPEN"); return CWAL_SCR_CANNOT_CONSUME; } *next = t = TNEXT(t) /* Reminders to self: When we encounter an operator we treat following newlines as blanks, skipping over them. */; rc = th1ish_eval_3(ie, t, next, &vR); if(rc) { return rc; } if(!IE_SKIP_MODE){ assert(vR); iL = cwal_value_get_integer( vL ); iR = cwal_value_get_integer( vR ); switch(ttype){ case TT_ChCaret: res = iL^iR; break; case TT_ChPipe: res = iL|iR; break; case TT_ChAmp: res = iL&iR; break; default: assert(!"cannot happen"); } *rv = cwal_new_integer(ie->e, (cwal_int_t)res ); if(!*rv) rc = CWAL_RC_OOM; } /* dump_token(t,"Checking for trailing | or &"); */ if(!rc){ t = *next = th1ish_skip(ie,*next); switch(t ? t->ttype : 0){ case TT_ChCaret: case TT_ChPipe: case TT_ChAmp: vL = *rv; goto start; break; #if 0 case TT_ChQuestion: rc = th1ish_eval_ternary_if( ie, *rv, t, next, rv ); break; #endif default: break; } } return rc; } int th1ish_eval_3(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_value * vL = 0; cwal_simple_token * n = 0; th1ish_reset_this(ie); rc = th1ish_eval_4(ie, t, next, rv); th1ish_eval_check_exit(ie,rc, t, next); if(rc) return rc; vL = *rv ? *rv : cwal_value_undefined(); n = *next; /* cwal_st_skip(*next); */ switch(n ? n->ttype : 0){ case TT_ChCaret: case TT_ChPipe: case TT_ChAmp: rc = th1ish_eval_binary_and_or( ie, vL, n, next, rv ); break; case TT_OpDivide: case TT_OpModulo: case TT_OpMultiply: rc = th1ish_eval_binary_multdivmod( ie, vL, n, next, rv ); break; default: break; } #if 1 /* Reminder to self: we have to catch the '?' as a "tail check", instead of having it somewhere up the eval chain, because if we do not then: [print true ? 1 : 2] gets 2 arguments: (true, 1) */ if(!rc){ t = cwal_st_skip(*next); switch(t ? t->ttype : 0){ case TT_ChQuestion: rc = th1ish_eval_ternary_if( ie, *rv, t, next, rv ); break; default: break; } } #endif return rc; } int th1ish_eval_4(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ enum { OP_UMINUS = -1, OP_UPLUS = 1, OP_BNEG = 2 /* ACHTUNG: -1, +1 are magic values below */}; int rc; char const * opString = NULL; cwal_size_t opStrLen = 0; int const op = t->ttype; /* check for unary +/- or logical NOT */ int prefixOp = 0; unsigned doNot = 0 /* "There is no try." */; switch(op){ case TT_ChTilde: prefixOp = OP_BNEG; t = *next = t->right; opString = "operator~"; opStrLen = 9; break; case TT_OpAdd: prefixOp = OP_UPLUS; t = *next = t->right; opString = "operator+unary"; opStrLen = 14; break; case TT_OpSubtract: prefixOp = OP_UMINUS; opString = "operator-unary"; opStrLen = 14; t = *next = t->right; break; case TT_ChExcl:{ /* Process a series of '!' to avoid a downstream parsing error if we expect recursion to handle the second one (it doesn't). */ cwal_simple_token * mv = t; for( ; mv && (TT_ChExcl==mv->ttype); mv = th1ish_skip(ie,mv->right)){ ++doNot; } if(cwal_st_eof(mv)){ return th1ish_toss_token(ie, t, CWAL_SCR_UNEXPECTED_EOF, "Unexpected EOF after '!'"); } t = mv; *next = t; break; } default: break; } th1ish_reset_this(ie); rc = th1ish_eval_5(ie, t, next, rv); th1ish_eval_check_exit(ie,rc, t, next); if(rc) return rc; else if(prefixOp && !IE_SKIP_MODE){ /* Apply unary +/-/~ */ char overridden = 0; cwal_type_id const rvTid = cwal_value_type_id(*rv); switch(rvTid){ /* Do not allow overloading of this op on these types: */ case CWAL_TYPE_UNDEF: case CWAL_TYPE_NULL: case CWAL_TYPE_BOOL: case CWAL_TYPE_INTEGER: case CWAL_TYPE_DOUBLE: case CWAL_TYPE_STRING: break; default: /* Check for overloaded operator. */ assert(opString && opStrLen); overridden = th1ish_operator_proxy( ie, (unsigned char const *)t->src.begin, opString, opStrLen, *rv, NULL, 0, rv, &rc); break; } if(!overridden && !rc){ switch(prefixOp){ case OP_BNEG: *rv = cwal_new_integer(ie->e, (cwal_int_t)~cwal_value_get_integer( *rv ) ); break; default:{ /* Apply numeric unary +/- */ char const doDbl = cwal_value_is_double(*rv) /* This leads to the following not working: +"1.2" Results in 0 because cwal_value_get_integer() doesn't like "1.2". But i'm not sure how to fix that without special-casing it. */ ; cwal_double_t v = doDbl ? cwal_value_get_double( *rv ) : cwal_value_get_integer( *rv ); *rv = doDbl ? cwal_new_double( ie->e, v * prefixOp ) : cwal_new_integer( ie->e, v * prefixOp ); break; } } if(!*rv) rc = CWAL_RC_OOM; } } else if(doNot){ /* Workaround for a parsing error with "!!", since the logical ! is handled at a level which we won't wrap around to on the RHS token. */ char b = cwal_value_get_bool(*rv); for( ; doNot> 0; --doNot ) b = !b; *rv = cwal_new_bool( b ); } return rc; } /** Evaluates t, which must be a (...) group. Each entry in the list gets added to rvList. On input *rvLen must be the maximum length of the rvList array. On output it will be the number of items populated in rvList. If the list is too short an error is triggered. To only capture the last value in the list, pass a NULL rvLen and a non-NULL rvList with room for one element. On success, rvList[0] will contain the last evaluated result value. Returns 0 on success. Advances *next to the token in the chain following the closing ')' (or where an error occurred during parsing of a contained expression). */ static int th1ish_eval_list( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, uint16_t * rvLen, cwal_value ** rvList ); /** Handles obj.prop... dereferencing (iteratively over any number of dots). Each left-hand-side of a dot must resolve to a container type. Each right-hand side may be an arbitrary expression which is a key in the container on the left. The resulting value may be of an arbitrary type. On success it sets ie->currentThis to the "this" object from the dereference and ie->currentIdentKey to the key, and updates rv to the referenced value. e.g. in (obj.foo.bar), "this"===foo and the key is "bar". It behaves slightly differently that scope lookup: a missing property is returned as the undefined value, as opposed to throwing an unknown identifier exception. If an undefined value is followed by another dot then it will throw (because it cannot dereference the undefined value). Examples: OK: obj.foo.bar Eqivalent: obj.("foo").bar Eqivalent: obj.(1,2,3,"foo").bar Eqivalent: obj.([proc f {} {return "foo"}]).bar Exception: obj.someUndefinedThing.bar The property "prototype" requires special handling, and effectively resolves to the result of cwal_value_prototype_get(ie->currentThis). */ static int th1ish_eval_dot_deref( th1ish_interp * ie, cwal_value * self, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ); int th1ish_eval_5(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_simple_token * origin = t; char doDollar = 0; char const skipDot = (TH1ISH_F_EVAL_NO_DOT & ie->flags); if(cwal_st_eof(t)) return CWAL_SCR_EOF; ie->flags &= ~TH1ISH_F_EVAL_NO_DOT /* Required so that this embedded call will work: var a array [1,2,3] set a.([a.length]-1) 'TheEnd' // here Maybe we really need a counter instead of a flag? */ ; #define TH1ISH_ENABLE_EXPANDO 0 #if TH1ISH_ENABLE_EXPANDO /* @expando support: expand @ARRAY "inline" */ next_expando: if(ie->expando.ar){ cwal_size_t n, p; assert(ie->expando.next); assert(ie->expando.atPos); n = cwal_array_length_get(ie->expando.ar); p = ie->expando.pos++; if(pexpando.ar, p); if(!*rv) *rv = cwal_value_undefined(); if(ie->expando.pos==n){ t = *next = ie->expando.returnTo; ie->expando.next = NULL; ie->expando.returnTo = NULL; ie->expando.atPos = NULL; ie->expando.ar = NULL; ie->expando.pos = 0; /* cwal_dump_v(*rv,"Re-set expando"); */ /* dump_token(t,"Next token after expando"); */ } else { /** Fudge the jump-back value so we end up here again. */ *next = ie->expando.atPos; } /* cwal_dump_v(*rv, "Expando"); */ return 0; } else { t = *next = th1ish_skip(ie,ie->expando.returnTo); ie->expando.next = NULL; ie->expando.returnTo = NULL; ie->expando.atPos = NULL; ie->expando.ar = NULL; ie->expando.pos = 0; /* dump_token(t, "Re-setting for empty expando"); */ /* cwal_dump_v(*rv, "rv"); */ assert(t); rc = 0; switch(t ? t->ttype : 0){ /** Problem: empty list at the end of an expression will evaluate to [] instead of "nothing", and our engine has no way of dealing with "nothing" except for EOF. We need a way of dealing with/enabling this. */ case TT_EOF: /* Avoids a "cannot consume" error but evaluates as an empty array :(. */ return CWAL_SCR_EOF; default: break; } /* fall through ... */ /** NASTY BUG: [@array] inside a sub-parse will end evaluation of the whole token chain because ie->expando.returnTo is point to (@array)->right, which is the EOF of that sub-parse. This does not affect [foo @array] because that is handled in */ } } if(TT_ChAt==t->ttype){ /** Treat @ARRAY as an "inlined list"... */ cwal_value * av = NULL; cwal_array * ar; if(ie->expando.atPos && (ie->expando.atPos!=t)){ return th1ish_toss_token(ie, t, CWAL_RC_MISUSE, "Nested @-expansion is not supported (or possible?)."); } if(!ie->expando.next){ cwal_simple_token * atPos = t; t = *next = TNEXT(t); rc = th1ish_eval_0(ie, t, next, &av); if(rc) return rc; if(!av || !(ar=th1ish_value_array_part(ie, av))){ return th1ish_toss_token(ie, t, CWAL_RC_TYPE, "Expecting Array after '@'."); }else{ ie->expando.atPos = atPos; ie->expando.next = *next; ie->expando.returnTo = *next; ie->expando.ar = ar; } *rv = av; } goto next_expando; } else #endif /* TH1ISH_ENABLE_EXPANDO */ if(TT_ChDollar==t->ttype){ *next = t = th1ish_skip(ie,t->right) /* We really want TNEXT(t) here, but that breaks with: $foo // no args blah It calls it as [foo blah ...] */ ; /* dump_token(t,"Got dollar here"); */ th1ish_reset_this(ie); doDollar = 1; } switch(t ? t->ttype : 0){ case TT_ChSemicolon: rc = 0; *next = t->right; /* Leave *rv as is */ break; #if 1 case TT_ChParenOpen:{ cwal_value * argv = 0; rc = th1ish_eval_list(ie, t, next, NULL, &argv ); if(rc) return rc; /* assert(argv); */ *rv = argv ? argv : cwal_value_undefined() /* Are we just hiding a bug here? */; break; } #endif default: rc = th1ish_eval_atom(ie, t, next, rv); th1ish_eval_check_exit(ie,rc, t, next); break; } if(!skipDot && !rc){ /* Check for '.' dereference... */ t = cwal_st_skip(*next); if(th1ish_token_is_dot_op(t)){ cwal_value * self = *rv; /* th1ish_dump_val(self,"Running dot deref on this object"); */ rc = th1ish_eval_dot_deref( ie, self, t, next, rv ); th1ish_eval_check_exit(ie,rc, t, next); /* th1ish_dump_val(ie->currentThis,"ie->currentThis after dot-deref"); */ /* dump_token(*next,"next token after dot-deref"); */ if(!rc){ /* assert(ie->currentThis); */ } } } /* if(doDollar) dump_token(*next,"After eval-1, before $ deref"); */ if(!rc && doDollar){ /* Do $func ... calls. */ /* $functionCall i don't like this all that much, but it's better than having to wrap all calls in [...]. */ cwal_function * func = 0; if(IE_SKIP_MODE || (func = cwal_value_get_function(*rv))){ t = th1ish_skip(ie,*next); /* th1ish_dump_val(ie->currentThis,"ie->currentThis at $call() point"); */ /* dump_token(t,"collect+calling from this token"); */ rc = /* For proc'd functions this tokenizes the func params declared at the proc point if argc==0! */ th1ish_eval_func_collect_call( ie, t, next, rv, ie->currentThis, func, 0 ) ; th1ish_eval_check_exit(ie,rc, t, next); th1ish_reset_this( ie ); } else{ rc = th1ish_toss_token(ie, origin, CWAL_SCR_SYNTAX, "Expecting function after '$'."); } } while(!rc && (ie->flags & TH1ISH_F_CONVENTIONAL_CALL_OP)){ /* Check for func() calls. */ cwal_function * func = 0; if(IE_SKIP_MODE || (func = cwal_value_get_function(*rv))){ #if 0 if(IE_SKIP_MODE) *rv = cwal_value_undefined() /* Workaround for an uninitialized stack-living (*rv) somewhere up the call stack :/. Thanks to valgrind for noticing. */; #endif t = th1ish_skip(ie,*next); if(!cwal_st_isa(t, TT_ChParenOpen)) break; rc = th1ish_eval_func_collect_call( ie, t, next, rv, ie->currentThis, func, 1 ) ; th1ish_eval_check_exit(ie,rc, t, next); th1ish_reset_this( ie ); } break; } #if 0 /* Reminder to self: we have to catch the '?' as a "tail check", instead of having it somewhere up the eval chain, because if we do not then: [print true ? 1 : 2] gets 2 arguments: (true, 1) */ if(!rc){ t = th1ish_skip(ie,*next); switch(t ? t->ttype : 0){ case TT_ChQuestion: rc = th1ish_eval_ternary_if( ie, *rv, t, next, rv ); break; default: break; } } #endif if(!rc){ /* check for (X instanceof Y)... */ t = cwal_st_skip(*next); if(t && (TT_KeywordInstanceOf==t->ttype)){ cwal_value * vL = *rv; th1ish_reset_this(ie); t = *next = TNEXT(*next); rc = th1ish_eval_5(ie, t, next, rv); th1ish_eval_check_exit(ie,rc, t, next); if(!rc){ *rv = cwal_value_derives_from( ie->e, vL, *rv ) ? cwal_value_true() : cwal_value_false() ; } } } #if 0 if(rc && (CWAL_RC_EXCEPTION!=rc)){ rc = th1ish_toss_token(ie, origin, rc, "Non-Exception error #%d (%s)", rc, cwal_rc_cstr(rc)); } #endif return rc; } /** A lame attempt to try to determine if the token t represents part of a chain after "OBJECT." needs to be eval()'d or can be used as an identifier. Returns true (non-0) if it things the next part (starting at t) should be eval()'d, else false (in which case it thinks it is an identifier or literal which can be used as-is). It is not terribly smart, and can probably be easily confused. */ static char th1ish_set_might_need_eval( th1ish_interp *ie, cwal_simple_token * t ); /** Evaluator for the dot operator (EXPR.EXPR). */ int th1ish_eval_dot_deref( th1ish_interp * ie, cwal_value * self, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_value * v = 0; switch(t->ttype){ case TT_ChPeriod: case TT_OpArrow: break; default: assert(!"CANNOT HAPPEN"); return CWAL_SCR_CANNOT_CONSUME; } start: if(!IE_SKIP_MODE && !th1ish_props_can(ie, self)){ /* th1ish_dump_val(v,"Invalid 'self' for dot deref"); */ /* th1ish_dump_val(self,"Invalid 'self' for dot deref"); */ rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE, "Invalid left-hand type (%s) for " "the dot operator.", cwal_value_type_name(self)); goto end; } ie->currentThis = self; /* th1ish_dump_val(ie->currentThis,"ie->currentThis"); */ t = *next = TNEXT(t); #if 0 { int const oldIdentPolicy = ie->identPolicy; v = cwal_value_undefined(); if(!th1ish_set_might_need_eval(ie, t)){ ie->identPolicy = 1; } rc = th1ish_eval_5( ie, t, next, &v ); ie->identPolicy = oldIdentPolicy; th1ish_eval_check_exit(ie,rc, t, next); if(rc) goto end; } #else if(TT_KeywordPrototype==t->ttype){ /* We need special handling for the word prototype so that we are not forced to set it as a property on all objects. While that approach works, it muddles up any JSON output and looping, in that the prototype will appear there. Aaarrrggg: this will break once someone does: set foo.prototype = ... because lookups after that will not get the new value. Need to special-case that in 'set', it seems. */ rc = th1ish_st_create_value(ie, t, &ie->currentIdentKey); if(rc) goto end; v = th1ish_prototype_get(ie, self); *next = t->right; assert(ie->currentIdentKey || IE_SKIP_MODE); goto post; } else if(/*don't like this special handling!*/ cwal_st_isa(t,TT_Identifier) || th1ish_st_keyword_get(t)){ rc = th1ish_st_create_value(ie, t, &v); if(rc) goto end; assert(v); t = *next = (*next)->right; /* t = *next = th1ish_skip(ie,(*next)->right); */ } else { cwal_value * xrv = self; /* ++ie->identPolicy */ /* We want unknown identifiers to evaluate to their string value for this case. */; ie->flags |= TH1ISH_F_EVAL_NO_DOT; rc = th1ish_eval_5( ie, t, next, &xrv ); assert(!(TH1ISH_F_EVAL_NO_DOT & ie->flags)); th1ish_eval_check_exit(ie,rc, t, next); ie->currentThis = self; /* --ie->identPolicy; */ /* th1ish_dump_val(self,"self"); */ /* th1ish_dump_val(xrv,"xrv"); */ if(rc) goto end; v = xrv; } #endif /* th1ish_dump_val(self,"Dot-deref self"); */ /* th1ish_dump_val(v,"Dot-deref key"); */ ie->currentIdentKey = v; rc = th1ish_get_v( ie, self, v, &v ); if(rc) goto end; post: /* th1ish_dump_val(v,"Dot-deref val"); */ if(!v) v = *rv = cwal_value_undefined(); else { *rv = v; ie->currentThis = self /* Ensure it wasn't overwritten by a sub-parse */ ; assert(self); } check_tail: if(!rc){ t = th1ish_skip(ie,*next) /* We need this instead of cwal_st_skip() so that this doesn't throw: const x = someObj.method (x && ...) // here, might look like a func call */ ; if(th1ish_token_is_dot_op(t)){ *next = t; self = *rv; goto start; } /* When conventionalFuncCalling() is enabled we have to account for that here to enable them to be chained. */ else if((ie->flags & TH1ISH_F_CONVENTIONAL_CALL_OP) && cwal_st_isa(t,TT_ChParenOpen)){ /* Check for func() calls. */ cwal_function * func = 0; if(IE_SKIP_MODE || (func = cwal_value_get_function(*rv))){ /* cwal_value * oldRv = *rv; */ rc = th1ish_eval_func_collect_call( ie, t, next, rv, ie->currentThis, func, 1 ) ; th1ish_eval_check_exit(ie,rc, t, next); ie->currentThis = *rv /* Very important for proper 'this' in chaining. */ ; if(!rc) goto check_tail; }else{ rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "'(' following a value of type '%s' is not " "allowed when conventional call() syntax " "is enabled.", cwal_value_type_name(*rv)); } } } end: if(rc){ ie->currentThis = NULL; } return rc; } static int th1ish_eval_literal_number(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_value * v = 0; cwal_engine * e = ie->e; #define RC rc = v ? CWAL_RC_OK : CWAL_RC_OOM switch(t->ttype){ case TT_LiteralIntDec: if(!IE_SKIP_MODE){ v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin, NULL, 10)); RC; } break; case TT_LiteralIntOctal: if(!IE_SKIP_MODE){ v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+1/*0*/, NULL, 8)); t->ttype = TT_LiteralIntDec; RC; } break; case TT_LiteralIntHex: if(!IE_SKIP_MODE){ v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+2/*0x*/, NULL, 16)); t->ttype = TT_LiteralIntDec; RC; } break; case TT_LiteralDouble: if(!IE_SKIP_MODE){ v = cwal_new_double(e, (cwal_double_t)strtod(t->src.begin, NULL)); RC; } break; default: rc = CWAL_SCR_CANNOT_CONSUME; break; } *rv = v ? v : cwal_value_undefined(); if(!rc) { *next = t->right; } #undef RC return rc; } static int th1ish_eval_literal_string(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = CWAL_SCR_CANNOT_CONSUME; switch( t->ttype ){ case TT_LiteralStringDQ: case TT_LiteralStringSQ: case TT_ChBraceOpen: case TT_ChParenOpen: case TT_ChScopeOpen:{ *next = t->right; rc = IE_SKIP_MODE ? 0 : th1ish_st_create_value( ie, t, rv ); if(!rc){ assert(*rv || IE_SKIP_MODE); } break; } default: break; } return rc; } int th1ish_eval_identifier(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ if(TT_Identifier!=t->ttype) return CWAL_SCR_CANNOT_CONSUME; else if(IE_SKIP_MODE){ *next = t->right; return 0; } else { cwal_value * v = 0; cwal_value * iden = 0; int rc = th1ish_st_create_value( ie, t, &iden ); if(rc) return rc; *next = t->right; /* untested: if(IE_SKIP_MODE) return 0; */ /* FIXME? Return here if IE_SKIP_MODE? */ #if 1 if(1 == ie->identPolicy){ v = iden; } else { assert(iden); v = cwal_scope_chain_search_v( cwal_scope_current_get(ie->e), -1, iden, NULL ); if(!v){ if(-1==ie->identPolicy){ v = iden; } else { cwal_string * id = cwal_value_get_string(iden); /* dump_token(t,"unknown identifier"); */ rc = th1ish_toss_token(ie, t, CWAL_RC_NOT_FOUND, "Unknown identifier '%.*s'", cwal_string_length_bytes(id), cwal_string_cstr(id)); } } } #else if(ie->identPolicy>0){ v = t->value; } else { assert(t->value); v = cwal_scope_chain_search_v( cwal_scope_current_get(ie->e), -1, t->value, NULL ); if(!v){ rc = th1ish_toss_token( ie->e, t, CWAL_RC_NOT_FOUND, "Unknown identifier '%s'", cwal_value_get_cstr(t->value, 0)); } } #endif if(!rc) *rv = v; return rc; } } int th1ish_eval_list( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, uint16_t * rvLen, cwal_value ** rvList ){ /* Read list of (expr1 [,...exprN]). */ int rc = 0; char const * code = t->src.begin; cwal_size_t codeLen = t->src.end - t->src.begin; uint16_t const argc = rvLen ? *rvLen : (rvList ? 1 : 0); uint16_t count = 0; cwal_value * vv = 0; int const oldIdentLevel = ie->identPolicy; cwal_simple_token * head = 0; cwal_simple_token * origin = t; #if 1 /* Maybe let the baser-most evaluators do this, to catch syntax errors with more precision? */ if(!codeLen || IE_SKIP_MODE) { if(rvLen) *rvLen = 0; if(rvList) rvList[0] = cwal_value_undefined(); *next = t->right; return 0; } #endif assert(argc); if(TT_ChParenOpen != t->ttype) return CWAL_SCR_CANNOT_CONSUME; if(!argc) return CWAL_RC_MISUSE; rc = codeLen ? th1ish_tokenize_all( ie, code, codeLen, &head ) : 0; if(rc) return rc; else if(!head){ *rvList = cwal_value_undefined(); return 0; } /* Loop through the following expressions and collect their their values into rvList. */ ie->identPolicy = 0 /* We need to temporarily move out of "identPolicy" mode so that things like this can work: obj.subobj.([func ...]) */ ; vv = rvList[0]; if(!vv) vv = rvList[0] = cwal_value_undefined(); *next = t = cwal_st_skip(head); /* dump_token(*next, "token following '('"); */ for( ; t && (TT_EOF!=t->ttype); t = cwal_st_skip(*next)){ char doBreak = 1; assert(t); if(argc>1 && count==argc){ rc = CWAL_SCR_TOO_MANY_ARGUMENTS; goto end; } /* dump_token(t,"t"); */ rc = th1ish_eval_0(ie, t, next, &vv); th1ish_eval_check_exit(ie,rc, t, next); if(rc/* || *next==t */){ goto end; } assert(*next); assert((*next != t) || ie->expando.next); *next = cwal_st_skip(*next); /* dump_token(*next,"*next after eval-0"); */ if(argc>1){ rvList[count] = vv; *rvLen = ++count; } else rvList[0] = vv; /* th1ish_dump_val(vv,"List part"); */ if( cwal_st_eof(*next) ){ /* dump_token((*next)->right, "Closing (list). Next token"); */ break; } switch((*next)->ttype){ case TT_ChParenClose: #if 1 /* dump_token(*next, "')' in parens."); */ doBreak = 1; break; #endif case TT_ChComma: /* Inconsistency: (space separated lists) not working here. */ case TT_ChSpace: case TT_HTab: case TT_Whitespace: case TT_CR: case TT_EOL: *next = TNEXT(*next); doBreak = 0; break; default: /* dump_token(*next,"Breaking out of (group)."); */ /* cwal_dump_v(vv,"RC of (group)"); */ assert(!rc); break; } if(doBreak){ break; /*return cwal_st_toss(ie->e, *next, CWAL_SCR_SYNTAX, "Missing comma.");*/ } /* dump_token(*next,"next token"); */ } /* dump_token(*next,"end token"); */ end: ie->identPolicy = oldIdentLevel; if(head){ th1ish_st_free_chain(ie, head, 1); } #if 0 /* i would like to do this, but it nukes any temporaries the up-stream might be about to get via *rv. */ if(!rc){ cwal_scope_sweep(cwal_scope_current_get(ie->e)); } #endif *next = rc ? origin : th1ish_skip(ie,origin->right) /* Remember that *next likely lives in the head chain, so we need to set it whether we succeed or not. */ ; /* dump_token(*next, "token following ')'"); */ return rc; } int th1ish_eval_keywords( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ th1ish_keyword const * kw = th1ish_st_keyword_get(t); if(!kw || !kw->parser) return CWAL_SCR_CANNOT_CONSUME; else if(ie->identPolicy>0){ int rc = th1ish_st_create_value(ie, t, rv); if(rc) return rc; *next = t->right; return 0; } else { int rc = 0; rc = kw->parser(ie, t, next, rv); /* th1ish_dump_val(*rv, "Post-keyword rv"); */ return rc; } } /** Returns true if t is NULL or t->ttype represents an "end of expression" or EOF token. */ static char th1ish_st_eox( cwal_simple_token const * t ); int th1ish_keyword_refcount( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; assert(TT_KeywordRefcount==t->ttype); *next = t = th1ish_skip(ie,t->right) /* Note that this one does not skip EOLs like some keywords do. */; if(th1ish_st_eox(t)){ rc = th1ish_toss_token(ie, t, CWAL_SCR_UNEXPECTED_EOF, "Expecting expression after 'refcount'."); }else{ *rv = cwal_value_undefined() /* required for skip mode(?) and empty "return;". */; rc = th1ish_eval_0( ie, t, next, rv ); if(!rc){ *rv = cwal_new_integer( ie->e, (cwal_int_t)cwal_value_refcount(*rv) ); } } return rc; } void th1ish_exit_value_set( th1ish_interp * ie, cwal_value * v ){ /** Unlike a return value, we can stuff an exit result directly into the top scope and not have to deal with bubbling it up the call stack... */ assert(ie->topScope); if(v){ cwal_value_rescope( ie->topScope, v ); } ie->exitValue = v; /* we _might_ want to consider adding a reference to make sure a subsequent sweep does not wipe it out. Na. */ if(0 && v && !cwal_value_refcount(v)) cwal_value_ref(v); } int th1ish_keyword_return( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; int const op = t->ttype; switch(op){ case TT_KeywordReturn: case TT_KeywordExit: case TT_KeywordFatal: case TT_KeywordBreak: break; case TT_KeywordToss: if(!ie->catchLevel){ return th1ish_toss_token(ie, t, CWAL_RC_MISUSE, "Cannot 'toss' outside of a 'catch' " "or across function call boundaries."); } break; default: assert(!"CANNOT HAPPEN"); return CWAL_RC_CANNOT_HAPPEN; } *next = t = th1ish_skip(ie,t->right) /* Note that these do not skip EOLs like some keywords do. */; *rv = cwal_value_undefined() /* required for skip mode(?) and empty "return;". */; if(th1ish_st_eox(t)){ rc = 0; }else{ rc = th1ish_eval_entry( ie, t, next, rv ) /* Reminder using _entry() instead of _0() ONLY to work around a semicolon reporting as a cannot-consume error :/ */; } if(!rc && !IE_SKIP_MODE) switch(op){ case TT_KeywordBreak: case TT_KeywordReturn: case TT_KeywordToss: th1ish_result_set( ie, *rv ) /* We assume we're in our own scope and will be popped momentarily, and this forces the value to survive an additional scope. */ ; switch(op){ case TT_KeywordBreak: rc = CWAL_RC_BREAK; break; case TT_KeywordReturn: rc = CWAL_RC_RETURN; break; case TT_KeywordToss: rc = TH1ISH_RC_TOSS; break; } break; case TT_KeywordFatal: th1ish_toss_token_val( ie, t, CWAL_RC_FATAL, *rv ); /* fall through */ case TT_KeywordExit: th1ish_exit_value_set(ie, *rv); /* th1ish_dump_val(ie->exitValue,"EXIT VALUE"); */ rc = (TT_KeywordExit==op) ? CWAL_RC_EXIT : CWAL_RC_FATAL; break; default: assert(!"CANNOT HAPPEN"); break; } return rc; } int th1ish_keyword_continue( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; switch(t ? t->ttype : 0){ case 0: rc = CWAL_SCR_UNEXPECTED_EOF; break; case TT_KeywordContinue: *rv = cwal_value_undefined(); *next = t->right; rc = CWAL_RC_CONTINUE; break; default: rc = CWAL_SCR_CANNOT_CONSUME; break; } return IE_SKIP_MODE ? 0 : rc; } int th1ish_keyword_typename( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; assert(TT_KeywordTypename==t->ttype); *next = t = TNEXT(t); if(!IE_SKIP_MODE && (TT_Identifier==t->ttype)){ cwal_value * v = 0; cwal_value * key = 0; /* For Identifiers, check if it is defined, and return 'undefined' if it is not. */ rc = th1ish_st_create_value(ie,t, &key); if(rc) return rc; /** Check if the identifier can be resolved. If not, returned "undefined" as the typename. */ v = cwal_scope_chain_search_v( cwal_scope_current_get(ie->e), -1, key, NULL ); if(!v){ /* TODO: recycle this string somewhere. Set aside an object for that purpose in ie, e.g. ie->stash. If interning is on, that's not needed. */ v = cwal_new_string_value(ie->e, cwal_value_type_name(cwal_value_undefined()), 0); if(!v) return CWAL_RC_OOM; *rv = v; *next = th1ish_skip(ie,t->right); return 0; } /* else fall through and evaluate it. */ } rc = th1ish_eval_1( ie, t, next, rv ) /* Reminder: we should arguably use eval_0 so that we get assignments, but that leads to an inconsistency with most other operators. Using eval_1 requires a parens around: typename (a = 3) */ ; if(rc || IE_SKIP_MODE) return rc; else { cwal_size_t tlen = 0; char const * tn; if(*rv) tn = cwal_value_type_name2(*rv, &tlen); if(!tn){ tn = cwal_value_type_id_name(CWAL_TYPE_UNDEF); tlen = strlen(tn); } *rv = cwal_new_string_value(ie->e, tn, tlen); return *rv ? 0 : CWAL_RC_OOM; } } int th1ish_keyword_nameof( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_simple_token * idTok; assert(TT_KeywordNameof==t->ttype); *next = t = TNEXT(t); if(IE_SKIP_MODE){ *next = TNEXT(*next); return 0; } if(TT_Identifier!=t->ttype){ return th1ish_toss_token(ie, t, CWAL_RC_MISUSE, "nameof requires an identifer argument."); } idTok = t; t = *next = th1ish_skip(ie,t->right); #if 0 switch(t ? t->ttype : 0){ case TT_ChPeriod: case TT_OpArrow: /** TODO: if the following looks like it might be OBJ.KEY then use eval_0(t) to evaluate it, then look at ie->currentIdentKey. However, this precludes: nameof (foo).bar and similar. */ default: break; } #endif rc = th1ish_st_create_value(ie, idTok, rv); if(rc) return rc; *next = th1ish_skip(ie,idTok->right); return 0; } th1ish_interp * th1ish_args_interp( cwal_callback_args const * args ){ return cwal_args_state(args, &th1ish_interp_empty); } /** Continually parses all tokens in t until EOF or a ']'. Each expression evaluated is appended the given cwal_array. If count is not NULL then on return it will hold the number of values appended to the array. Returns 0 on success. It is intended that this be used to parse token chains derived from '[' tokens (which, during tokenization, get special handling). */ static int th1ish_collect_into_array(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_array * dest, cwal_size_t * count ){ int rc; cwal_size_t argc = 0; cwal_simple_token * origin = t; t = cwal_st_skip(t); if(cwal_st_eof(t)){ /*rc = CWAL_SCR_EOF; goto end;*/ rc = 0; goto end; } /* dump_token(t,"array parsing"); */ *next = t; rc = 0; for( ; !cwal_st_eof(t); t = TNEXT(*next) ){ cwal_value * v = 0; char doBreak = 0; /* dump_token(t,"list parsing"); */ *next = t; switch(t->ttype){ case TT_ChComma: rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Too many commas in list."); doBreak = 1; continue; case TT_ChScopeClose: case TT_ChBraceClose: /*happens in empty []. assert(!"We were not really expecting this.");*/ *next = t->right; doBreak = 1; break; case TT_EOF: /* We allow EOF because these chains are processed as sub-parses. */ *next = t; doBreak = 1; break; case TT_EOL: case TT_NL: case TT_CRNL: assert(!"Cannot happen due to TNEXT()"); continue; default: break; } if(rc) goto end; else if(doBreak) break; /* dump_token(t,"Evaling list element"); */ rc = th1ish_eval_1( ie, t, next, &v ); if(rc) goto end; /* th1ish_dump_val(v, "list entry"); */ rc = cwal_array_append( dest, v ? v : cwal_value_undefined() ); if(rc) goto end /* do not throw: this is likely an OOM */; ++argc; } end: if(!rc){ *next = origin->right; } if(count) *count = argc; return rc; } int th1ish_keyword_array(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_array * ar; cwal_simple_token * argChain = 0; cwal_simple_token * bogoNext = 0; cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * pushed = &pushedScope_; assert(TT_KeywordArray==t->ttype); t = TNEXT(t); switch(t ? t->ttype : 0){ case TT_ChBraceOpen: case TT_ChScopeOpen: break; default: return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Expecting '[' or '{' after array keyword."); } if(IE_SKIP_MODE){ *rv = cwal_value_undefined(); *next = t->right; return 0; } ar = th1ish_new_array(ie); if(!ar) return CWAL_RC_OOM; /** Reminder: we cannot sweep() after this because of: (1+8) && array[1,2,3] That would sweep up the LHS (the integer 9, which has already been evaluated but is not anywhere with a reference held do it. But we _can_ safely add a new scope, because insertion of the new temporaries into the array will upscope them to the array's owning scope. */ rc = cwal_scope_push(ie->e, &pushed); if(rc) { pushed = 0; goto end; } rc = th1ish_tokenize_all( ie, t->src.begin, t->src.end - t->src.begin, &argChain ); if(rc){ assert(!argChain); goto end; } *next = t->right; rc = th1ish_collect_into_array( ie, argChain, &bogoNext, ar, NULL ); end: if(argChain){ th1ish_st_free_chain( ie, argChain, 1 ); } if(pushed){ cwal_scope_pop(ie->e); } if(!rc){ *rv = cwal_array_value(ar); } else { cwal_array_unref(ar); } return rc; } /** Continually parses all key/value pairs in t until EOF or a '}'. Each expression evaluated is appended the given cwal_array. If count is not NULL then on return it will hold the number of values set in the destination object. Returns 0 on success. It is intended that this be used to parse token chains derived from '{' tokens (which, during tokenization, get special handling). Syntax: KEY: VALUE [, KEY: VALUE...] */ static int th1ish_inline_object(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value * dest, cwal_size_t * count ){ int rc; cwal_size_t argc = 0; cwal_simple_token * origin = t; t = cwal_st_skip(t); if(cwal_st_eof(t)){ /*rc = CWAL_SCR_EOF; goto end;*/ rc = 0; goto end; } /* dump_token(t,"array parsing"); */ *next = t; rc = 0; for( ; !cwal_st_eof(t); t = TNEXT(*next) ){ cwal_value * key = 0; cwal_value * v = 0; char doBreak = 0; /* dump_token(t,"list parsing"); */ *next = t; switch(t->ttype){ case TT_ChComma: rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Stray comma in object literal."); doBreak = 1; break; case TT_ChScopeClose: /*happens on empty objects: assert(!"We were not really expecting this.");*/ *next = t->right; doBreak = 1; break; case TT_EOF: /* We allow EOF because these chains are processed as sub-parses. */ *next = t; doBreak = 1; break; case TT_EOL: case TT_NL: case TT_CRNL: assert(!"Cannot happen due to TNEXT()"); continue; default: break; } if(rc) goto end; else if(doBreak) break; /* dump_token(t,"Evaling list element"); */ /* For ease of use, keys may be entered as identifiers... */ #if 0 if(cwal_st_isa(t,TT_Identifier)){ th1ish_st_create_value(ie, t, &key); *next = t->right; }else { rc = th1ish_eval_5( ie, t, next, &key ); if(rc) goto end; } #else { int const oldIdentPolicy = ie->identPolicy; ie->identPolicy = 1; rc = th1ish_eval_5( ie, t, next, &key ); ie->identPolicy = oldIdentPolicy; if(rc) goto end; } #endif assert(key); t = *next; *next = cwal_st_skip(*next); if(!cwal_st_isa(*next,TT_ChColon)){ rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Expecting ':' after key."); goto end; } *next = TNEXT(*next) /* skip colon */; if(cwal_st_isa(*next,TT_EOF)){ rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Unexpected EOF in object literal."); } else { t = *next; rc = th1ish_eval_0( ie, t, next, &v ); } if(rc) goto end; rc = cwal_prop_set_v( dest, key, v ? v : cwal_value_undefined() ); if(rc) goto end /* do not throw: this is likely an OOM */; ++argc; } end: if(!rc){ *next = origin->right; } if(count) *count = argc; return rc; } int th1ish_keyword_object(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_value * obj; cwal_simple_token * argChain = 0; cwal_simple_token * bogoNext = 0; cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * pushed = &pushedScope_; assert(TT_KeywordObject==t->ttype); t = TNEXT(t); if(!cwal_st_isa(t,TT_ChScopeOpen)){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Expecting '{' after object keyword."); } if(IE_SKIP_MODE){ *rv = cwal_value_undefined(); *next = t->right; return 0; } obj = th1ish_new_object(ie); if(!obj) return CWAL_RC_OOM; /** Reminder: we cannot sweep() after this because of: (1+8) && array[1,2,3] That would sweep up the LHS (the integer 9, which has already been evaluated but is not anywhere with a reference held do it. But we _can_ safely add a new scope, because insertion of any new temporaries into the object will upscope them to the object's owning scope (as well as give them a reference). */ rc = cwal_scope_push(ie->e, &pushed); if(rc){ pushed = 0; goto end; } rc = th1ish_tokenize_all( ie, t->src.begin, t->src.end - t->src.begin, &argChain ); if(rc){ assert(!argChain); goto end; } *next = t->right; rc = th1ish_inline_object( ie, argChain, &bogoNext, obj, NULL ); end: if(argChain){ th1ish_st_free_chain( ie, argChain, 1 ); } if(pushed){ cwal_scope_pop(ie->e); } if(!rc){ *rv = obj; } else { cwal_value_unref(obj); } return rc; } /** Internal property key for storing "imported symbols" in a Function instance. ACHTUNG: duplicated in th1ish_protos.c! */ #define FUNC_SYM_PROP "$imported$" #define FUNC_SYM_PROP_LEN (sizeof(FUNC_SYM_PROP)-1) #define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args) /** Script usage: assert expression If expression is false then we trigger a cwal-side exception with a code of CWAL_RC_ASSERT, containing the text of the expression token(s) from the expression. Reminder: due to how some token types are post-processed, e.g. comments are elided and {strings} are trimmed, the output messages might contain this which exist in the sources but might not be what the user expects to see. e.g.: (assert {one} === "two") The string part of that assertion, for purposes of creating the error mesage, is: one} === "two" */ int th1ish_keyword_assert(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; assert(TT_KeywordAssert==t->ttype); t = th1ish_skip(ie,t->right); *next = t; rc = th1ish_eval_0( ie, t, next, rv ); if(rc || IE_SKIP_MODE) return rc; else if(!cwal_value_get_bool(*rv)){ char const * b = t->src.begin; char const * e = (*next)->src.begin /* LOL: assert false // comments // comments [print "abc"] Will, because the comments are skipped at tokenization, lead 'e' to be the string starting at 'print "abc"' i.e. the assertion error text contains the following no-open (non-)tokens. Workaround is to end the assertion with a semicolon. */ ; assert(e>=b); /* dump_token(*next,"Next???"); */ return th1ish_toss_token(ie, t, CWAL_RC_ASSERT, "Assertion failed: %.*s", e-b, b); } else if(TH1ISH_F_TRACE_ASSERTIONS & ie->flags){ char const * b = t->src.begin; char const * e = (*next)->src.begin; cwal_size_t nameLen = 0; char const * name = (char const *) th1ish_script_name_for_pos(ie, (unsigned char const *)t->src.begin, &nameLen); unsigned int line = 0; unsigned int col = 0; if(!name){ name = ""; nameLen = 9; } th1ish_token_line_col( ie, t, &line, &col ); cwal_outputf( ie->e, "Assertion OK @[%.*s]:%u:%u: ", (int)nameLen, name, line, col); assert( e >= b ); cwal_output( ie->e, b, e - b); if(*(e-1) != '\n'){ cwal_output( ie->e, "\n", 1); } return 0; } else return 0; } static cwal_value * th1ish_token_to_string(th1ish_interp * ie, cwal_simple_token * t ){ char const * b = t->src.begin; char const * e = t->src.end; cwal_size_t const len = e - b; cwal_value * code; code = cwal_new_string_value( ie->e, len ? b : 0, len ); assert(code); return code; } static char th1ish_set_might_need_eval( th1ish_interp * ie, cwal_simple_token * t ){ t = th1ish_skip(ie,t); if(TT_Identifier!=t->ttype) return 1; else if(th1ish_st_keyword_get(t)) return 1; else { t = TNEXT(t); switch(t ? t->ttype : 0){ case TT_ChPeriod: case TT_OpArrow: return 1; default: return 0; } } } /** Handles the 'set' and 'unset' keywords: set IDENTIFIER [[=] VALUE, [IDENTIFIER ...] unset IDENTIFIER [, IDENTIFIER...] where IDENTIFIER may be a normal identifier, a string, a number, or a series of OBJECT.PROPERTY dereferences: set myObj.subObj.foo "bar" */ int th1ish_keyword_set(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_value * obj; cwal_value * setv = 0; cwal_value * key = 0; char const isUnset = t ? (t->ttype == TT_KeywordUnset) : 0; /* cwal_value * idString = 0; */ cwal_value * oldThis = ie->currentThis; cwal_value * oldKey = ie->currentIdentKey; int const oldIdentPolicy = ie->identPolicy; unsigned char const * callPoint = (unsigned char const *)t->src.begin; assert( (TT_KeywordSet==t->ttype) || (TT_KeywordUnset==t->ttype) ); start: key = obj = setv = 0; t = *next = TNEXT(t); th1ish_reset_this(ie); /* Reminder: i don't like this special case of identifiers here but we don't have much of a choice if we want to support both (set foo.bar 7) and (set a 3). If we disable identifier expansion temporarily that solves the latter case but causes the former to fail because the dot-deref operator does the work of finding the proper container/key pair for that case. */ if(!th1ish_set_might_need_eval(ie, t)){ /* dump_token(t,"Setting ie->identPolicy = 1"); */ ie->identPolicy = 1; } /* dump_token(t,"evaling SET KEY token"); */ rc = th1ish_eval_1( ie, t, next, &key ); ie->identPolicy = oldIdentPolicy; if(rc) goto end; if(ie->currentThis){ obj = ie->currentThis; /* th1ish_dump_val(obj,"SET ie->currentThis"); */ if(ie->currentIdentKey){ key = ie->currentIdentKey; /* th1ish_dump_val(obj,"SET ie->currentIdentKey"); */ } } /* th1ish_dump_val(key,"invalid key?"); */ if(!IE_SKIP_MODE){ if(!(key && cwal_value_is_string(key)) && !obj ){ /* th1ish_dump_val(key,"invalid key?"); */ rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE, "'set' expect (OBJECT.PROP ?VALUE?) " "or (KEY ?VALUE?) arguments."); goto end; } } t = *next = th1ish_skip(ie,*next); if(obj){ assert(key); } if(t && (TT_OpAssign == t->ttype)){ /* Skip over optional '='. '=' enables newline-skipping. */ t = *next = TNEXT(t); } if(isUnset){ setv = NULL; } else { setv = cwal_value_undefined(); switch(t ? t->ttype : 0){ case 0: break; case TT_EOL: /* case TT_ChComma: */ case TH1ISH_RC_EOC: /* Naive attempt at discovering whether or not we have an expression to parse. */ *next = t->right; break; default: /* dump_token(t,"Evaling VAL part"); */ rc = th1ish_eval_1( ie, t, next, &setv ); t = *next = th1ish_skip(ie,*next); break; } } if(rc) goto end; else if(IE_SKIP_MODE) { goto commaCheck; } assert(isUnset ? !setv : !!setv); assert(key); rc = th1ish_set_v( ie, callPoint, obj, key, setv ); if(isUnset) { if(obj && (CWAL_RC_NOT_FOUND==rc)){ rc = 0; setv = cwal_value_false(); } else if(!rc) setv = cwal_value_true(); } if(!rc) *rv = setv; else if((CWAL_RC_OOM!=rc) && (CWAL_RC_EXCEPTION!=rc)){ rc = th1ish_toss_token(ie, t, rc, "'%s' failed with code %d (%s).", (isUnset ? "unset" : "set"), rc, cwal_rc_cstr(rc)); goto end; } commaCheck: if(!rc){ t = th1ish_skip(ie,*next) /* Consider using cwal_st_skip(*next) here */ ; switch(t ? t->ttype : 0){ case TT_OpAssign: rc = th1ish_toss_token(ie, t, CWAL_RC_ERROR, "'set' cannot currently chain " "assignments (the '=' operator " "can!)."); goto end; case TT_ChComma: goto start; default: break; } } end: ie->currentThis = oldThis; ie->currentIdentKey = oldKey; return rc; } int th1ish_keyword_get(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_value * idv = cwal_value_undefined(); cwal_simple_token * idt; assert(TT_KeywordGet==t->ttype); /* FIXME: support obj.prop properly */ idt = *next = TNEXT(t); rc = th1ish_eval_1( ie, idt, next, &idv ); if(rc) { goto end; } assert(idv); if(!cwal_props_can(idv)){ idv = th1ish_var_get_v( ie, -1, idv ); *rv = idv ? idv : cwal_value_undefined(); } else { cwal_value * obj = idv; idv = cwal_value_undefined(); t = *next; rc = th1ish_eval_1( ie, t, next, &idv ); if(rc) goto end; assert(idv); /* if(obj) th1ish_dump_val(obj,"Object"); */ /* th1ish_dump_val(idv,"Property name"); */ if(cwal_value_is_array(obj) && cwal_value_is_integer(idv)){ cwal_int_t i = cwal_value_get_integer(idv); if(i>=0){ idv = cwal_array_get(cwal_value_get_array(obj), (cwal_size_t)i); } obj = 0; } else{ idv = obj ? cwal_prop_get_v( obj, idv ) : 0; } *rv = idv ? idv : cwal_value_undefined(); /* th1ish_dump_val(*rv,"GET result"); */ } end: return rc; } int th1ish_keyword_var(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ if((TT_KeywordVarDecl != t->ttype) && (TT_KeywordVarDeclConst != t->ttype)){ return CWAL_SCR_CANNOT_CONSUME; }else{ int rc; cwal_value * v = 0; cwal_string * key; cwal_value * keyV = 0; int const op = t->ttype; cwal_simple_token * idt; uint16_t flags = (TT_KeywordVarDeclConst==op) ? CWAL_VAR_F_CONST : CWAL_VAR_F_NONE; start: *next = t = idt = TNEXT(t); if(!cwal_st_isa(t,TT_Identifier)){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Expecting identifier in var declaration."); } rc = th1ish_st_create_value(ie, t, &keyV); if(rc) return rc; if(!IE_SKIP_MODE){ key = cwal_value_get_string(keyV); if(!key) { return th1ish_toss_token(ie, t, CWAL_RC_ERROR, "Identifier token is missing its string part."); } } *next = t = th1ish_skip(ie,t->right); if(t && (TT_OpAssign==t->ttype)){/* var x = ... */ *next = t = TNEXT(t); } switch(t ? t->ttype : 0){ case 0: v = 0; break; case TT_ChComma: v = 0; break; case TT_EOL: case TH1ISH_RC_EOC: /* Naive attempt at discovering whether or not we have an expression to parse. This will likely break at some point. */ *next = t->right; v = 0; break; default: rc = th1ish_eval_0( ie, t, next, &v ); if(rc) return rc; *rv = v; break; } if(!IE_SKIP_MODE){ if(!v && (CWAL_VAR_F_CONST & flags)){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Const declaration requires a value."); } /* th1ish_dump_val(cwal_string_value(key), "VAR key"); */ /* th1ish_dump_val(v, "VAR value"); */ rc = cwal_var_decl_s(ie->e, 0, key, v ? v : cwal_value_undefined(), flags ); if(rc){ return th1ish_toss_token(ie, idt, rc, "Var decl '%.*s' failed with code %d (%s).", (int)cwal_string_length_bytes(key), cwal_string_cstr(key), rc, cwal_rc_cstr(rc) ); } } /* Now goto the top if we find a comma... */ t = th1ish_skip(ie,*next) /* might want to consider using cwal_st_skip(*next) for this. */; if(t && (TT_ChComma == t->ttype)) goto start; return rc; } } /** Evaluator for builtin constant values. We do this as keywords, instead of as vars/constants, purely as a speed optimization. */ int th1ish_keyword_builtin_value(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ cwal_value * v =0; switch(t ? t->ttype : 0){ case TT_KeywordTrue: v = cwal_value_true(); break; case TT_KeywordFalse: v = cwal_value_false(); break; case TT_KeywordNull: v = cwal_value_null(); break; case TT_KeywordUndef: v = cwal_value_undefined(); break; default: return CWAL_SCR_CANNOT_CONSUME; } if(v){ *next = t->right; *rv = v; } return 0; } /** Holds state for Function instances, pointing back to their original source code and declared argument names. */ struct th1ish_proc_state { th1ish_interp * ie; /** Origin token for the parameter list. */ cwal_simple_token tParams; /** Origin token for the function body. */ cwal_simple_token tBody; /** We cache the argument names list here. */ cwal_array * argNames; /* cParams and cBody are only used when proc token caching is on, but tests show that these pointers don't cost us all that much when it's off, so caching is a runtime option as opposed to a macro-controlled option. */ /** Compiled form of tParams. */ cwal_simple_token * cParams; /** Compiled form of tBody. */ cwal_simple_token * cBody; /** Workaround for cleanup of cParams and cBody during recursive calls when caching is disabled. */ unsigned short recurseCount; }; typedef struct th1ish_proc_state th1ish_proc_state; static const th1ish_proc_state th1ish_proc_state_empty = { NULL, cwal_simple_token_empty_m, cwal_simple_token_empty_m, NULL, NULL, NULL, 0U }; /** cwal_finalizer_f() impl for th1ish_proc_state. */ static void th1ish_proc_state_finalize( cwal_engine * e, void * v ){ if(v){ /* MARKER("Freeing th1ish_proc_state@%p\n", v); */ th1ish_proc_state * s = (th1ish_proc_state*)v; char const recycling = (0 != s->ie->recycler.tokens.maxLength) /* If recycling is on here we can leak entries because cwal_engine cleanup can/will trigger this call. The catch is that th1ish_interp_finalize() has to use the engine to free the memory, but its recycle list would normally be modified by this function during engine destruction. After engine destruction it is not legal to use it for cwal_free(). Chicken, egg. */; if(s->cParams){ /* MARKER("Freeing th1ish_proc_state@%p PARAMS\n", v); */ th1ish_st_free_chain( s->ie, s->cParams, recycling ); s->cParams = 0; } if(s->cBody){ /* MARKER("Freeing th1ish_proc_state@%p BODY\n", v); */ th1ish_st_free_chain( s->ie, s->cBody, recycling ); s->cBody = 0; } cwal_free( e, v ); } } /** Parses: identifier [=] [value][, repeat] into var decls in the current scope. t must point to the first token in the param list chain. The _name_ of each parameter is appended to dest if dest is not NULL. */ static int th1ish_func_params_grok_chain( th1ish_interp * ie, cwal_simple_token * t, cwal_array * dest ){ int rc; cwal_simple_token * _next = 0; cwal_simple_token ** next = &_next; cwal_value * v = 0; cwal_value * key; start: v = cwal_value_undefined(); *next = t = cwal_st_skip(t); switch(t ? t->ttype : TT_EOF){ case TT_EOF: return 0/*??CWAL_SCR_EOF*/; case TT_Identifier: break; case TT_ChParenClose: case TT_ChScopeClose: case TT_ChBraceClose/*???*/: /** Allow a list closer only because their appearance is a consequence of how we sub-parse them. Note that we are not 100% correct here - we might be closed by a stray closer of a different type. FIXME: add the opener/close type to the params and only allow the proper one to close the list. */ *next = t->right; return 0; default: /* dump_token(t,"groking args list"); */ rc = th1ish_toss_token(ie, t, CWAL_SCR_EXPECTING_IDENTIFIER, "Expecting identifier."); return rc; } key = th1ish_token_to_string( ie, t ); if(!key) { rc = th1ish_toss_token(ie, t, CWAL_RC_ERROR, "Internal error (or OOM) while allocating " "value for token"); goto end; } *next = t = th1ish_skip(ie,t->right); if(t && (TT_OpAssign==t->ttype)){/* x = ... */ *next = t = TNEXT(t); } switch(t ? t->ttype : 0){ case 0: v = 0; break; case TT_ChComma: v = 0; break; case TT_EOL: case TH1ISH_RC_EOC: /* Naive attempt at discovering whether or not we have an expression to parse. This will likely break at some point. */ *next = t->right; v = 0; #if 0 /* Achtung: this might not be safe. It's an experiment. */ cwal_engine_sweep(ie->e); #endif break; default: rc = th1ish_eval_0( ie, t, next, &v ); if(rc) goto end; t = th1ish_skip(ie,*next); break; } if(!IE_SKIP_MODE){ if(!v) v = cwal_value_undefined(); rc = cwal_scope_chain_set_v( cwal_scope_current_get(ie->e), 0, key, v ); /*cwal_var_decl_v(ie->e, 0, key, v ? v : cwal_value_undefined(), flags )*/ ; if(!rc && dest){ rc = cwal_array_append( dest, key ); } if(rc) { rc = th1ish_toss_token(ie, t, rc, "Var decl failed."); goto end; } } /* Now goto the top if we find a comma... */ t = th1ish_skip(ie,*next); if(t && (TT_ChComma == t->ttype)) { t = TNEXT(t); goto start; } end: return rc; } #if 0 static int th1ish_func_params_grok( th1ish_interp * ie, cwal_simple_token * t, cwal_array * dest ){ int rc; cwal_simple_token * head = 0; rc = th1ish_tokenize_all( ie, t->src.begin, t->src.end-t->src.begin, &head); if(!rc) = th1ish_func_params_grok_chain( ie, head, dest ); if(head) th1ish_st_free_chain( ie, head, 1 ); return rc; } #endif /** A cwal_callback_f() impl which gets installed by the 'proc' keyword. Requires that args->state be a (th1ish_proc_state*). */ static int th1ish_cwal_f_callback( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; th1ish_proc_state * ps = (th1ish_proc_state *)args->state; cwal_array * argsA = ps->argNames; cwal_size_t aLen; th1ish_interp * ie = ps->ie; cwal_value * callRv = 0; cwal_size_t i; cwal_scope * scope = cwal_scope_current_get(ie->e); cwal_simple_token * cParams = 0; cwal_simple_token * cBody = 0; cwal_simple_token * bogoNext = 0; #if 0 unsigned tBodyLen = ps->tBody.src.end - ps->tBody.src.begin; unsigned tParamLen = ps->tParams.src.end - ps->tParams.src.begin; MARKER("call() w/ %u args\n", (unsigned) args->argc); MARKER("proc params: %.*s\n", (int)tParamLen, ps->tParams.src.begin); MARKER("proc body: %.*s\n", tBodyLen, ps->tBody.src.begin); #endif ++ps->recurseCount; cParams = ps->cParams; cBody = ps->cBody; if(!cParams){ /* Tokenize parameters (not arguments)... */ rc = th1ish_tokenize_all( ie, ps->tParams.src.begin, ps->tParams.src.end - ps->tParams.src.begin, &cParams); if(rc) goto end; /* Reminder to self: ALWAYS set ps->cParams and ps->cBody so that recursive calls do not re-tokenize. We will do the TH1ISH_F_CACHE_PROCS check at the end. */ if(cParams){ assert(1==ps->recurseCount); } ps->cParams = cParams; } if(!argsA){ /* Cache parameter names */ argsA = cwal_new_array(ie->e); if(!argsA){ rc = CWAL_RC_OOM; goto end; } /* Now hold a reference somewhere with a useful lifetime: */ cwal_prop_set_v( cwal_function_value(args->callee), cwal_value_null(), cwal_array_value( argsA ) ); /* th1ish_dump_val(cwal_array_value(argsA),"ARGS?"); */ } /** Required in order to keep the 'this' from being overridden with a Function in some contexts. ??? (Next time explain better, please!) In any case, we need "this" (as it were). Reminder: set "this" before parsing parameters so that we can do: proc(a,b=this){...} */ rc = cwal_scope_chain_set(cwal_scope_current_get(args->engine), 0, "this", 4, args->self); if(rc) goto end; /* Set var names in the current scope... We unfortunately have to grok this each time, instead of caching once, because we otherwise don't have access to the declared default values (unless we cache them). We cache the param name list only on the first call, though. */ rc = th1ish_func_params_grok_chain( ie, cParams, ps->argNames ? NULL : argsA ); if(rc) goto end; if(!ps->argNames) ps->argNames = argsA; /* th1ish_dump_val(cwal_array_value(argsA), "parameter list"); */ /* Set local variables for each named parameter... */ aLen = cwal_array_length_get( argsA ); for( i = 0; (i < args->argc) /* reminder: if we cache the default values we need to move this second part into the chain_set_v() call. reminder: we cannot safely cache default parameters if they live in a higher scope which gets destroyed between calls. */; ++i ){ cwal_value * key = (i < aLen) ? cwal_array_get( argsA, i ) : NULL; if(key){ rc = cwal_scope_chain_set_v( scope, 0, key, args->argv[i] ); if(rc) goto end; } /* th1ish_dump_val(args->argv[i],"args->argv[i]"); */ } if(!cBody){ /* Tokenize the code body */ cwal_size_t const codeLen = ps->tBody.src.end - ps->tBody.src.begin; if(codeLen){ rc = th1ish_tokenize_all( ie, ps->tBody.src.begin, codeLen, &cBody); if(CWAL_SCR_EOF==rc){ rc = 0; assert(!cBody); } /* MARKER("Caching code body. rc=%s\n",cwal_rc_cstr(rc)); */ /* dump_token(cBody,"cBody"); */ if(rc) goto end; if(cBody){ assert(1==ps->recurseCount); } ps->cBody = cBody; } } /* Evaluate the body... */ /* MARKER("Function body: <<<%.*s>>>\n", (int)codeLen, code ); */ /* th1ish_dump_val(cwal_object_value(cwal_scope_current_get(ie->e)->props), "Param props"); */ callRv = cwal_value_undefined(); /* dump_token(cBody,"cBody"); */ rc = cBody ? th1ish_eval_chain( ie, cBody, &bogoNext, &callRv ) : 0; if(CWAL_RC_RETURN==rc){ /* dump_token(cBody, "function caught RETURN"); */ /* cwal_exception_set( ie->e, 0 ); */ /* th1ish_dump_val( callRv, "proc callback got RETURN"); */ #if 1 if(cwal_value_scope(callRv)==scope){ /* This ref should not be necessary! OTOH, i haven't yet seen it triggered, so this appears to be dead code. */ if(cwal_value_refcount(callRv)){ th1ish_dump_val(callRv,"Adding painful reference"); assert(!"Never seen this triggered."); cwal_value_ref(callRv); } assert(cwal_scope_parent(scope)); cwal_value_rescope( cwal_scope_parent(scope), callRv ); } #endif /* cwal_value_rescope( callingScope, callRv ); */ /* th1ish_result_set(ie, callRv); */ assert( scope != cwal_value_scope(callRv) ); *rv = callRv; rc = 0; } end: #if 0 if(rc && (CWAL_RC_EXCEPTION!=rc)){ rc = th1ish_toss_token(ie, cBody ? cBody : cParams /* not exactly correct */, rc, "Function had non-Exception error #%d (%s).", rc, cwal_rc_cstr(rc)); } #endif --ps->recurseCount; if(!ps->recurseCount && !(TH1ISH_F_CACHE_PROCS & ie->flags)){ if(cParams){ th1ish_st_free_chain( ie, cParams, 1 ); ps->cParams = NULL; } if(cBody){ th1ish_st_free_chain( ie, cBody, 1 ); ps->cBody = NULL; } } return rc; } /** cwal_callback_f() which ONLY works on script-side functions. Trying to call it with a different 'this' context cause an exception. Maintenance reminder: not static (used in th1ish_protos.c) but not public. */ int th1ish_f_source_info( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_value * props; cwal_function * fSelf = cwal_value_get_function(args->self); th1ish_proc_state * ps = fSelf ? (th1ish_proc_state *)cwal_function_state_get(fSelf, &th1ish_proc_state_empty) : NULL; props = cwal_new_object_value(args->engine); if(!props) return CWAL_RC_OOM; if(!ps){ /* Non-script function */ cwal_engine * e = args->engine; #define STRF(X) cwal_string_value( cwal_new_stringf X ) rc = cwal_prop_set(props, "cwal_function", 13, STRF((e,"@%p",(void*)args->callee))); if(!rc) rc = cwal_prop_set(props, "args->state", 11, STRF((e,"@%p",(void*)args->state))); /* Other info requires internal access to cwal_function */ #undef STRF }else{ /* SCript function */ rc = th1ish_set_script_props(ps->ie, 0, ps->tParams.src.begin, props); if(!rc){ cwal_string * rcS; char const * begin = ps->tParams.src.begin; char const * end = ps->tBody.src.end; /* Now undo some of the damage done by the token post-processing phase... */ assert(end>begin); for( --begin; *begin && isspace(*begin); --begin ){} for( ++end; *end && !isspace(*end); ++end ){} rcS = cwal_new_stringf(args->engine, "proc%.*s", (int)(end - begin), begin); if(!rcS){ rc = CWAL_RC_OOM; goto end; } rc = cwal_prop_set(props, "sourceCode", 10, cwal_string_value(rcS)); if(!rc) rc = cwal_prop_set(props,"isCached", 8, (ps->cBody || ps->cParams) ? cwal_value_true() : cwal_value_false()); } } end: if(rc){ cwal_value_unref(props); }else{ *rv = props; } return rc; } int th1ish_keyword_proc(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_simple_token * tParams; cwal_simple_token * tBody; assert(TT_KeywordFunction == t->ttype); t = *next = TNEXT(t); switch(t ? t->ttype : 0){ case TT_ChParenOpen: case TT_ChScopeOpen: break; default: return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "'proc' expects {ARGS} {BODY} arguments"); } tParams = t; t = *next = TNEXT(t); if(!t || (TT_ChScopeOpen != t->ttype)){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "'proc' missing {BODY} argument"); } tBody = t; *next = t->right; if(IE_SKIP_MODE){ *rv = cwal_value_undefined(); return 0; } { cwal_value * fv; th1ish_proc_state * ps = cwal_malloc(ie->e, sizeof(th1ish_proc_state)); if(!ps){ rc = CWAL_RC_OOM; goto end; } memset(ps, 0, sizeof(th1ish_proc_state)); ps->ie = ie; ps->tParams = *tParams; ps->tBody = *tBody; ps->tParams.right =ps->tBody.right = NULL; ps->recurseCount = 0; fv = th1ish_new_function(ie, th1ish_cwal_f_callback, ps, th1ish_proc_state_finalize, &th1ish_proc_state_empty); if(!fv){ cwal_free( ie->e, ps ); rc = CWAL_RC_OOM; goto end; } *rv = fv; } end: return rc; } /** Script usage: if {condition} {body} if {condition} {body} else {body} if {condition} {body} else if {condition} {body} ... */ int th1ish_keyword_if(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_simple_token * tCond; cwal_simple_token * tBody; cwal_value * xrv = 0; char cond = 0; char hasTrued = 0; char scoped = 0; cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * subScope = &pushedScope_; if(IE_SKIP_MODE){ hasTrued = 1; } startIf: assert(TT_KeywordIf == t->ttype); t = *next = TNEXT(t); switch(t ? t->ttype : 0){ case TT_ChParenOpen: case TT_ChScopeOpen: break; default: return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "'if' expects {CONDITION} {BODY} arguments"); } tCond = t; t = *next = TNEXT(t); if(!t || (TT_ChScopeOpen != t->ttype)){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "'if' missing {BODY} argument"); } tBody = t; if(!scoped && !IE_SKIP_MODE){ rc = cwal_scope_push(ie->e, &subScope); if(rc) return rc; scoped = 1; } if(!IE_SKIP_MODE && !hasTrued){ cwal_size_t const lv = cwal_scope_current_get(ie->e)->level; assert(lv == pushedScope_.level); assert(cwal_scope_current_get(ie->e) == subScope); ++ie->skipNLLevel; rc = th1ish_eval_scope_string( ie, 0, tCond, next, &xrv ); --ie->skipNLLevel; assert(cwal_scope_current_get(ie->e) == subScope); assert(cwal_scope_current_get(ie->e)->level == lv); assert(lv == pushedScope_.level); if(rc) goto end; cond = cwal_value_get_bool(xrv); if(cond) hasTrued = 1; } else cond = 0; *next = t->right; startBody: if(cond){ rc = th1ish_eval_scope_string( ie, 0, tBody, next, &xrv ); if(rc) goto end; } *next = t->right; /** Check for else/if else ... */ t = TNEXT(t); if(!cwal_st_isa(t,TT_KeywordElse)) goto end; *next = t; t = TNEXT(t); switch(t ? t->ttype : 0){ case TT_KeywordIf: goto startIf; case TT_ChScopeOpen: tBody = t; *next = t->right; cond = !hasTrued; goto startBody; break; default: rc = th1ish_toss_token( ie, t, CWAL_SCR_SYNTAX, "'else' expects 'if' or {CODE}."); break; } end: if(scoped){ assert(pushedScope_.level); cwal_scope_pop(ie->e); assert(!pushedScope_.level); } return rc; } int th1ish_keyword_SCOPE(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ *next = t->right; *rv = cwal_scope_properties(cwal_scope_current_get(ie->e)); return *rv ? 0 : CWAL_RC_OOM; } int th1ish_keyword_FILE_LINE(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; switch(t->ttype){ case TT_Keyword__FILE:{ cwal_size_t nLen = 0; unsigned char const * n = (unsigned char const *) th1ish_script_name_for_pos(ie, (unsigned char const *)t->src.begin, &nLen); *rv = nLen ? cwal_new_string_value(ie->e, (char const *)n, nLen) : cwal_value_undefined(); rc = 0; break; } case TT_Keyword__COLUMN: case TT_Keyword__LINE:{ unsigned int line = 0; unsigned int col = 0; rc = th1ish_line_col(ie, t->src.begin, &line, &col); *rv = rc ? cwal_value_undefined() : cwal_new_integer(ie->e, (cwal_int_t)((TT_Keyword__LINE==t->ttype) ? line : col)); rc = 0; break; } default: assert(!"Cannot happen"); rc = CWAL_SCR_CANNOT_CONSUME; break; } if(!rc){ rc = *rv ? 0 : CWAL_RC_OOM; if(!rc) *next = t->right; } return rc; } int th1ish_keyword_SRC(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ unsigned int line = 0, col = 0; unsigned char const * scrName = th1ish_script_name_for_pos(ie, (unsigned char const *)t->src.begin, NULL); *next = t->right; th1ish_line_col( ie, t->src.begin, &line, &col ); *rv = cwal_string_value(cwal_new_stringf(ie->e,"%.*s:%u:%u", scrName ? (int)strlen((char const *)scrName) : 9, scrName ? (char const *)scrName : NULL, line, col)); return *rv ? 0 : CWAL_RC_OOM; } /** Script usage: while {condition} {body} */ int th1ish_keyword_while(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_simple_token * tCond; cwal_simple_token * tBody; cwal_simple_token * cCond = 0; cwal_simple_token * cBody = 0; cwal_simple_token * bogoNext = 0; cwal_value * xrv = 0; /* cwal_scope * callingScope = cwal_scope_current_get(ie->e); */ cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * subScope = &pushedScope_; th1ish_eval_f const evalFunc = (TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags) ? th1ish_eval_chain_push_scope : th1ish_eval_chain /** Function to use for parsing the body. The runtime difference is measurable when doing any significant looping, but we need the push-scope variant if we want to support in-loop var decls. In a script comprised only of looping, with an almost-empty loop body, i'm seeing roughly a 15% speed difference, which argues greatly in favour of punting on in-loop-body vars. Interestingly, the memory cost is the same as long as recycling is on and has enough capacity. */; assert(TT_KeywordWhile == t->ttype); t = *next = TNEXT(t); switch(t ? t->ttype : 0){ case TT_ChParenOpen: case TT_ChScopeOpen: break; default: return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "'while' expects {CONDITION} {BODY} arguments"); } tCond = t; t = *next = TNEXT(t); if(!t || (TT_ChScopeOpen != t->ttype)){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "'while' missing {BODY} argument"); } tBody = t; *next = t->right; *rv = cwal_value_undefined(); if(IE_SKIP_MODE){ return 0; } rc = cwal_scope_push(ie->e, &subScope); if(rc) return rc; rc = th1ish_tokenize_all( ie, tCond->src.begin, tCond->src.end - tCond->src.begin, &cCond ); if(rc) goto end; while(1){ xrv = cwal_value_undefined(); ++ie->skipNLLevel; rc = th1ish_eval_chain( ie, cCond, &bogoNext, &xrv ); --ie->skipNLLevel; if(rc) goto end; else if(!cwal_value_get_bool(xrv)) break; else { /* dump_token(cCond,"WHILE condition true"); */ if(!cBody){ /* Tokenize the code body */ cwal_size_t const codeLen = tBody->src.end - tBody->src.begin; if(codeLen){ rc = th1ish_tokenize_all( ie, tBody->src.begin, tBody->src.end - tBody->src.begin, &cBody ); if(rc) goto end; } } rc = cBody ? evalFunc( ie, cBody, &bogoNext, &xrv ) : 0; switch(rc){ case 0: break; case CWAL_RC_BREAK: goto end; case CWAL_RC_CONTINUE: rc = 0; break; default: goto end; } cwal_engine_sweep( ie->e ); } } end: if( cCond ) th1ish_st_free_chain( ie, cCond, 1 ); if( cBody ) th1ish_st_free_chain( ie, cBody, 1 ); switch( rc ){ case CWAL_RC_BREAK: assert(ie->returnVal); *rv = ie->returnVal; ie->returnVal = NULL; /* cwal_value_rescope(callingScope, *rv); */ /* th1ish_dump_val(*rv, "BREAK value?"); */ rc = 0; break; case CWAL_RC_RETURN: /* th1ish_dump_val(ie->returnVal, "WHILE passing a RETURN by: ie->returnVal"); */ /* th1ish_result_up( ie ); */ break; default: break; } assert(pushedScope_.level); cwal_scope_pop(ie->e); assert(!pushedScope_.level); return rc; } /** Script usage: for {pre} {condition} {post} {body} */ int th1ish_keyword_for(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_simple_token * tPre = 0; cwal_simple_token * tCond = 0; cwal_simple_token * tPost = 0; cwal_simple_token * tBody = 0; cwal_simple_token * cCond = 0; cwal_simple_token * cBody = 0; cwal_simple_token * cPost = 0; cwal_simple_token * bogoNext = 0; cwal_value * xrv = 0; cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * subScope = &pushedScope_; /* cwal_scope * callingScope = cwal_scope_current_get(ie->e); */ char const * usage = "'for' expects {PRE} {CONDITION} {POST} {BODY} arguments"; th1ish_eval_f const evalFunc = (TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags) ? th1ish_eval_chain_push_scope : th1ish_eval_chain /** Function to use for parsing the body. The runtime difference is measurable when doing any significant looping, but we need the push-scope variant if we want to support in-loop var decls. In a script comprised only of looping, with an almost-empty loop body, i'm seeing roughly a 15% speed difference, which argues greatly in favour of punting on in-loop-body vars. */ ; assert(TT_KeywordFor == t->ttype); #define PART(TOK) \ t = *next = TNEXT(t); \ if(!t || (TT_ChScopeOpen != t->ttype)){ \ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, usage); \ } \ TOK = t /* Collect {tPre} {tCond} {tPost} {tBody} */ PART(tPre); PART(tCond); PART(tPost); PART(tBody); #undef PART *next = t->right; *rv = cwal_value_undefined(); if(IE_SKIP_MODE){ return 0; } rc = cwal_scope_push(ie->e, &subScope); if(rc) return rc; ++ie->skipNLLevel; rc = th1ish_eval_scope_string( ie, 0, tPre, &bogoNext, &xrv ); --ie->skipNLLevel; if(rc) goto end; { /* Tokenize the condition part */ cwal_size_t const codeLen = tCond->src.end - tCond->src.begin; if(codeLen){ rc = th1ish_tokenize_all( ie, tCond->src.begin, codeLen, &cCond); if(rc) goto end; } } if(cCond) while(1){ /* cwal_scope * pushy = &pushed2; */ /* pushed2 = cwal_scope_empty; */ /*i would like to push an additional scope to solve the problem that var decls in loops break on the 2nd iteration, but doing so corrupts memory and i haven't the energy to track down why :/. */ if(!cBody){ /* Tokenize the code body */ cwal_size_t const codeLen = tBody->src.end - tBody->src.begin; if(codeLen){ rc = th1ish_tokenize_all( ie, tBody->src.begin, codeLen, &cBody); if(rc) goto end; } /* MARKER("Caching code body. rc=%s\n",cwal_rc_cstr(rc)); */ /* dump_token(cBody,"cBody"); */ } if(!cPost){ cwal_size_t const codeLen = tPost->src.end - tPost->src.begin; if(codeLen){ rc = th1ish_tokenize_all( ie, tPost->src.begin, tPost->src.end - tPost->src.begin, &cPost ); if(rc) goto end; } } /* rc = cwal_scope_push(ie->e, NULL) */ /* We do this so that var decls in a loop body do not error out on their second time around. */; assert(!rc); xrv = cwal_value_undefined(); ++ie->skipNLLevel; rc = th1ish_eval_chain( ie, cCond, &bogoNext, &xrv ); --ie->skipNLLevel; if(rc) goto end; else if(!cwal_value_get_bool(xrv)){ break; } else { xrv = cwal_value_undefined(); rc = cBody ? evalFunc(ie, cBody, &bogoNext, &xrv ) : 0; if( CWAL_RC_CONTINUE == rc ){ rc = 0; } else if(rc) goto end; xrv = cwal_value_undefined(); ++ie->skipNLLevel; rc = cPost ? evalFunc( ie, cPost, &bogoNext, &xrv ) : 0; --ie->skipNLLevel; } if(rc) goto end; cwal_engine_sweep( ie->e ); } end: if( cPost ) th1ish_st_free_chain( ie, cPost, 1 ); if( cCond ) th1ish_st_free_chain( ie, cCond, 1 ); if( cBody ) th1ish_st_free_chain( ie, cBody, 1 ); switch( rc ){ case CWAL_RC_BREAK: assert(ie->returnVal); *rv = ie->returnVal; ie->returnVal = NULL; /* cwal_value_rescope(callingScope, *rv); */ /* th1ish_dump_val(*rv, "BREAK value?"); */ rc = 0; break; case CWAL_RC_RETURN: /* th1ish_dump_val(ie->returnVal, "FOR passing a RETURN by: ie->returnVal"); */ /* th1ish_result_up( ie ); */ break; default: break; } assert(pushedScope_.level); cwal_scope_pop(ie->e); assert(!pushedScope_.level); return rc; } int th1ish_eval_scope_string(th1ish_interp * ie, char addScope, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_size_t codeLen = 0; char const * code = 0; cwal_buffer escBuf = cwal_buffer_empty; switch(t ? t->ttype : TT_EOF){ case TT_EOF: return 0 /* CWAL_SCR_EOF */; case TT_ChParenOpen: case TT_ChBraceOpen: case TT_ChScopeOpen: *next = t->right; if(IE_SKIP_MODE) return 0; code = t->src.begin; codeLen = t->src.end - t->src.begin; break; case TT_LiteralStringSQ: case TT_LiteralStringDQ: *next = t->right; if(IE_SKIP_MODE) return 0; /* Reminder: because we have to unescape the string, we are forced to allocate an unescape buffer. We cannot used ie->buffer because the code being evaluated might call back to here and it would overwrite the buffer, invalidating the script bytes pointed to by the tokens higher-up executions of this routine are doing. */ assert((t->src.end - t->src.begin)>=2); codeLen = t->src.end - t->src.begin; rc = cwal_buffer_reserve(ie->e, &escBuf, codeLen+1/*NUL*/ ); if(rc) goto end; rc = cwal_st_unescape_string(ie->e, (unsigned char const *)t->src.begin + 1 /*quote*/, (unsigned char const *)t->src.end - 1 /*quote*/, &escBuf ); if(rc) goto end; code = (char const *)escBuf.mem; codeLen = escBuf.used; *next = t->right; break; default: rc = CWAL_SCR_CANNOT_CONSUME; goto end; } if(!codeLen){ *rv = cwal_value_undefined(); rc = 0; goto end; } #if 0 MARKER("%sing code: <<<%.*s>>>\n", addScope ? "EVAL" : "SCOPE", (int)codeLen, code ); #endif if(codeLen){ rc = th1ish_eval_string( ie, addScope, code, codeLen, rv ); } else { *rv = cwal_value_undefined(); } switch(rc){ case CWAL_SCR_EOF: rc = 0; break; case CWAL_RC_RETURN: if(addScope>0){ /* *rv is up-scoped if necessary in th1ish_eval_string() */ rc = 0; } default: break; } end: th1ish_reset_this(ie); if(escBuf.mem){ cwal_buffer_reserve( ie->e, &escBuf, 0 ); } return rc; } /** Script usage: eval {CODE} scope {CODE} Tokenizes and evals the string and *rv is set. to the result. 'eval' is evaluated in the current scope, where 'scope' uses a new scope for evaluation and honors the CWAL_RC_RETURN value as "success", and sets rv to that result value. */ int th1ish_keyword_eval(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; char const doScope = (TT_KeywordScope==t->ttype); int const ttype = t->ttype; int const oldSweepGuard = ie->sweepGuard; cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * pushed = doScope ? &pushedScope_ : 0; assert((TT_KeywordEval==ttype) || (TT_KeywordScope==ttype)); *next = t = TNEXT(t); if(doScope) ie->sweepGuard = 0; switch(t ? t->ttype : 0){ /* This is the wrong way to do this. We "should" keep evaluating the chain and, based on the result, return either that RV or (if the result is a string) eval() it. That leads to an ambiguity, though: eval $funcref where $funcref returns some string. The current impl means we can't do: set code {...} eval code # or: eval $code However, the proper way of doing it has the drawback that evaling the script-side string representation loses the line/column numbers (resetting them, since they are inside a new string value. The current impl allows us to reference the original string sources, keeping error reporting intact. */ case TT_LiteralStringDQ: case TT_LiteralStringSQ: case TT_ChScopeOpen:{ rc = th1ish_eval_scope_string( ie, doScope ? 1 : 0, t, next, rv ) /* Reminder to self: to disable the "return swallowing" for 'scope', pass -1 as the 2nd parameter. */ ; break; } default: { cwal_scope * callingScope = doScope ? cwal_scope_current_get(ie->e) : 0; cwal_value * xrv = 0; if(doScope){ assert(callingScope); rc = cwal_scope_push( ie->e, &pushed ); if(rc) goto end; assert(pushedScope_.level); } /* dump_token(t, "Evaluating for EVAL/SCOPE"); */ rc = th1ish_eval_0(ie, t, next, &xrv); /* This isn't quite right - we don't really want to now re-eval all string contents, but OTOH we want (eval refToCodeString) to work */ if(!rc && cwal_value_is_string(xrv)){ cwal_string * str = cwal_value_get_string(xrv); char const * code = cwal_string_cstr(str); cwal_size_t const codeLen = cwal_string_length_bytes(str); rc = th1ish_eval_string( ie, 0, code, codeLen, &xrv ); if(!rc && rv && xrv) *rv = xrv; } if(doScope){ th1ish_possibly_bubble(ie, rc, callingScope, xrv ); } if(pushed){ cwal_scope_pop( ie->e ); assert(!pushedScope_.level); } /* th1ish_dump_val(*rv, "after eval_1"); */ break; } } end: if(doScope){ /* Workaround for a really wierd bug. */ th1ish_reset_this(ie); } ie->sweepGuard = oldSweepGuard; #if 0 /* Currently nukes *rv when running in the global scope because *rv value cannot be upscoped. */ if(!rc && !doScope){ /* Clean up temps created during EVAL. */ cwal_engine_sweep(ie->e); } #endif return rc; } /** Script usage: catch {CODE} The code is evaluated in a new scope and *rv is set to one of the following: - The undefined value if the code ran to completion without one of the following conditions. - A value is returned via the 'return' keyword. - An exception is triggered, either during tokenization, evaluation of the code, or via the 'throw' keyword. There is currently no way for client code to differentiate between those cases. */ int th1ish_keyword_catch( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_simple_token * open; cwal_value * vexc = 0; cwal_size_t codeLen; assert(TT_KeywordCatch == t->ttype); t = TNEXT(t); if(!t || (TT_ChScopeOpen != t->ttype)){ return th1ish_toss_token( ie, t, CWAL_SCR_SYNTAX, "Expecting {...} after 'catch'."); } open = t; codeLen = t->src.end - t->src.begin; if(!codeLen){ *next = t->right; *rv = cwal_value_undefined(); return 0; } /* th1ish_dump_val(t->value, "String to eval"); */ ++ie->catchLevel; rc = th1ish_eval_string( ie, 1, t->src.begin, codeLen, rv ); --ie->catchLevel; /* th1ish_dump_val(*rv, "eval result"); */ /* MARKER("rc=%d %s\n",rc, cwal_rc_cstr(rc)); */ *next = t->right; switch(rc){ case 0: *rv = cwal_value_undefined(); break; #if 0 /* TODO: phase this out */ case CWAL_RC_RETURN: rc = 0; break; #endif case TH1ISH_RC_TOSS: /* Was up-scoped by eval-string already */ *rv = ie->returnVal; /* th1ish_dump_val(*rv,"CATCH caught TOSS"); */ ie->returnVal = 0; rc = 0; break; case CWAL_RC_EXCEPTION: vexc = cwal_exception_get(ie->e); assert(vexc); cwal_exception_set(ie->e, NULL); *rv = vexc; rc = 0; break; default: break; } if(!rc) *next = open->right; return rc; } int th1ish_keyword_sweep( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ assert(TT_KeywordSweep == t->ttype); *next = t->right; *rv = cwal_value_undefined(); #if 1 { cwal_size_t const sw = cwal_engine_sweep( ie->e ); *rv = sw ? cwal_value_true() : cwal_value_false(); return 0; /* We don't return sw because it seems kinda silly to allocate for this operation. */ } #else return cwal_engine_sweep_non_vars(ie->e); #endif } int th1ish_keyword_throw( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_simple_token * origin = t; assert(TT_KeywordThrow == t->ttype); *next = t = th1ish_skip(ie,t->right) /* Reminder: not skipping newlines so that a simple "throw" eventually becomes possible. */; /* dump_token(t,"Throwing this"); */ *rv = cwal_value_undefined() /* required for skip mode. */; /* cwal_exception_set(ie->e, NULL); */ rc = th1ish_st_eox(t) ? 0 : th1ish_eval_0( ie, t, next, rv ); switch(rc){ case 0: break; case CWAL_SCR_EOF: rc = 0; break; default: /* MARKER("THROW EVAL RC=%s\n", cwal_rc_cstr(rc)); */ break; } if(!rc && !IE_SKIP_MODE){ /* FIXME: this code was made for re-throwing exceptions, but it "breaks" this case: var ex = ...somehow create an exception... ...customize ex... throw ex That will currently get the re-thrown behaviour (it will get wrapped in another exception!). ...later... it turns out the "wrap the exception" behaviour is arguably correct because we then enclose both the throw location and the instantiation location, both of which might be useful. But it doesn't make much sense for: throw [api.Exception 123 "..."] where we, in effect, duplicate the same stack trace in both the outer and inner exceptions. */ if(cwal_value_is_exception(*rv)){ /* cwal_value * callStackV; */ #if 1 /*cwal_exception * ex = cwal_value_get_exception(*rv);*/ cwal_value * exv = th1ish_new_exception(ie, #if 0 cwal_exception_code_get(ex), #else CWAL_RC_EXCEPTION, #endif 0); /* FIXME: missing(?) copy to th1ish_strace_copy()? The stack is (if enabled) already in the one we're re-throwing. */ /* th1ish_dump_val(*rv,"Re-throwing exception expression"); */ if(!exv){ rc = CWAL_RC_OOM; } else{ rc = cwal_prop_set(exv, "rethrown", 8, *rv ); if(!rc) rc = th1ish_toss_token_val( ie, origin, CWAL_RC_EXCEPTION, exv ); } /* th1ish_dump_val(*rv,"Re-threw exception expression"); */ #endif } else{ rc = th1ish_toss_token_val( ie, origin, CWAL_RC_EXCEPTION, *rv ); assert(CWAL_RC_EXCEPTION==rc); } *rv = cwal_value_undefined(); } return rc; } static int th1ish_eval_func_collect_call(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv, cwal_value * selfObj, cwal_function * func, char ignoreEOL){ /* This function is way too long. */ int rc = 0; enum { MaxArgs = CWAL_OPT_MAX_FUNC_CALL_ARGS }; cwal_value * fv = func ? cwal_function_value(func) : NULL; cwal_array * arguments = 0; cwal_value * argv[MaxArgs]; uint16_t argc = 0; cwal_simple_token * pHead = 0; cwal_simple_token * origT = t; cwal_simple_token const * errReportPos = t; cwal_simple_token const * callPoint = t; cwal_scope * callingScope = cwal_scope_current_get(ie->e); cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * subScope = 0; cwal_value * av = 0; memset( argv, 0, sizeof(argv) ) /* Reminder: MUST even be done when IE_SKIP_MODE or else we end up sending uninitialized memory to eval_0 below when in skip mode. */ ; if(!IE_SKIP_MODE){ assert(fv); subScope = &pushedScope_; rc = cwal_scope_push(ie->e, &subScope); if(rc) return rc; assert(pushedScope_.level); arguments = th1ish_new_array(ie); if(!arguments) { rc = CWAL_RC_OOM; goto end; } }else{ assert(!fv); } t = th1ish_skip(ie,t); if(cwal_st_eof(t)){ /*rc = CWAL_SCR_EOF; goto end;*/ goto call; } else if((TH1ISH_F_CONVENTIONAL_CALL_OP & ie->flags) && (TT_ChParenOpen==t->ttype)){ cwal_size_t const codeLen = t->src.end - t->src.begin; if(!codeLen){ *next = t->right; goto call; } rc = th1ish_tokenize_all(ie, t->src.begin, codeLen, &pHead); if(rc) goto end; t = *next = pHead; }else{ *next = t->right; } rc = 0; for( ; !cwal_st_eof(t); t = th1ish_skip(ie,*next) ){ char doBreak = 0; if(argc == MaxArgs) { rc = th1ish_toss_token(ie, t, CWAL_RC_RANGE, "Too many arguments passed " "to function call."); goto end; } /* dump_token(t,"args parsing"); */ *next = th1ish_skip(ie,t->right); switch(t->ttype){ /* If we allow commas in func arg lists then we open up a syntax ambiguity: ($func foo, bar) Could be 1 or 2 expressions in the (group). */ case TT_ChBraceClose: if(pHead){ rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Unexpected ']' in call arguments."); doBreak = 1; break; } case TT_EOF: /* return CWAL_SCR_EOF; */ /* Fall through... */ case TH1ISH_RC_EOC: /* *next = t->right; */ *next = t; doBreak = 1; break; case TT_ChComma: if(!pHead){ #if 1 *next = t; doBreak = 1; #else rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Unexpected , in call arguments."); doBreak = 1; #endif break; }else{ /* FIXME: add check for multiple commas. */ *next = t->right; continue; } case TT_CRNL: case TT_NL: assert(!"Missing translation of CRNL/NL to EOL somewhere."); case TT_EOL: /* MARKER("EOL in func args\n"); */ if(/*(ie->skipNLLevel<=0) &&*/ !ignoreEOL){ /* dump_token(t, "Ending arg collection for newline"); */ *next = t; doBreak = 1; } break; #if 1 /* @inlined-arrays are now done via eval_5() but we repeat it here for the time being because this handles _empty_ arrays the way they should be instead of they way the parser will handle them in some contexts (namely that they evaluate to an empty array, instead of "nothing" _when_ they evaluate at the end of an expression (because there is no next part to "overwrite" the array result value with). We need a mechanism/flag/heuristic for telling certain evaluators that their expression should be considered as "skipped", and its return value discarded. */ case TT_ChAt:{ /** Treat @ARRAY as an "inlined list", copying all entries from the array into the argument list. */ cwal_value * av = NULL; cwal_array * ar; t = *next = TNEXT(t); errReportPos = t; rc = th1ish_eval_atom(ie, t, next, &av); if(rc){ /* errReportPos = *next; */ goto end; } else if(IE_SKIP_MODE) break; /* if(av) cwal_dump_v(av,"@-> array"); */ else if(!av || /*!(ar=th1ish_value_array_part(ie, av))*/ !(ar=cwal_value_get_array(av)) ){ rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE, "Expecting Array after '@'."); goto end; }else{ cwal_size_t const alen = cwal_array_length_get(ar); cwal_size_t i; errReportPos = t; if((alen + argc) > MaxArgs){ rc = th1ish_toss_token(ie, t, CWAL_RC_RANGE, "'@' would expand to too " "many arguments. Limit is %d.\n", MaxArgs); goto end; } for( i = 0; i < alen; ++i ){ cwal_value * v = cwal_array_get(ar, i); argv[argc++] = v ? v : cwal_value_undefined(); } continue; } } #endif default: break; } if(rc) goto end; else if(doBreak) break; /* dump_token(t,"Evaling func call arg"); */ errReportPos = t; rc = th1ish_eval_0( ie, t, next, &argv[argc] ); if(rc){ /* errReportPos = *next; */ /*if(CWAL_SCR_EOF==rc) rc = 0; else*/ goto end; } if(arguments){ rc = cwal_array_append( arguments, argv[argc] ); if(rc) goto end; /* th1ish_dump_val(argv[argc], "Func call argument"); */ /* th1ish_dump_val(argv[argc],"argv[argc]"); */ } ++argc; } call: assert(argc<=MaxArgs); assert(0==rc); if(IE_SKIP_MODE) goto end; if(!selfObj) selfObj = fv; /* Set up 'this' and friends... */ assert(arguments); av = cwal_array_value(arguments); assert(av); /* cwal_value_ref(av); */ /* th1ish_dump_val(av,"argv"); */ /* th1ish_dump_val(selfObj,"Setting 'this'"); */ /* TODO: cache these keys somewhere. */ #if 1 cwal_scope_chain_set( subScope, 0, "this", 4, selfObj ); cwal_scope_chain_set( subScope, 0, "argv", 4, av ); assert(cwal_value_refcount(av)>0); /* assert(cwal_value_refcount(selfObj)>0); lol... broken by one an integer 0 (which has no refcount) once i added the integer prototype. */ #else /* BUG: these do the same thing as the above (more or less) but trigger an assertion in cwal sometime at cleanup if string interning is enabled. */ cwal_var_decl( ie->e, subScope, "this", 4, selfObj, CWAL_VAR_F_CONST ); cwal_var_decl( ie->e, subScope, "argv", 4, av, CWAL_VAR_F_CONST ); #endif cwal_prop_set( av, "callee", 6, fv ); {/* Import "closured" properties into the current scope */ cwal_value * imported = cwal_prop_get( fv, FUNC_SYM_PROP, FUNC_SYM_PROP_LEN ); if(imported){ cwal_value * scProp = cwal_scope_properties(subScope); assert(scProp); rc = cwal_props_copy( imported, scProp ); if(rc) goto end; } } { th1ish_stack_entry sta = th1ish_stack_entry_empty; cwal_value * xrv = 0; int const catchLevel = ie->catchLevel; assert(callPoint && callPoint->src.begin); th1ish_strace_push_pos(ie, (unsigned char const *)callPoint->src.begin, &sta) /* This is actually 1 token to the right of where we want to be, especially in the case of an anonymous function called directly: $proc {}{...} reports the end of the final brace as the trace position. */; ie->catchLevel = 0; rc = cwal_function_call_in_scope( subScope, func, selfObj, &xrv, argc, argc ? argv : NULL ); ie->catchLevel = catchLevel; if(RC_IS_EXIT(rc)){ /* th1ish_dump_val(ie->exitValue,"PASSING ON EXIT"); */ /* assert(ie->exitValue); fork() breaks this.*/ if(ie->exitValue){ *rv = xrv = ie->exitValue; } } th1ish_strace_pop(ie); assert(CWAL_RC_RETURN!=rc); #if 0 /** Interesting case: proc f {} { return argv } $print [f 1 2 3 4] outputs: [] because we set the length of argv to 0 to avoid keeping references to the arguments. For this very special case we are either required to document it away or... we clone it. BUG: cloning is currently buggy and this can lead to memory corruption in at least one script case. */ if(!rc && (av==xrv)){ th1ish_dump_val(xrv,"CLONING ARGV!"); rc = cwal_value_clone2( xrv, &xrv ); } #endif #if 0 /* Reminder: unsetting callee causes valgrind warnings. Not sure why but it is highly unexpected and needs to be investigated. Reproduction: var f = proc (){return argv} [f] // or f(), or $f Unset av->{callee} here, and turn off value recycling. */ /* No longer necessary... ?? */ /* cwal_prop_set( av, "callee", 6, NULL ); */ /* Unset our special vars to avoid undue orphaning... */ cwal_scope_chain_set( subScope, 0, "this", 4, NULL ); /* "argv" is the one causing the grief, it seems. */ /* cwal_scope_chain_set( subScope, 0, "argv", 4, NULL ); */ /* cwal_array_length_set( arguments, 0 ); */ #endif if(!rc){ if(xrv && rv){ *rv = xrv; /* th1ish_dump_val(xrv,"Rescoping return value"); */ cwal_value_rescope( callingScope, xrv ); } else if(rv) *rv = cwal_value_undefined(); } } errReportPos = callPoint; end: switch(rc){ case 0: break; case CWAL_SCR_EOF: /* rc = 0; */ break; case CWAL_RC_EXCEPTION: case CWAL_RC_EXIT: case CWAL_RC_FATAL:{ /* If we find an exception with no line/column info (i.e. native functions) then set the line/column properties of the exception to the location of the last known-OK token. */ cwal_value * exv = cwal_exception_get(ie->e); if(exv && cwal_value_is_exception(exv)){ cwal_exception * ex = cwal_value_get_exception(exv); cwal_value const * enriched = cwal_prop_get(exv, "line", 4); if(!enriched){ /* Enrich the exception with call location info. */ rc = th1ish_set_script_props( ie, cwal_exception_code_get(ex), errReportPos->src.begin, exv ); if(!rc) rc = CWAL_RC_EXCEPTION; } }/* potential TODO: create an exception here? So that the caller knows where a native function EXIT'd from? (Fatal and Exception are already supposed to have exception state.) */ break; } default:{ /* !cwal_exception_get(ie->e) reminder: we cannot currently safely unset the cwal exception state, so it is (almost) always set after one exception has been thrown. Later: i don't think that's true anymore. */ rc = th1ish_toss_token(ie, callPoint, rc, "Function triggered non-exception error " "#%d (%s).", rc, cwal_rc_cstr(rc)); break; } } if(pHead){ t = origT; *next = t->right; th1ish_st_free_chain(ie, pHead, 1); } if(subScope){ assert(pushedScope_.level); cwal_scope_pop(ie->e); assert(!pushedScope_.level); } return rc; } int th1ish_eval_func_call_raw(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv, char ignoreEOL){ int rc; cwal_value * fv = 0; cwal_function * func = 0; cwal_value * selfObj = 0; assert(!IE_SKIP_MODE); t = cwal_st_skip(t); if(cwal_st_eof(t)){ *next = t; rc = 0 /*CWAL_SCR_EOF*/; goto end; } *next = t; fv = cwal_value_undefined(); /* dump_token(t,"Initial eval inside [CALL]"); */ th1ish_reset_this(ie); rc = th1ish_eval_5( ie, t, next, &fv ) /* Reminder: must be eval_5 to avoid this corner case: [foo.func -1 2 3] incorrectly evals as: [(foo.func - 1) 3 4] (and similar cases) if we start at eval_4 or lower. Bug: $array [ 'cpdoish', 'linenoiseish' ].eachIndex proc(v,i){ catch{[api.loadModule v api]} && print("Warning: module not loaded:",v) } Works but wrapping it in [] instead does not. Conventional call() syntax works as well. */ ; assert(!IE_SKIP_MODE); /* dump_token(*next,"After eval inside [CALL]"); */ /* th1ish_dump_val(fv,"eval rv"); */ if(rc) goto end; t = *next = ignoreEOL ? cwal_st_skip(*next) : th1ish_skip(ie,*next); if(!fv || !(func = cwal_value_function_part(ie->e,fv))){ /* th1ish_dump_val(fv,"Not a function"); */ rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE, "Function call expects a function as its first argument."); goto end; } /* *next = t = th1ish_skip(ie,t->right); */ *next = t = th1ish_skip(ie,t); /* dump_token(t,"First argument token"); */ selfObj = ie->currentThis; /* th1ish_dump_val(selfObj,"selfObj for call_raw()"); */ rc = th1ish_eval_func_collect_call( ie, t, next, rv, selfObj ? selfObj : fv, func, ignoreEOL ); th1ish_reset_this(ie); end: return rc; } /** Evalutes [func ...args...] constructs. */ static int th1ish_eval_func_call(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; cwal_simple_token * head = 0; cwal_simple_token * myt = 0; assert(TT_ChBraceOpen==t->ttype); if(IE_SKIP_MODE){ *next = t->right; return 0; } /* MARKER("Function call body: %u bytes, %.*s\n", t->src.end-t->src.begin, t->src.end-t->src.begin, t->src.begin); */ rc = th1ish_tokenize_all( ie, t->src.begin, t->src.end - t->src.begin, &head ); if(rc){ assert(!head); return rc; } assert(head); if(0){ myt = head; while(myt){ dump_token(myt,"func call body token"); myt = myt->right; } } myt = cwal_st_skip(head); rc = th1ish_eval_func_call_raw( ie, myt, &myt, rv, 1 ); *next = t->right; if(head){ th1ish_st_free_chain( ie, head, 1 ); } return rc; } /** Evaluates [func call] blocks. Excepts t to be a "post-processed" TT_ChBraceOpen token, with [t->src.begin,t->src.end) pointing to the whole contents of the [...] block, stripped of leading and trailing whitespace. */ static int th1ish_eval_brace_block(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ if(TT_ChBraceOpen != t->ttype) return CWAL_SCR_CANNOT_CONSUME; else if(IE_SKIP_MODE) { *next = TNEXT(t) /* Remember that tokenization sets t->right to the token after the closing ']' token. */ ; return 0; } else{ return th1ish_eval_func_call( ie, t, next, rv ); } } int th1ish_eval_ternary_if(th1ish_interp * ie, cwal_value * lhs, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc = 0; cwal_simple_token * close; assert(TT_ChQuestion == t->ttype); close = cwal_st_find_matching_brace( t ); if(!close){ return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Missing ':' part of ternary IF expression."); } /* th1ish_dump_val(lhs, "ternary LHS"); */ if(!IE_SKIP_MODE && cwal_value_get_bool(lhs)){ cwal_value * ignored = 0; t = *next = TNEXT(t); rc = th1ish_eval_0( ie, t, next, rv ); if(rc) return rc; /* th1ish_dump_val(*rv, "Ternary TRUE part"); */ /* Skip RHS... */ t = *next = TNEXT(close); /* MARKER("Skipping the closing ':' part.\n"); */ ++ie->skipLevel; rc = th1ish_eval_0( ie, t, next, &ignored ); --ie->skipLevel; /* th1ish_dump_val(ignored, "Ternary FALSE part"); */ /* MARKER("Done skipping. Were there side-effects?\n"); */ }else { /* Skip to ':' part... */ /* MARKER("Skipping to closing ':' part.\n"); */ t = TNEXT(close); rc = th1ish_eval_0( ie, t, next, rv ); /* th1ish_dump_val(*rv, "Ternary FALSE part"); */ } return rc; } int th1ish_eval_atom(th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ int rc; switch(t->ttype){ case TT_Identifier: rc = th1ish_eval_identifier(ie, t, next, rv ); th1ish_eval_check_exit(ie,rc, t, next); break; case TT_ChBraceOpen: rc = th1ish_eval_brace_block(ie, t, next, rv ); th1ish_eval_check_exit(ie,rc, t, next); break; case TT_LiteralStringSQ: case TT_LiteralStringDQ: case TT_ChScopeOpen: rc = th1ish_eval_literal_string(ie, t, next, rv ); th1ish_eval_check_exit(ie,rc, t, next); break; case TT_LiteralDouble: case TT_LiteralIntDec: case TT_LiteralIntHex: case TT_LiteralIntOctal: rc = th1ish_eval_literal_number(ie, t, next, rv ); th1ish_eval_check_exit(ie,rc, t, next); break; #if 0 case TT_ChParenOpen:{ cwal_value * argv = 0; rc = th1ish_eval_list(ie, t, next, NULL, &argv ); /* assert(argv); */ th1ish_eval_check_exit(ie,rc, t, next); *rv = argv ? argv : cwal_value_undefined() /* Are we just hiding a bug here? */; break; } #endif break; default: rc = th1ish_eval_keywords(ie, t, next, rv ); th1ish_eval_check_exit(ie,rc, t, next); break; } /*MARKER("Ending eval_atom() w/ rc=%d (%s)\n",rc, cwal_rc_cstr(rc));*/ return rc; } char th1ish_st_eox( cwal_simple_token const * t ){ if(!t) return 1; else switch(t->ttype){ case TT_NL: case TT_CR: case TT_CRNL: case TT_EOL: case TT_ChSemicolon: case TT_EOF: case TT_ChComma: return 1; default: return 0; } } int th1ish_eval_entry( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv ){ if(!ie || !ie->e || !t || !next || !rv) return CWAL_RC_MISUSE; /* dump_token(t, "eval_entry()"); */ ie->exitValue = 0; th1ish_reset_this(ie); t = cwal_st_skip(t); if(cwal_st_eof(t)){ *next = NULL; return CWAL_SCR_EOF; } if((ie->sweepGuard<=0) && (ie->sweepInterval>0)){ if(++ie->sweepCounter == ie->sweepInterval){ cwal_size_t const sw = cwal_engine_sweep(ie->e); if(sw && (TH1ISH_F_LOG_AUTO_SWEEP & ie->flags)){ /* When we use cwal_outputf() here we break the [api.ob] unit tests :/. */ unsigned int line = 1, col = 0; cwal_size_t nameLen = 0; unsigned char const * scriptName = th1ish_script_name_for_pos( ie, (unsigned char const*)t->src.begin, &nameLen ); th1ish_token_line_col( ie, t, &line, &col ); MARKER(("%s:%d: %.*s:%u:%u: Auto-sweep swept up %"CWAL_SIZE_T_PFMT " value(s). interval=%d\n", __FILE__, __LINE__, (int)nameLen, (char const *)scriptName, line, col, sw, ie->sweepCounter)); } ie->sweepCounter = 0; } } switch(t->ttype){ #if 1 case TT_EOL: case TT_ChSemicolon: case TT_NL: case TT_CR: case TT_CRNL: assert(!"This block is apparenlty never triggered."); #endif case TH1ISH_RC_EOC: /* fall through */ case TT_ChComma: *next = t->right; return 0; default:{ const int rc = th1ish_eval_0(ie, t, next, rv); /* dump_token(*next, "eval_0 says next is"); */ /* th1ish_dump_val(*rv, "eval_0 result"); */ return rc; } } } int th1ish_eval_chain( th1ish_interp * ie, cwal_simple_token * head, cwal_simple_token ** next, cwal_value **rv ){ cwal_simple_token * _next = head; cwal_simple_token * t = head; cwal_value * _rv = 0; int rc = 0; if(!ie || !head) return CWAL_RC_MISUSE; if(!next) next = &_next; if(!rv) rv = &_rv; #if 0 /* Reminder: we cannot(?) do this reliably for some conceivable use cases, e.g.: $func scope {...} scope {...} :/ And, aside from that, it breaks plenty of the test scripts. */ cwal_engine_sweep( ie->e ); #endif for( t = head; t && 0==rc ; t = *next ){ *next = t->right; #if 0 dump_token(t,"eval()'ing token"); if(*rv) th1ish_dump_val(*rv,"rv before next token"); #endif if(cwal_st_eof(t)) break; else if(cwal_is_whitespace(t->ttype)) continue; else{ *next = t; rc = th1ish_eval_entry( ie, t, next, rv ); if(RC_IS_EXIT(rc)){ if(ie->exitValue){ *rv = ie->exitValue; /* ie->exitValue = 0; */ } break; } if(rc) break; else if(*next==t && !ie->expando.ar){ assert(!"Internal parser returned 0 but did not consume."); rc = CWAL_SCR_CANNOT_CONSUME; break; } } #if 0 if(*rv){ th1ish_dump_val(*rv, "th1ish_eval_entry() result value"); } #endif } #if 0 if(rv && *rv){ th1ish_dump_val(*rv, "Final th1ish_eval_chain() result value"); dump_token( *next, "Next token" ); } #endif if(CWAL_SCR_EOF==rc) { rc = CWAL_RC_OK; } else switch(rc){ case 0: break; case CWAL_RC_CONTINUE: case CWAL_RC_EXCEPTION: case CWAL_RC_EXIT: case CWAL_RC_FATAL: break; case TH1ISH_RC_TOSS: case CWAL_RC_BREAK: case CWAL_RC_RETURN: /* th1ish_dump_val( *rv, "Allowing a RETURN or BREAK to pass"); */ if(ie->returnVal){ *rv = ie->returnVal; th1ish_result_up( ie ) /* (possibly) necessary to keep its lifetime intact */ ; } break; default: if(!cwal_exception_get(ie->e)){ t = *next; /* MARKER("eval-entry rc=%s\n", cwal_rc_cstr(rc)); */ /* th1ish_dump_val(*rv,"eval-entry rv"); */ /* dump_token(t,"Setting generic exception."); */ rc = th1ish_toss_token(ie, t, rc, "Caught non-exception error #%d (%s)", rc, cwal_rc_cstr(rc) ); } } /* th1ish_dump_val(*rv, "eval_chain() rv"); */ return rc; } int th1ish_eval_chain_push_scope( th1ish_interp * ie, cwal_simple_token * head, cwal_simple_token ** next, cwal_value **rv ){ int rc; cwal_scope * sc = cwal_scope_current_get(ie->e); cwal_value * myRv = 0; cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * subScope = &pushedScope_; int const oldSweepGuard = ie->sweepGuard; rc = cwal_scope_push(ie->e, &subScope); if(rc) return rc; assert(pushedScope_.level); ie->sweepGuard = 0; rc = th1ish_eval_chain( ie, head, next, &myRv ); ie->sweepGuard = oldSweepGuard; if(rv){ *rv = myRv; cwal_value_rescope( sc, myRv ); } cwal_scope_pop(ie->e); assert(!pushedScope_.level); return rc; } int th1ish_tokenize_all( th1ish_interp * ie, char const * src, cwal_size_t slen, cwal_simple_token ** dest ){ cwal_simple_tokenizer t = cwal_simple_tokenizer_empty; cwal_simple_tokenizer tgt = cwal_simple_tokenizer_empty; cwal_simple_token * head = 0; cwal_simple_token * tail = 0; cwal_simple_token * theTok = 0; /*cwal_simple_token * tail = 0;*/ int rc = 0; if(!ie || !src) return CWAL_RC_MISUSE; else if(!slen) slen = strlen(src); if(!*src || !slen) return CWAL_SCR_EOF; /*escBuf = &ie->buffer;*/ rc = cwal_simple_tokenizer_init(&t, src, slen); while(0==rc){ char doBreak = 0; rc = th1ish_next_token(&t); if(rc) { rc = th1ish_toss_t10n(ie, &t, rc); break; } tgt = t; switch(t.ttype){ case TT_TokErr: assert(!"Cannot happen!"); MARKER(("Error while tokenizing. start of token=[%c] message=%s\n", *t.current.begin, t.errMsg)); dump_tokenizer(&t,"TT_TokErr"); rc = CWAL_RC_ERROR; doBreak = 1; break; case TT_UNDEF: assert(!"Cannot happen?"); MARKER(("Unknown token type around byte [%c]\n", *t.current.begin)); dump_tokenizer(&t,"Unknown token"); rc = CWAL_RC_ERROR; doBreak = 1; break; case TT_EOF: rc = CWAL_SCR_EOF; break; #if 1 /* case TT_CommentHash: */ case TT_CComment: case TT_CppComment: /* MARKER("Skipping comments.\n"); */ continue; break; #endif #if 1 /* XXXXXXX disabling this breaks the following: assert "undefined" == typename 3 * 9 % 7 evaluates to 0. FIXME: find the culprits and make them space-friendly. */ case TT_ChSpace: case TT_Blank: /* case TT_Whitespace: */ continue; #endif #if 1 case TT_ChSemicolon: tgt.ttype = TH1ISH_RC_EOC; break; case TT_NL: case TT_CR: case TT_CRNL: tgt.ttype = TT_EOL; break; #endif case TT_ChParenOpen: case TT_ChBraceOpen: case TT_ChScopeOpen: /* MARKER("Running tokenize postprocessing...\n"); */ rc = th1ish_tokenize_post( ie, &t, &tgt ); /*dump_tokenizer( &tgt, "Post-processed tgt token");*/ break; case TT_OpHeredocStart: rc = th1ish_tokenize_heredoc(ie, &t, &tgt); break; default:{ if(TT_EOF == tgt.ttype){ /* MARKER("EOF reached.\n"); */ rc = CWAL_SCR_EOF; break; } } } if(doBreak) break; /* dump_tokenizer( &tgt, "tokenize_all() got token" ); */ theTok = th1ish_st_alloc( ie ); if(!theTok) { rc = CWAL_RC_OOM; break; } theTok->src = tgt.current; theTok->ttype = tgt.ttype; if(!tail){ assert(!head); head = tail = theTok; }else{ assert(!tail->right); tail->right = theTok; tail = theTok; assert(!tail->right); } if(TH1ISH_RC_EOC == rc){ rc = 0; break; } else if(CWAL_SCR_EOF == rc/*our virtual EOF*/) { rc = 0; break; } #if 0 /* i would prefer to tag keywords here, as opposed to at the end, but the obj.prototype kludge in th1ish_chain_postprocess() breaks this. */ else if(TT_Identifier == theTok->ttype){ th1ish_chain_postprocess(ie,theTok); } #endif /* dump_tokenizer( &tgt, "Got token" ); */ } /*end:*/ #if 0 if(head){ MARKER("Grabbed %u byte(s): [%*.s]\n", tail->src.begin - head->src.begin, tail->src.begin - head->src.begin, head->src.begin /* why is this not what i expect? */ ); } #endif if(rc){ cwal_st_free_chain( ie->e, head ); } else{ if(dest){ if(head) th1ish_chain_postprocess( ie, head ); *dest = head; }else{ cwal_st_free_chain( ie->e, head ); } } return rc; } int th1ish_eval_string( th1ish_interp * ie, char pushScope, char const * src, cwal_size_t slen, cwal_value **rv ){ int rc; cwal_simple_token * head = 0; cwal_value * xrv = 0; cwal_scope * callingScope = cwal_scope_current_get(ie->e); cwal_scope pushedScope_ = cwal_scope_empty; cwal_scope * subScope = &pushedScope_; if(!src || !*src || !slen){ if(rv) *rv = cwal_value_undefined(); return 0; } /* cwal_scope * sc = cwal_scope_current_get(ie->e); */ rc = th1ish_tokenize_all( ie, src, slen, &head ); if(rc) { if(CWAL_SCR_EOF==rc){ rc = 0; if(rv) *rv = cwal_value_undefined(); } return rc; } if(pushScope){ rc = cwal_scope_push(ie->e, &subScope); if(rc) return rc; } xrv = NULL; assert(!pushScope || pushedScope_.level); rc = th1ish_eval_chain( ie, head, 0, &xrv ); assert(!pushScope || pushedScope_.level); if(RC_IS_EXIT(rc) && rv && xrv){ *rv = xrv = ie->exitValue; } else if((!rc||(CWAL_RC_RETURN==rc)||(TH1ISH_RC_TOSS==rc)) && rv && xrv){ if(pushScope){ /** An interesting case: 1 && scope { var dest array [] set dest.foo object {} // crashes w/o explicit return. // scope's implicit return value = dest.foo } The problem now is that dest will be cleaned up at the end of the scope, but we have no reference to dest.PROP, which we need pass up the scope chain (because dest.PROP is our implicit return value). So when the scope ends it passes back an invalid value (cleaned up during scope pop), which we then (illegally) reference as the right-hand side of the && expression. Unfortunately, this also applies to: [proc {} { var a = 3; return a }] the var holds a ref to a, which we cannot distinguish from a ref held by a container. A potential fix might be in the cwal core: when a container is cleaning up, if its value lives in a higher scope, don't really destroy it, but move it back into probationary state (in the higher-level scope) if its refcount goes back to 0. ... after making that change to cwal, we now only have to re-scope the value. However, we still have this problem for the prototype special property but only on implicit returns: false || scope { var a array[] set a.prototype } An explicit return does not have that problem. :/ Fixed in the mean time, but the above comments are enlightening. */ /* th1ish_dump_val(xrv,"Up-scoping"); */ /* if(cwal_value_scope(xrv) == subScope) */ cwal_value_rescope( callingScope, xrv ); } *rv = xrv; } assert(!pushScope || pushedScope_.level); th1ish_st_free_chain( ie, head, 1 ); if(pushScope){ assert(pushedScope_.level); cwal_scope_pop(ie->e); assert(!pushedScope_.level); } /* th1ish_dump_val( *rv, "eval_string()=>rv" ); */ return rc; } const th1ish_script th1ish_script_empty = th1ish_script_empty_m; #if 0 static th1ish_script * th1ish_script_alloc(th1ish_interp *ie){ th1ish_script * rc = cwal_malloc(ie->e, sizeof(th1ish_script)); if(rc){ *rc = th1ish_script_empty; rc->allocStamp = &th1ish_script_empty; rc->ie = ie; } return rc; } #endif static void th1ish_script_finalize2(th1ish_script *scr){ if(scr && scr->ie){ th1ish_interp *ie = scr->ie; unsigned char * bufMem = scr->memory; /** Because the token compiler can inject new tokens into our chain (which lives in bufMem), we must jump through some hoops to clean it up... */ #define ADDR(X) ((unsigned char const *)(X)) #define IS_MINE(T) ((ADDR(T)>=bufMem) && (ADDR(T)<(bufMem+scr->memoryLength))) cwal_simple_token * t = scr->head; cwal_simple_token * toFree = 0; for( ; t; ){ if(IS_MINE(t)){ t = t->right; } else { cwal_simple_token * n = t->right; t->right = toFree; toFree = t; t = n; } } if(toFree){ th1ish_st_free_chain(ie, toFree, 1); } assert(!scr->chain); *scr = th1ish_script_empty; cwal_free(ie->e, bufMem); /* Reminder: if this API allocated scr then ((void*)scr == bufMem), else the client allocated the th1ish_script part of the construct. */ #undef IS_MINE #undef ADDR } } void th1ish_script_finalize(th1ish_script *scr){ if(scr && scr->ie){ th1ish_script * s = scr->ie->scripts.head; th1ish_script * prev = 0; for( ; s && (scr != s); prev = s, s = s->chain ){ } if(s == scr){ if(prev){ prev->chain = s->chain; s->chain = 0; }else{ assert( s == scr->ie->scripts.head ); scr->ie->scripts.head = s->chain; s->chain = 0; } } th1ish_script_finalize2(scr); } } /** Internal impl of th1ish_script_compile() and friends which takes a cwal_buffer as source and expands/re-uses it. This allow us to avoid a temporary extra copy from th1ish_script_compile_filename() and potential future work-alikes. On success ownership of buf->mem is transfered to (*rv)->memory and buf's state is re-set. *rv may or may not be allocated as part of that buffer, as documented for th1ish_script_compile(). On error non-0 is returned and ownership of buf->mem is not transfered (but it may have been expanded and appended to by this operation before it failed). */ int th1ish_script_compile_buffer(th1ish_interp *ie, char const * name, cwal_buffer * buf, th1ish_script ** rv){ /* Achtung, code duplication: this is mostly a clone of th1ish_tokenize_all(). */ cwal_simple_tokenizer t; cwal_simple_tokenizer tgt /* "interim" tokenizer which may be modified by post-processing. */; cwal_simple_token * head = 0 /* Head of the token chain */; cwal_simple_token * tail = 0 /* Current tail of the chain */; cwal_simple_token * theTok = 0 /* Current token */; cwal_simple_token * tokenArray = 0 /* Array of tokens tCount items long */; int rc = 0; char mode = 0 /* controls the loop described below */; cwal_size_t tCount = 0 /* Number of tokens to allocate */; cwal_size_t tIndex = 0 /* Current index in tokenArray */; th1ish_script * script = 0; unsigned char const * codePos = 0; unsigned char const * namePos = 0; cwal_size_t const nameLen = name ? strlen(name) : 0; unsigned char const * src = buf ? buf->mem : NULL; cwal_size_t const slen = buf ? buf->used : 0; cwal_st_src oldSrc; if(!ie || !src || !rv) return CWAL_RC_MISUSE; /* else if(!*src || !slen) return CWAL_SCR_EOF; */ else if(*rv && (*rv)->ie) return CWAL_RC_MISUSE; oldSrc = ie->src; ie->src.begin = (char const *)src; ie->src.end = ie->src.begin + slen; /** Tokenize the input twice. The first time (0==mode) we simply count the tokens (and incidentally confirm the basic tokenization). The second time (1==mode) we allocate a single block to store a copy of the source, the name, and all tokens we'll need. We need to tokenize again at that point so that the created tokens point to the our copy of the sources. The reason for this approach is to reduce compilation of a script to a single allocation. We lose the ability to modify the token chain later, though. */ start: t = tgt = cwal_simple_tokenizer_empty; assert(mode==0 || mode==1); rc = cwal_simple_tokenizer_init(&t, (char const *)src, slen); while(0==rc){ char doBreak = 0; rc = th1ish_next_token(&t); if(rc) { assert(0==mode); rc = th1ish_toss_t10n(ie, &t, rc); break; } tgt = t; switch(t.ttype){ case TT_TokErr: assert(!"Cannot happen! Caught above."); /*MARKER(("Error while tokenizing. start of token=[%c] message=%s\n", *t.current.begin, t.errMsg));*/ rc = CWAL_RC_ERROR; doBreak = 1; break; case TT_UNDEF: assert(!"Cannot happen?"); MARKER(("Unknown token type around byte [%c]\n", *t.current.begin)); dump_tokenizer(&t,"Unknown token"); rc = CWAL_RC_ERROR; doBreak = 1; break; case TT_EOF: rc = CWAL_SCR_EOF; break; #if 1 /* case TT_CommentHash: */ case TT_CComment: case TT_CppComment: continue; break; #endif #if 1 /* XXXXXXX disabling this breaks the following: assert "undefined" == typename 3 * 9 % 7 evaluates to 0. FIXME: find the culprits and make them space-friendly. */ case TT_ChSpace: case TT_Blank: /* case TT_Whitespace: */ continue; #endif case TT_ChSemicolon: tgt.ttype = TH1ISH_RC_EOC; break; case TT_NL: case TT_CR: case TT_CRNL: tgt.ttype = TT_EOL; break; case TT_ChParenOpen: case TT_ChBraceOpen: case TT_ChScopeOpen: /* MARKER("Running tokenize postprocessing...\n"); */ rc = th1ish_tokenize_post( ie, &t, &tgt ); /*dump_tokenizer( &tgt, "Post-processed tgt token");*/ break; case TT_OpHeredocStart: rc = th1ish_tokenize_heredoc(ie, &t, &tgt); break; default: break; } if(doBreak) break; else if(0==mode){ ++tCount; if(CWAL_SCR_EOF != rc) continue; /* else fall through and start the second round of tokenization... */ } if(0==mode && (buf->used==slen)){ /* Reserve enough space in buf for: src + NUL + name + NUL if name was provided + 1 th1ish_script instance if !*rv + (array of tCount tokens) */ cwal_size_t const sz = + slen + 1/*NUL*/ + nameLen + 1/*NUL*/ + (*rv ? 0 : sizeof(th1ish_script)) + (sizeof(cwal_simple_token) * tCount) ; rc = cwal_buffer_reserve(ie->e, buf, sz ); if(rc) break; /* Reminder: cwal_buffer_append() "cannot fail" here because we have already reserved all memory. */ codePos = buf->mem; buf->mem[buf->used++] = 0; if(nameLen){ namePos = buf->mem + buf->used; memcpy( buf->mem + buf->used, name, nameLen ); buf->used += nameLen; buf->mem[buf->used++] = 0; } if(!*rv){ script = (th1ish_script*)(buf->mem+buf->used); buf->used += sizeof(th1ish_script); } tokenArray = (cwal_simple_token*)(buf->mem+buf->used); mode = 1; src = codePos /* we need the tokens to point to this copy! */; buf->used += (sizeof(cwal_simple_token)*tCount); assert(buf->used <= buf->capacity); goto start; } /* Now create the token and append it to the chain... */ /* dump_tokenizer( &tgt, "tokenize_all() got token" ); */ assert(tIndexsrc = tgt.current; theTok->ttype = tgt.ttype; if(!tail){ assert(!head); head = tail = theTok; }else{ assert(!tail->right); tail->right = theTok; tail = theTok; assert(!tail->right); } if(CWAL_SCR_EOF == rc/*our virtual EOF*/) { rc = 0; break; } /* dump_tokenizer( &tgt, "Got token" ); */ } if(!rc){ assert(1==mode); assert(tCount==tIndex); if(script){ assert(!*rv); *rv = script; } else{ assert(*rv); script = *rv; } *script = th1ish_script_empty; script->ie = ie; if(head) th1ish_chain_postprocess( ie, head ) /* Reminder: i would prefer to do this during tokenization but special-casing in th1ish_chain_postprocess() breaks if i do that. Bad, bad special case(s). */; script->memory = buf->mem /* take ownership of this memory */; script->memoryLength = buf->capacity; script->code = codePos; script->codeLength = slen; script->head = script->cursor = head; script->nameLength = nameLen; if(namePos){ script->name = namePos; } *buf = cwal_buffer_empty; } ie->src = oldSrc; return rc; } int th1ish_script_compile(th1ish_interp *ie, char const * name, unsigned char const * src, cwal_size_t slen, th1ish_script ** rv){ if(!ie || !src) return CWAL_RC_MISUSE; else { int rc; cwal_buffer buf = cwal_buffer_empty; rc = cwal_buffer_append( ie->e, &buf, src, slen ); if(!rc){ assert(0==buf.mem[buf.used]); rc = th1ish_script_compile_buffer( ie, name, &buf, rv ); if(rc){ cwal_buffer_reserve(ie->e, &buf, 0 ); }else{ assert(!buf.mem); assert(!buf.used); assert(!buf.capacity); } } return rc; } } int th1ish_script_compile_filename(th1ish_interp *ie, char const * filename, th1ish_script ** rv){ if(!ie || !filename || !*filename ) return CWAL_RC_MISUSE; else { cwal_buffer buf = cwal_buffer_empty; int rc = cwal_buffer_fill_from_filename( ie->e, &buf, filename ); if(!rc){ rc = th1ish_script_compile_buffer( ie, filename, &buf, rv ); if(rc){ cwal_buffer_reserve(ie->e, &buf, 0 ); }else{ assert(!buf.mem); assert(!buf.used); assert(!buf.capacity); } } return rc; } } int th1ish_script_add( th1ish_interp * ie, th1ish_script * sc ){ if(!ie || !sc || (ie!=sc->ie) || sc->chain) return CWAL_RC_MISUSE; sc->chain = ie->scripts.head; ie->scripts.head = sc; return 0; } int th1ish_script_run(th1ish_script *sc, char pushScope, cwal_value **rv){ if(!sc || !sc->ie) return CWAL_RC_MISUSE; else { int rc; th1ish_script * prevScript = sc->ie->scripts.current; int const oldSweepGuard = sc->ie->sweepGuard; sc->ie->scripts.current = sc; sc->cursor = sc->head; sc->ie->sweepGuard = 0; rc = rv ? th1ish_eval_chain( sc->ie, sc->cursor, &sc->cursor, rv ) : th1ish_eval_chain_push_scope( sc->ie, sc->cursor, &sc->cursor, rv ); sc->ie->sweepGuard = oldSweepGuard; sc->ie->scripts.current = prevScript; return rc; } } int th1ish_token_recycler_size_set( th1ish_interp * ie, cwal_size_t maxSize ){ if(!ie) return CWAL_RC_MISUSE; else { cwal_recycler * r = &ie->recycler.tokens; cwal_simple_token * t = 0; while( r->count > maxSize ){ t = (cwal_simple_token *)r->list; assert(t); r->list = t->right; t->right = 0; th1ish_st_free_chain(ie, t, 0); --r->count; } r->maxLength = maxSize; return 0; } } int th1ish_var_decl( th1ish_interp * ie, char const * key, cwal_size_t keyLen, cwal_value * v, uint16_t flags ){ return ((ie && ie->e) || !key) ? cwal_var_decl( ie->e, 0, key, keyLen, v, flags ) : CWAL_RC_MISUSE; } int th1ish_var_decl_v( th1ish_interp * ie, cwal_value * key, cwal_value * v, uint16_t flags ){ return ((ie && ie->e) || !key) ? cwal_var_decl_v( ie->e, 0, key, v, flags ) : CWAL_RC_MISUSE; } int th1ish_var_set( th1ish_interp * ie, int scopeDepth, char const * key, cwal_size_t keyLen, cwal_value * v ){ return (!ie || !ie->e || !key) ? CWAL_RC_MISUSE : cwal_scope_chain_set( cwal_scope_current_get(ie->e), scopeDepth, key, keyLen, v ); } int th1ish_var_set_v( th1ish_interp * ie, int scopeDepth, cwal_value * key, cwal_value * v ){ return (!ie || !ie->e || !key) ? CWAL_RC_MISUSE : cwal_scope_chain_set_v( cwal_scope_current_get(ie->e), scopeDepth, key, v ); } cwal_value * th1ish_var_get( th1ish_interp * ie, int scopeDepth, char const * key, cwal_size_t keyLen ){ return (!ie || !key) ? NULL : cwal_scope_chain_search( cwal_scope_current_get(ie->e), scopeDepth, key, keyLen, NULL ); } cwal_value * th1ish_var_get_v( th1ish_interp * ie, int scopeDepth, cwal_value * key ){ return (!ie || !key) ? NULL : cwal_scope_chain_search_v( cwal_scope_current_get(ie->e), scopeDepth, key, NULL ); } int th1ish_eval_filename( th1ish_interp * ie, char pushScope, char const * filename, cwal_value **rv ){ int rc; cwal_buffer buf = cwal_buffer_empty; cwal_st_src origSrc = ie->src; char const * oldName = ie->srcName; enum { BufStartSize = 1024 * 4 }; rc = cwal_buffer_reserve( ie->e, &buf, (cwal_size_t)BufStartSize ); if(rc) goto end; rc = cwal_buffer_fill_from_filename( ie->e, &buf, filename ); if(rc) goto end; /* Fudge ie->src so that error reporting will work. This assumes, however... that the erroring code is not failing while calling back into tokens from a higher-level chain (if any). */ ie->src.begin = (char const *)buf.mem; ie->src.end = (char const *)buf.mem + buf.used; ie->srcName = filename; rc = th1ish_eval_string( ie, pushScope, (char const *)buf.mem, buf.used, rv ); ie->srcName = oldName; ie->src = origSrc; end: cwal_buffer_reserve(ie->e, &buf, 0); return rc; } void th1ish_trace_assertions( th1ish_interp * ie, char enable ){ if(ie){ if(enable) ie->flags |= TH1ISH_F_TRACE_ASSERTIONS; else ie->flags &= ~TH1ISH_F_TRACE_ASSERTIONS; } } void th1ish_enable_proc_token_caching( th1ish_interp * ie, char enable ){ if(ie){ if(enable) ie->flags |= TH1ISH_F_CACHE_PROCS; else ie->flags &= ~TH1ISH_F_CACHE_PROCS; } } void th1ish_enable_conventional_call_op( th1ish_interp * ie, char enable ){ if(ie){ if(enable) ie->flags |= TH1ISH_F_CONVENTIONAL_CALL_OP; else ie->flags &= ~TH1ISH_F_CONVENTIONAL_CALL_OP; } } void th1ish_enable_extra_loop_scopes( th1ish_interp * ie, char enable ){ if(ie){ if(enable) ie->flags |= TH1ISH_F_LOOPS_EXTRA_SCOPES; else ie->flags &= ~TH1ISH_F_LOOPS_EXTRA_SCOPES; } } void th1ish_enable_autosweep_logging( th1ish_interp * ie, char enable ){ if(ie){ if(enable) ie->flags |= TH1ISH_F_LOG_AUTO_SWEEP; else ie->flags &= ~TH1ISH_F_LOG_AUTO_SWEEP; } } void th1ish_enable_string_interning( th1ish_interp * ie, char enable ){ if(ie && ie->e){ int rc = cwal_engine_feature_flags(ie->e,-1); if(enable) rc |= CWAL_FEATURE_INTERN_STRINGS; else rc &= ~CWAL_FEATURE_INTERN_STRINGS; cwal_engine_feature_flags(ie->e, rc ); } } void th1ish_enable_stacktrace( th1ish_interp * ie, char enable ){ if(ie){ if(enable) ie->flags |= TH1ISH_F_TRACE_STACK; else ie->flags &= ~TH1ISH_F_TRACE_STACK; } } static void th1ish_strace_push_pos( th1ish_interp * ie, unsigned char const * pos, th1ish_stack_entry * ent ){ assert(ie && ie->e); ent->scriptPos = pos; if(ie->stack.tail){ ie->stack.tail->down = ent; ent->up = ie->stack.tail; ie->stack.tail = ent; }else{ assert(!ie->stack.head); ie->stack.head = ie->stack.tail = ent; } ++ie->stack.count; } int th1ish_strace_pop( th1ish_interp * ie ){ assert(ie && ie->e); assert(ie->stack.count); assert(ie->stack.tail); if(!ie->stack.count) return CWAL_RC_RANGE; else { th1ish_stack_entry * x = ie->stack.tail; assert(!x->down); if(x->up){ assert(x->up->down == x); ie->stack.tail = x->up; x->up->down = NULL; x->up = NULL; }else{ ie->stack.head = ie->stack.tail = NULL; } --ie->stack.count; } return 0; } int th1ish_install_argv( th1ish_interp *ie, char const * varName, int argc, char const * const * argv ){ int rc; cwal_value * av = NULL; int i = 0; cwal_array * all; av = cwal_new_array_value(ie->e); if(!av) return CWAL_RC_OOM; all = cwal_value_get_array(av); if(argc>0){ rc = cwal_array_reserve(all, (cwal_size_t)argc); if(rc) goto end; } for( i = 0; !rc && (i < argc); ++i ){ cwal_value * v = cwal_value_from_arg(ie->e, argv[i]); if(!v){ rc = CWAL_RC_OOM; goto end; } rc = cwal_array_append(all, v); } if(rc) goto end; for( i = 0; argc>0; ++i, --argc){ if(0==strcmp("--",argv[i])){ --argc; ++i; break; } } rc = cwal_parse_argv_flags( ie->e, argc, argv+i, &av ); if(!rc){ rc = th1ish_var_set(ie, 0, (varName&&*varName) ? varName : "ARGV", 0, av); } end: if(rc && av) cwal_value_unref(av); return rc; } struct Th1ishObBuffer { th1ish_interp * ie; cwal_buffer buf; }; typedef struct Th1ishObBuffer Th1ishObBuffer; static void th1ish_Th1ishObBuffer_finalize( cwal_engine * e, void * m ){ Th1ishObBuffer * st = (Th1ishObBuffer *)m; cwal_buffer_reserve(e, &st->buf, 0); st->ie = NULL; cwal_free(e, m); } static int th1ish_Th1ishObBuffer_output_f( void * state, void const * src, cwal_size_t n ){ Th1ishObBuffer * ob = (Th1ishObBuffer*) state; return cwal_buffer_append(ob->ie->e, &ob->buf, src, n); } static int th1ish_ob_push_outputer( th1ish_interp * ie, cwal_outputer const * out ){ if(!ie || !out) return CWAL_RC_MISUSE; else{ int rc; cwal_outputer * co = (cwal_outputer *)cwal_malloc(ie->e, sizeof(cwal_outputer)); if(!co){ rc = CWAL_RC_OOM; goto end; } rc = cwal_list_append( ie->e, &ie->ob, co ); end: if(rc){ if(co) cwal_free(ie->e, co); }else{ *co = ie->e->vtab->outputer; ie->e->vtab->outputer = *out; } return rc; } } #if 0 /** This is how we "could" integrate OB's flush() with cwal_output(), but doing so causes functions which flush (e.g. the default print() impl) to Do The Wrong Thing when we do that. So api.io.flush() becomes a no-op while buffering is on. */ int th1ish_ob_flush( th1ish_interp * ie ); static int th1ish_Th1ishObBuffer_flush(void * ob){ return th1ish_ob_flush( ((Th1ishObBuffer*)ob)->ie ); } #endif cwal_size_t th1ish_ob_level( th1ish_interp * ie ){ return ie ? ie->ob.count : 0; } int th1ish_ob_push( th1ish_interp * ie ){ int rc; cwal_outputer out = cwal_outputer_empty; Th1ishObBuffer * ob; assert(ie); ob = (Th1ishObBuffer *)cwal_malloc(ie->e, sizeof(Th1ishObBuffer)); if(!ob) return CWAL_RC_OOM; ob->ie = ie; ob->buf = cwal_buffer_empty; out.state.data = ob; out.state.finalize = th1ish_Th1ishObBuffer_finalize; out.output = th1ish_Th1ishObBuffer_output_f; /* out.flush = th1ish_Th1ishObBuffer_flush; */ rc = th1ish_ob_push_outputer( ie, &out ); if(rc){ cwal_free( ie->e, ob ); } return rc; } int th1ish_ob_pop( th1ish_interp * ie ){ if(!ie) return CWAL_RC_MISUSE; else if(!ie->ob.count) return CWAL_RC_RANGE; else{ cwal_size_t const i = ie->ob.count-1; cwal_outputer * ob = (cwal_outputer *)ie->ob.list[i]; cwal_outputer * io = &ie->e->vtab->outputer; if(io->state.finalize){ io->state.finalize(ie->e, io->state.data); } *io = *ob; cwal_free(ie->e, ob); --ie->ob.count; return 0; } } int th1ish_ob_get( th1ish_interp * ie, cwal_buffer ** tgt ){ if(!ie || !tgt) return CWAL_RC_MISUSE; else if(!ie->ob.count) return CWAL_RC_RANGE; else{ cwal_outputer * io = &ie->e->vtab->outputer; Th1ishObBuffer * ob; assert(io->state.data); ob = (Th1ishObBuffer*)io->state.data; *tgt = &ob->buf; return 0; } } int th1ish_ob_take( th1ish_interp * ie, cwal_buffer * tgt ){ if(!ie || !tgt) return CWAL_RC_MISUSE; else if(!ie->ob.count) return CWAL_RC_RANGE; else{ cwal_outputer * io = &ie->e->vtab->outputer; Th1ishObBuffer * ob; assert(io->state.data); ob = (Th1ishObBuffer*)io->state.data; if(tgt != &ob->buf) *tgt = ob->buf; ob->buf = cwal_buffer_empty; return 0; } } int th1ish_ob_clear( th1ish_interp * ie, char releaseBufferMem ){ if(!ie) return CWAL_RC_MISUSE; else if(!ie->ob.count) return CWAL_RC_RANGE; else{ cwal_outputer * io = &ie->e->vtab->outputer; Th1ishObBuffer * ob; assert(io->state.data); ob = (Th1ishObBuffer*)io->state.data; if(releaseBufferMem) cwal_buffer_reserve(ie->e, &ob->buf, 0); else ob->buf.used = 0; return 0; } } int th1ish_ob_flush( th1ish_interp * ie ){ if(!ie) return CWAL_RC_MISUSE; else if(!ie->ob.count) return CWAL_RC_RANGE; else{ int rc = 0; cwal_size_t const i = ie->ob.count-1; cwal_outputer * to = (cwal_outputer *)ie->ob.list[i]; cwal_outputer * io = &ie->e->vtab->outputer; Th1ishObBuffer * ob; assert(io->state.data); assert(to != io); ob = (Th1ishObBuffer*)io->state.data; if(ob->buf.used){ rc = to->output( to->state.data, ob->buf.mem, ob->buf.used ); } ob->buf.used = 0; return rc; } } int th1ish_f_ob_pop( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = th1ish_args_interp(args); assert(ie); return th1ish_ob_pop( ie ); } int th1ish_f_ob_push( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = th1ish_args_interp(args); assert(ie); return th1ish_ob_push( ie ); } int th1ish_f_ob_level( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = th1ish_args_interp(args); assert(ie); *rv = cwal_new_integer(args->engine, (cwal_int_t)ie->ob.count); return *rv ? 0 : CWAL_RC_OOM; } int th1ish_f_ob_get( cwal_callback_args const * args, cwal_value **rv ){ int rc; th1ish_interp * ie = th1ish_args_interp(args); cwal_buffer * buf = NULL; assert(ie); rc = th1ish_ob_get(ie, &buf); if(rc) return rc; else{ assert(buf); } *rv = cwal_new_string_value(args->engine, buf->used ? (char const *)buf->mem : NULL, buf->used); return *rv ? 0 : CWAL_RC_OOM; } int th1ish_f_ob_take_string( cwal_callback_args const * args, cwal_value **rv ){ int rc; th1ish_interp * ie = th1ish_args_interp(args); cwal_buffer buf = cwal_buffer_empty; assert(ie); rc = th1ish_ob_take(ie, &buf); if(rc) return rc; #if 0 *rv = cwal_new_string_value(args->engine, buf.used ? (char const *)buf.mem : NULL, buf.used); rc = *rv ? 0 : CWAL_RC_OOM; #else { /* Finally get to use a z-string... */ cwal_string * s = cwal_new_zstring(args->engine, buf.used ? (char *)buf.mem : NULL, buf.used); if(!s) rc = CWAL_RC_OOM; else{ *rv = cwal_string_value(s); buf = cwal_buffer_empty /* we just took over ownership of buf.mem */; } } #endif cwal_buffer_reserve(args->engine, &buf, 0); return rc; } int th1ish_f_ob_take_buffer( cwal_callback_args const * args, cwal_value **rv ){ int rc; th1ish_interp * ie = th1ish_args_interp(args); cwal_buffer * buf; cwal_value * bv; assert(ie); bv = cwal_new_buffer_value(args->engine, 0); if(!bv) return CWAL_RC_OOM; buf = cwal_value_get_buffer(bv); rc = th1ish_ob_take(ie, buf); if(rc){ cwal_value_unref(bv); return rc; } *rv = bv; return 0; } int th1ish_f_ob_clear( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = (th1ish_interp *)args->state; assert(ie); return th1ish_ob_clear( ie, 0 ); } int th1ish_f_ob_flush( cwal_callback_args const * args, cwal_value **rv ){ th1ish_interp * ie = (th1ish_interp *)args->state; assert(ie); return th1ish_ob_flush( ie ); } int th1ish_install_ob( th1ish_interp * ie, cwal_value * tgt ){ if(!ie || !tgt) return CWAL_RC_MISUSE; else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE; else{ int rc; cwal_value * v; #define CHECKV if(!v)return CWAL_RC_OOM #define FUNC(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ rc = cwal_prop_set( tgt, NAME, strlen(NAME), v ); \ if(rc) goto end; FUNC("clear", th1ish_f_ob_clear); FUNC("flush", th1ish_f_ob_flush); FUNC("getString", th1ish_f_ob_get); FUNC("pop", th1ish_f_ob_pop); FUNC("push", th1ish_f_ob_push); FUNC("level", th1ish_f_ob_level); FUNC("takeBuffer", th1ish_f_ob_take_buffer); FUNC("takeString", th1ish_f_ob_take_string); #undef FUNC #undef CHECKV end: if(rc && v) cwal_value_unref(v); return rc; } } int th1ish_install_ob_2( th1ish_interp * ie, cwal_value * tgt, char const * name ){ if(!ie || !tgt || !name || !*name) return CWAL_RC_MISUSE; else { int rc; cwal_value * ob = cwal_new_object_value(ie->e); if(!ob) return CWAL_RC_OOM; rc = th1ish_install_ob(ie, ob); if(!rc) rc = cwal_prop_set(tgt, name, strlen(name), ob); return rc; } } int th1ish_f_slurp_file( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_value * fnv; cwal_buffer buf = cwal_buffer_empty; ARGS_IE; if(1!=args->argc) goto misuse; fnv = args->argv[0]; if(!cwal_value_is_string(fnv)) goto misuse; rc = cwal_buffer_fill_from_filename( args->engine, &buf, cwal_string_cstr(cwal_value_get_string(fnv))); if(rc) return rc; #if 1 /* Use a Z-string... */ #if 0 /* Maybe add this when we get around to testing it. */ if(buf.capacity > (buf.used * 10 / 8)){ void * m; MARKER(("Buffer used=%u, capacity=%u\n",(unsigned)buf.used, (unsigned)buf.capacity)); m = cwal_realloc(args->engine, buf.mem, buf.used); if(m){ buf.mem = m; buf.capacity = buf.used; MARKER(("Shrunk buffer used=%u, capacity=%u\n",(unsigned)buf.used, (unsigned)buf.capacity)); } /* else ignore - keep the memory we have */ } #endif *rv = cwal_new_zstring_value( args->engine, (char *)buf.mem, buf.used ); /* Ownership of buf->mem is transfered even on error. */ buf = cwal_buffer_empty; #else /* Use an X-string */ *rv = cwal_new_string_value( args->engine, (char const *)buf.mem, buf.used ); cwal_buffer_reserve(args->engine, &buf, 0); #endif if(!*rv) rc = CWAL_RC_OOM; return rc; misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a single string " "(filename) argument."); } static int th1ish_f_json_parse_impl( cwal_callback_args const * args, cwal_value **rv, char isFilename ){ int rc; cwal_value * root = NULL; cwal_size_t slen = 0; cwal_json_parse_info pInfo = cwal_json_parse_info_empty; th1ish_interp * ie = th1ish_args_interp(args); char const * cstr; cwal_value * arg = args->argc ? args->argv[0] : args->self; assert(ie); cstr = cwal_value_get_cstr(arg, &slen); if(!cstr){ cwal_buffer * b = cwal_value_buffer_part(ie->e, arg); if(b){ cstr = (char const *)b->mem; slen = b->used; } if(!cstr){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "Expecting a %s as argument or 'this'.", isFilename ? "filename" : "JSON (string|Buffer)"); } } rc = isFilename ? cwal_json_parse_filename( args->engine, cstr, &root, &pInfo ) : cwal_json_parse_cstr( args->engine, cstr, slen, &root, &pInfo ); if(rc){ if(pInfo.errorCode){ return cwal_exception_setf(args->engine, rc, "Parsing JSON failed at byte " "offset %"CWAL_SIZE_T_PFMT ", line %"CWAL_SIZE_T_PFMT ", column %"CWAL_SIZE_T_PFMT " with code %d (%s).", pInfo.length, pInfo.line, pInfo.col, rc, cwal_rc_cstr(rc)); }else{ return cwal_exception_setf(args->engine, rc, "Parsing JSON failed with code %d (%s).", rc, cwal_rc_cstr(rc)); } } assert(root); *rv = root; return 0; } int th1ish_f_json_parse_string( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_f_json_parse_impl(args, rv, 0); } int th1ish_f_json_parse_file( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_f_json_parse_impl(args, rv, 1); } static int th1ish_f_to_json_token_impl( cwal_callback_args const * args, cwal_value **rv, char useSelf /*0=use args->argv[0], else args->self*/ ){ if(!args->argc && !useSelf){ misuse: return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "'toJSONString' requires one argument."); }else{ int rc = 0; cwal_value * v = NULL; cwal_value * vIndent; cwal_json_output_opt outOpt = cwal_json_output_opt_empty; cwal_buffer buf = cwal_buffer_empty; cwal_buffer * jb = NULL; cwal_size_t oldUsed; char selfBuf = 0; int indentIndex = -1; th1ish_interp * ie = th1ish_args_interp(args); assert(ie); if(useSelf){ jb = cwal_value_buffer_part(ie->e, args->self); /* Change semantics when this===Buffer */ v = jb ? (args->argc ? args->argv[0] : NULL) : args->self; indentIndex = jb ? 1 : 0; } else{ v = args->argv[0]; indentIndex = 1; } if(!v) goto misuse; vIndent = (indentIndex >= (int)args->argc) ? NULL : args->argv[indentIndex]; selfBuf = (NULL!=jb); if(!selfBuf) jb = &buf; oldUsed = jb->used; outOpt.indent = vIndent ? cwal_value_get_integer(vIndent) : 0; rc = cwal_json_output_buffer( args->engine, v, jb, &outOpt ); if(!rc){ v = selfBuf ? cwal_new_integer(args->engine, (cwal_int_t)(jb->used - oldUsed)) : cwal_new_string_value( args->engine, jb->used ? ((char const *)jb->mem + oldUsed) : NULL, jb->used - oldUsed ); if(!v) rc = CWAL_RC_OOM; else *rv = v; } if(buf.mem) cwal_buffer_reserve( args->engine, &buf, 0 ); return rc; } } int th1ish_f_this_to_json_token( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_f_to_json_token_impl( args, rv, 1 ); } int th1ish_f_arg_to_json_token( cwal_callback_args const * args, cwal_value **rv ){ return th1ish_f_to_json_token_impl( args, rv, 0 ); } int th1ish_f_import_script( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_value * fnv; th1ish_script * sc = 0; ARGS_IE; char fromCache = 0; char const * fname; assert(ie); if(1!=args->argc) goto misuse; fnv = args->argv[0]; if(!cwal_value_is_string(fnv)) goto misuse; fname = cwal_string_cstr(cwal_value_get_string(fnv)); /** See if ie already has a script with this name... TODO?: add a boolean parameter to disable cache lookup? Or to disable re-evaluation of cached results? */ sc = th1ish_script_by_name(ie, fname); if(sc) fromCache = 1; else { rc = th1ish_script_compile_filename( ie, fname, &sc ); if(rc) return rc; rc = th1ish_script_add( ie, sc ); if(rc){ th1ish_script_finalize(sc); return rc; } } rc = th1ish_script_run( sc, 0, rv ); if(rc && !fromCache){ th1ish_script_finalize(sc); } return rc; misuse: return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a single string " "(filename) argument."); } int th1ish_f_value_compare( cwal_callback_args const * args, cwal_value **rv ){ int rc; if(!args->argc || (args->argc>2)){ return cwal_exception_setf( args->engine, CWAL_RC_MISUSE, "Expecting 1 or 2 arguments."); } rc = (args->argc > 1) ? cwal_value_compare(args->argv[0], args->argv[1]) : cwal_value_compare(args->self, args->argv[0]) ; *rv = cwal_new_integer(args->engine, (cwal_int_t)rc); return *rv ? 0 : CWAL_RC_OOM; } int th1ish_f_clone_value( cwal_callback_args const * args, cwal_value **rv ){ int rc; ARGS_IE; assert(ie); if(1 != args->argc){ rc = th1ish_toss(ie, CWAL_RC_MISUSE, "clone requires a single argument."); } else { rc = cwal_value_clone2( args->argv[0], rv ); if(rc){ rc = th1ish_toss(ie, rc, "Clone failed with code %d (%s).", rc, cwal_rc_cstr(rc)); } } return rc; } int th1ish_f_time( cwal_callback_args const * args, cwal_value **rv ){ time_t const tm = time(NULL); cwal_int_t const it = (cwal_int_t)tm; if(tm != (time_t)it){ /* too large for our int type... */ *rv = cwal_new_double(args->engine, (double)tm); }else{ *rv = cwal_new_integer(args->engine, it); } return *rv ? 0 : CWAL_RC_OOM; } int th1ish_f_hwtime( cwal_callback_args const * args, cwal_value **rv ){ #if 1 return cwal_exception_setf(args->engine, CWAL_RC_ERROR, "hwtime() broken due to weird portability problem."); #else uint64_t const t = sqlite3Hwtime(); *rv = (t > (uint64_t)CWAL_INT_T_MAX) ? cwal_new_double(args->engine, (cwal_double_t)t) : cwal_new_integer(args->engine, (cwal_int_t)t) ; return *rv ? 0 : CWAL_RC_OOM; #endif } int th1ish_f_getenv( cwal_callback_args const * args, cwal_value **rv ){ char const * cstr = args->argc ? cwal_value_get_cstr(args->argv[0], NULL) : NULL; if(!cstr){ return cwal_exception_setf( args->engine, CWAL_RC_MISUSE, "Expecting a string argument."); }else{ char const * v = getenv(cstr); *rv = v ? cwal_new_string_value(args->engine, v, 0) : cwal_value_undefined(); return *rv ? 0 : CWAL_RC_OOM; } } int th1ish_f_rand_int( cwal_callback_args const * args, cwal_value **rv ){ static cwal_size_t seed = 0; cwal_int_t v; if( !seed ){ seed = (cwal_size_t)((unsigned long)args->self); srand( seed ); } v = (cwal_int_t)(rand() % CWAL_INT_T_MAX); *rv = cwal_new_integer( args->engine, v); return *rv ? 0 : CWAL_RC_OOM; } cwal_size_t th1ish_strftime(char *s, cwal_size_t maxsize, const char *format, const struct tm *timeptr); int th1ish_f_strftime( cwal_callback_args const * args, cwal_value **rv ){ time_t timt = -1; enum {BufSize = 512}; char buf[BufSize]; struct tm * tim; cwal_size_t sf; char const * fmt = args->argc ? cwal_value_get_cstr(args->argv[0], NULL) : NULL; if(!fmt){ return cwal_exception_setf(args->engine, CWAL_RC_MISUSE, "Expecting a format string."); } if(args->argc>1){ timt = (time_t)cwal_value_get_double(args->argv[1]); } if(timt < 0){ timt = time(NULL); } tim = localtime(&timt); memset(buf, 0, BufSize); sf = th1ish_strftime( buf, (cwal_size_t)BufSize, fmt, tim ); if(!sf && *buf){ return cwal_exception_setf(args->engine, CWAL_RC_RANGE, "strftime() failed. Possibly some " "value is too long or some buffer " "too short."); } assert(!buf[sf]); *rv = cwal_new_string_value(args->engine, buf, sf); return *rv ? 0 : CWAL_RC_OOM; } int th1ish_install_json( th1ish_interp * ie, cwal_value * ns ){ cwal_value * mod = NULL; char const * modKey = "json"; cwal_size_t const keyLen = 4; cwal_value * v; int rc; if(!ie || !ns) return CWAL_RC_MISUSE; else if(!cwal_props_can(ns)) return CWAL_RC_TYPE; else if( cwal_prop_get(ns, modKey, keyLen) ) return 0; mod = cwal_new_object_value(ie->e); if(!mod) return CWAL_RC_OOM; rc = cwal_prop_set(ns, modKey, keyLen, mod); if(rc){ cwal_value_unref(mod); return rc; } #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define SET(NAME) \ CHECKV; \ rc = cwal_prop_set( mod, NAME, strlen(NAME), v ); \ if(rc) goto end #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ SET(NAME) FUNC2("parse", th1ish_f_json_parse_string); FUNC2("parseFile", th1ish_f_json_parse_file); #undef SET #undef FUNC2 #undef CHECKV end: return rc; } /* Defined in th1ish_str.c */ int th1ish_setup_strings( th1ish_interp * ie, cwal_value * ns ); /* Defined in th1ish_io.c */ int th1ish_setup_io( th1ish_interp * ie, cwal_value * ns ); /* Defined in th1ish_number.c */ int th1ish_setup_number( th1ish_interp * ie, cwal_value * ns ); /* Defined in th1ish_protos.c */ int th1ish_setup_hash( th1ish_interp * ie, cwal_value * ns ); int th1ish_install_api( th1ish_interp * ie ){ int rc; cwal_value * vApi; cwal_value * v; cwal_value * protoList; /** Install base prototypes. */ #define CHECKV if(!v) { rc = CWAL_RC_OOM; goto end; } (void)0 #define RC if(rc) goto end #define ADDV(NAME) CHECKV; \ rc = cwal_prop_set(protoList, NAME, strlen(NAME), v) vApi = th1ish_new_object(ie); if(!vApi) { rc = CWAL_RC_OOM; goto end; } #if 1 rc = cwal_var_decl( ie->e, cwal_scope_current_get(ie->e), "api", 3, vApi, CWAL_VAR_F_CONST ); #else rc = cwal_scope_chain_set( cwal_scope_current_get(ie->e), 0, "api", 3, vApi ); #endif RC; v = protoList = th1ish_new_object(ie); CHECKV; rc = cwal_prop_set(vApi, "prototypes", 10, protoList); if(rc){ cwal_value_unref(protoList); protoList = 0; goto end; } v = th1ish_prototype_array(ie); CHECKV; ADDV("Array"); v = th1ish_prototype_object(ie); CHECKV; ADDV("Object"); v = th1ish_prototype_function(ie); CHECKV; ADDV("Function"); v = th1ish_prototype_hash(ie); CHECKV; ADDV("Hash"); v = th1ish_prototype_integer(ie); CHECKV; ADDV("Integer"); v = th1ish_prototype_double(ie); CHECKV; ADDV("Double"); v = th1ish_prototype_buffer(ie); CHECKV; ADDV("Buffer"); v = th1ish_prototype_exception(ie); CHECKV; ADDV("Exception"); v = th1ish_prototype_string(ie); CHECKV; ADDV("String"); v = th1ish_prototype_pf(ie); CHECKV; ADDV("PathFinder"); rc = th1ish_setup_strings( ie, vApi ); RC; rc = th1ish_setup_io( ie, vApi ); RC; rc = th1ish_setup_number( ie, vApi ); RC; rc = th1ish_setup_hash( ie, vApi ); RC; rc = th1ish_install_pf( ie, vApi ); RC; /* 'ob' installs directly into the namespace we give it, so add an extra namespace for it. */ rc = th1ish_install_ob_2(ie, vApi, "ob"); RC; rc = th1ish_install_json( ie, vApi ); RC; end: #undef ADDV #undef CHECKV #undef RC return rc; } #undef TNEXT #undef IE_SKIP_MODE #undef dump_token #undef dump_tokenizer #undef th1ish_dump_val #undef TH1ISH_PROC_CACHE_TOKENS #undef th1ish_reset_this #undef FUNC_SYM_PROP #undef FUNC_SYM_PROP_LEN #undef ARGS_IE #undef th1ish_eval_check_exit #undef RC_IS_EXIT #undef MARKER /* end of file th1ish.c */ /* start of file strftime.c */ /******************************************************************************* * The Elm Mail System - $Revision: 1.3 $ $State: Exp $ * * Public-domain relatively quick-and-dirty implemenation of * ANSI library routine for System V Unix systems. * * Arnold Robbins * ******************************************************************************* * Bug reports, patches, comments, suggestions should be sent to: * (Note: this routine is provided as is, without support for those sites that * do not have strftime in their library) * * Syd Weinstein, Elm Coordinator * elm@DSI.COM dsinc!elm * ******************************************************************************* * $Log: strftime.c,v $ * Revision 1.3 1993/10/09 19:38:51 smace * Update to elm 2.4 pl23 release version * * Revision 5.8 1993/08/23 02:46:51 syd * Test ANSI_C, not __STDC__ (which is not set on e.g. AIX). * From: decwrl!uunet.UU.NET!fin!chip (Chip Salzenberg) * * Revision 5.7 1993/08/03 19:28:39 syd * Elm tries to replace the system toupper() and tolower() on current * BSD systems, which is unnecessary. Even worse, the replacements * collide during linking with routines in isctype.o. This patch adds * a Configure test to determine whether replacements are really needed * (BROKE_CTYPE definition). The header file is now included * globally through hdrs/defs.h and the BROKE_CTYPE patchup is handled * there. Inclusion of was removed from *all* the individual * files, and the toupper() and tolower() routines in lib/opt_utils.c * were dropped. * From: chip@chinacat.unicom.com (Chip Rosenthal) * * Revision 5.6 1993/08/03 19:20:31 syd * Implement new timezone handling. New file lib/get_tz.c with new timezone * routines. Added new TZMINS_USE_xxxxxx and TZNAME_USE_xxxxxx configuration * definitions. Obsoleted TZNAME, ALTCHECK, and TZ_MINUTESWEST configuration * definitions. Updated Configure. Modified lib/getarpdate.c and * lib/strftime.c to use new timezone routines. * From: chip@chinacat.unicom.com (Chip Rosenthal) * * Revision 5.5 1993/06/10 03:17:45 syd * Change from TZNAME_MISSING to TZNAME * From: Syd via request from Dan Blanchard * * Revision 5.4 1993/05/08 19:56:45 syd * update to newer version * From: Syd * * Revision 5.3 1993/04/21 01:42:23 syd * avoid name conflicts on min and max * * Revision 5.2 1993/04/16 04:29:34 syd * attempt to bsdize a bit strftime * From: many via syd * * Revision 5.1 1993/01/27 18:52:15 syd * Initial checkin of contributed public domain routine. * This routine is provided as is and not covered by Elm Copyright. ******************************************************************************/ /* * strftime.c * * Public-domain relatively quick-and-dirty implementation of * ANSI library routine for System V Unix systems. * * It's written in old-style C for maximal portability. * However, since I'm used to prototypes, I've included them too. * * If you want stuff in the System V ascftime routine, add the SYSV_EXT define. * For extensions from SunOS, add SUNOS_EXT. * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE. * For complete POSIX semantics, add POSIX_SEMANTICS. * * The code for %c, %x, and %X is my best guess as to what's "appropriate". * This version ignores LOCALE information. * It also doesn't worry about multi-byte characters. * So there. * * This file is also shipped with GAWK (GNU Awk), gawk specific bits of * code are included if GAWK is defined. * * Arnold Robbins * January, February, March, 1991 * Updated March, April 1992 * Updated May, 1993 * * Fixes from ado@elsie.nci.nih.gov * February 1991, May 1992 * Fixes from Tor Lillqvist tor@tik.vtt.fi * May, 1993 * * Fast-forward to July 2013... * * stephan@wanderinghorse.net copied these sources from: * * ftp://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/elm/lib/strftime.c * * And made the following changes: * * Removed ancient non-ANSI decls. Added some headers to get it to * compile for me. Renamed functions to fit into my project. Replaced * hard tabs with 4 spaces. Added #undefs for all file-private #defines * to safe-ify inclusion from/with other files. * */ #include /* #include */ #include /* strchr() and friends */ #include /* sprintf() */ #include /* toupper(), islower() */ #define HAVE_GET_TZ_NAME 0 #if HAVE_GET_TZ_NAME extern char *get_tz_name(); #endif #ifndef BSD extern void tzset (void); #endif static int weeknumber (const struct tm *timeptr, int firstweekday); /* defaults: season to taste */ #define SYSV_EXT 1 /* stuff in System V ascftime routine */ #define SUNOS_EXT 1 /* stuff in SunOS strftime routine */ #define POSIX2_DATE 1 /* stuff in Posix 1003.2 date command */ #define VMS_EXT 1 /* include %v for VMS date format */ #if 0 #define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */ #endif #ifdef POSIX_SEMANTICS #include /* malloc() and friends */ #endif #if defined(POSIX2_DATE) #if ! defined(SYSV_EXT) #define SYSV_EXT 1 #endif #if ! defined(SUNOS_EXT) #define SUNOS_EXT 1 #endif #endif #if defined(POSIX2_DATE) static int iso8601wknum(); #endif #undef strchr /* avoid AIX weirdness */ #ifdef __GNUC__ #define inline __inline__ #else #define inline /**/ #endif #define range(low, item, hi) maximum(low, minimum(item, hi)) /* minimum --- return minimum of two numbers */ static inline int minimum(int a, int b) { return (a < b ? a : b); } /* maximum --- return maximum of two numbers */ static inline int maximum(int a, int b) { return (a > b ? a : b); } /* strftime --- produce formatted time */ cwal_size_t th1ish_strftime(char *s, cwal_size_t maxsize, const char *format, const struct tm *timeptr) { char *endp = s + maxsize; char *start = s; char tbuf[100]; int i; static short first = 1; #ifdef POSIX_SEMANTICS static char *savetz = NULL; static int savetzlen = 0; char *tz; #endif /* POSIX_SEMANTICS */ /* various tables, useful in North America */ static char *days_a[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", }; static char *days_l[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", }; static char *months_a[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static char *months_l[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; static char *ampm[] = { "AM", "PM", }; if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0) return 0; if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) return 0; #ifndef POSIX_SEMANTICS if (first) { tzset(); first = 0; } #else /* POSIX_SEMANTICS */ tz = getenv("TZ"); if (first) { if (tz != NULL) { int tzlen = strlen(tz); savetz = (char *) malloc(tzlen + 1); if (savetz != NULL) { savetzlen = tzlen + 1; strcpy(savetz, tz); } } tzset(); first = 0; } /* if we have a saved TZ, and it is different, recapture and reset */ if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) { i = strlen(tz) + 1; if (i > savetzlen) { savetz = (char *) realloc(savetz, i); if (savetz) { savetzlen = i; strcpy(savetz, tz); } } else strcpy(savetz, tz); tzset(); } #endif /* POSIX_SEMANTICS */ for (; *format && s < endp - 1; format++) { tbuf[0] = '\0'; if (*format != '%') { *s++ = *format; continue; } again: switch (*++format) { case '\0': *s++ = '%'; goto out; case '%': *s++ = '%'; continue; case 'a': /* abbreviated weekday name */ if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) strcpy(tbuf, "?"); else strcpy(tbuf, days_a[timeptr->tm_wday]); break; case 'A': /* full weekday name */ if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) strcpy(tbuf, "?"); else strcpy(tbuf, days_l[timeptr->tm_wday]); break; #ifdef SYSV_EXT case 'h': /* abbreviated month name */ #endif case 'b': /* abbreviated month name */ if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) strcpy(tbuf, "?"); else strcpy(tbuf, months_a[timeptr->tm_mon]); break; case 'B': /* full month name */ if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) strcpy(tbuf, "?"); else strcpy(tbuf, months_l[timeptr->tm_mon]); break; case 'c': /* appropriate date and time representation */ sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d", days_a[range(0, timeptr->tm_wday, 6)], months_a[range(0, timeptr->tm_mon, 11)], range(1, timeptr->tm_mday, 31), range(0, timeptr->tm_hour, 23), range(0, timeptr->tm_min, 59), range(0, timeptr->tm_sec, 61), timeptr->tm_year + 1900); break; case 'd': /* day of the month, 01 - 31 */ i = range(1, timeptr->tm_mday, 31); sprintf(tbuf, "%02d", i); break; case 'H': /* hour, 24-hour clock, 00 - 23 */ i = range(0, timeptr->tm_hour, 23); sprintf(tbuf, "%02d", i); break; case 'I': /* hour, 12-hour clock, 01 - 12 */ i = range(0, timeptr->tm_hour, 23); if (i == 0) i = 12; else if (i > 12) i -= 12; sprintf(tbuf, "%02d", i); break; case 'j': /* day of the year, 001 - 366 */ sprintf(tbuf, "%03d", timeptr->tm_yday + 1); break; case 'm': /* month, 01 - 12 */ i = range(0, timeptr->tm_mon, 11); sprintf(tbuf, "%02d", i + 1); break; case 'M': /* minute, 00 - 59 */ i = range(0, timeptr->tm_min, 59); sprintf(tbuf, "%02d", i); break; case 'p': /* am or pm based on 12-hour clock */ i = range(0, timeptr->tm_hour, 23); if (i < 12) strcpy(tbuf, ampm[0]); else strcpy(tbuf, ampm[1]); break; case 'S': /* second, 00 - 61 */ i = range(0, timeptr->tm_sec, 61); sprintf(tbuf, "%02d", i); break; case 'U': /* week of year, Sunday is first day of week */ sprintf(tbuf, "%d", weeknumber(timeptr, 0)); break; case 'w': /* weekday, Sunday == 0, 0 - 6 */ i = range(0, timeptr->tm_wday, 6); sprintf(tbuf, "%d", i); break; case 'W': /* week of year, Monday is first day of week */ sprintf(tbuf, "%d", weeknumber(timeptr, 1)); break; case 'x': /* appropriate date representation */ sprintf(tbuf, "%s %s %2d %d", days_a[range(0, timeptr->tm_wday, 6)], months_a[range(0, timeptr->tm_mon, 11)], range(1, timeptr->tm_mday, 31), timeptr->tm_year + 1900); break; case 'X': /* appropriate time representation */ sprintf(tbuf, "%02d:%02d:%02d", range(0, timeptr->tm_hour, 23), range(0, timeptr->tm_min, 59), range(0, timeptr->tm_sec, 61)); break; case 'y': /* year without a century, 00 - 99 */ i = timeptr->tm_year % 100; sprintf(tbuf, "%d", i); break; case 'Y': /* year with century */ sprintf(tbuf, "%d", 1900 + timeptr->tm_year); break; #if HAVE_GET_TZ_NAME case 'Z': /* time zone name or abbrevation */ strcpy(tbuf, get_tz_name(timeptr)); break; #endif #ifdef SYSV_EXT case 'n': /* same as \n */ tbuf[0] = '\n'; tbuf[1] = '\0'; break; case 't': /* same as \t */ tbuf[0] = '\t'; tbuf[1] = '\0'; break; case 'D': /* date as %m/%d/%y */ th1ish_strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr); break; case 'e': /* day of month, blank padded */ sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31)); break; case 'r': /* time as %I:%M:%S %p */ th1ish_strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr); break; case 'R': /* time as %H:%M */ th1ish_strftime(tbuf, sizeof tbuf, "%H:%M", timeptr); break; case 'T': /* time as %H:%M:%S */ th1ish_strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr); break; #endif #ifdef SUNOS_EXT case 'k': /* hour, 24-hour clock, blank pad */ sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23)); break; case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ i = range(0, timeptr->tm_hour, 23); if (i == 0) i = 12; else if (i > 12) i -= 12; sprintf(tbuf, "%2d", i); break; #endif #ifdef VMS_EXT case 'v': /* date as dd-bbb-YYYY */ sprintf(tbuf, "%2d-%3.3s-%4d", range(1, timeptr->tm_mday, 31), months_a[range(0, timeptr->tm_mon, 11)], timeptr->tm_year + 1900); for (i = 3; i < 6; i++) if (islower(tbuf[i])) tbuf[i] = toupper(tbuf[i]); break; #endif #ifdef POSIX2_DATE case 'C': sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100); break; case 'E': case 'O': /* POSIX locale extensions, ignored for now */ goto again; case 'V': /* week of year according ISO 8601 */ #if defined(GAWK) && defined(VMS_EXT) { extern int do_lint; extern void warning(); static int warned = 0; if (! warned && do_lint) { warned = 1; warning( "conversion %%V added in P1003.2/11.3; for VMS style date, use %%v"); } } #endif sprintf(tbuf, "%d", iso8601wknum(timeptr)); break; case 'u': /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */ sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 : timeptr->tm_wday); break; #endif /* POSIX2_DATE */ default: tbuf[0] = '%'; tbuf[1] = *format; tbuf[2] = '\0'; break; } i = strlen(tbuf); if (i){ if (s + i < endp - 1) { strcpy(s, tbuf); s += i; } else return 0; /* reminder: above IF originally had ambiguous else placement (no braces). This placement _appears_ to be correct.*/ } } out: if (s < endp && *format == '\0') { *s = '\0'; return (s - start); } else return 0; } #ifdef POSIX2_DATE /* iso8601wknum --- compute week number according to ISO 8601 */ static int iso8601wknum(const struct tm *timeptr) { /* * From 1003.2 D11.3: * If the week (Monday to Sunday) containing January 1 * has four or more days in the new year, then it is week 1; * otherwise it is week 53 of the previous year, and the * next week is week 1. * * ADR: This means if Jan 1 was Monday through Thursday, * it was week 1, otherwise week 53. */ int simple_wknum, jan1day, diff, ret; /* get week number, Monday as first day of the week */ simple_wknum = weeknumber(timeptr, 1) + 1; /* * With thanks and tip of the hatlo to tml@tik.vtt.fi * * What day of the week does January 1 fall on? * We know that * (timeptr->tm_yday - jan1.tm_yday) MOD 7 == * (timeptr->tm_wday - jan1.tm_wday) MOD 7 * and that * jan1.tm_yday == 0 * and that * timeptr->tm_wday MOD 7 == timeptr->tm_wday * from which it follows that. . . */ jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7); if (jan1day < 0) jan1day += 7; /* * If Jan 1 was a Monday through Thursday, it was in * week 1. Otherwise it was last year's week 53, which is * this year's week 0. */ if (jan1day >= 1 && jan1day <= 4) diff = 0; else diff = 1; ret = simple_wknum - diff; if (ret == 0) /* we're in the first week of the year */ ret = 53; return ret; } #endif /* weeknumber --- figure how many weeks into the year */ /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */ static int weeknumber(const struct tm *timeptr, int firstweekday) { if (firstweekday == 0) return (timeptr->tm_yday + 7 - timeptr->tm_wday) / 7; else return (timeptr->tm_yday + 7 - (timeptr->tm_wday ? (timeptr->tm_wday - 1) : 6)) / 7; } #undef SYSV_EXT #undef SUNOS_EXT #undef POSIX2_DATE #undef VMS_EXT #undef POSIX_SEMANTICS #undef adddecl #undef inline #undef range #undef maximum #undef minimum #undef HAVE_GET_TZ_NAME #undef GAWK /* end of file strftime.c */