merges constsstr branch

Mon, 14 May 2018 17:56:03 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 14 May 2018 17:56:03 +0200
changeset 306
90b6d69bb499
parent 305
353d71349e61 (diff)
parent 300
d1f814633049 (current diff)
child 308
d6f580621512

merges constsstr branch

configure.ac file | annotate | diff | comparison | revisions
src/string.c file | annotate | diff | comparison | revisions
src/ucx/string.h file | annotate | diff | comparison | revisions
src/ucx/ucx.h file | annotate | diff | comparison | revisions
test/string_tests.c file | annotate | diff | comparison | revisions
--- a/.hgtags	Sun May 13 07:13:09 2018 +0200
+++ b/.hgtags	Mon May 14 17:56:03 2018 +0200
@@ -1,2 +1,3 @@
 a6184aff5108ba849759030fc75df9f4178f6e86 v1.0
 9c1591b3c4a4b259de601d1da6cf06e7c445595d v1.0.1
+1f9237cfeb265dbb820e673fe5578fb02154766f v1.1
--- a/configure.ac	Sun May 13 07:13:09 2018 +0200
+++ b/configure.ac	Mon May 14 17:56:03 2018 +0200
@@ -27,8 +27,9 @@
 #
 
 # the package version must match the macros in ucx.h
-# if you change the package version, don't forget to adjust the library version
-AC_INIT([ucx], [1.0.1], [olaf.wintermann@gmail.com])
+# the lib version must follow the libtool versioning convention
+AC_INIT([ucx], [2.0.0], [olaf.wintermann@gmail.com])
+AC_SUBST([UCX_LIB_VERSION], [3:0:0])
 
 # don't place everything in the project root
 AC_CONFIG_AUX_DIR([build-aux])
--- a/docs/src/header.html	Sun May 13 07:13:09 2018 +0200
+++ b/docs/src/header.html	Mon May 14 17:56:03 2018 +0200
@@ -21,17 +21,17 @@
                     <li><a href="modules.html">Modules</a>
                     <ul>
                         <li><a href="modules.html#allocator">Allocator</a></li>
-                        <li><a href="modules.html#avl">AVL Tree</a></li>
+                        <li><a href="modules.html#avl-tree">AVL Tree</a></li>
                         <li><a href="modules.html#buffer">Buffer</a></li>
                         <li><a href="modules.html#list">List</a></li>
                         <li><a href="modules.html#logging">Logging</a></li>
                         <li><a href="modules.html#map">Map</a></li>
-                        <li><a href="modules.html#mempool">Memory Pool</a></li>
+                        <li><a href="modules.html#memory-pool">Memory Pool</a></li>
                         <li><a href="modules.html#properties">Properties</a></li>
                         <li><a href="modules.html#stack">Stack</a></li>
                         <li><a href="modules.html#string">String</a></li>
-                        <li><a href="modules.html#test">Testing</a></li>
-                        <li><a href="modules.html#utils">Utilities</a></li>
+                        <li><a href="modules.html#testing">Testing</a></li>
+                        <li><a href="modules.html#utilities">Utilities</a></li>
                     </ul>
                     </li>
                     <li><a target="_blank" href="api/index.html">API Reference</a></li>
--- a/docs/src/modules.md	Sun May 13 07:13:09 2018 +0200
+++ b/docs/src/modules.md	Mon May 14 17:56:03 2018 +0200
@@ -9,22 +9,20 @@
 For instance, the [Allocator](#allocator) module is used by many other modules
 to allow flexible memory allocation.
 By default the header files are placed into an `ucx` directory within your
-systems include directory. In this case you can use an module by including it
+systems include directory. In this case you can use a module by including it
 via `#include <ucx/MODULENAME.h>`.
 Required modules are included automatically.
 
 <div id="modules" align="center">
     
------------------------ ---------------------- ---------------------------- -------------------------
-[Allocator](#allocator) [AVL&nbsp;Tree](#avl)  [Buffer](#buffer)            [List](#list)
-[Logging](#logging)     [Map](#map)            [Memory&nbsp;Pool](#mempool) [Properties](#properties)
-[Stack](#stack)         [String](#string)      [Testing](#test)             [Utilities](#utils)
------------------------ ---------------------- ---------------------------- -------------------------
+----------------------- ----------------------      ----------------------------     -------------------------
+[Allocator](#allocator) [AVL&nbsp;Tree](#avl-tree)  [Buffer](#buffer)                [List](#list)
+[Logging](#logging)     [Map](#map)                 [Memory&nbsp;Pool](#memory-pool) [Properties](#properties)
+[Stack](#stack)         [String](#string)           [Testing](#testing)              [Utilities](#utilities)
+----------------------- ----------------------      ----------------------------     -------------------------
 
 </div>
 
-<a name="allocator"></a>
-
 ## Allocator
 
 *Header file:* [allocator.h](api/allocator_8h.html)  
@@ -41,9 +39,7 @@
 
 As the pointer to the memory area / pool can be arbitrarily chosen, any data
 can be provided to the memory management functions. One example is the
-[UCX Memory Pool](#mempool).
-
-<a name="avl"></a>
+[UCX Memory Pool](#memory-pool).
 
 ## AVL Tree
 
@@ -55,7 +51,46 @@
 All common binary tree operations are implemented. Furthermore, this module
 provides search functions via lower and upper bounds.
 
-<a name="buffer"></a>
+### Filtering items with a time window
+
+Suppose you have a list of items which contain a `time_t` value and your task
+is to find all items within a time window `[t_start, t_end]`.
+With AVL Trees this is easy:
+```C
+/* ---------------------
+ * Somewhere in a header
+ */
+typedef struct {
+    time_t ts;
+    /* other important data */
+} MyObject;
+
+/* -----------
+ * Source code
+ */
+
+UcxAVLTree* tree = ucx_avl_new(ucx_longintcmp);
+/* ... populate tree with objects, use '& MyObject.ts' as key ... */
+
+
+/* Now find every item, with 30 <= ts <= 70 */
+time_t ts_start = 30;
+time_t ts_end = 70;
+
+printf("Values in range:\n");
+for (
+        UcxAVLNode* node = ucx_avl_find_node(
+            tree, (intptr_t) &ts_start,
+            ucx_longintdist, UCX_AVL_FIND_LOWER_BOUNDED);
+        node && (*(time_t*)node->key) <= ts_end;
+        node = ucx_avl_succ(node)
+    ) {
+    printf(" ts: %ld\n", ((MyObject*)node->value)->ts);
+}
+
+ucx_avl_free_content(tree, free);
+ucx_avl_free(tree);
+```
 
 ## Buffer
 
@@ -64,8 +99,8 @@
 
 Instances of this buffer implementation can be used to read from or to write to
 memory like you would do with a stream. This allows the use of
-`ucx_stream_copy` from the [Utilities](#utils) module to copy contents from one
-buffer to another or from file or network streams to the buffer and
+`ucx_stream_copy()` from the [Utilities](#utilities) module to copy contents
+from one buffer to another or from file or network streams to the buffer and
 vice-versa.
 
 More features for convenient use of the buffer can be enabled, like automatic
@@ -73,7 +108,79 @@
 See the documentation of the macro constants in the header file for more
 information.
 
-<a name="list"></a>
+### Add line numbers to a file
+
+When reading a file line by line, you have three options: first, you could limit
+the maximum supported line length.
+Second, you allocate a god buffer large
+enough for the most lines a text file could have.
+And third, undoubtedly the best option, you start with a small buffer, which
+adjusts on demand.
+An `UcxBuffer` can be created to do just that for you.
+Just pass the `UCX_BUFFER_AUTOEXTEND` option to the initialization function.
+Here is a full working program, which adds line numbers to a file.
+```C
+#include <stdio.h>
+#include <ucx/buffer.h>
+#include <ucx/utils.h>
+
+int main(int argc, char** argv) {
+
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
+        return 1;
+    }
+
+    FILE* input = fopen(argv[1], "r");
+    if (!input) {
+        perror("Canno read input");
+        return 1;
+    }
+
+    const size_t chunksize = 256;
+
+    UcxBuffer* linebuf =
+        ucx_buffer_new(
+            NULL,       /* the buffer should manage the memory area for us */
+            2*chunksize,  /* initial size should be twice the chunk size */
+            UCX_BUFFER_AUTOEXTEND); /* the buffer will grow when necessary */
+
+    size_t lineno = 1;
+    do {
+        /* read line chunk */
+        size_t read = ucx_stream_ncopy(
+                input, linebuf, fread, ucx_buffer_write, chunksize);
+        if (read == 0) break;
+        
+        /* handle line endings */
+        do {
+            sstr_t bufstr = ucx_buffer_to_sstr(linebuf);
+            sstr_t nl = sstrchr(bufstr, '\n');
+            if (nl.length == 0) break;
+
+            size_t linelen = bufstr.length - nl.length;
+            sstr_t linestr = sstrsubsl(bufstr, 0, linelen);
+
+            printf("%zu: %" PRIsstr "\n", lineno++, SFMT(linestr));
+
+            /* shift the buffer to the next line */
+            ucx_buffer_shift_left(linebuf, linelen+1);
+        } while(1);
+
+    } while(1);
+
+    /* print the 'noeol' line, if any */
+    sstr_t lastline = ucx_buffer_to_sstr(linebuf);
+    if (lastline.length > 0) {
+        printf("%zu: %" PRIsstr, lineno, SFMT(lastline));
+    }
+
+    fclose(input);
+    ucx_buffer_free(linebuf);
+
+    return 0;
+}
+```
 
 ## List
 
@@ -84,7 +191,52 @@
 linked list. Among the common operations like insert, remove, search and sort,
 we allow convenient iteration via a special `UCX_FOREACH` macro.
 
-<a name="logging"></a>
+### Remove duplicates from an array of strings
+
+Assume you are given an array of `sstr_t` and want to create a list of these
+strings without duplicates.
+```C
+#include <stdio.h>
+#include <ucx/list.h>
+#include <ucx/string.h>
+#include <ucx/utils.h>
+
+UcxList* remove_duplicates(sstr_t* array, size_t arrlen) {
+    UcxList* list = NULL;
+    for (size_t i = 0 ; i < arrlen ; ++i) {
+        if (ucx_list_find(list, array+i, ucx_sstrcmp, NULL) == -1) {
+            sstr_t* s = malloc(sizeof(sstr_t));
+            *s = sstrdup(array[i]);
+            list = ucx_list_append(list, s);
+        }
+    }
+    return list;
+}
+
+/* we will need this function to clean up the list contents later */
+void free_sstr(void* ptr) {
+    sstr_t* s = ptr;
+    free(s->ptr);
+    free(s);
+}
+
+/* ... */
+
+sstr_t* array = /* some array of strings */
+size_t arrlen = /* the length of the array */
+
+UcxList* list = remove_duplicates(array,arrlen);
+
+/* Iterate over the list and print the elements */
+UCX_FOREACH(elem, list) {
+    sstr_t s = *((sstr_t*)elem->data);
+    printf("%" PRIsstr "\n", SFMT(s));
+}
+
+/* Use our free function to free the duplicated strings. */
+ucx_list_free_content(list, free_sstr);
+ucx_list_free(list);
+```
 
 ## Logging
 
@@ -94,9 +246,29 @@
 The logging module comes with some predefined log levels and allows some more
 customization. You may choose if you want to get timestamps or source file and
 line number logged automatically when outputting a message.
-
-
-<a name="map"></a>
+The following function call initializes a debug logger with all of the above
+information:
+```C
+    log = ucx_logger_new(stdout, UCX_LOGGER_DEBUG,
+            UCX_LOGGER_LEVEL | UCX_LOGGER_TIMESTAMP | UCX_LOGGER_SOURCE);
+```
+Afterwards you can use this logger with the predefined macros
+```C
+    ucx_logger_trace(log, "Verbose output");
+    ucx_logger_debug(log, "Debug message");
+    ucx_logger_info(log, "Information");
+    ucx_logger_warn(log, "Warning");
+    ucx_logger_error(log, "Error message");
+```
+or you use
+```C
+    ucx_logger_log(log, CUSTOM_LEVEL, "Some message")
+```
+When you use your custom log level, don't forget to register it with
+```C
+    ucx_logger_register_level(log, CUSTOM_LEVEL, "CUSTOM")
+```
+where the last argument must be a string literal.
 
 ## Map
 
@@ -107,7 +279,69 @@
 chaining with linked lists. Similarly to the list module, we provide a
 `UCX_MAP_FOREACH` macro to conveniently iterate through the key/value pairs.
 
-<a name="mempool"></a>
+### Parsing command line options
+
+Assume you want to parse command line options and record them within a map.
+One way to do this is shown by the following code sample:
+```C
+    UcxMap* options = ucx_map_new(16);
+    const char *NOARG = "";
+    
+    char *option = NULL;
+    char optchar = 0;
+    for(int i=1;i<argc;i++) {
+        char *arg = argv[i];
+        size_t len = strlen(arg);
+        if(len > 1 && arg[0] == '-') {
+            for(int c=1;c<len;c++) {
+                if(option) {
+                    fprintf(stderr,
+                            "Missing argument for option -%c\n", optchar);
+                    return 1;
+                }
+                switch(arg[c]) {
+                    default: {
+                        fprintf(stderr, "Unknown option -%c\n\n", arg[c]);
+                        return 1;
+                    }
+                    case 'v': {
+                        ucx_map_cstr_put(options, "verbose", NOARG);
+                        break;
+                    }
+                    case 'o': {
+                        option = "output";
+                        optchar = 'o';
+                        break;
+                    }
+                }
+            }
+        } else if(option) {
+            ucx_map_cstr_put(options, option, arg);
+            option = NULL;
+        } else {
+            /* ... handle argument that is not an option ... */
+        }
+    }
+    if(option) {
+        fprintf(stderr,
+                "Missing argument for option -%c\n", optchar);
+        return 1;
+    }
+```
+With the following loop, you can access the previously recorded options:
+```C
+    UcxMapIterator iter = ucx_map_iterator(options);
+    char *arg;
+    UCX_MAP_FOREACH(optkey, arg, iter) {
+        char* opt = optkey.data;
+        if (*arg) {
+            printf("%s = %s\n", opt, arg);
+        } else {
+            printf("%s active\n", opt);
+        }
+    }
+```
+Don't forget to call `ucx_map_free()`, when you are done with the map.
 
 ## Memory Pool
 
@@ -118,10 +352,109 @@
 This pool allows you to register destructor functions for the allocated memory,
 which are automatically called on the destruction of the pool.
 But you may also register *independent* destructor functions within a pool in
-case, some external library allocated memory for you, which you wish to be
+case some external library allocated memory for you, which should be
 destroyed together with this pool.
 
-<a name="properties"></a>
+Many UCX modules support the use of an allocator.
+The [String Module](#string), for instance, provides the `sstrdup_a()` function,
+which uses the specified allocator to allocate the memory for the duplicated
+string.
+This way, you can use a `UcxMempool` to keep track of the memory occupied by
+duplicated strings and cleanup everything with just a single call to
+`ucx_mempool_destroy()`.
+
+### Read CSV data into a structure
+
+The following code example shows some of the basic memory pool functions and
+how they can be used with other UCX modules.
+```C
+#include <stdio.h>
+#include <ucx/mempool.h>
+#include <ucx/list.h>
+#include <ucx/string.h>
+#include <ucx/buffer.h>
+#include <ucx/utils.h>
+
+typedef struct {
+    sstr_t column_a;
+    sstr_t column_b;
+    sstr_t column_c;
+} CSVData;
+
+int main(int argc, char** argv) {
+
+    UcxMempool* pool = ucx_mempool_new(128);
+
+    FILE *f = fopen("test.csv", "r");
+    if (!f) {
+        perror("Cannot open file");
+        return 1;
+    }
+    /* close the file automatically at pool destruction*/
+    ucx_mempool_reg_destr(pool, f, (ucx_destructor) fclose);
+
+    /* create a buffer and register it at the memory pool for destruction */
+    UcxBuffer* content = ucx_buffer_new(NULL, 256, UCX_BUFFER_AUTOEXTEND);
+    ucx_mempool_reg_destr(pool, content, (ucx_destructor) ucx_buffer_free);
+
+    /* read the file and split it by lines first */
+    ucx_stream_copy(f, content, fread, ucx_buffer_write);
+    sstr_t contentstr = ucx_buffer_to_sstr(content);
+    ssize_t lc = 0;
+    sstr_t* lines = sstrsplit_a(pool->allocator, contentstr, S("\n"), &lc);
+
+    /* skip the header and parse the remaining data */
+    UcxList* datalist = NULL;
+    for (size_t i = 1 ; i < lc ; i++) {
+        if (lines[i].length == 0) continue;
+        ssize_t fc = 3;
+        sstr_t* fields = sstrsplit_a(pool->allocator, lines[i], S(";"), &fc);
+        if (fc != 3) {
+            fprintf(stderr, "Syntax error in line %zu.\n", i);
+            ucx_mempool_destroy(pool);
+            return 1;
+        }
+        CSVData* data = ucx_mempool_malloc(pool, sizeof(CSVData));
+        data->column_a = fields[0];
+        data->column_b = fields[1];
+        data->column_c = fields[2];
+        datalist = ucx_list_append_a(pool->allocator, datalist, data);
+    }
+
+    /* control output */
+    UCX_FOREACH(elem, datalist) {
+        CSVData* data = elem->data;
+        printf("Column A: %" PRIsstr " | "
+               "Column B: %" PRIsstr " | "
+               "Column C: %" PRIsstr "\n",
+               SFMT(data->column_a), SFMT(data->column_b), SFMT(data->column_c)
+        );
+    }
+
+    /* cleanup everything, no manual free() needed */
+    ucx_mempool_destroy(pool);
+
+    return 0;
+} 
+```
+
+### Overriding the default destructor
+
+Sometimes you need to allocate memory with `ucx_mempool_malloc()`, but the
+memory is not supposed to be freed with a simple call to `free()`.
+In this case, you can overwrite the default destructor as follows:
+```C
+    MyObject* obj = ucx_mempool_malloc(pool, sizeof(MyObject));
+
+    /* some special initialization with own resource management */
+    my_object_init(obj);
+
+    /* register destructor function */
+    ucx_mempool_set_destr(obj, (ucx_destructor) my_object_destroy);
+```
+Be aware, that your destructor function should not free any memory, that is
+also managed by the pool.
+Otherwise you might be risking a double-free.
 
 ## Properties
 
@@ -131,7 +464,37 @@
 This module provides load and store function for `*.properties` files.
 The key/value pairs are stored within an UCX Map.
 
-<a name="stack"></a>
+### Example: Loading properties from a file
+
+```C
+/* Open the file as usual */
+FILE* file = fopen("myprops.properties", "r");
+if (!file) {
+    // error handling
+    return 1;
+}
+
+/* Load the properties from the file */
+UcxMap* myprops = ucx_map_new(16);
+if (ucx_properties_load(myprops, file)) {
+    /* ... error handling ... */
+    fclose(file);
+    ucx_map_free(myprops);
+    return 1;
+}
+
+/* Print out the key/value pairs */
+char* propval;
+UcxMapIterator propiter = ucx_map_iterator(myprops);
+UCX_MAP_FOREACH(key, propval, propiter) {
+    printf("%s = %s\n", (char*)key.data, propval);
+}
+
+/* Don't forget to free the values before freeing the map */
+ucx_map_free_content(myprops, NULL);
+ucx_map_free(myprops);
+fclose(file);
+```
 
 ## Stack
 
@@ -141,13 +504,54 @@
 This concrete implementation of an UCX Allocator allows you to grab some amount
 of memory which is then handled as a stack.
 Please note, that the term *stack* only refers to the behavior of this
-allocator. You may still choose if you want to use stack or heap memory
+allocator. You may still choose to use either stack or heap memory
 for the underlying space.
-
 A typical use case is an algorithm where you need to allocate and free large
 amounts of memory very frequently.
 
-<a name="string"></a>
+The following code sample shows how to initialize a stack and push and pop
+simple data.
+```C
+    const size_t len = 1024;
+    char space[len];
+    UcxStack stack;
+    ucx_stack_init(&stack, space, len);
+
+    int i = 42;
+    float f = 3.14f;
+    const char* str = "Hello!";
+    size_t strn = 7;
+
+    /* push the integer */
+    ucx_stack_push(&stack, sizeof(int), &i);
+
+    /* push the float and rember the address */
+    float* remember = ucx_stack_push(&stack, sizeof(float), &f);
+
+    /* push the string with zero terminator */
+    ucx_stack_push(&stack, strn, str);
+
+    /* if we forget, how big an element was, we can ask the stack */
+    printf("Length of string: %zu\n", ucx_stack_topsize(&stack)-1);
+
+    /* retrieve the string as sstr_t, without zero terminator! */
+    sstr_t s;
+    s.length = ucx_stack_topsize(&stack)-1;
+    s.ptr = malloc(s.length);
+    ucx_stack_popn(&stack, s.ptr, s.length);
+    printf("%" PRIsstr "\n", SFMT(s));
+
+    /* print the float directly from the stack and free it */
+    printf("Float: %f\n", *remember);
+    ucx_stack_free(&stack, remember);
+
+    /* the last element is the integer */
+    int j;
+    ucx_stack_pop(&stack, &j);
+    printf("Integer: %d\n", j);
+```
+
+
 
 ## String
 
@@ -232,8 +636,6 @@
 
 The memory pool ensures, that all strings are freed.
 
-<a name="test"></a>
-
 ## Testing
 
 *Header file:* [test.h](api/test_8h.html)  
@@ -244,7 +646,50 @@
 To avoid code duplication within tests, we also provide the possibility to
 define test subroutines.
 
-<a name="utils"></a>
+You should declare test cases and subroutines in a header file per test unit
+and implement them as you would implement normal functions.
+```C
+    /* myunit.h */
+    UCX_TEST(function_name);
+    UCX_TEST_SUBROUTINE(subroutine_name, paramlist); /* optional */
+
+
+    /* myunit.c */
+    UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+        /* ... reusable tests with UCX_TEST_ASSERT() ... */
+    }
+
+    UCX_TEST(function_name) {
+        /* ... resource allocation and other test preparation ... */
+
+        /* mandatory marker for the start of the tests */
+        UCX_TEST_BEGIN
+
+        /*  ... verifications with UCX_TEST_ASSERT() ...
+         * (and/or calls with UCX_TEST_CALL_SUBROUTINE())
+         */
+
+        /* mandatory marker for the end of the tests */
+        UCX_TEST_END
+
+        /* ... resource cleanup ...
+         * (all code after UCX_TEST_END is always executed)
+         */
+    }
+```
+If you want to use the `UCX_TEST_ASSERT()` macro in a function, you are
+*required* to use a `UCX_TEST_SUBROUTINE`.
+Otherwise the testing framework does not know where to jump, when the assertion
+fails.
+
+After implementing the tests, you can easily build a test suite and execute it:
+```C
+    UcxTestSuite* suite = ucx_test_suite_new();
+    ucx_test_register(suite, testMyTestCase01);
+    ucx_test_register(suite, testMyTestCase02);
+    /* ... */
+    ucx_test_run(suite, stdout); /* stdout, or any other FILE stream */
+```
 
 ## Utilities
 
@@ -256,3 +701,60 @@
 We also provide several `printf` variants to conveniently print formatted data
 to streams or strings.
 
+### A simple copy program
+
+The utilities package provides several stream copy functions.
+One of them has a very simple interface and can, for instance, be used to copy
+whole files in a single call.
+This is a minimal working example:
+```C
+#include <stdio.h>
+#include <ucx/utils.h>
+
+int main(int argc, char** argv) {
+
+    if (argc != 3) {
+        fprintf(stderr, "Use %s <src> <dest>", argv[0]);
+        return 1;
+    }
+
+    FILE *srcf = fopen(argv[1], "r");   /* insert error handling on your own */
+    FILE *destf = fopen(argv[2], "w");
+    
+    size_t n =  ucx_stream_copy(srcf, destf, fread, fwrite);
+    printf("%zu bytes copied.\n", n);
+
+    fclose(srcf);
+    fclose(destf);
+
+
+    return 0;
+}
+```
+
+### Automatic allocation for formatted strings
+
+The UCX utility function `ucx_asprintf()` and it's convenient shortcut
+`ucx_sprintf` allow easy formatting of strings, without ever having to worry
+about the required space.
+```C
+sstr_t mystring = ucx_sprintf("The answer is: %d!", 42);
+```
+Still, you have to pass `mystring.ptr` to `free()` (or the free function of
+your allocator, if you use `ucx_asprintf`).
+If you don't have all the information ready to build your string, you can even
+use a [UcxBuffer](#buffer) as a target with the utility function
+`ucx_bprintf()`.
+```C
+UcxBuffer* strbuffer = ucx_buffer_new(NULL, 512, UCX_BUFFER_AUTOEXTEND);
+
+for (unsigned int i = 2 ; i < 100 ; i++) {
+        ucx_bprintf(strbuffer, "Integer %d is %s\n",
+                        i, prime(i) ? "prime" : "not prime");
+}
+
+/* print the result to stdout */
+printf("%s", (char*)strbuffer->space);
+
+ucx_buffer_free(strbuffer);
+```
--- a/src/Makefile.am	Sun May 13 07:13:09 2018 +0200
+++ b/src/Makefile.am	Mon May 14 17:56:03 2018 +0200
@@ -27,7 +27,7 @@
 #
 
 lib_LTLIBRARIES = libucx.la
-libucx_la_LDFLAGS = -version-info 1:0:0
+libucx_la_LDFLAGS = -version-info $(UCX_LIB_VERSION)
 libucx_la_SOURCES = utils.c
 libucx_la_SOURCES += list.c
 libucx_la_SOURCES += map.c
--- a/src/avl.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/avl.c	Mon May 14 17:56:03 2018 +0200
@@ -136,6 +136,23 @@
     alfree(al, tree);
 }
 
+static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node,
+        ucx_destructor destr) {
+    if (node) {
+        ucx_avl_free_content_node(al, node->left, destr);
+        ucx_avl_free_content_node(al, node->right, destr);
+        if (destr) {
+            destr(node->value);
+        } else {
+            alfree(al, node->value);
+        }
+    }
+}
+
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) {
+    ucx_avl_free_content_node(tree->allocator, tree->root, destr);
+}
+
 UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) {
     UcxAVLNode *n = tree->root;
     int cmpresult;
--- a/src/buffer.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/buffer.c	Mon May 14 17:56:03 2018 +0200
@@ -237,6 +237,61 @@
     }
 }
 
-size_t ucx_buffer_puts(UcxBuffer *buffer, char *str) {
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) {
     return ucx_buffer_write((const void*)str, 1, strlen(str), buffer);
 }
+
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) {
+    if (shift >= buffer->size) {
+        buffer->pos = buffer->size = 0;
+    } else {
+        memmove(buffer->space, buffer->space + shift, buffer->size - shift);
+        buffer->size -= shift;
+        
+        if (buffer->pos >= shift) {
+            buffer->pos -= shift;
+        } else {
+            buffer->pos = 0;
+        }
+    }
+    return 0;
+}
+
+int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) {
+    size_t req_capacity = buffer->size + shift;
+    size_t movebytes;
+    
+    // auto extend buffer, if required and enabled
+    if (buffer->capacity < req_capacity) {
+        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) {
+                return 1;
+            }
+            movebytes = buffer->size;
+        } else {
+            movebytes = buffer->capacity - shift;
+        }
+    } else {
+        movebytes = buffer->size;
+    }
+    
+    memmove(buffer->space + shift, buffer->space, movebytes);
+    buffer->size = shift+movebytes;
+    
+    buffer->pos += shift;
+    if (buffer->pos > buffer->size) {
+        buffer->pos = buffer->size;
+    }
+    
+    return 0;
+}
+
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) {
+    if (shift < 0) {
+        return ucx_buffer_shift_left(buffer, (size_t) (-shift));
+    } else if (shift > 0) {
+        return ucx_buffer_shift_right(buffer, (size_t) shift);
+    } else {
+        return 0;
+    }
+}
--- a/src/list.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/list.c	Mon May 14 17:56:03 2018 +0200
@@ -77,6 +77,7 @@
 }
 
 void ucx_list_free_content(UcxList* list, ucx_destructor destr) {
+    if (!destr) destr = free;
     while (list != NULL) {
         destr(list->data);
         list = list->next;
@@ -141,6 +142,39 @@
     }
 }
 
+UcxList *ucx_list_prepend_once(UcxList *l, void *data,
+        cmp_func cmpfnc, void *cmpdata) {
+    return ucx_list_prepend_once_a(ucx_default_allocator(), l,
+            data, cmpfnc, cmpdata);
+}
+
+UcxList *ucx_list_prepend_once_a(UcxAllocator *alloc, UcxList *l, void *data,
+        cmp_func cmpfnc, void *cmpdata) {
+
+    UcxList* first = ucx_list_first(l);
+    {
+        UcxList *e = first;
+        while (e) {
+            if (cmpfnc(e->data, data, cmpdata) == 0) {
+                return l;
+            }
+            e = e->next;
+        }
+    }
+    
+    UcxList *nl = ucx_list_append_a(alloc, NULL, data);
+    if (!nl) {
+        return NULL;
+    }
+
+    if (first) {
+        nl->next = first;
+        first->prev = nl;
+    }
+    
+    return nl;
+}
+
 UcxList *ucx_list_prepend(UcxList *l, void *data) {
     return ucx_list_prepend_a(ucx_default_allocator(), l, data);
 }
--- a/src/logging.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/logging.c	Mon May 14 17:56:03 2018 +0200
@@ -50,6 +50,8 @@
         ucx_map_int_put(logger->levels, l, (void*) "[WARNING]");
         l = UCX_LOGGER_INFO;
         ucx_map_int_put(logger->levels, l, (void*) "[INFO]");
+        l = UCX_LOGGER_DEBUG;
+        ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]");
         l = UCX_LOGGER_TRACE;
         ucx_map_int_put(logger->levels, l, (void*) "[TRACE]");
     }
@@ -75,6 +77,9 @@
         
         if ((logger->mask & UCX_LOGGER_LEVEL) > 0) {
             text = (char*) ucx_map_int_get(logger->levels, level);
+            if (!text) {
+                text = "[UNKNOWN]";
+            }
             n = strlen(text);
             n = n > 256 ? 256 : n;
             memcpy(msg+k, text, n);
--- a/src/map.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/map.c	Mon May 14 17:56:03 2018 +0200
@@ -86,7 +86,11 @@
     UcxMapIterator iter = ucx_map_iterator(map);
     void *val;
     UCX_MAP_FOREACH(key, val, iter) {
-        destr(val);
+        if (destr) {
+            destr(val);
+        } else {
+            alfree(map->allocator, val);
+        }
     }
 }
 
--- a/src/stack.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/stack.c	Mon May 14 17:56:03 2018 +0200
@@ -120,13 +120,15 @@
         return;
     }
     
-    size_t len = ucx_stack_topsize(stack);
-    if (len > n) {
-        len = n;
+    if (dest) {
+        size_t len = ucx_stack_topsize(stack);
+        if (len > n) {
+            len = n;
+        }
+
+        memcpy(dest, stack->top, len);
     }
     
-    memcpy(dest, stack->top, len);
-    
     ucx_stack_free(stack, stack->top);
 }
 
@@ -142,3 +144,22 @@
         return 0;
     }
 }
+
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) {
+    void *space = ucx_stack_malloc(stack, n);
+    if (space) {
+        memcpy(space, data, n);
+    }
+    return space;
+}
+
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data) {
+    
+    // skip the memset by using malloc
+    void *space = ucx_stack_malloc(stack, nelem*elsize);
+    if (space) {
+        memcpy(space, data, nelem*elsize);
+    }
+    return space;
+}
--- a/src/string.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/string.c	Mon May 14 17:56:03 2018 +0200
@@ -227,9 +227,9 @@
 
 int ucx_strrchr(const char *string, size_t length, int chr, size_t *pos) {
     if(length > 0) {
-        for(size_t i=length-1;i>=0;i--) {
-            if(string[i] == chr) {
-                *pos = i;
+        for(size_t i=length ; i>0 ; i--) {
+            if(string[i-1] == chr) {
+                *pos = i-1;
                 return 1;
             }
         }
--- a/src/ucx/avl.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/avl.h	Mon May 14 17:56:03 2018 +0200
@@ -135,11 +135,36 @@
 
 /**
  * Destroys a UcxAVLTree.
+ * 
+ * Note, that the contents are not automatically freed.
+ * Use may use #ucx_avl_free_content() before calling this function.
+ * 
  * @param tree the tree to destroy
+ * @see ucx_avl_free_content()
  */
 void ucx_avl_free(UcxAVLTree *tree);
 
 /**
+ * Frees the contents of a UcxAVLTree.
+ * 
+ * This is a convenience function that iterates over the tree and passes all
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the tree's own allocator is used.
+ * 
+ * You must ensure, that it is valid to pass each value in the map to the same
+ * destructor function.
+ * 
+ * You should free the entire tree afterwards, as the contents will be invalid.
+ * 
+ * @param tree for which the contents shall be freed
+ * @param destr optional pointer to a destructor function
+ * @see ucx_avl_free()
+ */
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr);
+
+/**
  * Macro for initializing a new UcxAVLTree with the default allocator and a
  * ucx_ptrcmp() compare function.
  * 
--- a/src/ucx/buffer.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/buffer.h	Mon May 14 17:56:03 2018 +0200
@@ -137,6 +137,67 @@
 #define ucx_buffer_clone(src,flags) \
     ucx_buffer_extract(src, 0, (src)->capacity, flags)
 
+
+/**
+ * Shifts the contents of the buffer by the given offset.
+ * 
+ * If the offset is positive, the contents are shifted to the right.
+ * If auto extension is enabled, the buffer grows, if necessary.
+ * In case the auto extension fails, this function returns a non-zero value and
+ * no contents are changed.
+ * If auto extension is disabled, the contents that do not fit into the buffer
+ * are discarded.
+ * 
+ * If the offset is negative, the contents are shifted to the left where the
+ * first <code>shift</code> bytes are discarded.
+ * The new size of the buffer is the old size minus
+ * the absolute shift value.
+ * If this value is larger than the buffer size, the buffer is emptied (but
+ * not cleared, see the security note below).
+ * 
+ * The buffer position gets shifted alongside with the content but is kept
+ * within the boundaries of the buffer.
+ * 
+ * <b>Security note:</b> the shifting operation does <em>not</em> erase the
+ * previously occupied memory cells. You can easily do that manually, e.g. by
+ * calling <code>memset(buffer->space, 0, shift)</code> for a right shift or
+ * <code>memset(buffer->size, 0, buffer->capacity-buffer->size)</code>
+ * for a left shift.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset (negative means left shift)
+ * @return 0 on success, non-zero if a required auto-extension fails
+ */
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift);
+
+/**
+ * Shifts the buffer to the right.
+ * See ucx_buffer_shift() for details.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift);
+
+/**
+ * Shifts the buffer to the left.
+ * 
+ * See ucx_buffer_shift() for details. Note, however, that this method expects
+ * a positive shift offset.
+ * 
+ * Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return always zero
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift);
+
+
 /**
  * Moves the position of the buffer.
  * 
@@ -165,8 +226,8 @@
  * 
  * @param buffer the buffer to be cleared
  */
-#define ucx_buffer_clear(buffer) memset(buffer->space, 0, buffer->size); \
-        buffer->size = 0; buffer->pos = 0;
+#define ucx_buffer_clear(buffer) memset((buffer)->space, 0, (buffer)->size); \
+        (buffer)->size = 0; (buffer)->pos = 0;
 
 /**
  * Tests, if the buffer position has exceeded the buffer capacity.
@@ -260,7 +321,15 @@
  * @param str the string
  * @return the number of bytes written
  */
-size_t ucx_buffer_puts(UcxBuffer *buffer, char *str);
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str);
+
+/**
+ * Returns the complete buffer content as sstr_t.
+ * @param buffer the buffer
+ * @return the result of <code>sstrn()</code> with the buffer space and size
+ * as arguments
+ */
+#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size)
 
 #ifdef	__cplusplus
 }
--- a/src/ucx/list.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/list.h	Mon May 14 17:56:03 2018 +0200
@@ -173,9 +173,11 @@
  * 
  * Note, that the contents are not usable afterwards and the list should be
  * destroyed with ucx_list_free().
+ *
+ * If no destructor is specified (<code>NULL</code>), stdlib's free() is used.
  * 
  * @param list the list for which the contents shall be freed
- * @param destr the destructor function (e.g. stdlib free())
+ * @param destr optional destructor function
  * @see ucx_list_free()
  */
 void ucx_list_free_content(UcxList* list, ucx_destructor destr);
@@ -213,6 +215,12 @@
 /**
  * Inserts an element at the end of the list, if it is not present in the list.
  * 
+ * <b>Note:</b> You should not try to store a freshly allocated object. Since
+ * it might be a duplicate, the memory allocated for that copy would be leaking
+ * afterwards.
+ * 
+ * <b>Deprecation notice:</b> This function is considered to do more harm than
+ * it adds usefulness and is going to be removed in a future UCX release.
  * 
  * @param list the list where to append the data, or <code>NULL</code> to
  * create a new list
@@ -230,7 +238,12 @@
  * Inserts an element at the end of the list, if it is not present in the list,
  * using a UcxAllocator.
  * 
- * See ucx_list_append() for details.
+ * <b>Note:</b> You should not try to store a freshly allocated object. Since
+ * it might be a duplicate, the memory allocated for that copy would be leaking
+ * afterwards.
+ * 
+ * <b>Deprecation notice:</b> This function is considered to do more harm than
+ * it adds usefulness and is going to be removed in a future UCX release.
  * 
  * @param allocator the allocator to use
  * @param list the list where to append the data, or <code>NULL</code> to
@@ -246,6 +259,51 @@
         UcxList *list, void *data, cmp_func cmpfnc, void *cmpdata);
 
 /**
+ * Inserts an element at the beginning of the list, if it is not present
+ * in the list.
+ * 
+ * <b>Note:</b> You should not try to store a freshly allocated object. Since
+ * it might be a duplicate, the memory allocated for that copy would be leaking
+ * afterwards.
+ * 
+ * <b>Deprecation notice:</b> This function is considered to do more harm than
+ * it adds usefulness and is going to be removed in a future UCX release.
+ * 
+ * @param list the list where to prepend the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @param cmpfnc the compare function
+ * @param cmpdata additional data for the compare function
+ * @return a pointer to the new list head
+ * @see ucx_list_prepend()
+ */
+UcxList *ucx_list_prepend_once(UcxList *list, void *data,
+        cmp_func cmpfnc, void *cmpdata);
+
+/**
+ * Inserts an element at the beginning of the list, if it is not present in
+ * the list, using a UcxAllocator.
+ * 
+ * <b>Note:</b> You should not try to store a freshly allocated object. Since
+ * it might be a duplicate, the memory allocated for that copy would be leaking
+ * afterwards.
+ * 
+ * <b>Deprecation notice:</b> This function is considered to do more harm than
+ * it adds usefulness and is going to be removed in a future UCX release.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list where to prepend the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @param cmpfnc the compare function
+ * @param cmpdata additional data for the compare function
+ * @return a pointer to the new list head
+ * @see ucx_list_prepend_a()
+ */
+UcxList *ucx_list_prepend_once_a(UcxAllocator *allocator,
+        UcxList *list, void *data, cmp_func cmpfnc, void *cmpdata);
+
+/**
  * Inserts an element at the beginning of the list.
  * 
  * You <i>should</i> overwrite the old list pointer by calling
--- a/src/ucx/logging.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/logging.h	Mon May 14 17:56:03 2018 +0200
@@ -177,6 +177,18 @@
         const unsigned int line, const char* format, ...);
 
 /**
+ * Registers a custom log level.
+ * @param logger the logger
+ * @param level the log level as unsigned integer
+ * @param name a string literal describing the level
+ */
+#define ucx_logger_register_level(logger, level, name) {\
+        unsigned int l; \
+            l = level; \
+            ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \
+        } while (0);
+
+/**
  * Logs a message at the specified level.
  * @param logger the logger to use
  * @param level the level to log the message on
--- a/src/ucx/map.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/map.h	Mon May 14 17:56:03 2018 +0200
@@ -158,7 +158,10 @@
  * Frees the contents of a hash map.
  * 
  * This is a convenience function that iterates over the map and passes all
- * values to the specified destructor function (e.g. stdlib free()).
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the map's own allocator is used.
  * 
  * You must ensure, that it is valid to pass each value in the map to the same
  * destructor function.
@@ -166,7 +169,7 @@
  * You should free or clear the map afterwards, as the contents will be invalid.
  * 
  * @param map for which the contents shall be freed
- * @param destr pointer to the destructor function
+ * @param destr optional pointer to a destructor function
  * @see ucx_map_free()
  * @see ucx_map_clear()
  */
--- a/src/ucx/properties.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/properties.h	Mon May 14 17:56:03 2018 +0200
@@ -173,12 +173,15 @@
  * Retrieves all available key/value-pairs and puts them into a UcxMap.
  * 
  * This is done by successive calls to ucx_properties_next() until no more
- * key/value-pairs can be retrieved. 
+ * key/value-pairs can be retrieved.
+ * 
+ * The memory for the map values is allocated by the map's own allocator.
  * 
  * @param prop the UcxProperties object
  * @param map the target map
  * @return The UcxProperties.error code (i.e. 0 on success).
  * @see ucx_properties_fill()
+ * @see UcxMap.allocator
  */
 int ucx_properties2map(UcxProperties *prop, UcxMap *map);
 
--- a/src/ucx/stack.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/stack.h	Mon May 14 17:56:03 2018 +0200
@@ -91,19 +91,23 @@
  * 
  * @param stack a pointer to the stack
  * @param n amount of memory to allocate
- * @return a pointer to the allocated memory
+ * @return a pointer to the allocated memory or <code>NULL</code> on stack
+ * overflow
  * @see ucx_allocator_malloc()
  */
 void *ucx_stack_malloc(UcxStack *stack, size_t n);
 
 /**
- * Alias for #ucx_stack_malloc().
+ * Allocates memory with #ucx_stack_malloc() and copies the specified data if
+ * the allocation was successful.
+ * 
  * @param stack a pointer to the stack
  * @param n amount of memory to allocate
+ * @param data a pointer to the data to copy
  * @return a pointer to the allocated memory
  * @see ucx_stack_malloc
  */
-#define ucx_stack_push(stack, n) ucx_stack_malloc(stack, n)
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data);
 
 /**
  * Allocates an array of stack memory
@@ -119,15 +123,18 @@
 void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize);
 
 /**
- * Alias for #ucx_stack_calloc().
+ * Allocates memory with #ucx_stack_calloc() and copies the specified data if
+ * the allocation was successful.
  * 
  * @param stack a pointer to the stack
- * @param n amount of elements to allocate
+ * @param nelem amount of elements to allocate
  * @param elsize amount of memory per element
+ * @param data a pointer to the data
  * @return a pointer to the allocated memory
  * @see ucx_stack_calloc
  */
-#define ucx_stack_pusharr(stack,n,elsize) ucx_stack_calloc(stack,n,elssize)
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data);
 
 /**
  * Reallocates memory on the stack.
@@ -184,12 +191,13 @@
  * Removes the top most element from the stack and copies the content to <code>
  * dest</code>.
  * 
- * In contrast to #ucx_stack_pop() the <code>dest</code> pointer <code>MUST
- * NOT</code> be <code>NULL</code>.
+ * This function copies at most <code>n</code> bytes to the destination, but
+ * the element is always freed as a whole.
+ * If the element was larger than <code>n</code>, the remaining data is lost.
  * 
  * @param stack a pointer to the stack
  * @param dest the location where the contents shall be written to
- * @param n copies at most n elements to <code>dest</code>
+ * @param n copies at most n bytes to <code>dest</code>
  * @see ucx_stack_pop
  */
 void ucx_stack_popn(UcxStack *stack, void *dest, size_t n);
--- a/src/ucx/string.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/string.h	Mon May 14 17:56:03 2018 +0200
@@ -58,6 +58,12 @@
 /** Shortcut for the conversion of a C string to a <code>sstr_t</code>. */
 #define S(s) sstrn((char*)s, sizeof(s)-1)
 
+/** Expands a sstr_t to printf arguments. */
+#define SFMT(s) (int) (s).length, (s).ptr
+
+/** Format specifier for a sstr_t. */
+#define PRIsstr ".*s"
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
--- a/src/ucx/test.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/test.h	Mon May 14 17:56:03 2018 +0200
@@ -36,8 +36,8 @@
  * **** IN HEADER FILE: ****
  *
  * <pre>
- * UCX_TEST(function_name)
- * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) // optional
+ * UCX_TEST(function_name);
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
  * </pre>
  *
  * **** IN SOURCE FILE: ****
--- a/src/ucx/ucx.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/ucx.h	Mon May 14 17:56:03 2018 +0200
@@ -37,7 +37,7 @@
 #define	UCX_H
 
 /** Major UCX version as integer constant. */
-#define UCX_VERSION_MAJOR   1
+#define UCX_VERSION_MAJOR   2
 
 /** Minor UCX version as integer constant. */
 #define UCX_VERSION_MINOR   0
@@ -135,9 +135,54 @@
 
 #if defined(__GNUC__) || defined(__clang__)
 #define UCX_MUL_BUILTIN
+
+#if __WORDSIZE == 32
+/**
+ * Alias for <code>__builtin_umul_overflow</code>.
+ * 
+ * Performs a multiplication of size_t values and checks for overflow.
+ * 
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result)
+#else /* __WORDSIZE != 32 */
+/**
+ * Alias for <code>__builtin_umull_overflow</code>.
+ * 
+ * Performs a multiplication of size_t values and checks for overflow.
+ * 
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
 #define ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result)
-#else
+#endif /* __WORDSIZE */
+
+#else /* no GNUC or clang bultin */
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+ * 
+ * This is a custom implementation in case there is no compiler builtin
+ * available.
+ * 
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
 int ucx_szmul(size_t a, size_t b, size_t *result);
+
 #endif
 
 #ifdef	__cplusplus
--- a/src/ucx/utils.h	Sun May 13 07:13:09 2018 +0200
+++ b/src/ucx/utils.h	Mon May 14 17:56:03 2018 +0200
@@ -156,6 +156,15 @@
 int ucx_strncmp(const void *s1, const void *s2, void *n);
 
 /**
+ * Wraps the sstrcmp function.
+ * @param s1 sstr one
+ * @param s2 sstr two
+ * @param data ignored
+ * @return the result of sstrcmp(s1, s2)
+ */
+int ucx_sstrcmp(const void *s1, const void *s2, void *data);
+
+/**
  * Compares two integers of type int.
  * @param i1 pointer to integer one
  * @param i2 pointer to integer two
@@ -166,6 +175,35 @@
 int ucx_intcmp(const void *i1, const void *i2, void *data);
 
 /**
+ * Compares two integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_longintcmp(const void *i1, const void *i2, void *data);
+
+
+/**
+ * Distance function for integers of type int.
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_intdist(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_longintdist(const void *i1, const void *i2, void *data);
+
+/**
  * Compares two real numbers of type float.
  * @param f1 pointer to float one
  * @param f2 pointer to float two
--- a/src/utils.c	Sun May 13 07:13:09 2018 +0200
+++ b/src/utils.c	Mon May 14 17:56:03 2018 +0200
@@ -96,6 +96,12 @@
     return strncmp((const char*)s1, (const char*)s2, *((size_t*) n));
 }
 
+int ucx_sstrcmp(const void *s1, const void *s2, void *data) {
+    sstr_t a = *(const sstr_t*) s1;
+    sstr_t b = *(const sstr_t*) s2;
+    return sstrcmp(a, b);
+}
+
 int ucx_intcmp(const void *i1, const void *i2, void *data) {
    int a = *((const int*) i1);
    int b = *((const int*) i2);
@@ -106,6 +112,28 @@
    }
 }
 
+int ucx_longintcmp(const void *i1, const void *i2, void *data) {
+   int a = *((const long int*) i1);
+   int b = *((const long int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+intmax_t ucx_intdist(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int*) i1);
+   intmax_t b = *((const int*) i2);
+   return a - b;
+}
+
+intmax_t ucx_longintdist(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const long int*) i1);
+   intmax_t b = *((const long int*) i2);
+   return a - b;
+}
+
 int ucx_floatcmp(const void *f1, const void *f2, void *epsilon) {
    float a = *((const float*) f1);
    float b = *((const float*) f2);
--- a/test/buffer_tests.c	Sun May 13 07:13:09 2018 +0200
+++ b/test/buffer_tests.c	Mon May 14 17:56:03 2018 +0200
@@ -625,3 +625,114 @@
     
     ucx_buffer_free(b);
 }
+
+UCX_TEST(test_ucx_buffer_shl) {
+    
+    const char* hw = "Shift the World!";
+    
+    UcxBuffer *b = ucx_buffer_new(NULL, 20, UCX_BUFFER_DEFAULT);
+    ucx_buffer_puts(b, hw);
+    b->pos = 5;
+    
+    UCX_TEST_BEGIN
+    char* expected;
+    
+    ucx_buffer_shift_left(b, 2);
+    expected = "ift the World!";
+    
+    UCX_TEST_ASSERT(b->pos == 3, "position after normal shl wrong");
+    UCX_TEST_ASSERT(b->size == strlen(expected), "size after normal shl wrong");
+    UCX_TEST_ASSERT(!memcmp(b->space, expected, b->size),
+            "contents after normal shl wrong");
+    
+    
+    ucx_buffer_shift_left(b, 5);
+    expected = "he World!";
+    
+    UCX_TEST_ASSERT(b->pos == 0, "position after overshift left wrong");
+    UCX_TEST_ASSERT(b->size == strlen(expected),
+            "size after overshift left wrong");
+    UCX_TEST_ASSERT(!memcmp(b->space, expected, b->size),
+            "contents after overshift left wrong");
+    
+    ucx_buffer_shift_left(b, 10);
+    UCX_TEST_ASSERT(b->pos == 0, "position after 'shl everything away' wrong");
+    UCX_TEST_ASSERT(b->size == 0, "size after 'shl everything away' wrong");
+    
+    UCX_TEST_END
+    
+    ucx_buffer_free(b);    
+}
+
+UCX_TEST(test_ucx_buffer_shr) {
+    
+    const char* hw = "Shift the World!";
+    
+    UcxBuffer *b = ucx_buffer_new(NULL, 20, UCX_BUFFER_DEFAULT);
+    ucx_buffer_puts(b, hw);
+    b->pos = 12;
+    
+    UCX_TEST_BEGIN
+    char* expected;
+    
+    ucx_buffer_shift_right(b, 2);
+    expected = "ShShift the World!";
+    
+    UCX_TEST_ASSERT(b->pos == 14, "position after normal shr wrong");
+    UCX_TEST_ASSERT(b->size == strlen(expected), "size after normal shr wrong");
+    UCX_TEST_ASSERT(!memcmp(b->space, expected, b->size),
+            "contents after normal shr wrong");
+    
+    
+    ucx_buffer_shift_right(b, 5);
+    expected = "ShShiShShift the Wor";
+    UCX_TEST_ASSERT(strlen(expected) == b->capacity,
+            "Test data is wrong, please fix the test.");
+    
+    UCX_TEST_ASSERT(b->pos == 19,
+            "position after overshift right w/o auto-extend wrong");
+    UCX_TEST_ASSERT(b->size == 20,
+            "size after overshift right w/o auto-extend wrong");
+    UCX_TEST_ASSERT(b->capacity == 20,
+            "capacity after overshift right w/o auto-extend wrong");
+    UCX_TEST_ASSERT(!memcmp(b->space, expected, b->size),
+            "contents after overshift right w/o auto-extend wrong");
+    
+    ucx_buffer_shift_right(b, 15);
+    UCX_TEST_ASSERT(b->pos == b->capacity, "position after 'shr to eof' wrong");
+    UCX_TEST_ASSERT(b->size == b->capacity, "size after 'shr to eof' wrong");
+    UCX_TEST_ASSERT(ucx_buffer_eof(b), "buffer not eof after 'shr to eof'");
+    
+    UCX_TEST_END
+    
+    ucx_buffer_free(b);    
+}
+
+UCX_TEST(test_ucx_buffer_shr_ax) {
+    
+    const char* hw = "Shift the World!";
+    
+    UcxBuffer *b = ucx_buffer_new(NULL, 20, UCX_BUFFER_AUTOEXTEND);
+    ucx_buffer_puts(b, hw);
+    b->pos = 12;
+    
+    UCX_TEST_BEGIN
+    
+    const char* expected = "Shift the Shift the World!";
+    
+    ucx_buffer_shift_right(b, 10);
+    UCX_TEST_ASSERT(b->pos == 22, "position after shr w/ auto-extend wrong");
+    UCX_TEST_ASSERT(b->size == strlen(expected),
+            "size after shr w/ auto-extend wrong");
+    UCX_TEST_ASSERT(b->capacity >= b->size,
+            "auto-extension of capacity after shr w/ auto-extend failed");
+    UCX_TEST_ASSERT(!ucx_buffer_eof(b),
+            "buffer should not be eof after shr w/ auto-extend");
+    UCX_TEST_ASSERT(!memcmp(b->space, expected, b->size),
+            "contents wrong after shr w/ auto-extend");
+    
+    UCX_TEST_END
+    
+    ucx_buffer_free(b);    
+}
+
--- a/test/buffer_tests.h	Sun May 13 07:13:09 2018 +0200
+++ b/test/buffer_tests.h	Mon May 14 17:56:03 2018 +0200
@@ -60,6 +60,9 @@
 UCX_TEST(test_ucx_buffer_write);
 UCX_TEST(test_ucx_buffer_write_oob);
 UCX_TEST(test_ucx_buffer_write_ax);
+UCX_TEST(test_ucx_buffer_shl);
+UCX_TEST(test_ucx_buffer_shr);
+UCX_TEST(test_ucx_buffer_shr_ax);
 
 
 #ifdef	__cplusplus
--- a/test/logging_tests.c	Sun May 13 07:13:09 2018 +0200
+++ b/test/logging_tests.c	Mon May 14 17:56:03 2018 +0200
@@ -46,7 +46,7 @@
     UCX_TEST_ASSERT(strcmp(logger->dateformat, "%F %T %z ") == 0,
         "date format not set to \"%F %T %z\"");
     
-    UCX_TEST_ASSERT(logger->levels->count == 4,
+    UCX_TEST_ASSERT(logger->levels->count == 5,
         "incorrect number of registered log levels");
 
     int level = UCX_LOGGER_ERROR;
@@ -55,6 +55,9 @@
     level = UCX_LOGGER_WARN;
     UCX_TEST_ASSERT(strcmp((char*)ucx_map_int_get(logger->levels, level),
         "[WARNING]") == 0, "invalid warning level");
+    level = UCX_LOGGER_DEBUG;
+    UCX_TEST_ASSERT(strcmp((char*)ucx_map_int_get(logger->levels, level),
+        "[DEBUG]") == 0, "invalid debug level");
     level = UCX_LOGGER_INFO;
     UCX_TEST_ASSERT(strcmp((char*)ucx_map_int_get(logger->levels, level),
         "[INFO]") == 0, "invalid info level");
--- a/test/main.c	Sun May 13 07:13:09 2018 +0200
+++ b/test/main.c	Mon May 14 17:56:03 2018 +0200
@@ -125,6 +125,7 @@
         ucx_test_register(suite, test_ucx_default_allocator);
         
         /* sstring Tests */
+        ucx_test_register(suite, test_sstr_macros);
         ucx_test_register(suite, test_sstr);
         ucx_test_register(suite, test_sstr_len);
         ucx_test_register(suite, test_sstrcmp);
@@ -222,6 +223,9 @@
         ucx_test_register(suite, test_ucx_buffer_write);
         ucx_test_register(suite, test_ucx_buffer_write_oob);
         ucx_test_register(suite, test_ucx_buffer_write_ax);
+        ucx_test_register(suite, test_ucx_buffer_shl);
+        ucx_test_register(suite, test_ucx_buffer_shr);
+        ucx_test_register(suite, test_ucx_buffer_shr_ax);
         
         /* Utils Tests*/
         ucx_test_register(suite, test_ucx_fprintf);
--- a/test/string_tests.c	Sun May 13 07:13:09 2018 +0200
+++ b/test/string_tests.c	Mon May 14 17:56:03 2018 +0200
@@ -28,6 +28,19 @@
 
 #include "string_tests.h"
 
+UCX_TEST(test_sstr_macros) {
+    sstr_t hello = ST("Hello");
+    sstr_t world = S("World");
+    
+    char buf[20];
+    snprintf(buf, sizeof(buf), "%" PRIsstr ", %" PRIsstr "!", SFMT(hello), SFMT(world));
+    
+    UCX_TEST_BEGIN
+    const char* cmp = "Hello, World!";
+    UCX_TEST_ASSERT(!strcmp(cmp, buf), "Something weird happened.");
+    UCX_TEST_END
+}
+
 UCX_TEST(test_sstr) {
     sstr_t s1 = sstr((char*)"1234");
     sstr_t s2 = sstrn((char*)"ab", 2);
@@ -64,7 +77,12 @@
     UCX_TEST_BEGIN
     
     sstr_t notfound = sstrchr(str, 'x');
-    UCX_TEST_ASSERT(notfound.length == 0, "string length not 0");
+    UCX_TEST_ASSERT(notfound.length == 0,
+            "string length not 0 after forward search w/o result");
+    
+    notfound = sstrrchr(str, 'x');
+    UCX_TEST_ASSERT(notfound.length == 0,
+            "string length not 0 after reverse search w/o result");
     
     sstr_t result = sstrchr(str, 'w');
     UCX_TEST_ASSERT(result.length == 35, "sstrchr returned wrong length");
--- a/test/string_tests.h	Sun May 13 07:13:09 2018 +0200
+++ b/test/string_tests.h	Mon May 14 17:56:03 2018 +0200
@@ -36,6 +36,7 @@
 extern "C" {
 #endif
 
+UCX_TEST(test_sstr_macros);
 UCX_TEST(test_sstr);
 UCX_TEST(test_sstr_len);
 UCX_TEST(test_sstrcmp);

mercurial