src/properties.c

changeset 1031
8a90552bba29
parent 985
68754c7de906
--- a/src/properties.c	Fri Dec 20 15:00:05 2024 +0100
+++ b/src/properties.c	Fri Dec 20 15:00:31 2024 +0100
@@ -30,8 +30,6 @@
 
 #include <assert.h>
 
-static const int CX_PROPERTIES_FLAG_USE_STACK = 0x01;
-
 const CxPropertiesConfig cx_properties_config_default = {
         '=',
         //'\\',
@@ -49,60 +47,8 @@
 }
 
 void cxPropertiesDestroy(CxProperties *prop) {
-    if (0 == (prop->flags & CX_PROPERTIES_FLAG_USE_STACK)) {
-        free(prop->buf);
-    }
-    prop->buf = NULL;
-    prop->buf_capacity = prop->buf_size = 0;
-}
-
-static int cx_properties_ensure_buf_capacity(CxProperties *prop, size_t cap) {
-    if (prop->buf_capacity >= cap) {
-        return 0;
-    }
-
-    // not enough capacity - are we on the stack right now?
-    if ((prop->flags & CX_PROPERTIES_FLAG_USE_STACK) != 0) {
-        // move to the heap
-        char *newbuf = malloc(cap);
-        if (newbuf == NULL) return 1;
-        memcpy(newbuf, prop->buf, prop->buf_size);
-        prop->buf = newbuf;
-        prop->flags &= CX_PROPERTIES_FLAG_USE_STACK;
-    } else {
-        // we are on the heap already, reallocate
-        // this is legit, because realloc() behaves like malloc() when the
-        // current pointer is NULL
-        char *newbuf = realloc(prop->buf, cap);
-        if (newbuf == NULL) return 1;
-        prop->buf = newbuf;
-    }
-
-    // store new capacity and return
-    prop->buf_capacity = cap;
-    return 0;
-}
-
-static int cx_properties_rescuen_input(CxProperties *prop, size_t len) {
-    if (cx_properties_ensure_buf_capacity(prop, prop->buf_size + len)) {
-        return 1;
-    }
-    const char *src = prop->text + prop->text_pos;
-    char *dest = prop->buf + prop->buf_size;
-    memcpy(dest, src, len);
-    prop->buf_size += len;
-    prop->text_pos += len;
-    return 0;
-}
-
-static int cx_properties_rescue_input(CxProperties *prop) {
-    // someone fucked around with our integers, exit immediately
-    if (prop->text_pos > prop->text_size) return 0;
-
-    // determine the bytes needed
-    size_t len = prop->text_size - prop->text_pos;
-
-    return cx_properties_rescuen_input(prop, len);
+    cxBufferDestroy(&prop->input);
+    cxBufferDestroy(&prop->buffer);
 }
 
 int cxPropertiesFilln(
@@ -110,10 +56,15 @@
         const char *buf,
         size_t len
 ) {
-    if (cx_properties_rescue_input(prop)) return 1;
-    prop->text = buf;
-    prop->text_size = len;
-    prop->text_pos = 0;
+    if (cxBufferEof(&prop->input)) {
+        // destroy a possible previously initialized buffer
+        cxBufferDestroy(&prop->input);
+        cxBufferInit(&prop->input, (void*) buf, len,
+            NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND);
+        prop->input.size = len;
+    } else {
+        if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1;
+    }
     return 0;
 }
 
@@ -122,11 +73,7 @@
         char *buf,
         size_t capacity
 ) {
-    assert(prop->buf == NULL);
-    prop->buf = buf;
-    prop->buf_capacity = capacity;
-    prop->buf_size = 0;
-    prop->flags |= CX_PROPERTIES_FLAG_USE_STACK;
+    cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND);
 }
 
 CxPropertiesStatus cxPropertiesNext(
@@ -135,65 +82,42 @@
         cxstring *value
 ) {
     // check if we have a text buffer
-    if (prop->text == NULL) {
+    if (prop->input.space == NULL) {
         return CX_PROPERTIES_NULL_INPUT;
     }
+
+    // a pointer to the buffer we want to read from
+    CxBuffer *current_buffer = &prop->input;
+
     // check if we have rescued data
-    if (prop->buf_size > 0) {
+    if (!cxBufferEof(&prop->buffer)) {
         // check if we can now get a complete line
-        const char *buf = prop->text + prop->text_pos;
-        size_t len = prop->text_size - prop->text_pos;
-        cxstring str = cx_strn(buf, len);
-        cxstring nl = cx_strchr(str, '\n');
-        if(nl.length > 0) {
+        cxstring input = cx_strn(prop->input.space + prop->input.pos,
+            prop->input.size - prop->input.pos);
+        cxstring nl = cx_strchr(input, '\n');
+        if (nl.length > 0) {
             // we add as much data to the rescue buffer as we need
             // to complete the line
-            size_t len_until_nl = (size_t)(nl.ptr - buf) + 1;
+            size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1;
 
-            if (cx_properties_rescuen_input(prop, len_until_nl)) {
+            if (cxBufferAppend(input.ptr, 1,
+                len_until_nl, &prop->buffer) < len_until_nl) {
                 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
             }
 
-            // the tmp buffer contains exactly one line now
-            // we use a trick here: we swap the buffers and recurse
-            const char *orig_text = prop->text;
-            size_t orig_size = prop->text_size;
-            prop->text = prop->buf;
-            prop->text_size = prop->buf_size;
-            prop->text_pos = 0;
-            prop->buf_size = 0;
-
-            CxPropertiesStatus result;
-            result = cxPropertiesNext(prop, key, value);
-
-            // restore original buffer
-            prop->text = orig_text;
-            prop->text_size = orig_size;
-
-            // set the position to after the newline
-            prop->text_pos = len_until_nl;
+            // advance the position in the input buffer
+            prop->input.pos += len_until_nl;
 
-            // check the result
-            if (result == CX_PROPERTIES_NO_ERROR) {
-                // reset the rescue buffer and return with the result
-                prop->buf_size = 0;
-                return result;
-            } else if (result == CX_PROPERTIES_NO_DATA) {
-                // rescue buffer contained only blanks or comments
-                // reset the rescue buffer and retry with text buffer
-                prop->buf_size = 0;
-                return cxPropertiesNext(prop, key, value);
-            } else {
-                // CX_PROPERTIES_INCOMPLETE_DATA is not possible
-                // so it must have been another error
-                // do not reset the rescue buffer and return the error
-                return result;
-            }
+            // we now want to read from the rescue buffer
+            current_buffer = &prop->buffer;
         } else {
-            // still not enough data
-            if (cx_properties_rescue_input(prop)) {
+            // still not enough data, copy input buffer to internal buffer
+            if (cxBufferAppend(input.ptr, 1,
+                input.length, &prop->buffer) < input.length) {
                 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
             }
+            // reset the input buffer (make way for a re-fill)
+            cxBufferReset(&prop->input);
             return CX_PROPERTIES_INCOMPLETE_DATA;
         }
     }
@@ -204,9 +128,9 @@
     char delimiter = prop->config.delimiter;
 
     // get one line and parse it
-    while (prop->text_pos < prop->text_size) {
-        const char *buf = prop->text + prop->text_pos;
-        size_t len = prop->text_size - prop->text_pos;
+    while (!cxBufferEof(current_buffer)) {
+        const char *buf = current_buffer->space + current_buffer->pos;
+        size_t len = current_buffer->size - current_buffer->pos;
 
         /*
          * First we check if we have at least one line. We also get indices of
@@ -235,10 +159,23 @@
         }
 
         if (c != '\n') {
-            // we don't have enough data for a line
-            if (cx_properties_rescue_input(prop)) {
+            // we don't have enough data for a line, use the rescue buffer
+            assert(current_buffer != &prop->buffer);
+            // make sure that the rescue buffer does not already contain something
+            assert(cxBufferEof(&prop->buffer));
+            if (prop->buffer.space == NULL) {
+                // initialize a rescue buffer, if the user did not provide one
+                cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND);
+            } else {
+                // from a previous rescue there might be already read data
+                // reset the buffer to avoid unnecessary buffer extension
+                cxBufferReset(&prop->buffer);
+            }
+            if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) {
                 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
             }
+            // reset the input buffer (make way for a re-fill)
+            cxBufferReset(&prop->input);
             return CX_PROPERTIES_INCOMPLETE_DATA;
         }
 
@@ -256,6 +193,21 @@
                 } else {
                     return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
                 }
+            } else {
+                // skip blank line
+                // if it was the rescue buffer, return to the original buffer
+                if (current_buffer == &prop->buffer) {
+                    // assert that the rescue buffer really does not contain more data
+                    assert(current_buffer->pos + i + 1 == current_buffer->size);
+                    // reset the rescue buffer, but don't destroy it!
+                    cxBufferReset(&prop->buffer);
+                    // continue with the input buffer
+                    current_buffer = &prop->input;
+                } else {
+                    // if it was the input buffer already, just advance the position
+                    current_buffer->pos += i + 1;
+                }
+                continue;
             }
         } else {
             cxstring k = cx_strn(buf, delimiter_index);
@@ -267,19 +219,21 @@
             if (k.length > 0) {
                 *key = k;
                 *value = val;
-                prop->text_pos += i + 1;
-                assert(prop->text_pos <= prop->text_size);
+                current_buffer->pos += i + 1;
+                assert(current_buffer->pos <= current_buffer->size);
                 return CX_PROPERTIES_NO_ERROR;
             } else {
                 return CX_PROPERTIES_INVALID_EMPTY_KEY;
             }
         }
-
-        prop->text_pos += i + 1;
+        // unreachable - either we returned or skipped a blank line
+        assert(false);
     }
 
     // when we come to this point, all data must have been read
-    assert(prop->text_pos == prop->text_size);
+    assert(cxBufferEof(&prop->buffer));
+    assert(cxBufferEof(&prop->input));
+
     return CX_PROPERTIES_NO_DATA;
 }
 
@@ -310,7 +264,7 @@
         CxPropertiesSource *src,
         cxstring *target
 ) {
-    if (prop->text == src->src) {
+    if (prop->input.space == src->src) {
         // when the input buffer already contains the string
         // we have nothing more to provide
         target->length = 0;
@@ -351,6 +305,7 @@
     CxPropertiesSource src;
     src.src = (void*) str.ptr;
     src.data_size = str.length;
+    src.data_ptr = NULL;
     src.read_func = cx_properties_read_string;
     src.read_init_func = NULL;
     src.read_clean_func = NULL;
@@ -361,6 +316,7 @@
     CxPropertiesSource src;
     src.src = (void*) str;
     src.data_size = len;
+    src.data_ptr = NULL;
     src.read_func = cx_properties_read_string;
     src.read_init_func = NULL;
     src.read_clean_func = NULL;
@@ -371,6 +327,7 @@
     CxPropertiesSource src;
     src.src = (void*) str;
     src.data_size = strlen(str);
+    src.data_ptr = NULL;
     src.read_func = cx_properties_read_string;
     src.read_init_func = NULL;
     src.read_clean_func = NULL;
@@ -381,6 +338,7 @@
     CxPropertiesSource src;
     src.src = file;
     src.data_size = chunk_size;
+    src.data_ptr = NULL;
     src.read_func = cx_properties_read_file;
     src.read_init_func = cx_properties_read_init_file;
     src.read_clean_func = cx_properties_read_clean_file;
@@ -420,9 +378,7 @@
         }
 
         // set the input buffer and read the k/v-pairs
-        prop->text = input.ptr;
-        prop->text_size = input.length;
-        prop->text_pos = 0;
+        cxPropertiesFill(prop, input);
 
         CxPropertiesStatus kv_status;
         do {

mercurial