adds new shift operations for UcxBuffer (including tests and a usage example in modules.md)

Wed, 09 May 2018 20:15:10 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 09 May 2018 20:15:10 +0200
changeset 290
d5d6ab809ad3
parent 289
a5eabd407774
child 291
deb0035635eb

adds new shift operations for UcxBuffer (including tests and a usage example in modules.md)

docs/src/modules.md file | annotate | diff | comparison | revisions
src/buffer.c file | annotate | diff | comparison | revisions
src/ucx/buffer.h file | annotate | diff | comparison | revisions
test/buffer_tests.c file | annotate | diff | comparison | revisions
test/buffer_tests.h file | annotate | diff | comparison | revisions
test/main.c file | annotate | diff | comparison | revisions
--- a/docs/src/modules.md	Wed May 09 15:04:15 2018 +0200
+++ b/docs/src/modules.md	Wed May 09 20:15:10 2018 +0200
@@ -108,6 +108,80 @@
 See the documentation of the macro constants in the header file for more
 information.
 
+### 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
+            chunksize,  // initial buffer size should be 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
 
 *Header file:* [list.h](api/list_8h.html)  
--- a/src/buffer.c	Wed May 09 15:04:15 2018 +0200
+++ b/src/buffer.c	Wed May 09 20:15:10 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/ucx/buffer.h	Wed May 09 15:04:15 2018 +0200
+++ b/src/ucx/buffer.h	Wed May 09 20:15:10 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.
  * 
@@ -260,7 +321,7 @@
  * @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.
--- a/test/buffer_tests.c	Wed May 09 15:04:15 2018 +0200
+++ b/test/buffer_tests.c	Wed May 09 20:15:10 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	Wed May 09 15:04:15 2018 +0200
+++ b/test/buffer_tests.h	Wed May 09 20:15:10 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/main.c	Wed May 09 15:04:15 2018 +0200
+++ b/test/main.c	Wed May 09 20:15:10 2018 +0200
@@ -223,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);

mercurial