diff --git a/src/policy_cache.hpp b/src/policy_cache.hpp index f2c96121..1b027b14 100644 --- a/src/policy_cache.hpp +++ b/src/policy_cache.hpp @@ -49,7 +49,8 @@ public: } public: - void + const + char* insert(std::string const &key_, std::string const &val_) { @@ -67,6 +68,8 @@ public: val_, token, ptr); + + return ptr; } const diff --git a/src/strpool.h b/src/strpool.h new file mode 100644 index 00000000..49892e8d --- /dev/null +++ b/src/strpool.h @@ -0,0 +1,1307 @@ +/* +------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. +------------------------------------------------------------------------------ + +strpool.h - v1.4 - Highly efficient string pool for C/C++. + +Do this: + #define STRPOOL_IMPLEMENTATION +before you include this file in *one* C/C++ file to create the implementation. +*/ + +#ifndef strpool_h +#define strpool_h + +#ifndef STRPOOL_U32 + #define STRPOOL_U32 unsigned int +#endif +#ifndef STRPOOL_U64 + #define STRPOOL_U64 unsigned long long +#endif + +typedef struct strpool_t strpool_t; + +typedef struct strpool_config_t + { + void* memctx; + int ignore_case; + int counter_bits; + int index_bits; + int entry_capacity; + int block_capacity; + int block_size; + int min_length; + } strpool_config_t; + +extern strpool_config_t const strpool_default_config; + +void strpool_init( strpool_t* pool, strpool_config_t const* config ); +void strpool_term( strpool_t* pool ); + +void strpool_defrag( strpool_t* pool ); + +STRPOOL_U64 strpool_inject( strpool_t* pool, char const* string, int length ); +void strpool_discard( strpool_t* pool, STRPOOL_U64 handle ); + +int strpool_incref( strpool_t* pool, STRPOOL_U64 handle ); +int strpool_decref( strpool_t* pool, STRPOOL_U64 handle ); +int strpool_getref( strpool_t* pool, STRPOOL_U64 handle ); + +int strpool_isvalid( strpool_t const* pool, STRPOOL_U64 handle ); + +char const* strpool_cstr( strpool_t const* pool, STRPOOL_U64 handle ); +int strpool_length( strpool_t const* pool, STRPOOL_U64 handle ); + +char* strpool_collate( strpool_t const* pool, int* count ); +void strpool_free_collated( strpool_t const* pool, char* collated_ptr ); + +#endif /* strpool_h */ + + +/** +strpool.h +========= + +Highly efficient string pool for C/C++. + + +Example +------- + + #define STRPOOL_IMPLEMENTATION + #include "strpool.h" + + #include // for printf + #include // for strlen + + int main( int argc, char** argv ) { + + strpool_config_t conf = strpool_default_config; + //conf.ignore_case = true; + + strpool_t pool; + strpool_init( &pool, &conf ); + + STRPOOL_U64 str_a = strpool_inject( &pool, "This is a test string", (int) strlen( "This is a test string" ) ); + STRPOOL_U64 str_b = strpool_inject( &pool, "THIS IS A TEST STRING", (int) strlen( "THIS IS A TEST STRING" ) ); + + printf( "%s\n", strpool_cstr( &pool, str_a ) ); + printf( "%s\n", strpool_cstr( &pool, str_b ) ); + printf( "%s\n", str_a == str_b ? "Strings are the same" : "Strings are different" ); + + strpool_term( &pool ); + return 0; + } + + +API Documentation +----------------- + +strpool.h is a system for string interning, where each string is stored only once. It makes comparing strings very fast, +as it will just be uint64 comparison rather than looping over strings and comparing character by character. strpool.h is +also optimized for fast creation of strings, and with an efficient memory allocation scheme. + +strpool.h is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use +it, you just include strpool.h to get the API declarations. To get the definitions, you must include strpool.h from +*one* single C or C++ file, and #define the symbol `STRPOOL_IMPLEMENTATION` before you do. + + +### Customization + +There are a few different things in strpool.h which are configurable by #defines. Most of the API use the `int` data +type, for integer values where the exact size is not important. However, for some functions, it specifically makes use +of 32 and 64 bit data types. These default to using `unsigned int` and `unsigned long long` by default, but can be +redefined by #defining STRPOOL_U32 and STRPOOL_U64 respectively, before including strpool.h. This is useful if you, for +example, use the types from `` in the rest of your program, and you want strpool.h to use compatible types. In +this case, you would include strpool.h using the following code: + + #define STRPOOL_U32 uint32_t + #define STRPOOL_U64 uint64_t + #include "strpool.h" + +Note that when customizing the data types, you need to use the same definition in every place where you include +strpool.h, as they affect the declarations as well as the definitions. + +The rest of the customizations only affect the implementation, so will only need to be defined in the file where you +have the #define STRPOOL_IMPLEMENTATION. + +Note that if all customizations are utilized, strpool.h will include no external files whatsoever, which might be useful +if you need full control over what code is being built. + + +#### Custom memory allocators + +To store strings and the internal structures (entry list, hashtable etc) strpool.h needs to dodynamic allocation by +calling `malloc`. Programs might want to keep track of allocations done, or use custom defined pools to allocate memory +from. strpool.h allows for specifying custom memory allocation functions for `malloc` and `free`. +This is done with the following code: + + #define STRPOOL_IMPLEMENTATION + #define STRPOOL_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) ) + #define STRPOOL_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) ) + #include "strpool.h" + +where `my_custom_malloc` and `my_custom_free` are your own memory allocation/deallocation functions. The `ctx` parameter +is an optional parameter of type `void*`. When `strpool_init` is called, you can set the `memctx` field of the `config` +parameter, to a pointer to anything you like, and which will be passed through as the `ctx` parameter to every +`STRPOOL_MALLOC`/`STRPOOL_FREE` call. For example, if you are doing memory tracking, you can pass a pointer to your +tracking data as `memctx`, and in your custom allocation/deallocation function, you can cast the `ctx` param back to the +right type, and access the tracking data. + +If no custom allocator is defined, strpool.h will default to `malloc` and `free` from the C runtime library. + + +#### Custom assert + +strpool.h makes use of asserts to report usage errors and failed allocation errors. By default, it makes use of the C +runtime library `assert` macro, which only executes in debug builds. However, it allows for substituting with your own +assert function or macro using the following code: + + #define STRPOOL_IMPLEMENTATION + #define STRPOOL_ASSERT( condition ) ( my_custom_assert( condition ) ) + #include "strpool.h" + +Note that if you only want the asserts to trigger in debug builds, you must add a check for this in your custom assert. + + +#### Custom C runtime function + +The library makes use of four additional functions from the C runtime library, and for full flexibility, it allows you +to substitute them for your own. Here's an example: + + #define STRPOOL_IMPLEMENTATION + #define STRPOOL_MEMSET( ptr, val, cnt ) ( my_memset_func( ptr, val, cnt ) ) + #define STRPOOL_MEMCPY( dst, src, cnt ) ( my_memcpy_func( dst, src, cnt ) ) + #define STRPOOL_MEMCMP( pr1, pr2, cnt ) ( my_memcmp_func( pr1, pr2, cnt ) ) + #define STRPOOL_STRNICMP( s1, s2, len ) ( my_strnicmp_func( s1, s2, len ) ) + #include "strpool.h" + +If no custom function is defined, strpool.h will default to the C runtime library equivalent. + + +strpool_init +------------ + + void strpool_init( strpool_t* pool, strpool_config_t const* config ) + +Initializes an instance of the string pool. The `config` parameter allows for controlling the behavior of the pool +instance. It contains the following fields: + +* memctx - pointer to user defined data which will be passed through to custom STRPOOL_MALLOC/STRPOOL_FREE calls. May + be NULL. +* ignore_case - set to 0 to make strings case sensitive, set to 1 to make strings case insensitive. Default is 0. +* counter_bits - how many bits of the string handle to use for keeping track of handle reuse and invalidation. Default + is 32. See below for details about the handle bits. +* index_bits - how many bits of the string handle to use for referencing string instances. Default is 32. See below for + details about the handle bits. +* entry_capacity - number of string instance entries to pre-allocate space for when pool is initialized. Default + is space for 4096 string entries. +* block_capacity - number of string storage block entries to pre-allocate space for when pool is initialized. Default + is space for 32 entries of block information - though only a single block will be pre-allocated. +* block_size - size to allocate for each string storage block. A higher value might mean you often have more memory + allocated than is actually being used. A lower value means you will be making allocations more often. Default + is 256 kilobyte. +* min_length - minimum space to allocate for each string. A higher value wastes more space, but makes it more likely + that recycled storage can be re-used by subsequent requests. Default is a string length of 23 characters. + +The function `strpool_inject` returns a 64-bit handle. Using the settings `counter_bits`/`index_bits`, you can control +how many bits of the handle is in use, and how many are used for index vs counter. For example, setting `counter_bits` +to 8 and `index_bits` to 24, you will get a handle which only uses 32 bits in total, can store some 16 million different +strings (2^24 -1), with a reuse counter going up to 255 before wrapping around. In this scenario, the 64-bit handle +returned from strpool_inject can be safely truncated and stored in a 32-bit variable. Any combination of counter vs +index bits is possible, but the number of strings which can be stored will be limited by `index_bits`. + +The counter bits need a more detailed description. When a string is added via `strpool_inject`, you get a handle back, +which is used to reference the string. You might store this handle in various data structures. When removing a string by +calling `strpool_discard`, the index part of that handle will be recycled, and re-used for future `strpool_inject`. +However, when a string is discarded, the counter part of the handle will be increased, so the system knows that any +handles that are still being kept around in any data structure, does no longer point to a valid string (and thus will +return a NULL string pointer when queried through `strpool_cstr`). If your use case involves creating a bunch of strings +and just keeping them around until the application terminates, it is fine to specify 0 for `counter_bits`, thereby +effectively disabling the handle validation all together. If you have a case where strings are being repeatedly created +and removed, but strings are queried very frequently, then you can specify a low number for `counter_bits` (since you +can invalidate any stored handles as soon as you find out it's been invalidated). If you have a case where handles might +sit inside data structures for a long period of time before you check their validity, you best specify a high value for +`counter_bits`. The default value is 32 bits for index and 32 bits for counter. + + +strpool_term +------------ + + void strpool_term( strpool_t* pool ) + +Terminates a string pool instance, releasing all memory used by it. No further calls to the strpool API are valid until +the instance is reinitialized by another call to `strpool_init`. + + +strpool_defrag +-------------- + + void strpool_defrag( strpool_t* pool ) + +As strings are added to and removed from the pool, the memory blocks holding the strings may get fragmented, making them +take up more allocated memory than is actually needed to store the set of strings, should they be packed tightly, +`strpool_defrag` consolidates all strings into a single memory block (which might well be larger than the block size +specified when the pool was initialized), and recreates the internal hash table. Any additionally allocated memory +blocks will be deallocated, making the memory used by the pool as little as it can be to fit the current set of strings. +All string handles remain valid after a call to `strpool_defrag`. + + +strpool_inject +-------------- + + STRPOOL_U64 strpool_inject( strpool_t* pool, char const* string, int length ) + +Adds a string to the pool, and returns a handle for the added string. If the string doesn't exist, it will be stored in +an unused part of an already allocated storage block (or a new block will be allocated if there is no free space large +enough to hold the string), and inserted into the internal hash table. If the string already exists, a handle to the +existing string will be returned. If the `string` parameter is already pointing to a string stored in the pool, there +are specific optimizations for avoiding to loop over it to calculate the hash for it, with the idea being that you +should be able to use char* types when you're passing strings around, and use the string pool for storage, without too +much of a performance penalty. `string` does not have to be null terminated, but when it is retrieved from the string +pool, it will be. If `string` is NULL or length is 0, a handle with a value of 0 will be returned, to signify empty +string. + + +strpool_discard +--------------- + + void strpool_discard( strpool_t* pool, STRPOOL_U64 handle ) + +Removes a string from the pool. Any handles held for the string will be invalid after this. Memory used for storing the +string will be recycled and used for further `strpool_inject` calls. If `handle` is invalid, `strpool_discard` will do +nothing. + + +strpool_incref +-------------- + + int strpool_incref( strpool_t* pool, STRPOOL_U64 handle ) + +`strpool.h` supports reference counting of strings. It is optional and not automatic, and does not automatically discard +strings when the reference count reaches 0. To use reference counting, make sure to call `strpool_incref` whenever you +add a string (after having called `strpool_inject`), and to call `strpool_decref` whenever you want to remove a string +(but only call `strpool_discard` if reference count have reached 0). It would be advisable to write wrapper functions +to ensure consistency in this, or if C++ is used, a wrapper class with constructors/destructor. `strpool_incref` returns +the reference count after increasing it. If `handle` is invalid, `strpool_incref` will do nothing, and return 0. + + +strpool_decref +-------------- + + int strpool_decref( strpool_t* pool, STRPOOL_U64 handle ) + +Decreases the reference count of the specified string by 1, returning the reference count after decrementing. If the +reference count is less than 1, an assert will be triggered. If `handle` is invalid, `strpool_decref` will do nothing, +and return 0. + + +strpool_getref +-------------- + + int strpool_getref( strpool_t* pool, STRPOOL_U64 handle ) + +Returns the current reference count for the specified string. If `handle` is invalid, `strpool_getref` will do nothing, +and return 0. + + +strpool_isvalid +--------------- + + int strpool_isvalid( strpool_t const* pool, STRPOOL_U64 handle ) + +Returns 1 if the specified string handle is valid, and 0 if it is not. + + +strpool_cstr +------------ + + char const* strpool_cstr( strpool_t const* pool, STRPOOL_U64 handle ) + +Returns the zero-terminated C string for the specified string handle. The resulting string pointer is only valid as long +as no call is made to `strpool_init`, `strpool_term`, `strpool_defrag` or `strpool_discard`. It is therefor recommended +to never store the C string pointer, and always grab it fresh by another call to `strpool_cstr` when it is needed. +`strpool_cstr` is a very fast function to call - it does little more than an array lookup. If `handle` is invalid, +`strpool_cstr` returns NULL. + + +strpool_length +-------------- + + int strpool_length( strpool_t const* pool, STRPOOL_U64 handle ) + +Returns the length, in characters, of the specified string. The resulting value is only valid as long as no call is made +to `strpool_init`, `strpool_term`, `strpool_defrag` or `strpool_discard`. It is therefor recommended to never store the +value, and always grab it fresh by another call to `strpool_length` when it is needed. `strpool_length` is a very fast +function to call - it does little more than an array lookup. If `handle` is invalid, `strpool_length` returns 0. + + +strpool_collate +--------------- + + char* strpool_collate( strpool_t const* pool, int* count ) + +Returns a list of all the strings currently stored in the string pool, and stores the number of strings in the int +variable pointed to by `count`. If there are no strings in the string pool, `strpool_collate` returns NULL. The pointer +returned points to the first character of the first string. Strings are zero-terminated, and immediately after the +termination character, comes the first character of the next string. + + +strpool_free_collated +--------------------- + + void strpool_free_collated( strpool_t const* pool, char* collated_ptr ) + +Releases the memory returned by `strpool_collate`. + +*/ + + +/* +---------------------- + IMPLEMENTATION +---------------------- +*/ + +#ifndef strpool_impl +#define strpool_impl + +struct strpool_internal_hash_slot_t; +struct strpool_internal_entry_t; +struct strpool_internal_handle_t; +struct strpool_internal_block_t; + +struct strpool_t + { + void* memctx; + int ignore_case; + int counter_shift; + STRPOOL_U64 counter_mask; + STRPOOL_U64 index_mask; + + int initial_entry_capacity; + int initial_block_capacity; + int block_size; + int min_data_size; + + struct strpool_internal_hash_slot_t* hash_table; + int hash_capacity; + + struct strpool_internal_entry_t* entries; + int entry_capacity; + int entry_count; + + struct strpool_internal_handle_t* handles; + int handle_capacity; + int handle_count; + int handle_freelist_head; + int handle_freelist_tail; + + struct strpool_internal_block_t* blocks; + int block_capacity; + int block_count; + int current_block; + }; + + +#endif /* strpool_impl */ + + +#ifdef STRPOOL_IMPLEMENTATION +#undef STRPOOL_IMPLEMENTATION + +#define _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_SECURE_NO_WARNINGS +#include + +#ifndef STRPOOL_ASSERT + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #define STRPOOL_ASSERT( expression, message ) assert( ( expression ) && ( message ) ) +#endif + +#ifndef STRPOOL_MEMSET + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #define STRPOOL_MEMSET( ptr, val, cnt ) ( memset( ptr, val, cnt ) ) +#endif + +#ifndef STRPOOL_MEMCPY + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #define STRPOOL_MEMCPY( dst, src, cnt ) ( memcpy( dst, src, cnt ) ) +#endif + +#ifndef STRPOOL_MEMCMP + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #define STRPOOL_MEMCMP( pr1, pr2, cnt ) ( memcmp( pr1, pr2, cnt ) ) +#endif + +#ifndef STRPOOL_STRNICMP + #ifdef _WIN32 + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #define STRPOOL_STRNICMP( s1, s2, len ) ( strnicmp( s1, s2, len ) ) + #else + #include + #define STRPOOL_STRNICMP( s1, s2, len ) ( strncasecmp( s1, s2, len ) ) + #endif +#endif + +#ifndef STRPOOL_MALLOC + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #define STRPOOL_MALLOC( ctx, size ) ( malloc( size ) ) + #define STRPOOL_FREE( ctx, ptr ) ( free( ptr ) ) +#endif + + +typedef struct strpool_internal_hash_slot_t + { + STRPOOL_U32 hash_key; + int entry_index; + int base_count; + } strpool_internal_hash_slot_t; + + +typedef struct strpool_internal_entry_t + { + int hash_slot; + int handle_index; + char* data; + int size; + int length; + int refcount; + } strpool_internal_entry_t; + + +typedef struct strpool_internal_handle_t + { + int entry_index; + int counter; + } strpool_internal_handle_t; + + +typedef struct strpool_internal_block_t + { + int capacity; + char* data; + char* tail; + int free_list; + } strpool_internal_block_t; + + +typedef struct strpool_internal_free_block_t + { + int size; + int next; + } strpool_internal_free_block_t; + + +strpool_config_t const strpool_default_config = + { + /* memctx = */ 0, + /* ignore_case = */ 0, + /* counter_bits = */ 32, + /* index_bits = */ 32, + /* entry_capacity = */ 4096, + /* block_capacity = */ 32, + /* block_size = */ 256 * 1024, + /* min_length = */ 23, + }; + + + +static STRPOOL_U32 strpool_internal_pow2ceil( STRPOOL_U32 v ) + { + --v; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + ++v; + v += ( v == 0 ); + return v; + } + + +static int strpool_internal_add_block( strpool_t* pool, int size ) + { + if( pool->block_count >= pool->block_capacity ) + { + pool->block_capacity *= 2; + strpool_internal_block_t* new_blocks = (strpool_internal_block_t*) STRPOOL_MALLOC( pool->memctx, + pool->block_capacity * sizeof( *pool->blocks ) ); + STRPOOL_ASSERT( new_blocks, "Allocation failed" ); + STRPOOL_MEMCPY( new_blocks, pool->blocks, pool->block_count * sizeof( *pool->blocks ) ); + STRPOOL_FREE( pool->memctx, pool->blocks ); + pool->blocks = new_blocks; + } + pool->blocks[ pool->block_count ].capacity = size; + pool->blocks[ pool->block_count ].data = (char*) STRPOOL_MALLOC( pool->memctx, (size_t) size ); + STRPOOL_ASSERT( pool->blocks[ pool->block_count ].data, "Allocation failed" ); + pool->blocks[ pool->block_count ].tail = pool->blocks[ pool->block_count ].data; + pool->blocks[ pool->block_count ].free_list = -1; + return pool->block_count++; + } + + +void strpool_init( strpool_t* pool, strpool_config_t const* config ) + { + if( !config ) config = &strpool_default_config; + + pool->memctx = config->memctx; + pool->ignore_case = config->ignore_case; + + STRPOOL_ASSERT( config->counter_bits + config->index_bits <= 64, "Total bit count exceeds 64" ); + pool->counter_shift = config->index_bits; + pool->counter_mask = ( 1ULL << (STRPOOL_U64) config->counter_bits ) - 1; + pool->index_mask = ( 1ULL << (STRPOOL_U64) config->index_bits ) - 1; + + pool->initial_entry_capacity = + (int) strpool_internal_pow2ceil( config->entry_capacity > 1 ? (STRPOOL_U32)config->entry_capacity : 2U ); + pool->initial_block_capacity = + (int) strpool_internal_pow2ceil( config->block_capacity > 1 ? (STRPOOL_U32)config->block_capacity : 2U ); + pool->block_size = + (int) strpool_internal_pow2ceil( config->block_size > 256 ? (STRPOOL_U32)config->block_size : 256U ); + pool->min_data_size = + (int) ( sizeof( int ) * 2 + 1 + ( config->min_length > 8 ? (STRPOOL_U32)config->min_length : 8U ) ); + + pool->hash_capacity = pool->initial_entry_capacity * 2; + pool->entry_capacity = pool->initial_entry_capacity; + pool->handle_capacity = pool->initial_entry_capacity; + pool->block_capacity = pool->initial_block_capacity; + + pool->handle_freelist_head = -1; + pool->handle_freelist_tail = -1; + pool->block_count = 0; + pool->handle_count = 0; + pool->entry_count = 0; + + pool->hash_table = (strpool_internal_hash_slot_t*) STRPOOL_MALLOC( pool->memctx, + pool->hash_capacity * sizeof( *pool->hash_table ) ); + STRPOOL_ASSERT( pool->hash_table, "Allocation failed" ); + STRPOOL_MEMSET( pool->hash_table, 0, pool->hash_capacity * sizeof( *pool->hash_table ) ); + pool->entries = (strpool_internal_entry_t*) STRPOOL_MALLOC( pool->memctx, + pool->entry_capacity * sizeof( *pool->entries ) ); + STRPOOL_ASSERT( pool->entries, "Allocation failed" ); + pool->handles = (strpool_internal_handle_t*) STRPOOL_MALLOC( pool->memctx, + pool->handle_capacity * sizeof( *pool->handles ) ); + STRPOOL_ASSERT( pool->handles, "Allocation failed" ); + pool->blocks = (strpool_internal_block_t*) STRPOOL_MALLOC( pool->memctx, + pool->block_capacity * sizeof( *pool->blocks ) ); + STRPOOL_ASSERT( pool->blocks, "Allocation failed" ); + + pool->current_block = strpool_internal_add_block( pool, pool->block_size ); + } + + +void strpool_term( strpool_t* pool ) + { +#if 0 + // Debug statistics + printf( "\n\n" ); + printf( "Handles: %d/%d\n", pool->handle_count, pool->handle_capacity ); + printf( "Entries: %d/%d\n", pool->entry_count, pool->entry_capacity ); + printf( "Hashtable: %d/%d\n", pool->entry_count, pool->hash_capacity ); + printf( "Blocks: %d/%d\n", pool->block_count, pool->block_capacity ); + for( int i = 0; i < pool->block_count; ++i ) + { + printf( "\n" ); + printf( "BLOCK: %d\n", i ); + printf( "Capacity: %d\n", pool->blocks[ i ].capacity ); + printf( "Free: [ %d ]", (int)( pool->blocks[ i ].capacity - ( pool->blocks[ i ].tail - pool->blocks[ i ].data ) ) ); + int fl = pool->blocks[ i ].free_list; + int count = 0; + int size = 0; + int total = 0; + while( fl >= 0 ) + { + strpool_internal_free_block_t* free_entry = (strpool_internal_free_block_t*) ( pool->blocks[ i ].data + fl ); + total += free_entry->size; + if( size == 0 ) { size = free_entry->size; } + if( size != free_entry->size ) + { + printf( ", %dx%d", count, size ); + count = 1; + size = free_entry->size; + } + else + { + ++count; + } + fl = free_entry->next; + } + if( size != 0 ) printf( ", %dx%d", count, size ); + printf( ", { %d }\n", total ); + } + printf( "\n\n" ); +#endif + + for( int i = 0; i < pool->block_count; ++i ) STRPOOL_FREE( pool->memctx, pool->blocks[ i ].data ); + STRPOOL_FREE( pool->memctx, pool->blocks ); + STRPOOL_FREE( pool->memctx, pool->handles ); + STRPOOL_FREE( pool->memctx, pool->entries ); + STRPOOL_FREE( pool->memctx, pool->hash_table ); + } + + +void strpool_defrag( strpool_t* pool ) + { + int data_size = 0; + int count = 0; + for( int i = 0; i < pool->entry_count; ++i ) + { + strpool_internal_entry_t* entry = &pool->entries[ i ]; + if( entry->refcount > 0 ) + { + data_size += entry->size; + ++count; + } + } + + int data_capacity = data_size < pool->block_size ? + pool->block_size : (int)strpool_internal_pow2ceil( (STRPOOL_U32)data_size ); + + int hash_capacity = count + count / 2; + hash_capacity = hash_capacity < ( pool->initial_entry_capacity * 2 ) ? + ( pool->initial_entry_capacity * 2 ) : (int)strpool_internal_pow2ceil( (STRPOOL_U32)hash_capacity ); + strpool_internal_hash_slot_t* hash_table = (strpool_internal_hash_slot_t*) STRPOOL_MALLOC( pool->memctx, + hash_capacity * sizeof( *hash_table ) ); + STRPOOL_ASSERT( hash_table, "Allocation failed" ); + STRPOOL_MEMSET( hash_table, 0, hash_capacity * sizeof( *hash_table ) ); + + char* data = (char*) STRPOOL_MALLOC( pool->memctx, (size_t) data_capacity ); + STRPOOL_ASSERT( data, "Allocation failed" ); + int capacity = count < pool->initial_entry_capacity ? + pool->initial_entry_capacity : (int)strpool_internal_pow2ceil( (STRPOOL_U32)count ); + strpool_internal_entry_t* entries = (strpool_internal_entry_t*) STRPOOL_MALLOC( pool->memctx, + capacity * sizeof( *entries ) ); + STRPOOL_ASSERT( entries, "Allocation failed" ); + int index = 0; + char* tail = data; + for( int i = 0; i < pool->entry_count; ++i ) + { + strpool_internal_entry_t* entry = &pool->entries[ i ]; + if( entry->refcount > 0 ) + { + entries[ index ] = *entry; + + STRPOOL_U32 hash = pool->hash_table[ entry->hash_slot ].hash_key; + int base_slot = (int)( hash & (STRPOOL_U32)( hash_capacity - 1 ) ); + int slot = base_slot; + while( hash_table[ slot ].hash_key ) + slot = (slot + 1 ) & ( hash_capacity - 1 ); + STRPOOL_ASSERT( hash, "Invalid hash" ); + hash_table[ slot ].hash_key = hash; + hash_table[ slot ].entry_index = index; + ++hash_table[ base_slot ].base_count; + + entries[ index ].hash_slot = slot; + entries[ index ].data = tail; + entries[ index ].handle_index = entry->handle_index; + pool->handles[ entry->handle_index ].entry_index = index; + STRPOOL_MEMCPY( tail, entry->data, entry->length + 1 + 2 * sizeof( STRPOOL_U32 ) ); + tail += entry->size; + ++index; + } + } + + + STRPOOL_FREE( pool->memctx, pool->hash_table ); + STRPOOL_FREE( pool->memctx, pool->entries ); + for( int i = 0; i < pool->block_count; ++i ) STRPOOL_FREE( pool->memctx, pool->blocks[ i ].data ); + + if( pool->block_capacity != pool->initial_block_capacity ) + { + STRPOOL_FREE( pool->memctx, pool->blocks ); + pool->blocks = (strpool_internal_block_t*) STRPOOL_MALLOC( pool->memctx, + pool->initial_block_capacity * sizeof( *pool->blocks ) ); + STRPOOL_ASSERT( pool->blocks, "Allocation failed" ); + } + pool->block_capacity = pool->initial_block_capacity; + pool->block_count = 1; + pool->current_block = 0; + pool->blocks[ 0 ].capacity = data_capacity; + pool->blocks[ 0 ].data = data; + pool->blocks[ 0 ].tail = tail; + pool->blocks[ 0 ].free_list = -1; + + pool->hash_table = hash_table; + pool->hash_capacity = hash_capacity; + + pool->entries = entries; + pool->entry_capacity = capacity; + pool->entry_count = count; + } + + +static STRPOOL_U64 strpool_internal_make_handle( int index, int counter, STRPOOL_U64 index_mask, int counter_shift, + STRPOOL_U64 counter_mask ) + { + STRPOOL_U64 i = (STRPOOL_U64) ( index + 1 ); + STRPOOL_U64 c = (STRPOOL_U64) counter; + return ( ( c & counter_mask ) << counter_shift ) | ( i & index_mask ); + } + + +static int strpool_internal_counter_from_handle( STRPOOL_U64 handle, int counter_shift, STRPOOL_U64 counter_mask ) + { + return (int) ( ( handle >> counter_shift ) & counter_mask ) ; + } + + +static int strpool_internal_index_from_handle( STRPOOL_U64 handle, STRPOOL_U64 index_mask ) + { + return ( (int) ( handle & index_mask ) ) - 1; + } + + +static strpool_internal_entry_t* strpool_internal_get_entry( strpool_t const* pool, STRPOOL_U64 handle ) + { + int index = strpool_internal_index_from_handle( handle, pool->index_mask ); + int counter = strpool_internal_counter_from_handle( handle, pool->counter_shift, pool->counter_mask ); + + if( index >= 0 && index < pool->handle_count && + counter == (int) ( pool->handles[ index ].counter & pool->counter_mask ) ) + return &pool->entries[ pool->handles[ index ].entry_index ]; + + return 0; + } + + +static STRPOOL_U32 strpool_internal_find_in_blocks( strpool_t const* pool, char const* string, int length ) + { + for( int i = 0; i < pool->block_count; ++i ) + { + strpool_internal_block_t* block = &pool->blocks[ i ]; + // Check if string comes from pool + if( string >= block->data + 2 * sizeof( STRPOOL_U32 ) && string < block->data + block->capacity ) + { + STRPOOL_U32* ptr = (STRPOOL_U32*) string; + int stored_length = (int)( *( ptr - 1 ) ); // Length is stored immediately before string + if( stored_length != length || string[ length ] != '\0' ) return 0; // Invalid string + STRPOOL_U32 hash = *( ptr - 2 ); // Hash is stored before the length field + return hash; + } + } + + return 0; + } + + +static STRPOOL_U32 strpool_internal_calculate_hash( char const* string, int length, int ignore_case ) + { + STRPOOL_U32 hash = 5381U; + + if( ignore_case) + { + for( int i = 0; i < length; ++i ) + { + char c = string[ i ]; + c = ( c <= 'z' && c >= 'a' ) ? c - ( 'a' - 'A' ) : c; + hash = ( ( hash << 5U ) + hash) ^ c; + } + } + else + { + for( int i = 0; i < length; ++i ) + { + char c = string[ i ]; + hash = ( ( hash << 5U ) + hash) ^ c; + } + } + + hash = ( hash == 0 ) ? 1 : hash; // We can't allow 0-value hash keys, but dupes are ok + return hash; + } + + +static void strpool_internal_expand_hash_table( strpool_t* pool ) + { + int old_capacity = pool->hash_capacity; + strpool_internal_hash_slot_t* old_table = pool->hash_table; + + pool->hash_capacity *= 2; + + pool->hash_table = (strpool_internal_hash_slot_t*) STRPOOL_MALLOC( pool->memctx, + pool->hash_capacity * sizeof( *pool->hash_table ) ); + STRPOOL_ASSERT( pool->hash_table, "Allocation failed" ); + STRPOOL_MEMSET( pool->hash_table, 0, pool->hash_capacity * sizeof( *pool->hash_table ) ); + + for( int i = 0; i < old_capacity; ++i ) + { + STRPOOL_U32 hash_key = old_table[ i ].hash_key; + if( hash_key ) + { + int base_slot = (int)( hash_key & (STRPOOL_U32)( pool->hash_capacity - 1 ) ); + int slot = base_slot; + while( pool->hash_table[ slot ].hash_key ) + slot = ( slot + 1 ) & ( pool->hash_capacity - 1 ); + STRPOOL_ASSERT( hash_key, "Invalid hash" ); + pool->hash_table[ slot ].hash_key = hash_key; + pool->hash_table[ slot ].entry_index = old_table[ i ].entry_index; + pool->entries[ pool->hash_table[ slot ].entry_index ].hash_slot = slot; + ++pool->hash_table[ base_slot ].base_count; + } + } + + STRPOOL_FREE( pool->memctx, old_table ); + } + + +static void strpool_internal_expand_entries( strpool_t* pool ) + { + pool->entry_capacity *= 2; + strpool_internal_entry_t* new_entries = (strpool_internal_entry_t*) STRPOOL_MALLOC( pool->memctx, + pool->entry_capacity * sizeof( *pool->entries ) ); + STRPOOL_ASSERT( new_entries, "Allocation failed" ); + STRPOOL_MEMCPY( new_entries, pool->entries, pool->entry_count * sizeof( *pool->entries ) ); + STRPOOL_FREE( pool->memctx, pool->entries ); + pool->entries = new_entries; + } + + +static void strpool_internal_expand_handles( strpool_t* pool ) + { + pool->handle_capacity *= 2; + strpool_internal_handle_t* new_handles = (strpool_internal_handle_t*) STRPOOL_MALLOC( pool->memctx, + pool->handle_capacity * sizeof( *pool->handles ) ); + STRPOOL_ASSERT( new_handles, "Allocation failed" ); + STRPOOL_MEMCPY( new_handles, pool->handles, pool->handle_count * sizeof( *pool->handles ) ); + STRPOOL_FREE( pool->memctx, pool->handles ); + pool->handles = new_handles; + } + + +static char* strpool_internal_get_data_storage( strpool_t* pool, int size, int* alloc_size ) + { + if( size < sizeof( strpool_internal_free_block_t ) ) size = sizeof( strpool_internal_free_block_t ); + if( size < pool->min_data_size ) size = pool->min_data_size; + size = (int)strpool_internal_pow2ceil( (STRPOOL_U32)size ); + + // Try to find a large enough free slot in existing blocks + for( int i = 0; i < pool->block_count; ++i ) + { + int free_list = pool->blocks[ i ].free_list; + int prev_list = -1; + while( free_list >= 0 ) + { + strpool_internal_free_block_t* free_entry = + (strpool_internal_free_block_t*) ( pool->blocks[ i ].data + free_list ); + if( free_entry->size / 2 < size ) + { + // At this point, all remaining slots are too small, so bail out if the current slot is not large enough + if( free_entry->size < size ) break; + + if( prev_list < 0 ) + { + pool->blocks[ i ].free_list = free_entry->next; + } + else + { + strpool_internal_free_block_t* prev_entry = + (strpool_internal_free_block_t*) ( pool->blocks[ i ].data + prev_list ); + prev_entry->next = free_entry->next; + } + *alloc_size = free_entry->size; + return (char*) free_entry; + } + prev_list = free_list; + free_list = free_entry->next; + } + } + + // Use current block, if enough space left + int offset = (int) ( pool->blocks[ pool->current_block ].tail - pool->blocks[ pool->current_block ].data ); + if( size <= pool->blocks[ pool->current_block ].capacity - offset ) + { + char* data = pool->blocks[ pool->current_block ].tail; + pool->blocks[ pool->current_block ].tail += size; + *alloc_size = size; + return data; + } + + // Allocate a new block + pool->current_block = strpool_internal_add_block( pool, size > pool->block_size ? size : pool->block_size ); + char* data = pool->blocks[ pool->current_block ].tail; + pool->blocks[ pool->current_block ].tail += size; + *alloc_size = size; + return data; + } + + +STRPOOL_U64 strpool_inject( strpool_t* pool, char const* string, int length ) + { + if( !string || length <= 0 ) return 0; + + STRPOOL_U32 hash = strpool_internal_find_in_blocks( pool, string, length ); + // If no stored hash, calculate it from data + if( !hash ) hash = strpool_internal_calculate_hash( string, length, pool->ignore_case ); + + // Return handle to existing string, if it is already in pool + int base_slot = (int)( hash & (STRPOOL_U32)( pool->hash_capacity - 1 ) ); + int base_count = pool->hash_table[ base_slot ].base_count; + int slot = base_slot; + int first_free = slot; + while( base_count > 0 ) + { + STRPOOL_U32 slot_hash = pool->hash_table[ slot ].hash_key; + if( slot_hash == 0 && pool->hash_table[ first_free ].hash_key != 0 ) first_free = slot; + int slot_base = (int)( slot_hash & (STRPOOL_U32)( pool->hash_capacity - 1 ) ); + if( slot_base == base_slot ) + { + STRPOOL_ASSERT( base_count > 0, "Invalid base count" ); + --base_count; + if( slot_hash == hash ) + { + int index = pool->hash_table[ slot ].entry_index; + strpool_internal_entry_t* entry = &pool->entries[ index ]; + if( entry->length == length && + ( + ( !pool->ignore_case && STRPOOL_MEMCMP( entry->data + 2 * sizeof( STRPOOL_U32 ), string, (size_t)length ) == 0 ) + || ( pool->ignore_case && STRPOOL_STRNICMP( entry->data + 2 * sizeof( STRPOOL_U32 ), string, (size_t)length ) == 0 ) + ) + ) + { + int handle_index = entry->handle_index; + return strpool_internal_make_handle( handle_index, pool->handles[ handle_index ].counter, + pool->index_mask, pool->counter_shift, pool->counter_mask ); + } + } + } + slot = ( slot + 1 ) & ( pool->hash_capacity - 1 ); + } + + // This is a new string, so let's add it + + if( pool->entry_count >= ( pool->hash_capacity - pool->hash_capacity / 3 ) ) + { + strpool_internal_expand_hash_table( pool ); + + base_slot = (int)( hash & (STRPOOL_U32)( pool->hash_capacity - 1 ) ); + slot = base_slot; + first_free = slot; + while( base_count ) + { + STRPOOL_U32 slot_hash = pool->hash_table[ slot ].hash_key; + if( slot_hash == 0 && pool->hash_table[ first_free ].hash_key != 0 ) first_free = slot; + int slot_base = (int)( slot_hash & (STRPOOL_U32)( pool->hash_capacity - 1 ) ); + if( slot_base == base_slot ) --base_count; + slot = ( slot + 1 ) & ( pool->hash_capacity - 1 ); + } + } + + slot = first_free; + while( pool->hash_table[ slot ].hash_key ) + slot = ( slot + 1 ) & ( pool->hash_capacity - 1 ); + + if( pool->entry_count >= pool->entry_capacity ) + strpool_internal_expand_entries( pool ); + + STRPOOL_ASSERT( !pool->hash_table[ slot ].hash_key && ( hash & ( (STRPOOL_U32) pool->hash_capacity - 1 ) ) == (STRPOOL_U32) base_slot, "Invalid slot" ); + STRPOOL_ASSERT( hash, "Invalid hash" ); + pool->hash_table[ slot ].hash_key = hash; + pool->hash_table[ slot ].entry_index = pool->entry_count; + ++pool->hash_table[ base_slot ].base_count; + + int handle_index; + + if( pool->handle_count < pool->handle_capacity ) + { + handle_index = pool->handle_count; + pool->handles[ pool->handle_count ].counter = 1; + ++pool->handle_count; + } + else if( pool->handle_freelist_head >= 0 ) + { + handle_index = pool->handle_freelist_head; + if( pool->handle_freelist_tail == pool->handle_freelist_head ) + pool->handle_freelist_tail = pool->handles[ pool->handle_freelist_head ].entry_index; + pool->handle_freelist_head = pool->handles[ pool->handle_freelist_head ].entry_index; + } + else + { + strpool_internal_expand_handles( pool ); + handle_index = pool->handle_count; + pool->handles[ pool->handle_count ].counter = 1; + ++pool->handle_count; + } + + pool->handles[ handle_index ].entry_index = pool->entry_count; + + strpool_internal_entry_t* entry = &pool->entries[ pool->entry_count ]; + ++pool->entry_count; + + int data_size = length + 1 + (int) ( 2 * sizeof( STRPOOL_U32 ) ); + char* data = strpool_internal_get_data_storage( pool, data_size, &data_size ); + entry->hash_slot = slot; + entry->handle_index = handle_index; + entry->data = data; + entry->size = data_size; + entry->length = length; + entry->refcount = 0; + + *(STRPOOL_U32*)(data) = hash; + data += sizeof( STRPOOL_U32 ); + *(STRPOOL_U32*)(data) = (STRPOOL_U32) length; + data += sizeof( STRPOOL_U32 ); + STRPOOL_MEMCPY( data, string, (size_t) length ); + data[ length ] = 0; // Ensure trailing zero + + return strpool_internal_make_handle( handle_index, pool->handles[ handle_index ].counter, pool->index_mask, + pool->counter_shift, pool->counter_mask ); + } + + +void strpool_discard( strpool_t* pool, STRPOOL_U64 handle ) + { + strpool_internal_entry_t* entry = strpool_internal_get_entry( pool, handle ); + if( entry && entry->refcount == 0 ) + { + int entry_index = pool->handles[ entry->handle_index ].entry_index; + + // recycle string mem + for( int i = 0; i < pool->block_count; ++i ) + { + strpool_internal_block_t* block = &pool->blocks[ i ]; + if( entry->data >= block->data && entry->data <= block->tail ) + { + if( block->free_list < 0 ) + { + strpool_internal_free_block_t* new_entry = (strpool_internal_free_block_t*) ( entry->data ); + block->free_list = (int) ( entry->data - block->data ); + new_entry->next = -1; + new_entry->size = entry->size; + } + else + { + int free_list = block->free_list; + int prev_list = -1; + while( free_list >= 0 ) + { + strpool_internal_free_block_t* free_entry = + (strpool_internal_free_block_t*) ( pool->blocks[ i ].data + free_list ); + if( free_entry->size <= entry->size ) + { + strpool_internal_free_block_t* new_entry = (strpool_internal_free_block_t*) ( entry->data ); + if( prev_list < 0 ) + { + new_entry->next = pool->blocks[ i ].free_list; + pool->blocks[ i ].free_list = (int) ( entry->data - block->data ); + } + else + { + strpool_internal_free_block_t* prev_entry = + (strpool_internal_free_block_t*) ( pool->blocks[ i ].data + prev_list ); + prev_entry->next = (int) ( entry->data - block->data ); + new_entry->next = free_entry->next; + } + new_entry->size = entry->size; + break; + } + prev_list = free_list; + free_list = free_entry->next; + } + } + break; + } + } + + // recycle handle + if( pool->handle_freelist_tail < 0 ) + { + STRPOOL_ASSERT( pool->handle_freelist_head < 0, "Freelist error" ); + pool->handle_freelist_head = entry->handle_index; + pool->handle_freelist_tail = entry->handle_index; + } + else + { + pool->handles[ pool->handle_freelist_tail ].entry_index = entry->handle_index; + pool->handle_freelist_tail = entry->handle_index; + } + ++pool->handles[ entry->handle_index ].counter; // invalidate handle via counter + pool->handles[ entry->handle_index ].entry_index = -1; + + // recycle hash slot + STRPOOL_U32 hash = pool->hash_table[ entry->hash_slot ].hash_key; + int base_slot = (int)( hash & (STRPOOL_U32)( pool->hash_capacity - 1 ) ); + STRPOOL_ASSERT( hash, "Invalid hash" ); + --pool->hash_table[ base_slot ].base_count; + pool->hash_table[ entry->hash_slot ].hash_key = 0; + + // recycle entry + if( entry_index != pool->entry_count - 1 ) + { + pool->entries[ entry_index ] = pool->entries[ pool->entry_count - 1 ]; + pool->hash_table[ pool->entries[ entry_index ].hash_slot ].entry_index = entry_index; + pool->handles[ pool->entries[ entry_index ].handle_index ].entry_index = entry_index; + } + --pool->entry_count; + } + + } + + +int strpool_incref( strpool_t* pool, STRPOOL_U64 handle ) + { + strpool_internal_entry_t* entry = strpool_internal_get_entry( pool, handle ); + if( entry ) + { + ++entry->refcount; + return entry->refcount; + } + return 0; + } + + +int strpool_decref( strpool_t* pool, STRPOOL_U64 handle ) + { + strpool_internal_entry_t* entry = strpool_internal_get_entry( pool, handle ); + if( entry ) + { + STRPOOL_ASSERT( entry->refcount > 0, "Invalid ref count" ); + --entry->refcount; + return entry->refcount; + } + return 0; + } + + +int strpool_getref( strpool_t* pool, STRPOOL_U64 handle ) + { + strpool_internal_entry_t* entry = strpool_internal_get_entry( pool, handle ); + if( entry ) return entry->refcount; + return 0; + } + + +int strpool_isvalid( strpool_t const* pool, STRPOOL_U64 handle ) + { + strpool_internal_entry_t const* entry = strpool_internal_get_entry( pool, handle ); + if( entry ) return 1; + return 0; + } + + +char const* strpool_cstr( strpool_t const* pool, STRPOOL_U64 handle ) + { + strpool_internal_entry_t const* entry = strpool_internal_get_entry( pool, handle ); + if( entry ) return entry->data + 2 * sizeof( STRPOOL_U32 ); // Skip leading hash value + return NULL; + } + + +int strpool_length( strpool_t const* pool, STRPOOL_U64 handle ) + { + strpool_internal_entry_t const* entry = strpool_internal_get_entry( pool, handle ); + if( entry ) return entry->length; + return 0; + } + + +char* strpool_collate( strpool_t const* pool, int* count ) + { + int size = 0; + for( int i = 0; i < pool->entry_count; ++i ) size += pool->entries[ i ].length + 1; + if( size == 0 ) return NULL; + + char* strings = (char*) STRPOOL_MALLOC( pool->memctx, (size_t) size ); + STRPOOL_ASSERT( strings, "Allocation failed" ); + *count = pool->entry_count; + char* ptr = strings; + for( int i = 0; i < pool->entry_count; ++i ) + { + int len = pool->entries[ i ].length + 1; + char* src = pool->entries[ i ].data + 2 * sizeof( STRPOOL_U32 ); + STRPOOL_MEMCPY( ptr, src, (size_t) len ); + ptr += len; + } + return strings; + } + + +void strpool_free_collated( strpool_t const* pool, char* collated_ptr ) + { + (void) pool; + STRPOOL_FREE( pool->memctx, collated_ptr ); + } + + +#endif /* STRPOOL_IMPLEMENTATION */ + + +/* +revision history: + 1.4 fixed find_in_blocks substring bug, removed realloc, added docs + 1.3 fixed typo in mask bit shift + 1.2 made it possible to override standard library functions + 1.1 added is_valid function to query a handles validity + 1.0 first released version +*/ + + +/* +------------------------------------------------------------------------------ + +This software is available under 2 licenses - you may choose the one you like. + +------------------------------------------------------------------------------ + +ALTERNATIVE A - MIT License + +Copyright (c) 2015 Mattias Gustavsson + +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. + +------------------------------------------------------------------------------ + +ALTERNATIVE B - Public Domain (www.unlicense.org) + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ +*/