all: $(APP_BIN)
-$(APP_BIN): $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
- $(CC) -o $(APP_BIN) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS)
+$(APP_BIN): $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
+ $(CC) -o $(APP_BIN) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav -ldbutils $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
../build/application/%.$(OBJ_EXT): %.c
$(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
Options:
--debug add extra compile flags for debug builds
--release add extra compile flags for release builds
- --toolkit=(libadwaita|gtk4|gtk3|cocoa)
+ --toolkit=(libadwaita|gtk4|gtk3|cocoa|motif)
__EOF__
}
__EOF__
return 0
}
+checkopt_toolkit_motif()
+{
+ VERR=0
+ if dependency_error_motif ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = motif
+__EOF__
+ return 0
+}
#
# TARGETS
ERROR=0
break
fi
+ if checkopt_toolkit_motif ; then
+ echo " toolkit: motif" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
break
done
if [ $ERROR -ne 0 ]; then
ERROR=1
DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
fi
+ elif [ "$OPT_TOOLKIT" = "motif" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_motif ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
fi
fi
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2018 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+include ../config.mk
+
+# list of source files
+SRC = dbutils.c
+SRC += class.c
+SRC += field.c
+SRC += db.c
+SRC += object.c
+SRC += sqlite.c
+
+OBJ = $(SRC:%.c=../build/dbutils/%$(OBJ_EXT))
+
+LIBDBUTILS = ../build/lib/libdbutils$(LIB_EXT)
+
+all: ../build/ucx $(LIBDBUTILS)
+
+$(LIBDBUTILS): $(OBJ)
+ $(AR) $(ARFLAGS) $(AOFLAGS)$@ $(OBJ)
+
+../build/dbutils/%$(OBJ_EXT): %.c
+ $(CC) -I../ucx $(CFLAGS) $(DBU_CFLAGS) -c -o $@ $<
+
+../build/ucx:
+ test -d '$@'
+
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "class.h"
+#include "field.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <cx/hash_map.h>
+
+static void field_destructor(DBUField *f) {
+ if(f->destructor) {
+ f->destructor(f);
+ }
+ free(f);
+}
+
+DBUClass* dbuClassCreate(const char *name) {
+ DBUClass *cls = malloc(sizeof(DBUClass));
+ memset(cls, 0, sizeof(DBUClass));
+
+ cls->name = cx_strdup(cx_str(name));
+ cls->fields = cxHashMapCreateSimple(CX_STORE_POINTERS);
+ cls->fields->collection.simple_destructor = (cx_destructor_func)field_destructor;
+ cls->obj_fields = cxHashMapCreateSimple(CX_STORE_POINTERS);
+ cls->obj_fields->collection.simple_destructor = (cx_destructor_func)field_destructor;
+ cls->foreign_keys = cxHashMapCreateSimple(sizeof(DBUForeignKeyField));
+
+ return cls;
+}
+
+void dbuClassFree(DBUClass *cls) {
+ cxMapFree(cls->fields);
+ free(cls->name.ptr);
+ free(cls->primary_key_column.ptr);
+ free(cls);
+}
+
+void dbuClassAddField(DBUClass *cls, const char *name, DBUField *field) {
+ free(field->name.ptr);
+ field->name = cx_strdup(cx_str(name));
+ cxMapPut(cls->fields, name, field);
+}
+
+void dbuClassAddFKField(DBUClass *cls, const char *name, DBUField *field, DBUClass *fkcls) {
+ DBUForeignKeyField val;
+ val.field = field;
+ val.cls = fkcls;
+ field->foreignKeyClass = fkcls;
+ cxMapPut(cls->foreign_keys, name, &val);
+ dbuClassAddField(cls, name, field);
+}
+
+void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls) {
+ free(field->name.ptr);
+ field->name = name ? cx_strdup(cx_str(name)) : (cxmutstr){NULL,0};
+ cxMapPut(cls->obj_fields, foreign_cls->name, field);
+}
+
+
+void dbuClassSetPrimaryKeyInt32(DBUClass *cls, const char *column_name, off_t offset) {
+ cls->primary_key_column = cx_strdup(cx_str(column_name));
+ cls->primary_key = dbuFieldCreateInt32(offset);
+ dbuClassAddField(cls, column_name, cls->primary_key);
+}
+
+void dbuClassSetPrimaryKeyUInt32(DBUClass *cls, const char *column_name, off_t offset) {
+ cls->primary_key_column = cx_strdup(cx_str(column_name));
+ cls->primary_key = dbuFieldCreateUInt32(offset);
+ dbuClassAddField(cls, column_name, cls->primary_key);
+}
+
+void dbuClassSetPrimaryKeyInt64(DBUClass *cls, const char *column_name, off_t offset) {
+ cls->primary_key_column = cx_strdup(cx_str(column_name));
+ cls->primary_key = dbuFieldCreateInt64(offset);
+ dbuClassAddField(cls, column_name, cls->primary_key);
+}
+
+void dbuClassSetPrimaryKeyUInt64(DBUClass *cls, const char *column_name, off_t offset) {
+ cls->primary_key_column = cx_strdup(cx_str(column_name));
+ cls->primary_key = dbuFieldCreateInt64(offset);
+ dbuClassAddField(cls, column_name, cls->primary_key);
+}
+
+void dbuClassSetPrimaryKeyString(DBUClass *cls, const char *column_name, off_t offset) {
+ cls->primary_key_column = cx_strdup(cx_str(column_name));
+ cls->primary_key = dbuFieldCreateString(offset);
+ dbuClassAddField(cls, column_name, cls->primary_key);
+}
+
+void dbuClassSetPrimaryKeyCxMutStr(DBUClass *cls, const char *column_name, off_t offset) {
+ cls->primary_key_column = cx_strdup(cx_str(column_name));
+ cls->primary_key = dbuFieldCreateCxMutStr(offset);
+ dbuClassAddField(cls, column_name, cls->primary_key);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DBU_CLASS_H
+#define DBU_CLASS_H
+
+#include "dbutils/dbutils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void dbuClassFree(DBUClass *cls);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DBU_CLASS_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "db.h"
+#include "dbutils/db.h"
+
+int dbuQuerySetSQL(DBUQuery *q, const char *sql) {
+ return q->setSQL(q, sql);
+}
+
+int dbuQuerySetParamString(DBUQuery *q, int index, cxstring str) {
+ return q->setParamString(q, index, str);
+}
+
+int dbuQuerySetParamInt(DBUQuery *q, int index, int i) {
+ return q->setParamInt(q, index, i);
+}
+
+int dbuQuerySetParamInt64(DBUQuery *q, int index, int64_t i) {
+ return q->setParamInt64(q, index, i);
+}
+
+int dbuQuerySetParamDouble(DBUQuery *q, int index, double d) {
+ return q->setParamDouble(q, index, d);
+}
+
+int dbuQuerySetParamNull(DBUQuery *q, int index) {
+ return q->setParamNull(q, index);
+}
+
+int dbuQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len) {
+ return q->setParamBytes(q, index, bytes, len);
+}
+
+int dbuQueryExec(DBUQuery *q) {
+ return q->exec(q);
+}
+
+DBUResult* dbuQueryGetResult(DBUQuery *q) {
+ return q->getResult(q);
+}
+
+void dbuQueryFree(DBUQuery *q) {
+ q->free(q);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DBU_DB_H
+#define DBU_DB_H
+
+#include "dbutils/dbutils.h"
+#include "dbutils/db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DBU_DB_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dbutils/dbutils.h"
+#include "class.h"
+#include "field.h"
+
+#include <cx/hash_map.h>
+
+DBUContext* dbuContextCreate(void) {
+ DBUContext *ctx = malloc(sizeof(DBUContext));
+ ctx->classes = cxHashMapCreateSimple(CX_STORE_POINTERS);
+ ctx->classes->collection.simple_destructor = (cx_destructor_func)dbuClassFree;
+ return ctx;
+}
+
+void dbuContextFree(DBUContext *context) {
+ cxMapFree(context->classes);
+ free(context);
+}
+
+
+DBUClass* dbuRegisterClassWithPrimaryKeyInt32(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset)
+{
+ DBUClass *cls = dbuClassCreate(name);
+ cls->context = context;
+ cls->obj_size = obj_size;
+ dbuClassSetPrimaryKeyInt32(cls, primary_key_column, primary_key_offset);
+ cxMapPut(context->classes, name, cls);
+ return cls;
+}
+
+DBUClass* dbuRegisterClassWithPrimaryKeyUInt32(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset)
+{
+ DBUClass *cls = dbuClassCreate(name);
+ cls->context = context;
+ cls->obj_size = obj_size;
+ dbuClassSetPrimaryKeyUInt32(cls, primary_key_column, primary_key_offset);
+ cxMapPut(context->classes, name, cls);
+ return cls;
+}
+
+DBUClass* dbuRegisterClassWithPrimaryKeyInt64(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset)
+{
+ DBUClass *cls = dbuClassCreate(name);
+ cls->context = context;
+ cls->obj_size = obj_size;
+ dbuClassSetPrimaryKeyInt64(cls, primary_key_column, primary_key_offset);
+ cxMapPut(context->classes, name, cls);
+ return cls;
+}
+
+DBUClass* dbuRegisterClassWithPrimaryKeyUInt64(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset)
+{
+ DBUClass *cls = dbuClassCreate(name);
+ cls->context = context;
+ cls->obj_size = obj_size;
+ dbuClassSetPrimaryKeyUInt64(cls, primary_key_column, primary_key_offset);
+ cxMapPut(context->classes, name, cls);
+ return cls;
+}
+
+DBUClass* dbuRegisterClassWithPrimaryKeyString(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset)
+{
+ DBUClass *cls = dbuClassCreate(name);
+ cls->context = context;
+ cls->obj_size = obj_size;
+ dbuClassSetPrimaryKeyString(cls, primary_key_column, primary_key_offset);
+ cxMapPut(context->classes, name, cls);
+ return cls;
+}
+
+DBUClass* dbuRegisterClassWithPrimaryKeyCxMutStr(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset)
+{
+ DBUClass *cls = dbuClassCreate(name);
+ cls->context = context;
+ cls->obj_size = obj_size;
+ dbuClassSetPrimaryKeyCxMutStr(cls, primary_key_column, primary_key_offset);
+ cxMapPut(context->classes, name, cls);
+ return cls;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBDBU_DB_H
+#define LIBDBU_DB_H
+
+#include "dbutils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DBUConnection DBUConnection;
+typedef struct DBUQuery DBUQuery;
+typedef struct DBUResult DBUResult;
+
+typedef enum DBUFieldType DBUFieldType;
+
+enum DBUFieldType {
+ DBU_FIELD_NULL = 0,
+ DBU_FIELD_TEXT,
+ DBU_FIELD_INT,
+ DBU_FIELD_DOUBLE,
+ DBU_FIELD_BINARY
+};
+
+typedef struct DBUBytes {
+ const unsigned char *bytes;
+ size_t length;
+} DBUBytes;
+
+struct DBUConnection {
+ DBUQuery* (*createQuery)(DBUConnection *connection, const CxAllocator *a);
+ int (*isActive)(DBUConnection *connection);
+ void (*free)(DBUConnection *connection);
+ void *data;
+};
+
+struct DBUQuery {
+ int (*setSQL)(DBUQuery *q, const char *sql);
+ int (*setParamString)(DBUQuery *q, int index, cxstring str);
+ int (*setParamInt)(DBUQuery *q, int index, int i);
+ int (*setParamInt64)(DBUQuery *q, int index, int64_t i);
+ int (*setParamDouble)(DBUQuery *q, int index, double d);
+ int (*setParamNull)(DBUQuery *q, int index);
+ int (*setParamBytes)(DBUQuery *q, int index, void *bytes, int len);
+ int (*exec)(DBUQuery *q);
+ DBUResult* (*getResult)(DBUQuery *q);
+ int (*reset)(DBUQuery *q);
+ void (*free)(DBUQuery *q);
+ DBUConnection *connection;
+ const CxAllocator *allocator;
+ int ref;
+};
+
+struct DBUResult {
+ int (*optional_numRows)(DBUResult *result);
+ int (*numFields)(DBUResult *result);
+ int (*hasData)(DBUResult *result);
+ int (*isOk)(DBUResult *result);
+ int (*nextRow)(DBUResult *result);
+ const char* (*fieldName)(DBUResult *result, int field);
+ DBUFieldType (*fieldType)(DBUResult *result, int field);
+ int (*isNull)(DBUResult *result, int field);
+ cxstring (*getText)(DBUResult *result, int field);
+ int64_t (*optional_getInt)(DBUResult *result, int field);
+ double (*optional_getDouble)(DBUResult *result, int field);
+ DBUBytes (*optional_getBinary)(DBUResult *result, int field);
+ void (*free)(DBUResult *result);
+ const CxAllocator *allocator;
+ int rowIndex;
+};
+
+
+int dbuQuerySetSQL(DBUQuery *q, const char *sql);
+int dbuQuerySetParamString(DBUQuery *q, int index, cxstring str);
+int dbuQuerySetParamInt(DBUQuery *q, int index, int i);
+int dbuQuerySetParamInt64(DBUQuery *q, int index, int64_t i);
+int dbuQuerySetParamDouble(DBUQuery *q, int index, double d);
+int dbuQuerySetParamNull(DBUQuery *q, int index);
+int dbuQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len);
+int dbuQueryExec(DBUQuery *q);
+DBUResult* dbuQueryGetResult(DBUQuery *q);
+void dbuQueryFree(DBUQuery *q);
+
+
+
+DBUObjectBuilder* dbuObjectBuilder(DBUClass *type, DBUQuery *query, const CxAllocator *a);
+void dbuObjectBuilderSetDenseResult(DBUObjectBuilder *builder, bool dense);
+
+// TODO: add variants for specifying the field or field name
+int dbuObjectBuilderAddAdditionalQuery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *query);
+CxList* dbuObjectBuilderGetList(DBUObjectBuilder *builder);
+CxList* dbuObjectBuilderGetValueList(DBUObjectBuilder *builder);
+int dbuObjectBuilderGetArray(DBUObjectBuilder *builder, void **array, size_t *size);
+void dbuObjectBuilderDestroy(DBUObjectBuilder *builder);
+
+// TODO: implement
+int dbuObjectBuilderAddSubquery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *subquery);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBDBU_DB_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIB_DBU_H
+#define LIB_DBU_H
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include <cx/allocator.h>
+#include <cx/string.h>
+#include <cx/list.h>
+#include <cx/map.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DBUContext DBUContext;
+typedef struct DBUClass DBUClass;
+typedef struct DBUField DBUField;
+
+typedef struct DBUObjectBuilder DBUObjectBuilder;
+typedef struct DBUForeignKeyField DBUForeignKeyField;
+
+typedef char* DBUObject;
+
+typedef int(*DBUFieldDefInitFunc)(DBUField *f, const CxAllocator *a, DBUObject obj);
+typedef int(*DBUFieldInitFunc)(DBUField *f, const CxAllocator *a, DBUObject obj, const char *value, size_t length);
+
+struct DBUContext {
+ /*
+ * key: class name
+ * value: DBUClass*
+ */
+ CxMap *classes;
+};
+
+struct DBUClass {
+ /*
+ * context, that contains this class
+ */
+ DBUContext *context;
+
+ /*
+ * class/table name
+ */
+ cxmutstr name;
+
+ /*
+ * primary key column name
+ */
+ cxmutstr primary_key_column;
+
+ /*
+ * primary key struct member initializer
+ */
+ DBUField *primary_key;
+
+ /*
+ * primitive fields
+ *
+ * key: field name
+ * value: DBUField*
+ */
+ CxMap *fields;
+
+ /*
+ * object fields
+ *
+ * key: type name
+ * value: DBUField*
+ */
+ CxMap *obj_fields;
+
+ /*
+ * foreign keys
+ *
+ * key: field name
+ * value: DBUForeignKeyField
+ */
+ CxMap *foreign_keys;
+
+ /*
+ * object size used for allocation, typically sizeof(some_struct)
+ */
+ size_t obj_size;
+
+ /*
+ * optional initializer function
+ *
+ */
+ int (*init)(void *obj, const CxAllocator *a);
+};
+
+typedef struct DBUObjectResult DBUObjectResult;
+struct DBUObjectResult {
+ void *userdata1;
+ void *userdata2;
+ int int1;
+ int int2;
+ DBUObject (*create)(DBUObjectResult *result, DBUClass *type, const CxAllocator *a);
+ int (*add)(DBUObjectResult *result, DBUObject parent, DBUClass *type, void *obj, CxList *fk, const CxAllocator *a);
+ void (*free)(DBUObjectResult *result);
+};
+
+struct DBUForeignKeyField {
+ DBUField *field;
+ DBUClass *cls;
+};
+
+/*
+ * abstract field
+ */
+struct DBUField {
+ /*
+ * field name
+ */
+ cxmutstr name;
+
+ /*
+ * called, if the field is null (optional)
+ */
+ int (*initDefaultValue)(DBUField *f, const CxAllocator *a, DBUObject obj);
+
+ /*
+ * called, if a field was not requested in a query (optional)
+ */
+ int (*initExcluded)(DBUField *f, const CxAllocator *a, DBUObject obj);
+
+ /*
+ * init primitve type (required)
+ *
+ * This must be implemented
+ */
+ int (*initValue)(DBUField *f, const CxAllocator *a, DBUObject obj, const char *value, size_t length);
+
+ /*
+ * integer initialization function (optional)
+ *
+ * if a result columns contains an integer and this pointer is
+ * non-null, this function is called
+ */
+ int (*initIntValue)(DBUField *f, const CxAllocator *a, DBUObject obj, int64_t value);
+
+ /*
+ * double initialization function (optional)
+ */
+ int (*initDoubleValue)(DBUField *f, const CxAllocator *a, DBUObject obj, double value);
+
+ /*
+ * set/add an child to obj
+ */
+ int (*initObjValue)(DBUField *f, const CxAllocator *a, DBUObject obj, void *child);
+
+ /*
+ * destructor (optional)
+ *
+ * this callback must not free the DBUField* ptr
+ */
+ void (*destructor)(DBUField *f);
+
+ /*
+ * object result builder callback struct (optional for list/array types)
+ *
+ * DBUObjectResult contains callback for adding objects to
+ * list/array-fields
+ */
+ DBUObjectResult builder;
+
+ DBUClass *foreignKeyClass;
+
+ /*
+ * if true, null values are not accepted and result in an error
+ */
+ bool nonnull;
+ bool query_length; // TODO: remove
+};
+
+DBUContext* dbuContextCreate(void);
+void dbuContextFree(DBUContext *context);
+void dbuContextAddClass(DBUContext *context, DBUClass *cls);
+
+/*
+ * incomplete constructor for a DBUClass
+ *
+ * To complete the class definition, either obj_size or constructor must be set.
+ */
+DBUClass* dbuClassCreate(const char *name);
+
+
+
+#define dbuRegisterClass(context, name, type, primarykey) \
+ dbuRegisterClassWithPKName(context, name, type, primarykey, #primarykey)
+#define dbuRegisterClassWithPKName(context, name, type, primarykey, column) \
+ _Generic(((type*)0)->primarykey, \
+ int32_t: dbuRegisterClassWithPrimaryKeyInt32, \
+ uint32_t: dbuRegisterClassWithPrimaryKeyUInt32, \
+ int64_t: dbuRegisterClassWithPrimaryKeyInt64, \
+ uint64_t: dbuRegisterClassWithPrimaryKeyUInt64, \
+ char*: dbuRegisterClassWithPrimaryKeyString, \
+ cxmutstr: dbuRegisterClassWithPrimaryKeyCxMutStr) \
+ (context, name, sizeof(type), column, offsetof(type, primarykey))
+
+DBUClass* dbuRegisterClassWithPrimaryKeyInt32(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset);
+DBUClass* dbuRegisterClassWithPrimaryKeyUInt32(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset);
+DBUClass* dbuRegisterClassWithPrimaryKeyInt64(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset);
+DBUClass* dbuRegisterClassWithPrimaryKeyUInt64(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset);
+DBUClass* dbuRegisterClassWithPrimaryKeyString(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset);
+DBUClass* dbuRegisterClassWithPrimaryKeyCxMutStr(
+ DBUContext *context,
+ const char *name,
+ size_t obj_size,
+ const char *primary_key_column,
+ off_t primary_key_offset);
+
+void dbuClassSetPrimaryKeyInt32(DBUClass *cls, const char *column_name, off_t offset);
+void dbuClassSetPrimaryKeyUInt32(DBUClass *cls, const char *column_name, off_t offset);
+void dbuClassSetPrimaryKeyInt64(DBUClass *cls, const char *column_name, off_t offset);
+void dbuClassSetPrimaryKeyUInt64(DBUClass *cls, const char *column_name, off_t offset);
+void dbuClassSetPrimaryKeyString(DBUClass *cls, const char *column_name, off_t offset);
+void dbuClassSetPrimaryKeyCxMutStr(DBUClass *cls, const char *column_name, off_t offset);
+
+void dbuClassAddField(DBUClass *cls, const char *name, DBUField *field);
+void dbuClassAddFKField(DBUClass *cls, const char *name, DBUField *field, DBUClass *fkcls);
+void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls);
+
+#define dbuClassAdd(cls, type, member) \
+ dbuClassAddWithName(cls, type, member, #member)
+#define dbuClassAddWithName(cls, type, member, member_name) \
+ _Generic(((type*)0)->member, \
+ int8_t: dbuClassAddInt8, \
+ uint8_t: dbuClassAddUInt8, \
+ int16_t: dbuClassAddInt16, \
+ uint16_t: dbuClassAddUInt16, \
+ int32_t: dbuClassAddInt32, \
+ uint32_t: dbuClassAddUInt32, \
+ int64_t: dbuClassAddInt64, \
+ uint64_t: dbuClassAddUInt64, \
+ bool: dbuClassAddBool, \
+ float: dbuClassAddFloat, \
+ double: dbuClassAddDouble, \
+ char*: dbuClassAddString, \
+ cxmutstr: dbuClassAddCXMutStr) \
+ (cls, member_name, offsetof(type, member), 0)
+
+#define dbuClassAddForeignKey(cls, type, member, foreigncls) \
+ _Generic(((type*)0)->member, \
+ int32_t: dbuClassAddInt32FK, \
+ uint32_t: dbuClassAddUInt32FK, \
+ int64_t: dbuClassAddInt64FK, \
+ uint64_t: dbuClassAddUInt64FK, \
+ char*: dbuClassAddStringFK, \
+ cxmutstr: dbuClassAddCXMutStrFK) \
+ (cls, #member, offsetof(type, member), foreigncls, 0)
+
+void dbuClassAddInt(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddUInt(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddInt8(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddUInt8(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddInt16(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddUInt16(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddInt32(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddUInt32(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddInt64(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddUInt64(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddSize(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddSSize(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddBool(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddFloat(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddDouble(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddString(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddCXMutStr(DBUClass *cls, const char *name, off_t offset, bool nonnull);
+void dbuClassAddStringSize(DBUClass *cls, const char *name, off_t offset, off_t size_offset, bool nonnull);
+void dbuClassAddStringIntLen(DBUClass *cls, const char *name, off_t offset, off_t int_offset, bool nonnull);
+void dbuClassAddBuf(DBUClass *cls, const char *name, off_t offset, off_t size_offset, bool nonnull);
+void dbuClassAddBufIntLen(DBUClass *cls, const char *name, off_t offset, off_t int_offset, bool nonnull);
+
+void dbuClassAddInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddUInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddUInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddStringFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddCXMutStrFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+
+void dbuClassAddIntDef(DBUClass *cls, const char *name, off_t offset, int def);
+void dbuClassAddUIntDef(DBUClass *cls, const char *name, off_t offset, unsigned int def);
+void dbuClassAddInt16Def(DBUClass *cls, const char *name, off_t offset, int16_t def);
+void dbuClassAddUInt16Def(DBUClass *cls, const char *name, off_t offset, uint16_t def);
+void dbuClassAddInt32Def(DBUClass *cls, const char *name, off_t offset, int32_t def);
+void dbuClassAddUInt32Def(DBUClass *cls, const char *name, off_t offset, uint32_t def);
+void dbuClassAddInt64Def(DBUClass *cls, const char *name, off_t offset, int64_t def);
+void dbuClassAddUInt64Def(DBUClass *cls, const char *name, off_t offset, uint64_t def);
+void dbuClassAddSizeDef(DBUClass *cls, const char *name, off_t offset, size_t def);
+void dbuClassAddSSizeDef(DBUClass *cls, const char *name, off_t offset, ssize_t def);
+void dbuClassAddFloatDef(DBUClass *cls, const char *name, off_t offset, float def);
+void dbuClassAddDoubleDef(DBUClass *cls, const char *name, off_t offset, double def);
+
+void dbuClassAddObj(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
+void dbuClassAddCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
+void dbuClassAddValueCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
+void dbuClassAddCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_clst);
+void dbuClassAddValueCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
+void dbuClassAddArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls);
+void dbuClassAddValueArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIB_DBU_H */
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBDBU_SQLITE_H
+#define LIBDBU_SQLITE_H
+
+#include "dbutils.h"
+#include "db.h"
+#include <sqlite3.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DBUConnection* dbuSQLiteConnection(const char *filename);
+DBUConnection* dbuSQLiteConnectionFromDB(sqlite3 *db, bool autoclose);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBDBU_SQLITE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the followign disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+#include "field.h"
+
+/* -------------------- Default Initializer Functions -------------------- */
+
+static int field_def_init_int(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(int*)(obj+f->offset) = (int)f->def.def;
+ return 0;
+}
+
+static int field_def_init_uint(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(unsigned int*)(obj+f->offset) = (unsigned int)f->def.udef;
+ return 0;
+}
+
+static int field_def_init_int16(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(int16_t*)(obj+f->offset) = (int16_t)f->def.def;
+ return 0;
+}
+
+static int field_def_init_uint16(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(uint16_t*)(obj+f->offset) = (uint16_t)f->def.udef;
+ return 0;
+}
+
+static int field_def_init_int32(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(int32_t*)(obj+f->offset) = (int32_t)f->def.def;
+ return 0;
+}
+
+static int field_def_init_uint32(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(uint32_t*)(obj+f->offset) = (int32_t)f->def.udef;
+ return 0;
+}
+
+static int field_def_init_int64(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(int64_t*)(obj+f->offset) = f->def.def;
+ return 0;
+}
+
+static int field_def_init_uint64(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(uint64_t*)(obj+f->offset) = (uint64_t)f->def.udef;
+ return 0;
+}
+
+static int field_def_init_size(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(size_t*)(obj+f->offset) = (size_t)f->def.udef;
+ return 0;
+}
+
+static int field_def_init_ssize(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(ssize_t*)(obj+f->offset) = (ssize_t)f->def.def;
+ return 0;
+}
+
+static int field_def_init_float(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(float*)(obj+f->offset) = (float)f->def.fdef;
+ return 0;
+}
+
+static int field_def_init_double(DBUOffsetField *f, const CxAllocator *a, DBUObject obj) {
+ *(double*)(obj+f->offset) = (double)f->def.ddef;
+ return 0;
+}
+
+
+/* -------------------- Initializer Functions -------------------- */
+
+static int str2int(const char *str, int64_t *i) {
+ if(!str || *str == 0) {
+ return 0;
+ }
+
+ char *endptr;
+ long long v = strtoll(str, &endptr, 10);
+ *i = v;
+ return *endptr == 0;
+}
+
+static int str2uint(const char *str, uint64_t *u) {
+ if(!str || *str == 0) {
+ return 0;
+ }
+
+ char *endptr;
+ unsigned long long v = strtoull(str, &endptr, 10);
+ *u = v;
+ return *endptr == 0;
+}
+
+static int field_init_int(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ int64_t i;
+ if(!str2int(value, &i)) {
+ return 1;
+ }
+ if(i < INT_MIN || i > INT_MAX) {
+ return 1;
+ }
+ *(int*)(obj+f->offset) = (int)i;
+ return 0;
+}
+
+static int field_init_uint(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ uint64_t i;
+ if(!str2uint(value, &i)) {
+ return 1;
+ }
+ if(i > UINT_MAX) {
+ return 1;
+ }
+ *(unsigned int*)(obj+f->offset) = (unsigned int)i;
+ return 0;
+}
+
+static int field_init_int8(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ int64_t i;
+ if(!str2int(value, &i)) {
+ return 1;
+ }
+ if(i < INT8_MIN || i > INT8_MAX) {
+ return 1;
+ }
+ *(int8_t*)(obj+f->offset) = (int8_t)i;
+ return 0;
+}
+
+static int field_init_uint8(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ uint64_t i;
+ if(!str2uint(value, &i)) {
+ return 1;
+ }
+ if(i > UINT8_MAX) {
+ return 1;
+ }
+ *(uint8_t*)(obj+f->offset) = (uint8_t)i;
+ return 0;
+}
+
+static int field_init_int16(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ int64_t i;
+ if(!str2int(value, &i)) {
+ return 1;
+ }
+ if(i < INT16_MIN || i > INT16_MAX) {
+ return 1;
+ }
+ *(int16_t*)(obj+f->offset) = (int16_t)i;
+ return 0;
+}
+
+static int field_init_uint16(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ uint64_t i;
+ if(!str2uint(value, &i)) {
+ return 1;
+ }
+ if(i > UINT16_MAX) {
+ return 1;
+ }
+ *(uint16_t*)(obj+f->offset) = (uint16_t)i;
+ return 0;
+}
+
+static int field_init_int32(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ int64_t i;
+ if(!str2int(value, &i)) {
+ return 1;
+ }
+ if(i < INT32_MIN || i > INT32_MAX) {
+ return 1;
+ }
+ *(int32_t*)(obj+f->offset) = (int32_t)i;
+ return 0;
+}
+
+static int field_init_uint32(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ uint64_t i;
+ if(!str2uint(value, &i)) {
+ return 1;
+ }
+ if(i > UINT32_MAX) {
+ return 1;
+ }
+ *(uint32_t*)(obj+f->offset) = (uint32_t)i;
+ return 0;
+}
+
+static int field_init_int64(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ int64_t i;
+ if(!str2int(value, &i)) {
+ return 1;
+ }
+ *(int64_t*)(obj+f->offset) = i;
+ return 0;
+}
+
+static int field_init_uint64(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ uint64_t i;
+ if(!str2uint(value, &i)) {
+ return 1;
+ }
+ *(uint64_t*)(obj+f->offset) = i;
+ return 0;
+}
+
+static int field_init_size(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ uint64_t i;
+ if(!str2uint(value, &i)) {
+ return 1;
+ }
+ *(uint64_t*)(obj+f->offset) = i;
+ return 0;
+}
+
+static int field_init_ssize(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ int64_t i;
+ if(!str2int(value, &i)) {
+ return 1;
+ }
+ *(ssize_t*)(obj+f->offset) = (ssize_t)i;
+ return 0;
+}
+
+static int field_init_bool(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ bool boolvalue = 0;
+ if(value) {
+ char c = value[0];
+ if(c == 't' || c == 'T' || c == '1') {
+ boolvalue = 1;
+ }
+ }
+ *(bool*)(obj+f->offset) = boolvalue;
+ return 0;
+}
+
+static int field_init_float(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ char *endptr;
+ float v = strtof(value, &endptr);
+ if(!endptr || *endptr != 0) {
+ return 1;
+ }
+ *(float*)(obj+f->offset) = v;
+ return 0;
+}
+
+static int field_init_double(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ char *endptr;
+ double v = strtod(value, &endptr);
+ if(!endptr || *endptr != 0) {
+ return 1;
+ }
+ *(double*)(obj+f->offset) = v;
+ return 0;
+}
+
+
+static int field_init_str(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ cxmutstr m = cx_strdup_a(a, cx_strn(value, length));
+ if(!m.ptr) {
+ return 1;
+ }
+ *(char**)(obj+f->offset) = m.ptr;
+ return 0;
+}
+
+static int field_init_cxmutstr(
+ DBUOffsetField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ cxmutstr m = cx_strdup_a(a, cx_strn(value, length));
+ if(!m.ptr) {
+ return 1;
+ }
+ *(cxmutstr*)(obj+f->offset) = m;
+ return 0;
+}
+
+static int field_init_str_size(
+ DBUObjLenField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ cxmutstr m = cx_strdup_a(a, cx_strn(value, length));
+ if(!m.ptr) {
+ return 1;
+ }
+ *(char**)(obj+f->offset_obj) = m.ptr;
+ *(size_t*)(obj+f->offset_len) = m.length;
+ return 0;
+}
+
+static int field_init_str_intlen(
+ DBUObjLenField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ cxmutstr m = cx_strdup_a(a, cx_strn(value, length));
+ if(!m.ptr) {
+ return 1;
+ }
+ *(char**)(obj+f->offset_obj) = m.ptr;
+ *(int*)(obj+f->offset_len) = (int)m.length;
+ return 0;
+}
+
+static int field_init_bytes_size(
+ DBUObjLenField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ char *bytes = cxMalloc(a, length);
+ if(!bytes) {
+ return 1;
+ }
+ memcpy(bytes, value, length);
+ *(char**)(obj+f->offset_obj) = bytes;
+ *(size_t*)(obj+f->offset_len) = length;
+ return 0;
+}
+
+static int field_init_bytes_intlen(
+ DBUObjLenField *f,
+ const CxAllocator *a,
+ DBUObject obj,
+ const char *value,
+ size_t length)
+{
+ char *bytes = cxMalloc(a, length);
+ if(!bytes) {
+ return 1;
+ }
+ memcpy(bytes, value, length);
+ *(char**)(obj+f->offset_obj) = bytes;
+ *(int*)(obj+f->offset_len) = (int)length;
+ return 0;
+}
+
+
+/* -------------------- FIELD CONSTRUCTOR -------------------- */
+
+static DBUField* create_offset_def_field(
+ off_t offset,
+ DBUFieldDefInitFunc def_init,
+ DBUFieldInitFunc init,
+ bool nonnull,
+ union DBUDefValue def)
+{
+ DBUOffsetField *field = calloc(1, sizeof(DBUOffsetField));
+ field->field.initDefaultValue = def_init;
+ field->field.initValue = init;
+ field->field.nonnull = nonnull;
+ field->field.query_length = false;
+ field->offset = offset;
+ field->def = def;
+ return (DBUField*)field;
+}
+
+static void add_offset_def_field(
+ DBUClass *cls,
+ const char *name,
+ off_t offset,
+ DBUFieldDefInitFunc def_init,
+ DBUFieldInitFunc init,
+ bool nonnull,
+ union DBUDefValue def)
+{
+ dbuClassAddField(cls, name, create_offset_def_field(offset, def_init, init, nonnull, def));
+}
+
+static void add_offset_fk_field(
+ DBUClass *cls,
+ const char *name,
+ off_t offset,
+ DBUClass *fkcls,
+ DBUFieldInitFunc init,
+ bool nonnull)
+{
+ dbuClassAddFKField(cls, name, create_offset_def_field(offset, NULL, init, nonnull, (union DBUDefValue){ 0 }), fkcls);
+}
+
+static void add_bool(
+ DBUClass *cls,
+ const char *name,
+ off_t offset,
+ bool nonnull)
+{
+ DBUOffsetField *field = calloc(1, sizeof(DBUOffsetField));
+ field->field.initDefaultValue = NULL;
+ field->field.initValue = (DBUFieldInitFunc)field_init_bool;
+ field->field.nonnull = nonnull;
+ field->field.query_length = false;
+ field->offset = offset;
+ field->def.def = 0;
+ dbuClassAddField(cls, name, (DBUField*)field);
+}
+
+static DBUField* create_offset_str_field(
+ DBUFieldInitFunc init,
+ off_t offset,
+ bool nonnull)
+{
+ DBUOffsetField *field = calloc(1, sizeof(DBUOffsetField));
+ field->field.initDefaultValue = NULL;
+ field->field.initValue = (DBUFieldInitFunc)init;
+ field->field.nonnull = nonnull;
+ field->field.query_length = true;
+ field->offset = offset;
+ field->def.def = 0;
+ return (DBUField*)field;
+}
+
+static void add_offset_str_field(
+ DBUClass *cls,
+ const char *name,
+ DBUFieldInitFunc init,
+ off_t offset,
+ bool nonnull)
+{
+ DBUOffsetField *field = calloc(1, sizeof(DBUOffsetField));
+ field->field.initDefaultValue = NULL;
+ field->field.initValue = (DBUFieldInitFunc)init;
+ field->field.nonnull = nonnull;
+ field->field.query_length = true;
+ field->offset = offset;
+ field->def.def = 0;
+ dbuClassAddField(cls, name, create_offset_str_field(init, offset, nonnull));
+}
+
+static void add_objlen_field(
+ DBUClass *cls,
+ const char *name,
+ DBUFieldInitFunc init,
+ off_t offset,
+ off_t size_offset,
+ bool nonnull)
+{
+ DBUObjLenField *field = malloc(sizeof(DBUObjLenField));
+ memset(field, 0, sizeof(DBUObjLenField));
+ field->field.initDefaultValue = NULL;
+ field->field.initValue = (DBUFieldInitFunc)init;
+ field->field.nonnull = nonnull;
+ field->field.query_length = true;
+ field->offset_obj = offset;
+ field->offset_len = size_offset;
+ dbuClassAddField(cls, name, (DBUField*)field);
+}
+
+
+
+
+DBUField* dbuFieldCreateInt32(off_t offset) {
+ return create_offset_def_field(offset, NULL, (DBUFieldInitFunc)field_init_int32, false, (union DBUDefValue){ 0 });
+}
+
+DBUField* dbuFieldCreateUInt32(off_t offset) {
+ return create_offset_def_field(offset, NULL, (DBUFieldInitFunc)field_init_uint32, false, (union DBUDefValue){ 0 });
+}
+
+DBUField* dbuFieldCreateInt64(off_t offset) {
+ return create_offset_def_field(offset, NULL, (DBUFieldInitFunc)field_init_int64, false, (union DBUDefValue){ 0 });
+}
+
+DBUField* dbuFieldCreateUInt64(off_t offset) {
+ return create_offset_def_field(offset, NULL, (DBUFieldInitFunc)field_init_uint64, false, (union DBUDefValue){ 0 });
+}
+
+DBUField* dbuFieldCreateString(off_t offset) {
+ return create_offset_str_field((DBUFieldInitFunc)field_init_str, offset, false);
+}
+
+DBUField* dbuFieldCreateCxMutStr(off_t offset) {
+ return create_offset_str_field((DBUFieldInitFunc)field_init_cxmutstr, offset, false);
+}
+
+/* -------------------- PUBLIC -------------------- */
+
+
+void dbuClassAddInt(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_int,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_uint,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddInt8(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_int8,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt8(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_uint8,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddInt16(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_int16,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt16(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_uint16,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddInt32(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_int32,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt32(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_uint32,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddInt64(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_int64,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt64(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_uint64,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddSize(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_size,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddSSize(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_ssize,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddBool(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_bool(
+ cls,
+ name,
+ offset,
+ nonnull);
+}
+
+void dbuClassAddFloat(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_float,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddDouble(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ NULL,
+ (DBUFieldInitFunc)field_init_double,
+ nonnull,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddString(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_str_field(
+ cls,
+ name,
+ (DBUFieldInitFunc)field_init_str,
+ offset,
+ nonnull);
+}
+
+void dbuClassAddCXMutStr(DBUClass *cls, const char *name, off_t offset, bool nonnull) {
+ add_offset_str_field(
+ cls,
+ name,
+ (DBUFieldInitFunc)field_init_cxmutstr,
+ offset,
+ nonnull);
+}
+
+void dbuClassAddStringSize(DBUClass *cls, const char *name, off_t offset, off_t size_offset, bool nonnull) {
+ add_objlen_field(
+ cls,
+ name,
+ (DBUFieldInitFunc)field_init_str_size,
+ offset,
+ size_offset,
+ nonnull);
+}
+
+void dbuClassAddStringIntLen(DBUClass *cls, const char *name, off_t offset, off_t int_offset, bool nonnull) {
+ add_objlen_field(
+ cls,
+ name,
+ (DBUFieldInitFunc)field_init_str_intlen,
+ offset,
+ int_offset,
+ nonnull);
+}
+
+void dbuClassAddBuf(DBUClass *cls, const char *name, off_t offset, off_t size_offset, bool nonnull) {
+ add_objlen_field(
+ cls,
+ name,
+ (DBUFieldInitFunc)field_init_bytes_size,
+ offset,
+ size_offset,
+ nonnull);
+}
+
+void dbuClassAddBufIntLen(DBUClass *cls, const char *name, off_t offset, off_t int_offset, bool nonnull) {
+ add_objlen_field(
+ cls,
+ name,
+ (DBUFieldInitFunc)field_init_bytes_intlen,
+ offset,
+ int_offset,
+ nonnull);
+}
+
+
+void dbuClassAddInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull) {
+ add_offset_fk_field(
+ cls,
+ name,
+ offset,
+ foreign_cls,
+ (DBUFieldInitFunc)field_init_int32,
+ nonnull);
+}
+
+void dbuClassAddUInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+ add_offset_fk_field(
+ cls,
+ name,
+ offset,
+ foreign_cls,
+ (DBUFieldInitFunc)field_init_uint32,
+ nonnull);
+}
+
+void dbuClassAddInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+ add_offset_fk_field(
+ cls,
+ name,
+ offset,
+ foreign_cls,
+ (DBUFieldInitFunc)field_init_int64,
+ nonnull);
+}
+
+void dbuClassAddUInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+ add_offset_fk_field(
+ cls,
+ name,
+ offset,
+ foreign_cls,
+ (DBUFieldInitFunc)field_init_uint64,
+ nonnull);
+}
+
+void dbuClassAddStringFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+ add_offset_fk_field(
+ cls,
+ name,
+ offset,
+ foreign_cls,
+ (DBUFieldInitFunc)field_init_str,
+ nonnull);
+}
+
+void dbuClassAddCXMutStrFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+ add_offset_fk_field(
+ cls,
+ name,
+ offset,
+ foreign_cls,
+ (DBUFieldInitFunc)field_init_cxmutstr,
+ nonnull);
+}
+
+
+void dbuClassAddIntDef(DBUClass *cls, const char *name, off_t offset, int def) {
+ union DBUDefValue defvalue;
+ defvalue.def = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_int,
+ (DBUFieldInitFunc)field_init_int,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUIntDef(DBUClass *cls, const char *name, off_t offset, unsigned int def) {
+ union DBUDefValue defvalue;
+ defvalue.udef = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_uint,
+ (DBUFieldInitFunc)field_init_uint,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddInt16Def(DBUClass *cls, const char *name, off_t offset, int16_t def) {
+ union DBUDefValue defvalue;
+ defvalue.def = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_int16,
+ (DBUFieldInitFunc)field_init_int16,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt16Def(DBUClass *cls, const char *name, off_t offset, uint16_t def) {
+ union DBUDefValue defvalue;
+ defvalue.udef = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_uint16,
+ (DBUFieldInitFunc)field_init_uint16,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddInt32Def(DBUClass *cls, const char *name, off_t offset, int32_t def) {
+ union DBUDefValue defvalue;
+ defvalue.def = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_int32,
+ (DBUFieldInitFunc)field_init_int32,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt32Def(DBUClass *cls, const char *name, off_t offset, uint32_t def) {
+ union DBUDefValue defvalue;
+ defvalue.udef = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_uint32,
+ (DBUFieldInitFunc)field_init_uint32,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddInt64Def(DBUClass *cls, const char *name, off_t offset, int64_t def) {
+ union DBUDefValue defvalue;
+ defvalue.def = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_int64,
+ (DBUFieldInitFunc)field_init_int64,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddUInt64Def(DBUClass *cls, const char *name, off_t offset, uint64_t def) {
+ union DBUDefValue defvalue;
+ defvalue.def = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_uint64,
+ (DBUFieldInitFunc)field_init_uint64,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddSizeDef(DBUClass *cls, const char *name, off_t offset, size_t def) {
+ union DBUDefValue defvalue;
+ defvalue.udef = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_size,
+ (DBUFieldInitFunc)field_init_size,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddSSizeDef(DBUClass *cls, const char *name, off_t offset, ssize_t def) {
+ union DBUDefValue defvalue;
+ defvalue.def = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_ssize,
+ (DBUFieldInitFunc)field_init_ssize,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddFloatDef(DBUClass *cls, const char *name, off_t offset, float def) {
+ union DBUDefValue defvalue;
+ defvalue.fdef = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_float,
+ (DBUFieldInitFunc)field_init_float,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+void dbuClassAddDoubleDef(DBUClass *cls, const char *name, off_t offset, double def) {
+ union DBUDefValue defvalue;
+ defvalue.ddef = def;
+ add_offset_def_field(
+ cls,
+ name,
+ offset,
+ (DBUFieldDefInitFunc)field_def_init_double,
+ (DBUFieldInitFunc)field_init_double,
+ false,
+ (union DBUDefValue){ 0 });
+}
+
+
+// add complex members
+
+static int set_obj_ptr(DBUField *f, const CxAllocator *a, DBUObject obj, void *child) {
+ DBUOffsetField *field = (DBUOffsetField*)f;
+ *(void**)(obj+field->offset) = child;
+ return 0;
+}
+
+void dbuClassAddObj(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
+ DBUOffsetField *field = malloc(sizeof(DBUOffsetField));
+ memset(field, 0, sizeof(DBUOffsetField));
+ field->field.initObjValue = set_obj_ptr;
+ field->offset = offset;
+ field->def.def = 0;
+ dbuClassAddObjField(cls, name, (DBUField*)field, foreign_cls);
+}
+
+static int linked_list_add(DBUField *f, const CxAllocator *a, DBUObject obj, void *child) {
+ DBUOffsetField *field = (DBUOffsetField*)f;
+ CxList **list = (CxList**)(obj+field->offset);
+ if(*list) {
+ *list = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
+ if(!(*list)) {
+ return 1;
+ }
+ }
+ return cxListAdd(*list, child);;
+}
+
+static DBUObject linkedlist_create_obj(DBUObjectResult *result, DBUClass *type, const CxAllocator *a) {
+ void *obj = cxMalloc(a, type->obj_size);
+ if(obj) {
+ memset(obj, 0, type->obj_size);
+ }
+ return obj;
+}
+static int linkedlist_add(DBUObjectResult *result, DBUObject parent, DBUClass *type, void *obj, CxList *fk, const CxAllocator *a) {
+ DBUOffsetField *field = result->userdata1;
+ DBUClass *cls = result->userdata2;
+ CxList **list = (CxList**)(parent+field->offset);
+ if(!*list) {
+ *list = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
+ if(!(*list)) {
+ return 1;
+ }
+ }
+ cxListAdd(*list, obj);;
+ return 0;
+}
+
+void dbuClassAddCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
+ DBUOffsetField *field = malloc(sizeof(DBUOffsetField));
+ memset(field, 0, sizeof(DBUOffsetField));
+ field->offset = offset;
+ field->def.def = 0;
+ field->field.builder.userdata1 = field;
+ field->field.builder.userdata2 = foreign_cls;
+ field->field.builder.create = linkedlist_create_obj;
+ field->field.builder.add = linkedlist_add;
+ dbuClassAddObjField(cls, NULL, (DBUField*)field, foreign_cls);
+ //dbuClassAddParentObjField(foreign_cls, name, cls, (DBUField*)field);
+}
+
+void dbuClassAddValueLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
+
+}
+
+void dbuClassAddCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
+
+}
+
+void dbuClassAddValueArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
+
+}
+
+void dbuClassAddArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls) {
+
+}
+
+void dbuClassAddValueArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls) {
+
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DBU_FIELD_H
+#define DBU_FIELD_H
+
+#include "dbutils/dbutils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+union DBUDefValue {
+ int64_t def;
+ uint64_t udef;
+ float fdef;
+ double ddef;
+};
+
+/*
+ * used in most cases, when there is exactly one struct member
+ */
+typedef struct DBUOffsetField {
+ DBUField field;
+ off_t offset;
+ union DBUDefValue def;
+} DBUOffsetField;
+
+/*
+ * fields with 2 struct members: a obj pointer and length
+ */
+typedef struct DBUObjLenField {
+ DBUField field;
+ off_t offset_obj;
+ off_t offset_len;
+} DBUObjLenField;
+
+
+DBUField* dbuFieldCreateInt32(off_t offset);
+
+DBUField* dbuFieldCreateUInt32(off_t offset);
+
+DBUField* dbuFieldCreateInt64(off_t offset);
+
+DBUField* dbuFieldCreateUInt64(off_t offset);
+
+DBUField* dbuFieldCreateString(off_t offset);
+
+DBUField* dbuFieldCreateCxMutStr(off_t offset);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DBU_FIELD_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/array_list.h>
+#include <cx/linked_list.h>
+#include <cx/hash_map.h>
+#include <cx/printf.h>
+
+#include "object.h"
+
+
+
+
+
+DBUObjectBuilder* dbuObjectBuilder(DBUClass *type, DBUQuery *query, const CxAllocator *a) {
+ CxList *additionalQueries = cxLinkedListCreateSimple(sizeof(DBUBuilderQuery));
+ if(!additionalQueries) {
+ return NULL;
+ }
+ CxMap *subQueries = cxHashMapCreateSimple(CX_STORE_POINTERS);
+ if(!subQueries) {
+ cxListFree(additionalQueries);
+ return NULL;
+ }
+
+ DBUObjectBuilder *builder = malloc(sizeof(DBUObjectBuilder));
+ if(!builder) {
+ cxListFree(additionalQueries);
+ cxMapFree(subQueries);
+ return NULL;
+ }
+
+ memset(builder, 0, sizeof(DBUObjectBuilder));
+ builder->allocator = a;
+ builder->ctx = type->context;
+ builder->mainQuery = query;
+ builder->resultType = type;
+ builder->additionalQueries = additionalQueries;
+
+ return builder;
+}
+
+void dbuObjectBuilderSetDenseResult(DBUObjectBuilder *builder, bool dense) {
+ builder->denseResult = dense;
+}
+
+int dbuObjectBuilderAddSubquery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *subquery) {
+ return cxMapPut(builder->subQueries, type->name, subquery);
+}
+
+int dbuObjectBuilderAddAdditionalQuery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *query) {
+ return cxListAdd(builder->additionalQueries, &(DBUBuilderQuery){ type, NULL, query });
+}
+
+
+
+
+
+// builder result constructors
+
+static DBUObject result_create_obj(DBUObjectResult *result, DBUClass *type, const CxAllocator *a) {
+ void *obj = cxMalloc(a, type->obj_size);
+ if(obj) {
+ memset(obj, 0, type->obj_size);
+ }
+ return obj;
+}
+
+static DBUObject valuelist_result_create(DBUObjectResult *result, DBUClass *type, const CxAllocator *a) {
+ memset(result->userdata2, 0, result->int1);
+ return result->userdata2;
+}
+
+static int list_result_add(DBUObjectResult *result, DBUObject parent, DBUClass *type, void *obj, CxList *fk, const CxAllocator *a) {
+ return cxListAdd(result->userdata1, obj);
+}
+
+CxList* dbuObjectBuilderGetList(DBUObjectBuilder *builder) {
+ CxList *result_list = cxArrayListCreate(builder->allocator, NULL, CX_STORE_POINTERS, 8);
+ if(!result_list) {
+ return NULL;
+ }
+ // TODO: the class needs a obj destructor func, because we need to be able
+ // to destroy partialy created lists
+ DBUObjectResult result = {
+ .userdata1 = result_list,
+ .userdata2 = NULL,
+ .int1 = CX_STORE_POINTERS,
+ .create = result_create_obj,
+ .add = list_result_add
+ };
+ if(dbuObjectExec(builder, &result)) {
+ cxListFree(result_list);
+ return NULL;
+ }
+ return result_list;
+}
+
+
+CxList* dbuObjectBuilderGetValueList(DBUObjectBuilder *builder) {
+ CxList *result_list = cxArrayListCreate(builder->allocator, NULL, builder->resultType->obj_size, 8);
+ if(!result_list) {
+ return NULL;
+ }
+ // TODO: the class needs a obj destructor func, because we need to be able
+ // to destroy partialy created lists
+ DBUObjectResult result = {
+ .userdata1 = result_list,
+ .userdata2 = malloc(builder->resultType->obj_size),
+ .int1 = builder->resultType->obj_size,
+ .create = valuelist_result_create,
+ .add = list_result_add
+ };
+ if(!result.userdata2) {
+ cxListFree(result_list);
+ return NULL;
+ }
+ if(dbuObjectExec(builder, &result)) {
+ cxListFree(result_list);
+ return NULL;
+ }
+ return result_list;
+}
+
+int dbuObjectBuilderGetArray(DBUObjectBuilder *builder, void **array, size_t *size) {
+
+}
+
+void dbuObjectBuilderDestroy(DBUObjectBuilder *builder) {
+
+}
+
+
+
+/* ------------------------- Object Builder -----------------------------*/
+
+static int add_to_parent(DBUObjectResult *result, DBUObject parent, DBUClass *type, void *obj, CxList *fklist, const CxAllocator *a) {
+ DBUBuilderQuery *query = result->userdata1;
+ DBUObjectBuilder *builder = result->userdata2;
+ CxIterator i = cxListIterator(fklist);
+ cx_foreach(DBUFK*, fk, i) {
+ // check if the elm can be added
+ DBUField *field = cxMapGet(fk->cls->obj_fields, type->name);
+ if(!field) {
+ continue;
+ }
+
+ // find cached object for all foreign keys
+ DBUBuilderObjCache *parent_cached = cxMapGet(builder->cache, fk->cache_key);
+ if(!parent_cached) {
+ continue;
+ }
+
+ if(field->builder.add) {
+ field->builder.add(&field->builder, parent_cached->obj, type, obj, NULL, a);
+ }
+ }
+
+ return 0;
+}
+
+int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectResult *objresult) {
+ if(cxListSize(builder->additionalQueries) > 0 || builder->denseResult) {
+ builder->cache = cxHashMapCreateSimple(sizeof(DBUBuilderObjCache));
+ if(!builder->cache) {
+ return 1;
+ }
+ }
+
+ int ret = dbuObjectExecuteQuery(builder, builder->mainQuery, builder->resultType, objresult, builder->denseResult);
+ if(!ret) {
+ // execute additional queries
+ CxIterator i = cxListIterator(builder->additionalQueries);
+ cx_foreach(DBUBuilderQuery *, q, i) {
+ // default result builder for additional queries
+ // uses malloc to allocate objects and adds checks all foreign keys
+ DBUObjectResult result = {
+ .userdata1 = q,
+ .userdata2 = builder,
+ .int1 = CX_STORE_POINTERS,
+ .create = result_create_obj,
+ .add = add_to_parent
+ };
+ DBUObjectResult *objresult = &result;
+ if(q->field && q->field->builder.add) {
+ objresult = &q->field->builder;
+ }
+
+ ret = dbuObjectExecuteQuery(builder, q->query, q->type, objresult, false);
+ if(ret) {
+ break;
+ }
+ }
+ }
+
+ if(builder->cache) {
+ cxMapFree(builder->cache);
+ }
+ builder->cache = NULL;
+ return ret;
+}
+
+void dbufkelm_free(DBUFK *elm) {
+ free(elm->cache_key);
+}
+
+static void* create_or_get_obj(CxAllocator *a, DBUObjectResult *objresult, DBUClass *type, int pk_index, DBUResult *result) {
+ return NULL;
+}
+
+int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult, bool dense) {
+ DBUClass *cls = type;
+
+ // execute sql
+ if(query->exec(query)) {
+ query->free(query);
+ return 1;
+ }
+
+ DBUResult *result = query->getResult(query);
+ if(!result) {
+ return 1;
+ }
+ query->free(query);
+
+ // map result to class fields
+ int numcols = result->numFields(result);
+ DBUFieldMapping *field_mapping = calloc(numcols, sizeof(DBUFieldMapping));
+ if(!field_mapping) {
+ result->free(result);
+ return 1;
+ }
+
+ bool is_main = true;
+ DBUClass *field_class = cls;
+
+ int main_pk_index = -1;
+ int end_main_fields = numcols;
+
+ // list of all classes returned by the result
+ // used when dense == true, to check, if a new result row contains
+ // the same objects (primary keys haven't changed)
+ DBUResultType result_types_static[16];
+ size_t result_types_capacity = 16;
+ size_t result_types_size = 0;
+ DBUResultType *result_types = result_types_static;
+ CxArrayReallocator ar = cx_array_reallocator(NULL, result_types_static);
+
+ DBUResultType mainResult = { .cls = cls };
+ cx_array_add(&result_types, &result_types_size, &result_types_capacity, sizeof(DBUResultType), &mainResult, &ar);
+
+ for(int i=0;i<numcols;i++) {
+ cxstring fieldname = cx_str(result->fieldName(result, i));
+ DBUField *field = NULL;
+ if(cx_strprefix(fieldname, CX_STR("__"))) {
+ // columns starting with __ are reserved for marking table names
+ // __<table>__<fieldname>
+ // __<table> (value ignored)
+ //
+ // after a column is marked as the start of the new table
+ // all following columns will be treated as columns of this table
+ cxstring tabname = cx_strsubs(fieldname, 2);
+ cxstring remaining = cx_strstr(tabname, CX_STR("__"));
+ DBUClass *fcls = NULL;
+ if(remaining.length > 2) {
+ tabname.length = remaining.ptr - tabname.ptr;
+
+ }
+ fcls = cxMapGet(builder->ctx->classes, tabname);
+
+ if(fcls) {
+ if(fcls != field_class) {
+ DBUResultType resultCls = { .cls = fcls };
+ cx_array_add(&result_types, &result_types_size, &result_types_capacity, sizeof(DBUResultType), &resultCls, &ar);
+ }
+ if(remaining.length > 2) {
+ field = cxMapGet(fcls->fields, cx_strsubs(remaining, 2));
+ }
+ field_mapping[i].cls = fcls;
+ field_class = fcls;
+ }
+ } else {
+ field = cxMapGet(field_class->fields, fieldname);
+ }
+
+ if(field) {
+ DBUFieldMapping mapping;
+ mapping.cls = field_class;
+ mapping.field = field;
+ mapping.type = result->fieldType(result, i);
+ mapping.is_main = is_main;
+ field_mapping[i] = mapping;
+
+ if(field == cls->primary_key) {
+ main_pk_index = i;
+ }
+ if(field == field_class->primary_key) {
+ result_types[result_types_size-1].pk_col = i;
+ }
+
+ if(end_main_fields == numcols && field_class != cls) {
+ end_main_fields = i;
+ }
+ }
+ }
+
+ if(main_pk_index < 0) {
+ dense = false;
+ }
+
+ const CxAllocator *a = builder->allocator;
+
+ // collect all foreign keys per row
+ CxList *fklist = cxArrayListCreateSimple(sizeof(DBUFK), 4);
+ fklist->collection.simple_destructor = (cx_destructor_func)dbufkelm_free;
+
+ // get result
+ int err = 0;
+ while(result->hasData(result)) {
+ // create main result obj
+ bool addobj = true;
+ void *obj = NULL;
+ int skip_fields = 0;
+ int cls_index = 0;
+ if(dense) {
+ cxstring text = result->getText(result, main_pk_index);
+ cxmutstr prev_pk = result_types[0].prev_key;
+ if(prev_pk.ptr && !cx_strcmp(text, cx_strcast(prev_pk))) {
+ obj = result_types[0].prev_obj;
+ addobj = false;
+ if(1 < result_types_size) {
+ skip_fields = result_types[1].pk_col;
+ }
+ }
+ }
+
+ if(!obj) {
+ //printf("create obj [%s]\n", cls->name.ptr);
+ obj = objresult->create(objresult, cls, a);
+ memset(obj, 0, sizeof(cls->obj_size));
+ if(cls->init) {
+ cls->init(obj, a);
+ }
+ }
+ result_types[0].prev_obj = obj;
+
+ if(!obj) {
+ break;
+ }
+
+ void *current_obj = obj;
+ void *child_obj = NULL;
+ DBUClass *current_cls = cls;
+ for(int i=skip_fields;i<numcols;i++) {
+ DBUFieldMapping field = field_mapping[i];
+ int isnull = result->isNull(result, i);
+
+ if(field.cls && field.cls != current_cls) {
+ // following columns are for this new class
+ cls_index++;
+ //printf("new class[%d]: %s\n", cls_index, field.cls->name.ptr);
+
+ if(dense) {
+ // check if this object was already added
+ if(cls_index < result_types_size) {
+ cxstring text = result->getText(result, result_types[cls_index].pk_col);
+ cxmutstr prev_pk = result_types[cls_index].prev_key;
+ if(prev_pk.ptr && !cx_strcmp(text, cx_strcast(prev_pk))) {
+ //printf("already added -> skip\n");
+ if(cls_index+1 < result_types_size) {
+ i = result_types[cls_index+1].pk_col-1; // -1 because i++ by loop
+ //printf("next col %d\n", i);
+ continue;
+ }
+ }
+ }
+ }
+
+ current_cls = field.cls;
+ if(field.field) {
+ // check if an object of this class can be added to the main obj
+ DBUField *obj_field = cxMapGet(cls->obj_fields, field.cls->name);
+ if(obj_field && obj_field->initObjValue) {
+ //printf("create child obj [%s]\n", field.cls->name.ptr);
+ child_obj = objresult->create(objresult, field.cls, a);
+ current_obj = child_obj;
+ if(!child_obj) {
+ err = 1;
+ break;
+ }
+
+ if(field.cls->init) {
+ field.cls->init(child_obj, a); // TODO: check return
+ }
+
+ obj_field->initObjValue(obj_field, a, obj, child_obj);
+ } else {
+ //printf("list field\n");
+
+ DBUField *parent_field = NULL;
+ for(int c=0;c<result_types_size;c++) {
+ DBUResultType parentType = result_types[c];
+ DBUField *f = cxMapGet(parentType.cls->obj_fields, field.cls->name);
+ if(f && f->builder.create) {
+ current_obj = f->builder.create(&f->builder, field.cls, a);
+ void *parent_obj = result_types[c].prev_obj;
+ f->builder.add(&f->builder, parent_obj, field.cls, current_obj, fklist, a);
+ break;
+ }
+ }
+ if(!current_obj) {
+ //printf("ignore\n");
+ }
+ }
+ }
+
+ result_types[cls_index].prev_obj = current_obj;
+ }
+
+ DBUField *type_field = field.field;
+ if(type_field && current_obj) {
+ if(isnull) {
+ field.field->initDefaultValue(field.field, a, current_obj);
+ } else {
+ cxstring text = result->getText(result, i);
+ //printf("getText(%d) = %s\n", i, text.ptr);
+ field.field->initValue(field.field, a, current_obj, text.ptr, text.length);
+
+ // if obj caching is enabled and the current field contains
+ // the primary key, add this object to the cache
+ if(builder->cache) {
+ if(field.field == current_cls->primary_key) {
+ cxmutstr cache_key = cx_asprintf("%.*s::%.*s",
+ (int)current_cls->name.length, current_cls->name.ptr,
+ (int)text.length, text.ptr);
+ if(!cache_key.ptr) {
+ err = 1;
+ break;
+ }
+ DBUBuilderObjCache cache_elm;
+ cache_elm.class = current_cls;
+ cache_elm.obj = current_obj;
+ int r = cxMapPut(builder->cache, cache_key, &cache_elm);
+ free(cache_key.ptr);
+ if(r) {
+ err = 1;
+ break;
+ }
+ } else if(field.field->foreignKeyClass) {
+ cxmutstr fkey = cx_asprintf("%.*s::%.*s",
+ (int)field.field->foreignKeyClass->name.length, field.field->foreignKeyClass->name.ptr,
+ (int)text.length, text.ptr);
+ DBUFK fkelm;
+ fkelm.cache_key = fkey.ptr;
+ fkelm.cls = field.field->foreignKeyClass;
+ cxListAdd(fklist, &fkelm);
+ }
+ }
+ }
+ }
+ }
+
+ if(err) {
+ break;
+ }
+
+ if(addobj) {
+ objresult->add(objresult, NULL, cls, obj, fklist, a);
+ }
+
+ cxListClear(fklist);
+
+ if(dense) {
+ for(int i=0;i<result_types_size;i++) {
+ cxstring text = result->getText(result, result_types[i].pk_col);
+ free(result_types[i].prev_key.ptr);
+ result_types[i].prev_key = cx_strdup(text);
+ }
+ }
+
+ // load next row
+ result->nextRow(result);
+ //printf("next row\n");
+ }
+
+ cxListFree(fklist);
+
+ for(int i=0;i<result_types_size;i++) {
+ free(result_types[i].prev_key.ptr);
+ }
+ if(result_types != result_types_static) {
+ free(result_types);
+ }
+
+ result->free(result);
+
+
+ return err;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DBU_OBJECT_H
+#define DBU_OBJECT_H
+
+#include "dbutils/dbutils.h"
+#include "dbutils/db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DBUFieldMapping {
+ DBUClass *cls;
+ DBUField *field;
+ DBUFieldType type;
+ bool is_main;
+} DBUFieldMapping;
+
+typedef struct DBUBuilderQuery DBUBuilderQuery;
+struct DBUBuilderQuery {
+ DBUClass *type;
+ DBUField *field;
+ DBUQuery *query;
+};
+
+typedef struct DBUBuilderObjCache {
+ DBUClass *class;
+ void *obj;
+} DBUBuilderObjCache;
+
+struct DBUObjectBuilder {
+ const CxAllocator *allocator;
+
+ DBUContext *ctx;
+
+ /*
+ * Main query, that us used for generating the result list.
+ */
+ DBUQuery *mainQuery;
+
+ /*
+ * result type
+ */
+ DBUClass *resultType;
+
+ /*
+ * key: type name
+ * value: DBUQuery*
+ *
+ * Subqueries, that should be executed for all main result rows.
+ * Before a subquery is executed, the first parameter is set to the
+ * primary key of the main table.
+ */
+ CxMap *subQueries; // TODO: implement
+
+ /*
+ * value: DBUBuilderQuery
+ *
+ * Additional queries are executed after the main query and the result
+ * objects are added to the previous queried objects
+ */
+ CxList *additionalQueries;
+
+ /*
+ * result builder
+ */
+ DBUObjectResult *result;
+
+ /*
+ * object cache
+ *
+ * key: <tablename> + <primary key>
+ * value: DBUBuilderObjCache
+ *
+ */
+ CxMap *cache;
+
+ /*
+ * if true, the main result does not contain duplicated entries
+ * with the same primary key
+ */
+ bool denseResult;
+};
+
+typedef struct DBUFK {
+ char *cache_key;
+ DBUClass *cls;
+} DBUFK;
+
+/*
+ * an array of ResultType is used to describe, which object types
+ * a result contains
+ */
+typedef struct DBUResultType {
+ /*
+ * class
+ */
+
+ DBUClass *cls;
+ /*
+ * column index of the primary key of this class
+ */
+ int pk_col;
+
+ /*
+ * previous PK value (allocated copy)
+ */
+ cxmutstr prev_key;
+
+ /*
+ * previous object pointer
+ */
+ void *prev_obj;
+} DBUResultType;
+
+int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectResult *objresult);
+
+int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult, bool dense);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DBU_OBJECT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef DBU_SQLITE
+
+#include "sqlite.h"
+#include "db.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <cx/array_list.h>
+
+
+
+DBUConnection* dbuSQLiteConnection(const char *filename) {
+ sqlite3 *db;
+ int rc = sqlite3_open(filename, &db);
+ if(rc != SQLITE_OK) {
+ return NULL;
+ }
+
+ DBUConnection *conn = dbuSQLiteConnectionFromDB(db, true);
+ if(!conn) {
+ sqlite3_close(db);
+ }
+ return conn;
+}
+
+DBUConnection* dbuSQLiteConnectionFromDB(sqlite3 *db, bool autoclose) {
+ DBUSQLiteConnection *connection = malloc(sizeof(DBUSQLiteConnection));
+ if(!connection) {
+ return NULL;
+ }
+ memset(connection, 0, sizeof(DBUSQLiteConnection));
+ connection->connection.createQuery = dbuSQLiteConnCreateQuery;
+ connection->connection.isActive = dbuSQLiteConnIsActive;
+ connection->connection.free = dbuSQLiteConnFree;
+ connection->connection.data = db;
+ connection->db = db;
+ connection->autoclose = autoclose;
+
+ return (DBUConnection*)connection;
+}
+
+/* ------------------- SQLite Connection Implementation -------------------*/
+
+DBUQuery* dbuSQLiteConnCreateQuery(DBUConnection *connection, const CxAllocator *a) {
+ DBUSQLiteQuery *query = malloc(sizeof(DBUSQLiteQuery));
+ if(!query) {
+ return NULL;
+ }
+ memset(query, 0, sizeof(DBUSQLiteQuery));
+ query->query.allocator = a;
+ query->query.connection = connection;
+ query->query.setSQL = dbuSQLiteQuerySetSQL;
+ query->query.setParamString = dbuSQLiteQuerySetParamString;
+ query->query.setParamInt = dbuSQLiteQuerySetParamInt;
+ query->query.setParamInt64 = dbuSQLiteQuerySetParamInt64;
+ query->query.setParamDouble = dbuSQLiteQuerySetParamDouble;
+ query->query.setParamNull = dbuSQLiteQuerySetParamNull;
+ query->query.setParamBytes = dbuSQLiteQuerySetParamBytes;
+ query->query.exec = dbuSQLiteQueryExec;
+ query->query.getResult = dbuSQLiteQueryGetResult;
+ query->query.reset = dbuSQLiteQueryReset;
+ query->query.free = dbuSQLiteQueryFree;
+ query->db = connection->data;
+ query->query.ref = 1;
+ return (DBUQuery*)query;
+}
+
+int dbuSQLiteConnIsActive(DBUConnection *connection) {
+ return 1;
+}
+
+void dbuSQLiteConnFree(DBUConnection *connection) {
+ DBUSQLiteConnection *sqlite = (DBUSQLiteConnection*)connection;
+ if(sqlite->autoclose) {
+ sqlite3_close(sqlite->db);
+ }
+ free(connection);
+}
+
+
+/* --------------------- SQLite Query Implementation ---------------------*/
+
+int dbuSQLiteQuerySetSQL(DBUQuery *q, const char *sql) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ if(query->stmt) {
+ return 1;
+ }
+ return sqlite3_prepare_v2(query->db, sql, -1, &query->stmt, 0) != SQLITE_OK;
+}
+
+int dbuSQLiteQuerySetParamString(DBUQuery *q, int index, cxstring str) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ return sqlite3_bind_text(query->stmt, index, str.ptr, str.length, NULL);
+}
+
+int dbuSQLiteQuerySetParamInt(DBUQuery *q, int index, int i) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ return sqlite3_bind_int(query->stmt, index, i);
+}
+
+int dbuSQLiteQuerySetParamInt64(DBUQuery *q, int index, int64_t i) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ return sqlite3_bind_int64(query->stmt, index, i);
+}
+
+int dbuSQLiteQuerySetParamDouble(DBUQuery *q, int index, double d) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ return sqlite3_bind_double(query->stmt, index, d);
+}
+
+int dbuSQLiteQuerySetParamNull(DBUQuery *q, int index) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ return sqlite3_bind_null(query->stmt, index);
+}
+
+int dbuSQLiteQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ return sqlite3_bind_blob(query->stmt, index, bytes, len, NULL);
+}
+
+int dbuSQLiteQueryExec(DBUQuery *q) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ int step = sqlite3_step(query->stmt);
+ if(step == SQLITE_ROW || step == SQLITE_DONE) {
+ query->step = step;
+ return 0;
+ }
+ return 1;
+}
+
+DBUResult* dbuSQLiteQueryGetResult(DBUQuery *q) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ if(query->result) {
+ return (DBUResult*)query->result;
+ }
+
+ DBUSQLiteResult *result = malloc(sizeof(DBUSQLiteResult));
+ if(!result) {
+ return NULL;
+ }
+ memset(result, 0, sizeof(DBUSQLiteResult));
+ query->query.ref++;
+ result->query = query;
+ query->result = result;
+
+ result->result.optional_numRows = NULL;
+ result->result.numFields = dbuSQLiteResultNumFields;
+ result->result.hasData = dbuSQLiteResultHasData;
+ result->result.isOk = dbuSQLiteResultIsOk;
+ result->result.nextRow = dbuSQLiteResultNextRow;
+ result->result.fieldName = dbuSQLiteResultFieldName;
+ result->result.fieldType = dbuSQLiteResultFieldType;
+ result->result.isNull = dbuSQLiteResultIsNull;
+ result->result.getText = dbuSQLiteResultGetText;
+ result->result.optional_getInt = dbuSQLiteResultGetInt;
+ result->result.optional_getDouble = dbuSQLiteResultGetDouble;
+ result->result.optional_getBinary = dbuSQLiteResultGetBinary;
+ result->result.free = dbuSQLiteResultFree;
+ result->result.allocator = q->allocator;
+ result->stmt = query->stmt;
+ result->step = query->step;
+
+ return (DBUResult*)result;
+}
+
+int dbuSQLiteQueryReset(DBUQuery *q) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ // TODO
+ return 0;
+}
+
+void dbuSQLiteQueryFree(DBUQuery *q) {
+ DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+ if(--query->query.ref > 0) {
+ return;
+ }
+ sqlite3_finalize(query->stmt);
+ free(query);
+}
+
+
+/* --------------------- SQLite Result Implementation ---------------------*/
+
+int dbuSQLiteResultNumFields(DBUResult *result) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ return sqlite3_column_count(r->stmt);
+}
+
+int dbuSQLiteResultHasData(DBUResult *result) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ return r->step == SQLITE_ROW;
+}
+
+int dbuSQLiteResultIsOk(DBUResult *result) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ return r->step == SQLITE_DONE;
+}
+
+int dbuSQLiteResultNextRow(DBUResult *result) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ r->step = sqlite3_step(r->stmt);
+ int done = r->step == SQLITE_DONE;
+ r->result.rowIndex++;
+ return done;
+}
+
+const char* dbuSQLiteResultFieldName(DBUResult *result, int field) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ return sqlite3_column_name(r->stmt, field);
+}
+
+DBUFieldType dbuSQLiteResultFieldType(DBUResult *result, int field) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ int type = sqlite3_column_type(r->stmt, field);
+ DBUFieldType dbuType;
+ switch(type) {
+ default: dbuType = DBU_FIELD_TEXT; break;
+ case SQLITE_INTEGER: dbuType = DBU_FIELD_INT; break;
+ case SQLITE_FLOAT: dbuType = DBU_FIELD_DOUBLE; break;
+ case SQLITE_TEXT: dbuType = DBU_FIELD_TEXT; break;
+ case SQLITE_BLOB: dbuType = DBU_FIELD_BINARY; break;
+ case SQLITE_NULL: dbuType = DBU_FIELD_NULL; break;
+ }
+ return dbuType;
+}
+
+int dbuSQLiteResultIsNull(DBUResult *result, int field) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ return sqlite3_column_type(r->stmt, field) == SQLITE_NULL;
+}
+
+cxstring dbuSQLiteResultGetText(DBUResult *result, int field) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ const char *s = (const char*)sqlite3_column_text(r->stmt, field);
+ size_t len = (size_t)sqlite3_column_bytes(r->stmt, field);
+ return cx_strn(s, len);
+}
+
+int64_t dbuSQLiteResultGetInt(DBUResult *result, int field) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ return sqlite3_column_int64(r->stmt, field);
+}
+
+double dbuSQLiteResultGetDouble(DBUResult *result, int field) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ return sqlite3_column_double(r->stmt, field);
+}
+
+DBUBytes dbuSQLiteResultGetBinary(DBUResult *result, int field) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ const unsigned char *s = sqlite3_column_blob(r->stmt, field);
+ size_t len = (size_t)sqlite3_column_bytes(r->stmt, field);
+ return (DBUBytes) { s, len };
+}
+
+void dbuSQLiteResultFree(DBUResult *result) {
+ DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+ r->query->query.free((DBUQuery*)r->query);
+ free(r);
+}
+
+#endif /* DBU_SQLITE */
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef DBU_SQLITE
+
+#ifndef DBU_SQLITE_H
+#define DBU_SQLITE_H
+
+#include "dbutils/sqlite.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DBUSQLiteQuery DBUSQLiteQuery;
+typedef struct DBUSQLiteResult DBUSQLiteResult;
+
+typedef struct DBUSQLiteConnection {
+ DBUConnection connection;
+ sqlite3 *db;
+ bool autoclose;
+} DBUSQLiteConnection;
+
+struct DBUSQLiteQuery {
+ DBUQuery query;
+ sqlite3 *db;
+ sqlite3_stmt *stmt;
+ DBUSQLiteResult *result;
+ int step;
+};
+
+struct DBUSQLiteResult {
+ DBUResult result;
+ sqlite3_stmt *stmt;
+ DBUSQLiteQuery *query;
+ int step;
+};
+
+DBUQuery* dbuSQLiteConnCreateQuery(DBUConnection *connection, const CxAllocator *a) ;
+int dbuSQLiteConnIsActive(DBUConnection *connection);
+void dbuSQLiteConnFree(DBUConnection *connection);
+
+int dbuSQLiteQuerySetSQL(DBUQuery *q, const char *sql);
+int dbuSQLiteQuerySetParamString(DBUQuery *q, int index, cxstring str);
+int dbuSQLiteQuerySetParamInt(DBUQuery *q, int index, int i);
+int dbuSQLiteQuerySetParamInt64(DBUQuery *q, int index, int64_t i);
+int dbuSQLiteQuerySetParamDouble(DBUQuery *q, int index, double d);
+int dbuSQLiteQuerySetParamNull(DBUQuery *q, int index);
+int dbuSQLiteQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len);
+int dbuSQLiteQueryExec(DBUQuery *q);
+DBUResult* dbuSQLiteQueryGetResult(DBUQuery *q);
+int dbuSQLiteQueryReset(DBUQuery *q);
+void dbuSQLiteQueryFree(DBUQuery *q);
+
+int dbuSQLiteResultNumFields(DBUResult *result);
+int dbuSQLiteResultHasData(DBUResult *result);
+int dbuSQLiteResultIsOk(DBUResult *result);
+int dbuSQLiteResultNextRow(DBUResult *result);
+const char* dbuSQLiteResultFieldName(DBUResult *result, int field);
+DBUFieldType dbuSQLiteResultFieldType(DBUResult *result, int field);
+int dbuSQLiteResultIsNull(DBUResult *result, int field);
+cxstring dbuSQLiteResultGetText(DBUResult *result, int field);
+int64_t dbuSQLiteResultGetInt(DBUResult *result, int field);
+double dbuSQLiteResultGetDouble(DBUResult *result, int field);
+DBUBytes dbuSQLiteResultGetBinary(DBUResult *result, int field);
+void dbuSQLiteResultFree(DBUResult *result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DBU_SQLITE_H */
+
+#endif /* DBU_SQLITE */
include config.mk
BUILD_DIRS = build/bin build/lib
-BUILD_DIRS += build/application build/ucx build/libidav
+BUILD_DIRS += build/application build/ucx build/libidav build/dbutils
BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
-all: $(BUILD_DIRS) ucx ui libidav application
+all: $(BUILD_DIRS) ucx dbutils ui libidav application
$(BUILD_DIRS):
ucx: FORCE
cd ucx; $(MAKE) all
+dbutils: FORCE
+ cd dbutils; $(MAKE) all
+
libidav: ucx FORCE
cd libidav; $(MAKE) all
<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://unixwork.de/uwproj">
+<project version="0.3" xmlns="http://unixwork.de/uwproj">
<dependency>
<lang>c</lang>
</dependency>
<make>TOOLKIT = qt</make>
<make>LD = $(CXX)</make>
</value>
+ -->
<value str="motif">
<dependencies>motif</dependencies>
<make>TOOLKIT = motif</make>
</value>
- -->
<default value="winui" platform="windows" />
<default value="cocoa" platform="macos" />
<default value="libadwaita" />
<default value="gtk4" />
<default value="gtk3" />
+ <default value="motif" />
<!--
<default value="qt5" />
<default value="gtk2" />
<default value="qt4" />
- <default value="motif" />
-->
</option>
</target>
# POSSIBILITY OF SUCH DAMAGE.
#
-BUILD_ROOT = ../
include ../config.mk
# list of source files
SRC += compare.c
SRC += hash_key.c
SRC += hash_map.c
+SRC += iterator.c
SRC += linked_list.c
SRC += list.c
SRC += map.c
SRC += printf.c
SRC += string.c
-SRC += utils.c
SRC += tree.c
-SRC += iterator.c
+SRC += streams.c
+SRC += properties.c
+SRC += json.c
OBJ = $(SRC:%.c=../build/ucx/%$(OBJ_EXT))
#include "cx/allocator.h"
-__attribute__((__malloc__, __alloc_size__(2)))
+#include <errno.h>
+
static void *cx_malloc_stdlib(
- __attribute__((__unused__)) void *d,
+ cx_attr_unused void *d,
size_t n
) {
return malloc(n);
}
-__attribute__((__warn_unused_result__, __alloc_size__(3)))
static void *cx_realloc_stdlib(
- __attribute__((__unused__)) void *d,
+ cx_attr_unused void *d,
void *mem,
size_t n
) {
return realloc(mem, n);
}
-__attribute__((__malloc__, __alloc_size__(2, 3)))
static void *cx_calloc_stdlib(
- __attribute__((__unused__)) void *d,
+ cx_attr_unused void *d,
size_t nelem,
size_t n
) {
return calloc(nelem, n);
}
-__attribute__((__nonnull__))
static void cx_free_stdlib(
- __attribute__((__unused__)) void *d,
+ cx_attr_unused void *d,
void *mem
) {
free(mem);
};
CxAllocator *cxDefaultAllocator = &cx_default_allocator;
-
+#undef cx_reallocate
int cx_reallocate(
void **mem,
size_t n
) {
void *nmem = realloc(*mem, n);
if (nmem == NULL) {
- return 1;
+ return 1; // LCOV_EXCL_LINE
} else {
*mem = nmem;
return 0;
}
}
+#undef cx_reallocatearray
+int cx_reallocatearray(
+ void **mem,
+ size_t nmemb,
+ size_t size
+) {
+ size_t n;
+ if (cx_szmul(nmemb, size, &n)) {
+ errno = EOVERFLOW;
+ return 1;
+ } else {
+ void *nmem = realloc(*mem, n);
+ if (nmem == NULL) {
+ return 1; // LCOV_EXCL_LINE
+ } else {
+ *mem = nmem;
+ return 0;
+ }
+ }
+}
+
// IMPLEMENTATION OF HIGH LEVEL API
void *cxMalloc(
return allocator->cl->realloc(allocator->data, mem, n);
}
+void *cxReallocArray(
+ const CxAllocator *allocator,
+ void *mem,
+ size_t nmemb,
+ size_t size
+) {
+ size_t n;
+ if (cx_szmul(nmemb, size, &n)) {
+ errno = EOVERFLOW;
+ return NULL;
+ } else {
+ return allocator->cl->realloc(allocator->data, mem, n);
+ }
+}
+
+#undef cxReallocate
int cxReallocate(
const CxAllocator *allocator,
void **mem,
) {
void *nmem = allocator->cl->realloc(allocator->data, *mem, n);
if (nmem == NULL) {
- return 1;
+ return 1; // LCOV_EXCL_LINE
+ } else {
+ *mem = nmem;
+ return 0;
+ }
+}
+
+#undef cxReallocateArray
+int cxReallocateArray(
+ const CxAllocator *allocator,
+ void **mem,
+ size_t nmemb,
+ size_t size
+) {
+ void *nmem = cxReallocArray(allocator, *mem, nmemb, size);
+ if (nmem == NULL) {
+ return 1; // LCOV_EXCL_LINE
} else {
*mem = nmem;
return 0;
#include "cx/compare.h"
#include <assert.h>
#include <string.h>
+#include <errno.h>
// Default array reallocator
void *array,
size_t capacity,
size_t elem_size,
- __attribute__((__unused__)) struct cx_array_reallocator_s *alloc
+ cx_attr_unused CxArrayReallocator *alloc
) {
- return realloc(array, capacity * elem_size);
+ size_t n;
+ if (cx_szmul(capacity, elem_size, &n)) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ return realloc(array, n);
}
-struct cx_array_reallocator_s cx_array_default_reallocator_impl = {
+CxArrayReallocator cx_array_default_reallocator_impl = {
cx_array_default_realloc, NULL, NULL, 0, 0
};
-struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
+CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
+
+// Stack-aware array reallocator
+
+static void *cx_array_advanced_realloc(
+ void *array,
+ size_t capacity,
+ size_t elem_size,
+ cx_attr_unused CxArrayReallocator *alloc
+) {
+ // check for overflow
+ size_t n;
+ if (cx_szmul(capacity, elem_size, &n)) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+
+ // retrieve the pointer to the actual allocator
+ const CxAllocator *al = alloc->ptr1;
+
+ // check if the array is still located on the stack
+ void *newmem;
+ if (array == alloc->ptr2) {
+ newmem = cxMalloc(al, n);
+ if (newmem != NULL && array != NULL) {
+ memcpy(newmem, array, n);
+ }
+ } else {
+ newmem = cxRealloc(al, array, n);
+ }
+ return newmem;
+}
+
+struct cx_array_reallocator_s cx_array_reallocator(
+ const struct cx_allocator_s *allocator,
+ const void *stackmem
+) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ return (struct cx_array_reallocator_s) {
+ cx_array_advanced_realloc,
+ (void*) allocator, (void*) stackmem,
+ 0, 0
+ };
+}
// LOW LEVEL ARRAY LIST FUNCTIONS
-enum cx_array_result cx_array_copy(
+static size_t cx_array_align_capacity(
+ size_t cap,
+ size_t alignment,
+ size_t max
+) {
+ if (cap > max - alignment) {
+ return cap;
+ } else {
+ return cap - (cap % alignment) + alignment;
+ }
+}
+
+int cx_array_reserve(
+ void **array,
+ void *size,
+ void *capacity,
+ unsigned width,
+ size_t elem_size,
+ size_t elem_count,
+ CxArrayReallocator *reallocator
+) {
+ // assert pointers
+ assert(array != NULL);
+ assert(size != NULL);
+ assert(capacity != NULL);
+
+ // default reallocator
+ if (reallocator == NULL) {
+ reallocator = cx_array_default_reallocator;
+ }
+
+ // determine size and capacity
+ size_t oldcap;
+ size_t oldsize;
+ size_t max_size;
+ if (width == 0 || width == sizeof(size_t)) {
+ oldcap = *(size_t*) capacity;
+ oldsize = *(size_t*) size;
+ max_size = SIZE_MAX;
+ } else if (width == sizeof(uint16_t)) {
+ oldcap = *(uint16_t*) capacity;
+ oldsize = *(uint16_t*) size;
+ max_size = UINT16_MAX;
+ } else if (width == sizeof(uint8_t)) {
+ oldcap = *(uint8_t*) capacity;
+ oldsize = *(uint8_t*) size;
+ max_size = UINT8_MAX;
+ }
+#if CX_WORDSIZE == 64
+ else if (width == sizeof(uint32_t)) {
+ oldcap = *(uint32_t*) capacity;
+ oldsize = *(uint32_t*) size;
+ max_size = UINT32_MAX;
+ }
+#endif
+ else {
+ errno = EINVAL;
+ return 1;
+ }
+
+ // assert that the array is allocated when it has capacity
+ assert(*array != NULL || oldcap == 0);
+
+ // check for overflow
+ if (elem_count > max_size - oldsize) {
+ errno = EOVERFLOW;
+ return 1;
+ }
+
+ // determine new capacity
+ size_t newcap = oldsize + elem_count;
+
+ // reallocate if possible
+ if (newcap > oldcap) {
+ // calculate new capacity (next number divisible by 16)
+ newcap = cx_array_align_capacity(newcap, 16, max_size);
+
+ // perform reallocation
+ void *newmem = reallocator->realloc(
+ *array, newcap, elem_size, reallocator
+ );
+ if (newmem == NULL) {
+ return 1; // LCOV_EXCL_LINE
+ }
+
+ // store new pointer
+ *array = newmem;
+
+ // store new capacity
+ if (width == 0 || width == sizeof(size_t)) {
+ *(size_t*) capacity = newcap;
+ } else if (width == sizeof(uint16_t)) {
+ *(uint16_t*) capacity = (uint16_t) newcap;
+ } else if (width == sizeof(uint8_t)) {
+ *(uint8_t*) capacity = (uint8_t) newcap;
+ }
+#if CX_WORDSIZE == 64
+ else if (width == sizeof(uint32_t)) {
+ *(uint32_t*) capacity = (uint32_t) newcap;
+ }
+#endif
+ }
+
+ return 0;
+}
+
+int cx_array_copy(
void **target,
- size_t *size,
- size_t *capacity,
+ void *size,
+ void *capacity,
+ unsigned width,
size_t index,
const void *src,
size_t elem_size,
size_t elem_count,
- struct cx_array_reallocator_s *reallocator
+ CxArrayReallocator *reallocator
) {
// assert pointers
assert(target != NULL);
assert(size != NULL);
+ assert(capacity != NULL);
assert(src != NULL);
- // determine capacity
- size_t cap = capacity == NULL ? *size : *capacity;
+ // default reallocator
+ if (reallocator == NULL) {
+ reallocator = cx_array_default_reallocator;
+ }
+
+ // determine size and capacity
+ size_t oldcap;
+ size_t oldsize;
+ size_t max_size;
+ if (width == 0 || width == sizeof(size_t)) {
+ oldcap = *(size_t*) capacity;
+ oldsize = *(size_t*) size;
+ max_size = SIZE_MAX;
+ } else if (width == sizeof(uint16_t)) {
+ oldcap = *(uint16_t*) capacity;
+ oldsize = *(uint16_t*) size;
+ max_size = UINT16_MAX;
+ } else if (width == sizeof(uint8_t)) {
+ oldcap = *(uint8_t*) capacity;
+ oldsize = *(uint8_t*) size;
+ max_size = UINT8_MAX;
+ }
+#if CX_WORDSIZE == 64
+ else if (width == sizeof(uint32_t)) {
+ oldcap = *(uint32_t*) capacity;
+ oldsize = *(uint32_t*) size;
+ max_size = UINT32_MAX;
+ }
+#endif
+ else {
+ errno = EINVAL;
+ return 1;
+ }
+
+ // assert that the array is allocated when it has capacity
+ assert(*target != NULL || oldcap == 0);
+
+ // check for overflow
+ if (index > max_size || elem_count > max_size - index) {
+ errno = EOVERFLOW;
+ return 1;
+ }
// check if resize is required
size_t minsize = index + elem_count;
- size_t newsize = *size < minsize ? minsize : *size;
- bool needrealloc = newsize > cap;
+ size_t newsize = oldsize < minsize ? minsize : oldsize;
// reallocate if possible
- if (needrealloc) {
- // a reallocator and a capacity variable must be available
- if (reallocator == NULL || capacity == NULL) {
- return CX_ARRAY_REALLOC_NOT_SUPPORTED;
- }
-
+ size_t newcap = oldcap;
+ if (newsize > oldcap) {
// check, if we need to repair the src pointer
uintptr_t targetaddr = (uintptr_t) *target;
uintptr_t srcaddr = (uintptr_t) src;
bool repairsrc = targetaddr <= srcaddr
- && srcaddr < targetaddr + cap * elem_size;
+ && srcaddr < targetaddr + oldcap * elem_size;
// calculate new capacity (next number divisible by 16)
- cap = newsize - (newsize % 16) + 16;
- assert(cap > newsize);
+ newcap = cx_array_align_capacity(newsize, 16, max_size);
+ assert(newcap > newsize);
// perform reallocation
void *newmem = reallocator->realloc(
- *target, cap, elem_size, reallocator
+ *target, newcap, elem_size, reallocator
);
if (newmem == NULL) {
- return CX_ARRAY_REALLOC_FAILED;
+ return 1;
}
// repair src pointer, if necessary
src = ((char *) newmem) + (srcaddr - targetaddr);
}
- // store new pointer and capacity
+ // store new pointer
*target = newmem;
- *capacity = cap;
}
// determine target pointer
start += index * elem_size;
// copy elements and set new size
+ // note: no overflow check here, b/c we cannot get here w/o allocation
memmove(start, src, elem_count * elem_size);
- *size = newsize;
+
+ // if any of size or capacity changed, store them back
+ if (newsize != oldsize || newcap != oldcap) {
+ if (width == 0 || width == sizeof(size_t)) {
+ *(size_t*) capacity = newcap;
+ *(size_t*) size = newsize;
+ } else if (width == sizeof(uint16_t)) {
+ *(uint16_t*) capacity = (uint16_t) newcap;
+ *(uint16_t*) size = (uint16_t) newsize;
+ } else if (width == sizeof(uint8_t)) {
+ *(uint8_t*) capacity = (uint8_t) newcap;
+ *(uint8_t*) size = (uint8_t) newsize;
+ }
+#if CX_WORDSIZE == 64
+ else if (width == sizeof(uint32_t)) {
+ *(uint32_t*) capacity = (uint32_t) newcap;
+ *(uint32_t*) size = (uint32_t) newsize;
+ }
+#endif
+ }
// return successfully
- return CX_ARRAY_SUCCESS;
+ return 0;
}
-enum cx_array_result cx_array_insert_sorted(
+int cx_array_insert_sorted(
void **target,
size_t *size,
size_t *capacity,
const void *sorted_data,
size_t elem_size,
size_t elem_count,
- struct cx_array_reallocator_s *reallocator
+ CxArrayReallocator *reallocator
) {
// assert pointers
assert(target != NULL);
assert(capacity != NULL);
assert(cmp_func != NULL);
assert(sorted_data != NULL);
- assert(reallocator != NULL);
+
+ // default reallocator
+ if (reallocator == NULL) {
+ reallocator = cx_array_default_reallocator;
+ }
// corner case
if (elem_count == 0) return 0;
+ // overflow check
+ if (elem_count > SIZE_MAX - *size) {
+ errno = EOVERFLOW;
+ return 1;
+ }
+
// store some counts
size_t old_size = *size;
size_t needed_capacity = old_size + elem_count;
// if we need more than we have, try a reallocation
if (needed_capacity > *capacity) {
- size_t new_capacity = needed_capacity - (needed_capacity % 16) + 16;
+ size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX);
void *new_mem = reallocator->realloc(
*target, new_capacity, elem_size, reallocator
);
if (new_mem == NULL) {
// give it up right away, there is no contract
// that requires us to insert as much as we can
- return CX_ARRAY_REALLOC_FAILED;
+ return 1; // LCOV_EXCL_LINE
}
*target = new_mem;
*capacity = new_capacity;
// still buffer elements left?
// don't worry, we already moved them to the correct place
- return CX_ARRAY_SUCCESS;
+ return 0;
}
size_t cx_array_binary_search_inf(
return 0;
}
+ // special case: there is only one element and that is smaller
+ if (size == 1) return 0;
+
// check the last array element
result = cmp_func(elem, array + elem_size * (size - 1));
if (result >= 0) {
return result < 0 ? (pivot_index - 1) : pivot_index;
}
+size_t cx_array_binary_search(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ size_t index = cx_array_binary_search_inf(
+ arr, size, elem_size, elem, cmp_func
+ );
+ if (index < size &&
+ cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
+ return index;
+ } else {
+ return size;
+ }
+}
+
+size_t cx_array_binary_search_sup(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ size_t inf = cx_array_binary_search_inf(
+ arr, size, elem_size, elem, cmp_func
+ );
+ if (inf == size) {
+ // no infimum means, first element is supremum
+ return 0;
+ } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
+ return inf;
+ } else {
+ return inf + 1;
+ }
+}
+
#ifndef CX_ARRAY_SWAP_SBO_SIZE
#define CX_ARRAY_SWAP_SBO_SIZE 128
#endif
-unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
+const unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
void cx_array_swap(
void *arr,
struct cx_list_s base;
void *data;
size_t capacity;
- struct cx_array_reallocator_s reallocator;
+ CxArrayReallocator reallocator;
} cx_array_list;
-static void *cx_arl_realloc(
- void *array,
- size_t capacity,
- size_t elem_size,
- struct cx_array_reallocator_s *alloc
-) {
- // retrieve the pointer to the list allocator
- const CxAllocator *al = alloc->ptr1;
-
- // use the list allocator to reallocate the memory
- return cxRealloc(al, array, capacity * elem_size);
-}
-
static void cx_arl_destructor(struct cx_list_s *list) {
cx_array_list *arl = (cx_array_list *) list;
size_t elems_to_move = list->collection.size - index;
size_t start_of_moved = index + n;
- if (CX_ARRAY_SUCCESS != cx_array_copy(
+ if (cx_array_copy(
&arl->data,
&list->collection.size,
&arl->capacity,
+ 0,
start_of_moved,
first_to_move,
list->collection.elem_size,
// therefore, it is impossible to leave this function with an invalid array
// place the new elements
- if (CX_ARRAY_SUCCESS == cx_array_copy(
+ if (cx_array_copy(
&arl->data,
&list->collection.size,
&arl->capacity,
+ 0,
index,
array,
list->collection.elem_size,
n,
&arl->reallocator
)) {
- return n;
- } else {
// array list implementation is "all or nothing"
return 0;
+ } else {
+ return n;
}
}
// get a correctly typed pointer to the list
cx_array_list *arl = (cx_array_list *) list;
- if (CX_ARRAY_SUCCESS == cx_array_insert_sorted(
+ if (cx_array_insert_sorted(
&arl->data,
&list->collection.size,
&arl->capacity,
n,
&arl->reallocator
)) {
- return n;
- } else {
// array list implementation is "all or nothing"
return 0;
+ } else {
+ return n;
}
}
}
}
-static int cx_arl_remove(
+static size_t cx_arl_remove(
struct cx_list_s *list,
- size_t index
+ size_t index,
+ size_t num,
+ void *targetbuf
) {
cx_array_list *arl = (cx_array_list *) list;
// out-of-bounds check
+ size_t remove;
if (index >= list->collection.size) {
- return 1;
+ remove = 0;
+ } else if (index + num > list->collection.size) {
+ remove = list->collection.size - index;
+ } else {
+ remove = num;
}
- // content destruction
- cx_invoke_destructor(list, ((char *) arl->data) + index * list->collection.elem_size);
+ // easy exit
+ if (remove == 0) return 0;
- // short-circuit removal of last element
- if (index == list->collection.size - 1) {
- list->collection.size--;
- return 0;
+ // destroy or copy contents
+ if (targetbuf == NULL) {
+ for (size_t idx = index; idx < index + remove; idx++) {
+ cx_invoke_destructor(
+ list,
+ ((char *) arl->data) + idx * list->collection.elem_size
+ );
+ }
+ } else {
+ memcpy(
+ targetbuf,
+ ((char *) arl->data) + index * list->collection.elem_size,
+ remove * list->collection.elem_size
+ );
}
- // just move the elements starting at index to the left
- int result = cx_array_copy(
+ // short-circuit removal of last elements
+ if (index + remove == list->collection.size) {
+ list->collection.size -= remove;
+ return remove;
+ }
+
+ // just move the elements to the left
+ cx_array_copy(
&arl->data,
&list->collection.size,
&arl->capacity,
+ 0,
index,
- ((char *) arl->data) + (index + 1) * list->collection.elem_size,
+ ((char *) arl->data) + (index + remove) * list->collection.elem_size,
list->collection.elem_size,
- list->collection.size - index - 1,
+ list->collection.size - index - remove,
&arl->reallocator
);
- // cx_array_copy cannot fail, array cannot grow
- assert(result == 0);
-
// decrease the size
- list->collection.size--;
+ list->collection.size -= remove;
- return 0;
+ return remove;
}
static void cx_arl_clear(struct cx_list_s *list) {
for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) {
if (0 == list->collection.cmpfunc(elem, cur)) {
if (remove) {
- if (0 == cx_arl_remove(list, i)) {
+ if (1 == cx_arl_remove(list, i, 1, NULL)) {
return i;
} else {
- return -1;
+ // should be unreachable
+ return -1; // LCOV_EXCL_LINE
}
} else {
return i;
struct cx_iterator_s *iter = it;
if (iter->base.remove) {
iter->base.remove = false;
- cx_arl_remove(iter->src_handle.m, iter->index);
+ cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
} else {
iter->index++;
iter->elem_handle =
const cx_array_list *list = iter->src_handle.c;
if (iter->base.remove) {
iter->base.remove = false;
- cx_arl_remove(iter->src_handle.m, iter->index);
+ cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
}
iter->index--;
if (iter->index < list->base.collection.size) {
// allocate the array after the real elem_size is known
list->data = cxCalloc(allocator, initial_capacity, elem_size);
- if (list->data == NULL) {
+ if (list->data == NULL) { // LCOV_EXCL_START
cxFree(allocator, list);
return NULL;
- }
+ } // LCOV_EXCL_STOP
// configure the reallocator
- list->reallocator.realloc = cx_arl_realloc;
- list->reallocator.ptr1 = (void *) allocator;
+ list->reallocator = cx_array_reallocator(allocator, NULL);
return (CxList *) list;
}
*/
#include "cx/buffer.h"
-#include "cx/utils.h"
#include <stdio.h>
#include <string.h>
+#include <errno.h>
+
+static int buffer_copy_on_write(CxBuffer* buffer) {
+ if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0;
+ void *newspace = cxMalloc(buffer->allocator, buffer->capacity);
+ if (NULL == newspace) return -1;
+ memcpy(newspace, buffer->space, buffer->size);
+ buffer->space = newspace;
+ buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE;
+ buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+ return 0;
+}
int cxBufferInit(
CxBuffer *buffer,
const CxAllocator *allocator,
int flags
) {
- if (allocator == NULL) allocator = cxDefaultAllocator;
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ if (flags & CX_BUFFER_COPY_ON_EXTEND) {
+ flags |= CX_BUFFER_AUTO_EXTEND;
+ }
buffer->allocator = allocator;
buffer->flags = flags;
if (!space) {
buffer->bytes = cxMalloc(allocator, capacity);
if (buffer->bytes == NULL) {
- return 1;
+ return -1; // LCOV_EXCL_LINE
}
buffer->flags |= CX_BUFFER_FREE_CONTENTS;
} else {
buffer->size = 0;
buffer->pos = 0;
- buffer->flush_func = NULL;
- buffer->flush_target = NULL;
- buffer->flush_blkmax = 0;
- buffer->flush_blksize = 4096;
- buffer->flush_threshold = SIZE_MAX;
+ buffer->flush = NULL;
return 0;
}
+int cxBufferEnableFlushing(
+ CxBuffer *buffer,
+ CxBufferFlushConfig config
+) {
+ buffer->flush = malloc(sizeof(CxBufferFlushConfig));
+ if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE
+ memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig));
+ return 0;
+}
+
void cxBufferDestroy(CxBuffer *buffer) {
- if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+ if (buffer->flags & CX_BUFFER_FREE_CONTENTS) {
cxFree(buffer->allocator, buffer->bytes);
}
+ free(buffer->flush);
+ memset(buffer, 0, sizeof(CxBuffer));
}
CxBuffer *cxBufferCreate(
const CxAllocator *allocator,
int flags
) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
if (buf == NULL) return NULL;
if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
return buf;
} else {
+ // LCOV_EXCL_START
cxFree(allocator, buf);
return NULL;
+ // LCOV_EXCL_STOP
}
}
void cxBufferFree(CxBuffer *buffer) {
- if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
- cxFree(buffer->allocator, buffer->bytes);
- }
- cxFree(buffer->allocator, buffer);
+ if (buffer == NULL) return;
+ const CxAllocator *allocator = buffer->allocator;
+ cxBufferDestroy(buffer);
+ cxFree(allocator, buffer);
}
int cxBufferSeek(
npos += offset;
if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
+ errno = EOVERFLOW;
return -1;
}
- if (npos >= buffer->size) {
+ if (npos > buffer->size) {
return -1;
} else {
buffer->pos = npos;
}
void cxBufferClear(CxBuffer *buffer) {
- memset(buffer->bytes, 0, buffer->size);
+ if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) {
+ memset(buffer->bytes, 0, buffer->size);
+ }
buffer->size = 0;
buffer->pos = 0;
}
buffer->pos = 0;
}
-int cxBufferEof(const CxBuffer *buffer) {
+bool cxBufferEof(const CxBuffer *buffer) {
return buffer->pos >= buffer->size;
}
return 0;
}
- if (cxReallocate(buffer->allocator,
+ const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND;
+ if (buffer->flags & force_copy_flags) {
+ void *newspace = cxMalloc(buffer->allocator, newcap);
+ if (NULL == newspace) return -1;
+ memcpy(newspace, buffer->space, buffer->size);
+ buffer->space = newspace;
+ buffer->capacity = newcap;
+ buffer->flags &= ~force_copy_flags;
+ buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+ return 0;
+ } else if (cxReallocate(buffer->allocator,
(void **) &buffer->bytes, newcap) == 0) {
buffer->capacity = newcap;
return 0;
} else {
- return -1;
+ return -1; // LCOV_EXCL_LINE
}
}
-/**
- * Helps flushing data to the flush target of a buffer.
- *
- * @param buffer the buffer containing the config
- * @param space the data to flush
- * @param size the element size
- * @param nitems the number of items
- * @return the number of items flushed
- */
-static size_t cx_buffer_write_flush_helper(
- CxBuffer *buffer,
- const unsigned char *space,
+static size_t cx_buffer_flush_helper(
+ const CxBuffer *buffer,
size_t size,
+ const unsigned char *src,
size_t nitems
) {
- size_t pos = 0;
- size_t remaining = nitems;
- size_t max_items = buffer->flush_blksize / size;
- while (remaining > 0) {
- size_t items = remaining > max_items ? max_items : remaining;
- size_t flushed = buffer->flush_func(
- space + pos,
- size, items,
- buffer->flush_target);
+ // flush data from an arbitrary source
+ // does not need to be the buffer's contents
+ size_t max_items = buffer->flush->blksize / size;
+ size_t fblocks = 0;
+ size_t flushed_total = 0;
+ while (nitems > 0 && fblocks < buffer->flush->blkmax) {
+ fblocks++;
+ size_t items = nitems > max_items ? max_items : nitems;
+ size_t flushed = buffer->flush->wfunc(
+ src, size, items, buffer->flush->target);
if (flushed > 0) {
- pos += (flushed * size);
- remaining -= flushed;
+ flushed_total += flushed;
+ src += flushed * size;
+ nitems -= flushed;
} else {
// if no bytes can be flushed out anymore, we give up
break;
}
}
- return nitems - remaining;
+ return flushed_total;
+}
+
+static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) {
+ // flush the current contents of the buffer
+ unsigned char *space = buffer->bytes;
+ size_t remaining = buffer->pos / size;
+ size_t flushed_total = cx_buffer_flush_helper(
+ buffer, size, space, remaining);
+
+ // shift the buffer left after flushing
+ // IMPORTANT: up to this point, copy on write must have been
+ // performed already, because we can't do error handling here
+ cxBufferShiftLeft(buffer, flushed_total*size);
+
+ return flushed_total;
+}
+
+size_t cxBufferFlush(CxBuffer *buffer) {
+ if (buffer_copy_on_write(buffer)) return 0;
+ return cx_buffer_flush_impl(buffer, 1);
}
size_t cxBufferWrite(
) {
// optimize for easy case
if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
+ if (buffer_copy_on_write(buffer)) return 0;
memcpy(buffer->bytes + buffer->pos, ptr, nitems);
buffer->pos += nitems;
if (buffer->pos > buffer->size) {
}
size_t len;
- size_t nitems_out = nitems;
if (cx_szmul(size, nitems, &len)) {
+ errno = EOVERFLOW;
return 0;
}
- size_t required = buffer->pos + len;
- if (buffer->pos > required) {
+ if (buffer->pos > SIZE_MAX - len) {
+ errno = EOVERFLOW;
return 0;
}
+ size_t required = buffer->pos + len;
bool perform_flush = false;
if (required > buffer->capacity) {
- if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) {
- if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) {
+ if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
+ if (buffer->flush != NULL && required > buffer->flush->threshold) {
perform_flush = true;
} else {
if (cxBufferMinimumCapacity(buffer, required)) {
- return 0;
+ return 0; // LCOV_EXCL_LINE
}
}
} else {
- if (buffer->flush_blkmax > 0) {
+ if (buffer->flush != NULL) {
perform_flush = true;
} else {
- // truncate data to be written, if we can neither extend nor flush
+ // truncate data, if we can neither extend nor flush
len = buffer->capacity - buffer->pos;
if (size > 1) {
len -= len % size;
}
- nitems_out = len / size;
+ nitems = len / size;
}
}
}
+ // check here and not above because of possible truncation
if (len == 0) {
- return len;
+ return 0;
}
+ // check if we need to copy
+ if (buffer_copy_on_write(buffer)) return 0;
+
+ // perform the operation
if (perform_flush) {
- size_t flush_max;
- if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) {
- return 0;
- }
- size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL
- ? buffer->pos
- : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos);
- if (flush_pos == buffer->pos) {
- // entire buffer has been flushed, we can reset
- buffer->size = buffer->pos = 0;
-
- size_t items_flush; // how many items can also be directly flushed
- size_t items_keep; // how many items have to be written to the buffer
-
- items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size;
- if (items_flush > 0) {
- items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size);
- // in case we could not flush everything, keep the rest
- }
- items_keep = nitems - items_flush;
- if (items_keep > 0) {
- // try again with the remaining stuff
- const unsigned char *new_ptr = ptr;
- new_ptr += items_flush * size;
- // report the directly flushed items as written plus the remaining stuff
- return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer);
- } else {
- // all items have been flushed - report them as written
- return nitems;
+ size_t items_flush;
+ if (buffer->pos == 0) {
+ // if we don't have data in the buffer, but are instructed
+ // to flush, it means that we are supposed to relay the data
+ items_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems);
+ if (items_flush == 0) {
+ // we needed to flush, but could not flush anything
+ // give up and avoid endless trying
+ return 0;
}
- } else if (flush_pos == 0) {
- // nothing could be flushed at all, we immediately give up without writing any data
- return 0;
+ size_t ritems = nitems - items_flush;
+ const unsigned char *rest = ptr;
+ rest += items_flush * size;
+ return items_flush + cxBufferWrite(rest, size, ritems, buffer);
} else {
- // we were partially successful, we shift left and try again
- cxBufferShiftLeft(buffer, flush_pos);
+ items_flush = cx_buffer_flush_impl(buffer, size);
+ if (items_flush == 0) {
+ return 0;
+ }
return cxBufferWrite(ptr, size, nitems, buffer);
}
} else {
if (buffer->pos > buffer->size) {
buffer->size = buffer->pos;
}
- return nitems_out;
+ return nitems;
}
}
+size_t cxBufferAppend(
+ const void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+) {
+ size_t pos = buffer->pos;
+ buffer->pos = buffer->size;
+ size_t written = cxBufferWrite(ptr, size, nitems, buffer);
+ buffer->pos = pos;
+ return written;
+}
+
int cxBufferPut(
CxBuffer *buffer,
int c
}
}
+int cxBufferTerminate(CxBuffer *buffer) {
+ bool success = 0 == cxBufferPut(buffer, 0);
+ if (success) {
+ buffer->pos--;
+ buffer->size--;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
size_t cxBufferPutString(
CxBuffer *buffer,
const char *str
) {
size_t len;
if (cx_szmul(size, nitems, &len)) {
+ errno = EOVERFLOW;
return 0;
}
if (buffer->pos + len > buffer->size) {
if (shift >= buffer->size) {
buffer->pos = buffer->size = 0;
} else {
+ if (buffer_copy_on_write(buffer)) return -1;
memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
buffer->size -= shift;
CxBuffer *buffer,
size_t shift
) {
+ if (buffer->size > SIZE_MAX - shift) {
+ errno = EOVERFLOW;
+ return -1;
+ }
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 & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) {
+ if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
if (cxBufferMinimumCapacity(buffer, req_capacity)) {
- return 1;
+ return -1; // LCOV_EXCL_LINE
}
movebytes = buffer->size;
} else {
movebytes = buffer->size;
}
- memmove(buffer->bytes + shift, buffer->bytes, movebytes);
- buffer->size = shift + movebytes;
+ if (movebytes > 0) {
+ if (buffer_copy_on_write(buffer)) return -1;
+ memmove(buffer->bytes + shift, buffer->bytes, movebytes);
+ buffer->size = shift + movebytes;
+ }
buffer->pos += shift;
if (buffer->pos > buffer->size) {
#include <math.h>
+int cx_vcmp_int(int a, int b) {
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
int cx_cmp_int(const void *i1, const void *i2) {
int a = *((const int *) i1);
int b = *((const int *) i2);
+ return cx_vcmp_int(a, b);
+}
+
+int cx_vcmp_longint(long int a, long int b) {
if (a == b) {
return 0;
} else {
int cx_cmp_longint(const void *i1, const void *i2) {
long int a = *((const long int *) i1);
long int b = *((const long int *) i2);
+ return cx_vcmp_longint(a, b);
+}
+
+int cx_vcmp_longlong(long long a, long long b) {
if (a == b) {
return 0;
} else {
int cx_cmp_longlong(const void *i1, const void *i2) {
long long a = *((const long long *) i1);
long long b = *((const long long *) i2);
+ return cx_vcmp_longlong(a, b);
+}
+
+int cx_vcmp_int16(int16_t a, int16_t b) {
if (a == b) {
return 0;
} else {
int cx_cmp_int16(const void *i1, const void *i2) {
int16_t a = *((const int16_t *) i1);
int16_t b = *((const int16_t *) i2);
+ return cx_vcmp_int16(a, b);
+}
+
+int cx_vcmp_int32(int32_t a, int32_t b) {
if (a == b) {
return 0;
} else {
int cx_cmp_int32(const void *i1, const void *i2) {
int32_t a = *((const int32_t *) i1);
int32_t b = *((const int32_t *) i2);
+ return cx_vcmp_int32(a, b);
+}
+
+int cx_vcmp_int64(int64_t a, int64_t b) {
if (a == b) {
return 0;
} else {
int cx_cmp_int64(const void *i1, const void *i2) {
int64_t a = *((const int64_t *) i1);
int64_t b = *((const int64_t *) i2);
+ return cx_vcmp_int64(a, b);
+}
+
+int cx_vcmp_uint(unsigned int a, unsigned int b) {
if (a == b) {
return 0;
} else {
int cx_cmp_uint(const void *i1, const void *i2) {
unsigned int a = *((const unsigned int *) i1);
unsigned int b = *((const unsigned int *) i2);
+ return cx_vcmp_uint(a, b);
+}
+
+int cx_vcmp_ulongint(unsigned long int a, unsigned long int b) {
if (a == b) {
return 0;
} else {
int cx_cmp_ulongint(const void *i1, const void *i2) {
unsigned long int a = *((const unsigned long int *) i1);
unsigned long int b = *((const unsigned long int *) i2);
+ return cx_vcmp_ulongint(a, b);
+}
+
+int cx_vcmp_ulonglong(unsigned long long a, unsigned long long b) {
if (a == b) {
return 0;
} else {
int cx_cmp_ulonglong(const void *i1, const void *i2) {
unsigned long long a = *((const unsigned long long *) i1);
unsigned long long b = *((const unsigned long long *) i2);
+ return cx_vcmp_ulonglong(a, b);
+}
+
+int cx_vcmp_uint16(uint16_t a, uint16_t b) {
if (a == b) {
return 0;
} else {
int cx_cmp_uint16(const void *i1, const void *i2) {
uint16_t a = *((const uint16_t *) i1);
uint16_t b = *((const uint16_t *) i2);
+ return cx_vcmp_uint16(a, b);
+}
+
+int cx_vcmp_uint32(uint32_t a, uint32_t b) {
if (a == b) {
return 0;
} else {
int cx_cmp_uint32(const void *i1, const void *i2) {
uint32_t a = *((const uint32_t *) i1);
uint32_t b = *((const uint32_t *) i2);
+ return cx_vcmp_uint32(a, b);
+}
+
+int cx_vcmp_uint64(uint64_t a, uint64_t b) {
if (a == b) {
return 0;
} else {
int cx_cmp_uint64(const void *i1, const void *i2) {
uint64_t a = *((const uint64_t *) i1);
uint64_t b = *((const uint64_t *) i2);
- if (a == b) {
+ return cx_vcmp_uint64(a, b);
+}
+
+int cx_vcmp_float(float a, float b) {
+ if (fabsf(a - b) < 1e-6f) {
return 0;
} else {
return a < b ? -1 : 1;
int cx_cmp_float(const void *f1, const void *f2) {
float a = *((const float *) f1);
float b = *((const float *) f2);
- if (fabsf(a - b) < 1e-6f) {
+ return cx_vcmp_float(a, b);
+}
+
+int cx_vcmp_double(double a, double b) {
+ if (fabs(a - b) < 1e-14) {
return 0;
} else {
return a < b ? -1 : 1;
) {
double a = *((const double *) d1);
double b = *((const double *) d2);
- if (fabs(a - b) < 1e-14) {
+ return cx_vcmp_double(a, b);
+}
+
+int cx_vcmp_intptr(intptr_t p1, intptr_t p2) {
+ if (p1 == p2) {
return 0;
} else {
- return a < b ? -1 : 1;
+ return p1 < p2 ? -1 : 1;
}
}
) {
intptr_t p1 = *(const intptr_t *) ptr1;
intptr_t p2 = *(const intptr_t *) ptr2;
+ return cx_vcmp_intptr(p1, p2);
+}
+
+int cx_vcmp_uintptr(uintptr_t p1, uintptr_t p2) {
if (p1 == p2) {
return 0;
} else {
) {
uintptr_t p1 = *(const uintptr_t *) ptr1;
uintptr_t p2 = *(const uintptr_t *) ptr2;
- if (p1 == p2) {
- return 0;
- } else {
- return p1 < p2 ? -1 : 1;
- }
+ return cx_vcmp_uintptr(p1, p2);
}
int cx_cmp_ptr(
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file allocator.h
+ * @file allocator.h
* Interface for custom allocators.
*/
/**
* The allocator's realloc() implementation.
*/
- __attribute__((__warn_unused_result__))
void *(*realloc)(
void *data,
void *mem,
/**
* The allocator's free() implementation.
*/
- __attribute__((__nonnull__))
void (*free)(
void *data,
void *mem
* Function pointer type for destructor functions.
*
* A destructor function deallocates possible contents and MAY free the memory
- * pointed to by \p memory. Read the documentation of the respective function
- * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
- * particular implementation.
+ * pointed to by @p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in
+ * that particular implementation.
*
* @param memory a pointer to the object to destruct
*/
-__attribute__((__nonnull__))
typedef void (*cx_destructor_func)(void *memory);
/**
* Function pointer type for destructor functions.
*
* A destructor function deallocates possible contents and MAY free the memory
- * pointed to by \p memory. Read the documentation of the respective function
- * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
- * particular implementation.
+ * pointed to by @p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in
+ * that particular implementation.
*
* @param data an optional pointer to custom data
* @param memory a pointer to the object to destruct
*/
-__attribute__((__nonnull__(2)))
typedef void (*cx_destructor_func2)(
void *data,
void *memory
);
/**
- * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
*
- * \par Error handling
- * \c errno will be set by realloc() on failure.
+ * @par Error handling
+ * @c errno will be set by realloc() on failure.
*
* @param mem pointer to the pointer to allocated block
* @param n the new size in bytes
- * @return zero on success, non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_reallocatearray()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_reallocate(
void **mem,
size_t n
);
/**
- * Allocate \p n bytes of memory.
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ *
+ * The size is calculated by multiplying @p nemb and @p size.
+ *
+ * @par Error handling
+ * @c errno will be set by realloc() on failure or when the multiplication of
+ * @p nmemb and @p size overflows.
+ *
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_reallocate()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+int cx_reallocatearray(
+ void **mem,
+ size_t nmemb,
+ size_t size
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ *
+ * @par Error handling
+ * @c errno will be set by realloc() on failure.
+ *
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param n (@c size_t) the new size in bytes
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_reallocatearray()
+ */
+#define cx_reallocate(mem, n) cx_reallocate((void**)(mem), n)
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ *
+ * The size is calculated by multiplying @p nemb and @p size.
+ *
+ * @par Error handling
+ * @c errno will be set by realloc() on failure or when the multiplication of
+ * @p nmemb and @p size overflows.
+ *
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param nmemb (@c size_t) the number of elements
+ * @param size (@c size_t) the size of each element
+ * @retval zero success
+ * @retval non-zero failure
+ */
+#define cx_reallocatearray(mem, nmemb, size) \
+ cx_reallocatearray((void**)(mem), nmemb, size)
+
+/**
+ * Free a block allocated by this allocator.
+ *
+ * @note Freeing a block of a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem a pointer to the block to free
+ */
+cx_attr_nonnull_arg(1)
+void cxFree(
+ const CxAllocator *allocator,
+ void *mem
+);
+
+/**
+ * Allocate @p n bytes of memory.
*
* @param allocator the allocator
* @param n the number of bytes
* @return a pointer to the allocated memory
*/
-__attribute__((__malloc__))
-__attribute__((__alloc_size__(2)))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_malloc
+cx_attr_dealloc_ucx
+cx_attr_allocsize(2)
void *cxMalloc(
const CxAllocator *allocator,
size_t n
);
/**
- * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long.
- * This function may return the same pointer that was passed to it, if moving the memory
- * was not necessary.
+ * Re-allocate the previously allocated block in @p mem, making the new block
+ * @p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving
+ * the memory was not necessary.
*
- * \note Re-allocating a block allocated by a different allocator is undefined.
+ * @note Re-allocating a block allocated by a different allocator is undefined.
*
* @param allocator the allocator
* @param mem pointer to the previously allocated block
* @param n the new size in bytes
* @return a pointer to the re-allocated memory
*/
-__attribute__((__warn_unused_result__))
-__attribute__((__alloc_size__(3)))
+cx_attr_nodiscard
+cx_attr_nonnull_arg(1)
+cx_attr_dealloc_ucx
+cx_attr_allocsize(3)
void *cxRealloc(
const CxAllocator *allocator,
void *mem,
);
/**
- * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
- * This function acts like cxRealloc() using the pointer pointed to by \p mem.
+ * Re-allocate the previously allocated block in @p mem, making the new block
+ * @p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving
+ * the memory was not necessary.
*
- * \note Re-allocating a block allocated by a different allocator is undefined.
+ * The size is calculated by multiplying @p nemb and @p size.
+ * If that multiplication overflows, this function returns @c NULL and @c errno
+ * will be set.
*
- * \par Error handling
- * \c errno will be set, if the underlying realloc function does so.
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the previously allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @return a pointer to the re-allocated memory
+ */
+cx_attr_nodiscard
+cx_attr_nonnull_arg(1)
+cx_attr_dealloc_ucx
+cx_attr_allocsize(3, 4)
+void *cxReallocArray(
+ const CxAllocator *allocator,
+ void *mem,
+ size_t nmemb,
+ size_t size
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so.
*
* @param allocator the allocator
* @param mem pointer to the pointer to allocated block
* @param n the new size in bytes
- * @return zero on success, non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
*/
-__attribute__((__nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
int cxReallocate(
const CxAllocator *allocator,
void **mem,
);
/**
- * Allocate \p nelem elements of \p n bytes each, all initialized to zero.
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so.
+ *
+ * @param allocator (@c CxAllocator*) the allocator
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param n (@c size_t) the new size in bytes
+ * @retval zero success
+ * @retval non-zero failure
+ */
+#define cxReallocate(allocator, mem, n) \
+ cxReallocate(allocator, (void**)(mem), n)
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxReallocArray() using the pointer pointed to
+ * by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so or the
+ * multiplication of @p nmemb and @p size overflows.
*
* @param allocator the allocator
- * @param nelem the number of elements
- * @param n the size of each element in bytes
- * @return a pointer to the allocated memory
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @retval zero success
+ * @retval non-zero on failure
*/
-__attribute__((__malloc__))
-__attribute__((__alloc_size__(2, 3)))
-void *cxCalloc(
+cx_attr_nodiscard
+cx_attr_nonnull
+int cxReallocateArray(
const CxAllocator *allocator,
- size_t nelem,
- size_t n
+ void **mem,
+ size_t nmemb,
+ size_t size
);
/**
- * Free a block allocated by this allocator.
+ * Re-allocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxReallocArray() using the pointer pointed to
+ * by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
*
- * \note Freeing a block of a different allocator is undefined.
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so or the
+ * multiplication of @p nmemb and @p size overflows.
+ *
+ * @param allocator (@c CxAllocator*) the allocator
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param nmemb (@c size_t) the number of elements
+ * @param size (@c size_t) the size of each element
+ * @retval zero success
+ * @retval non-zero failure
+ */
+#define cxReallocateArray(allocator, mem, nmemb, size) \
+ cxReallocateArray(allocator, (void**) (mem), nmemb, size)
+
+/**
+ * Allocate @p nelem elements of @p n bytes each, all initialized to zero.
*
* @param allocator the allocator
- * @param mem a pointer to the block to free
+ * @param nelem the number of elements
+ * @param n the size of each element in bytes
+ * @return a pointer to the allocated memory
*/
-__attribute__((__nonnull__))
-void cxFree(
+cx_attr_nonnull_arg(1)
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc_ucx
+cx_attr_allocsize(2, 3)
+void *cxCalloc(
const CxAllocator *allocator,
- void *mem
+ size_t nelem,
+ size_t n
);
#ifdef __cplusplus
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file array_list.h
- * \brief Array list implementation.
- * \details Also provides several low-level functions for custom array list implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file array_list.h
+ * @brief Array list implementation.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#endif
/**
- * The maximum item size in an array list that fits into stack buffer when swapped.
+ * The maximum item size in an array list that fits into stack buffer
+ * when swapped.
*/
-extern unsigned cx_array_swap_sbo_size;
+extern const unsigned cx_array_swap_sbo_size;
/**
* Declares variables for an array that can be used with the convenience macros.
*
+ * @par Examples
+ * @code
+ * // integer array with at most 255 elements
+ * CX_ARRAY_DECLARE_SIZED(int, myarray, uint8_t)
+ *
+ * // array of MyObject* pointers where size and capacity are stored as unsigned int
+ * CX_ARRAY_DECLARE_SIZED(MyObject*, objects, unsigned int)
+ *
+ * // initializing code
+ * cx_array_initialize(myarray, 16); // reserve space for 16
+ * cx_array_initialize(objects, 100); // reserve space for 100
+ * @endcode
+ *
+ * @param type the type of the data
+ * @param name the name of the array
+ * @param size_type the type of the size (should be uint8_t, uint16_t, uint32_t, or size_t)
+ *
+ * @see cx_array_initialize()
* @see cx_array_simple_add()
* @see cx_array_simple_copy()
+ * @see cx_array_simple_add_sorted()
+ * @see cx_array_simple_insert_sorted()
+ */
+#define CX_ARRAY_DECLARE_SIZED(type, name, size_type) \
+ type * name; \
+ /** Array size. */ size_type name##_size; \
+ /** Array capacity. */ size_type name##_capacity
+
+/**
+ * Declares variables for an array that can be used with the convenience macros.
+ *
+ * The size and capacity variables will have @c size_t type.
+ * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type.
+ *
+ * @par Examples
+ * @code
+ * // int array
+ * CX_ARRAY_DECLARE(int, myarray)
+ *
+ * // initializing code
+ * cx_array_initialize(myarray, 32); // reserve space for 32
+ * @endcode
+ *
+ * @param type the type of the data
+ * @param name the name of the array
+ *
* @see cx_array_initialize()
+ * @see cx_array_simple_add()
+ * @see cx_array_simple_copy()
* @see cx_array_simple_add_sorted()
* @see cx_array_simple_insert_sorted()
*/
-#define CX_ARRAY_DECLARE(type, name) \
- type * name; \
- size_t name##_size; \
- size_t name##_capacity
+#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t)
/**
- * Initializes an array declared with CX_ARRAY_DECLARE().
+ * Initializes an array with the given capacity.
+ *
+ * The type of the capacity depends on the type used during declaration.
+ *
+ * @par Examples
+ * @code
+ * CX_ARRAY_DECLARE_SIZED(int, arr1, uint8_t)
+ * CX_ARRAY_DECLARE(int, arr2) // size and capacity are implicitly size_t
+ *
+ * // initializing code
+ * cx_array_initialize(arr1, 500); // error: maximum for uint8_t is 255
+ * cx_array_initialize(arr2, 500); // OK
+ * @endcode
+ *
*
* The memory for the array is allocated with stdlib malloc().
- * @param array the array
+ * @param array the name of the array
* @param capacity the initial capacity
+ * @see cx_array_initialize_a()
+ * @see CX_ARRAY_DECLARE_SIZED()
+ * @see CX_ARRAY_DECLARE()
*/
#define cx_array_initialize(array, capacity) \
array##_capacity = capacity; \
array##_size = 0; \
array = malloc(sizeof(array[0]) * capacity)
+/**
+ * Initializes an array with the given capacity using the specified allocator.
+ *
+ * @par Example
+ * @code
+ * CX_ARRAY_DECLARE(int, myarray)
+ *
+ *
+ * const CxAllocator *al = // ...
+ * cx_array_initialize_a(al, myarray, 128);
+ * // ...
+ * cxFree(al, myarray); // don't forget to free with same allocator
+ * @endcode
+ *
+ * The memory for the array is allocated with stdlib malloc().
+ * @param allocator (@c CxAllocator*) the allocator
+ * @param array the name of the array
+ * @param capacity the initial capacity
+ * @see cx_array_initialize()
+ * @see CX_ARRAY_DECLARE_SIZED()
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_initialize_a(allocator, array, capacity) \
+ array##_capacity = capacity; \
+ array##_size = 0; \
+ array = cxMalloc(allocator, sizeof(array[0]) * capacity)
+
/**
* Defines a reallocation mechanism for arrays.
+ * You can create your own, use cx_array_reallocator(), or
+ * use the #cx_array_default_reallocator.
*/
struct cx_array_reallocator_s {
/**
* @param capacity the new capacity (number of elements)
* @param elem_size the size of each element
* @param alloc a reference to this allocator
- * @return a pointer to the reallocated memory or \c NULL on failure
+ * @return a pointer to the reallocated memory or @c NULL on failure
*/
+ cx_attr_nodiscard
+ cx_attr_nonnull_arg(4)
+ cx_attr_allocsize(2, 3)
void *(*realloc)(
void *array,
size_t capacity,
size_t int2;
};
+/**
+ * Typedef for the array reallocator struct.
+ */
+typedef struct cx_array_reallocator_s CxArrayReallocator;
+
/**
* A default stdlib-based array reallocator.
*/
-extern struct cx_array_reallocator_s *cx_array_default_reallocator;
+extern CxArrayReallocator *cx_array_default_reallocator;
/**
- * Return codes for array functions.
+ * Creates a new array reallocator.
+ *
+ * When @p allocator is @c NULL, the stdlib default allocator will be used.
+ *
+ * When @p stackmem is not @c NULL, the reallocator is supposed to be used
+ * @em only for the specific array that is initially located at @p stackmem.
+ * When reallocation is needed, the reallocator checks, if the array is
+ * still located at @p stackmem and copies the contents to the heap.
+ *
+ * @note Invoking this function with both arguments @c NULL will return a
+ * reallocator that behaves like #cx_array_default_reallocator.
+ *
+ * @param allocator the allocator this reallocator shall be based on
+ * @param stackmem the address of the array when the array is initially located
+ * on the stack or shall not reallocated in place
+ * @return an array reallocator
*/
-enum cx_array_result {
- CX_ARRAY_SUCCESS,
- CX_ARRAY_REALLOC_NOT_SUPPORTED,
- CX_ARRAY_REALLOC_FAILED,
-};
+CxArrayReallocator cx_array_reallocator(
+ const struct cx_allocator_s *allocator,
+ const void *stackmem
+);
+
+/**
+ * Reserves memory for additional elements.
+ *
+ * This function checks if the @p capacity of the array is sufficient to hold
+ * at least @p size plus @p elem_count elements. If not, a reallocation is
+ * performed with the specified @p reallocator.
+ * You can create your own reallocator by hand, use #cx_array_default_reallocator,
+ * or use the convenience function cx_array_reallocator() to create a custom reallocator.
+ *
+ * This function can be useful to replace subsequent calls to cx_array_copy()
+ * with one single cx_array_reserve() and then - after guaranteeing a
+ * sufficient capacity - use simple memmove() or memcpy().
+ *
+ * The @p width in bytes refers to the size and capacity.
+ * Both must have the same width.
+ * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit
+ * architecture. If set to zero, the native word width is used.
+ *
+ * @param array a pointer to the target array
+ * @param size a pointer to the size of the array
+ * @param capacity a pointer to the capacity of the array
+ * @param width the width in bytes for the @p size and @p capacity or zero for default
+ * @param elem_size the size of one element
+ * @param elem_count the number of expected additional elements
+ * @param reallocator the array reallocator to use
+ * (@c NULL defaults to #cx_array_default_reallocator)
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_array_reallocator()
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+int cx_array_reserve(
+ void **array,
+ void *size,
+ void *capacity,
+ unsigned width,
+ size_t elem_size,
+ size_t elem_count,
+ CxArrayReallocator *reallocator
+);
/**
* Copies elements from one array to another.
*
- * The elements are copied to the \p target array at the specified \p index,
- * overwriting possible elements. The \p index does not need to be in range of
- * the current array \p size. If the new index plus the number of elements added
- * would extend the array's size, and \p capacity is not \c NULL, the remaining
- * capacity is used.
+ * The elements are copied to the @p target array at the specified @p index,
+ * overwriting possible elements. The @p index does not need to be in range of
+ * the current array @p size. If the new index plus the number of elements added
+ * would extend the array's size, the remaining @p capacity is used.
*
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made, unless the \p reallocator is set to \c NULL, in which case
- * this function ultimately returns a failure.
+ * If the @p capacity is also insufficient to hold the new data, a reallocation
+ * attempt is made with the specified @p reallocator.
+ * You can create your own reallocator by hand, use #cx_array_default_reallocator,
+ * or use the convenience function cx_array_reallocator() to create a custom reallocator.
+ *
+ * The @p width in bytes refers to the size and capacity.
+ * Both must have the same width.
+ * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit
+ * architecture. If set to zero, the native word width is used.
*
* @param target a pointer to the target array
* @param size a pointer to the size of the target array
- * @param capacity a pointer to the target array's capacity -
- * \c NULL if only the size shall be used to bound the array (reallocations
- * will NOT be supported in that case)
+ * @param capacity a pointer to the capacity of the target array
+ * @param width the width in bytes for the @p size and @p capacity or zero for default
* @param index the index where the copied elements shall be placed
* @param src the source array
* @param elem_size the size of one element
* @param elem_count the number of elements to copy
- * @param reallocator the array reallocator to use, or \c NULL
- * if reallocation shall not happen
- * @return zero on success, non-zero error code on failure
+ * @param reallocator the array reallocator to use
+ * (@c NULL defaults to #cx_array_default_reallocator)
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_array_reallocator()
*/
-__attribute__((__nonnull__(1, 2, 5)))
-enum cx_array_result cx_array_copy(
+cx_attr_nonnull_arg(1, 2, 3, 6)
+int cx_array_copy(
void **target,
- size_t *size,
- size_t *capacity,
+ void *size,
+ void *capacity,
+ unsigned width,
size_t index,
const void *src,
size_t elem_size,
size_t elem_count,
- struct cx_array_reallocator_s *reallocator
+ CxArrayReallocator *reallocator
);
/**
- * Convenience macro that uses cx_array_copy() with a default layout and the default reallocator.
- *
- * @param array the name of the array (NOT a pointer to the array)
- * @param index the index where the copied elements shall be placed
- * @param src the source array
- * @param count the number of elements to copy
+ * Convenience macro that uses cx_array_copy() with a default layout and
+ * the specified reallocator.
+ *
+ * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param index (@c size_t) the index where the copied elements shall be placed
+ * @param src (@c void*) the source array
+ * @param count (@c size_t) the number of elements to copy
+ * @retval zero success
+ * @retval non-zero failure
* @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_copy()
*/
-#define cx_array_simple_copy(array, index, src, count) \
+#define cx_array_simple_copy_a(reallocator, array, index, src, count) \
cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \
- index, src, sizeof((array)[0]), count, cx_array_default_reallocator)
+ sizeof(array##_size), index, src, sizeof((array)[0]), count, \
+ reallocator)
/**
- * Adds an element to an array with the possibility of allocating more space.
+ * Convenience macro that uses cx_array_copy() with a default layout and
+ * the default reallocator.
*
- * The element \p elem is added to the end of the \p target array which containing
- * \p size elements, already. The \p capacity must not be \c NULL and point a
- * variable holding the current maximum number of elements the array can hold.
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param index (@c size_t) the index where the copied elements shall be placed
+ * @param src (@c void*) the source array
+ * @param count (@c size_t) the number of elements to copy
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_copy_a()
+ */
+#define cx_array_simple_copy(array, index, src, count) \
+ cx_array_simple_copy_a(NULL, array, index, src, count)
+
+/**
+ * Convenience macro that uses cx_array_reserve() with a default layout and
+ * the specified reallocator.
+ *
+ * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param count (@c size_t) the number of expected @em additional elements
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_reserve()
+ */
+#define cx_array_simple_reserve_a(reallocator, array, count) \
+ cx_array_reserve((void**)&(array), &(array##_size), &(array##_capacity), \
+ sizeof(array##_size), sizeof((array)[0]), count, \
+ reallocator)
+
+/**
+ * Convenience macro that uses cx_array_reserve() with a default layout and
+ * the default reallocator.
*
- * If the capacity is insufficient to hold the new element, and the optional
- * \p reallocator is not \c NULL, an attempt increase the \p capacity is made
- * and the new capacity is written back.
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param count (@c size_t) the number of expected additional elements
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_reserve_a()
+ */
+#define cx_array_simple_reserve(array, count) \
+ cx_array_simple_reserve_a(NULL, array, count)
+
+/**
+ * Adds an element to an array with the possibility of allocating more space.
*
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the target array's capacity - must not be \c NULL
- * @param elem_size the size of one element
- * @param elem a pointer to the element to add
- * @param reallocator the array reallocator to use, or \c NULL if reallocation shall not happen
- * @return zero on success, non-zero error code on failure
+ * The element @p elem is added to the end of the @p target array which contains
+ * @p size elements, already. The @p capacity must point to a variable denoting
+ * the current maximum number of elements the array can hold.
+ *
+ * If the capacity is insufficient to hold the new element, an attempt to
+ * increase the @p capacity is made and the new capacity is written back.
+ *
+ * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
+ * It is important, however, that @p size and @p capacity are pointers to
+ * variables of the same type.
+ *
+ * @param target (@c void**) a pointer to the target array
+ * @param size (@c SIZE_TYPE*) a pointer to the size of the target array
+ * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array
+ * @param elem_size (@c size_t) the size of one element
+ * @param elem (@c void*) a pointer to the element to add
+ * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @retval zero success
+ * @retval non-zero failure
*/
#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \
- cx_array_copy((void**)(target), size, capacity, *(size), elem, elem_size, 1, reallocator)
+ cx_array_copy((void**)(target), size, capacity, sizeof(*(size)), \
+ *(size), elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro that uses cx_array_add() with a default layout and
+ * the specified reallocator.
+ *
+ * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_add()
+ */
+#define cx_array_simple_add_a(reallocator, array, elem) \
+ cx_array_simple_copy_a(reallocator, array, array##_size, &(elem), 1)
/**
* Convenience macro that uses cx_array_add() with a default layout and
* the default reallocator.
*
- * @param array the name of the array (NOT a pointer to the array)
+ * @param array the name of the array (NOT a pointer or alias to the array)
* @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @retval zero success
+ * @retval non-zero failure
* @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_add_a()
*/
#define cx_array_simple_add(array, elem) \
- cx_array_simple_copy(array, array##_size, &(elem), 1)
-
+ cx_array_simple_add_a(cx_array_default_reallocator, array, elem)
/**
* Inserts a sorted array into another sorted array.
*
* If either the target or the source array is not already sorted with respect
- * to the specified \p cmp_func, the behavior is undefined.
+ * to the specified @p cmp_func, the behavior is undefined.
*
* If the capacity is insufficient to hold the new data, a reallocation
* attempt is made.
+ * You can create your own reallocator by hand, use #cx_array_default_reallocator,
+ * or use the convenience function cx_array_reallocator() to create a custom reallocator.
*
* @param target a pointer to the target array
* @param size a pointer to the size of the target array
- * @param capacity a pointer to the target array's capacity
+ * @param capacity a pointer to the capacity of the target array
* @param cmp_func the compare function for the elements
* @param src the source array
* @param elem_size the size of one element
* @param elem_count the number of elements to insert
* @param reallocator the array reallocator to use
- * @return zero on success, non-zero error code on failure
+ * (@c NULL defaults to #cx_array_default_reallocator)
+ * @retval zero success
+ * @retval non-zero failure
*/
-__attribute__((__nonnull__))
-enum cx_array_result cx_array_insert_sorted(
+cx_attr_nonnull_arg(1, 2, 3, 5)
+int cx_array_insert_sorted(
void **target,
size_t *size,
size_t *capacity,
const void *src,
size_t elem_size,
size_t elem_count,
- struct cx_array_reallocator_s *reallocator
+ CxArrayReallocator *reallocator
);
/**
* Inserts an element into a sorted array.
*
* If the target array is not already sorted with respect
- * to the specified \p cmp_func, the behavior is undefined.
+ * to the specified @p cmp_func, the behavior is undefined.
*
* If the capacity is insufficient to hold the new data, a reallocation
* attempt is made.
*
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the target array's capacity
- * @param elem_size the size of one element
- * @param elem a pointer to the element to add
- * @param reallocator the array reallocator to use
- * @return zero on success, non-zero error code on failure
+ * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
+ * It is important, however, that @p size and @p capacity are pointers to
+ * variables of the same type.
+ *
+ * @param target (@c void**) a pointer to the target array
+ * @param size (@c SIZE_TYPE*) a pointer to the size of the target array
+ * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array
+ * @param elem_size (@c size_t) the size of one element
+ * @param elem (@c void*) a pointer to the element to add
+ * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @retval zero success
+ * @retval non-zero failure
*/
#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
+/**
+ * Convenience macro for cx_array_add_sorted() with a default
+ * layout and the specified reallocator.
+ *
+ * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_add_sorted()
+ */
+#define cx_array_simple_add_sorted_a(reallocator, array, elem, cmp_func) \
+ cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \
+ sizeof((array)[0]), &(elem), cmp_func, reallocator)
+
/**
* Convenience macro for cx_array_add_sorted() with a default
* layout and the default reallocator.
*
- * @param array the name of the array (NOT a pointer to the array)
+ * @param array the name of the array (NOT a pointer or alias to the array)
* @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func the compare function for the elements
+ * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @retval zero success
+ * @retval non-zero failure
* @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_add_sorted_a()
*/
#define cx_array_simple_add_sorted(array, elem, cmp_func) \
- cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \
- sizeof((array)[0]), &(elem), cmp_func, cx_array_default_reallocator)
+ cx_array_simple_add_sorted_a(NULL, array, elem, cmp_func)
+
+/**
+ * Convenience macro for cx_array_insert_sorted() with a default
+ * layout and the specified reallocator.
+ *
+ * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param src (@c void*) pointer to the source array
+ * @param n (@c size_t) number of elements in the source array
+ * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_insert_sorted()
+ */
+#define cx_array_simple_insert_sorted_a(reallocator, array, src, n, cmp_func) \
+ cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
+ cmp_func, src, sizeof((array)[0]), n, reallocator)
/**
* Convenience macro for cx_array_insert_sorted() with a default
* layout and the default reallocator.
*
- * @param array the name of the array (NOT a pointer to the array)
- * @param src pointer to the source array
- * @param n number of elements in the source array
- * @param cmp_func the compare function for the elements
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param src (@c void*) pointer to the source array
+ * @param n (@c size_t) number of elements in the source array
+ * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @retval zero success
+ * @retval non-zero failure
* @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_insert_sorted_a()
*/
#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \
- cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
- cmp_func, src, sizeof((array)[0]), n, cx_array_default_reallocator)
-
+ cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func)
/**
* Searches the largest lower bound in a sorted array.
*
* In other words, this function returns the index of the largest element
- * in \p arr that is less or equal to \p elem with respect to \p cmp_func.
- * When no such element exists, \p size is returned.
+ * in @p arr that is less or equal to @p elem with respect to @p cmp_func.
+ * When no such element exists, @p size is returned.
*
- * If \p elem is contained in the array, this is identical to
+ * If @p elem is contained in the array, this is identical to
* #cx_array_binary_search().
*
- * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
* is undefined.
*
* @param arr the array to search
* @param elem_size the size of one element
* @param elem the element to find
* @param cmp_func the compare function
- * @return the index of the largest lower bound, or \p size
+ * @return the index of the largest lower bound, or @p size
+ * @see cx_array_binary_search_sup()
+ * @see cx_array_binary_search()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
size_t cx_array_binary_search_inf(
const void *arr,
size_t size,
/**
* Searches an item in a sorted array.
*
- * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
* is undefined.
*
* @param arr the array to search
* @param elem_size the size of one element
* @param elem the element to find
* @param cmp_func the compare function
- * @return the index of the element in the array, or \p size if the element
+ * @return the index of the element in the array, or @p size if the element
* cannot be found
+ * @see cx_array_binary_search_inf()
+ * @see cx_array_binary_search_sup()
*/
-__attribute__((__nonnull__))
-static inline size_t cx_array_binary_search(
+cx_attr_nonnull
+size_t cx_array_binary_search(
const void *arr,
size_t size,
size_t elem_size,
const void *elem,
cx_compare_func cmp_func
-) {
- size_t index = cx_array_binary_search_inf(
- arr, size, elem_size, elem, cmp_func
- );
- if (index < size && cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
- return index;
- } else {
- return size;
- }
-}
+);
/**
* Searches the smallest upper bound in a sorted array.
*
* In other words, this function returns the index of the smallest element
- * in \p arr that is greater or equal to \p elem with respect to \p cmp_func.
- * When no such element exists, \p size is returned.
+ * in @p arr that is greater or equal to @p elem with respect to @p cmp_func.
+ * When no such element exists, @p size is returned.
*
- * If \p elem is contained in the array, this is identical to
+ * If @p elem is contained in the array, this is identical to
* #cx_array_binary_search().
*
- * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
* is undefined.
*
* @param arr the array to search
* @param elem_size the size of one element
* @param elem the element to find
* @param cmp_func the compare function
- * @return the index of the smallest upper bound, or \p size
+ * @return the index of the smallest upper bound, or @p size
+ * @see cx_array_binary_search_inf()
+ * @see cx_array_binary_search()
*/
-__attribute__((__nonnull__))
-static inline size_t cx_array_binary_search_sup(
+cx_attr_nonnull
+size_t cx_array_binary_search_sup(
const void *arr,
size_t size,
size_t elem_size,
const void *elem,
cx_compare_func cmp_func
-) {
- size_t inf = cx_array_binary_search_inf(arr, size, elem_size, elem, cmp_func);
- if (inf == size) {
- // no infimum means, first element is supremum
- return 0;
- } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
- return inf;
- } else {
- return inf + 1;
- }
-}
+);
/**
* Swaps two array elements.
* @param idx1 index of first element
* @param idx2 index of second element
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cx_array_swap(
void *arr,
size_t elem_size,
);
/**
- * Allocates an array list for storing elements with \p elem_size bytes each.
+ * Allocates an array list for storing elements with @p elem_size bytes each.
*
- * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if
* cxListStorePointers() was called immediately after creation and the compare
* function will be automatically set to cx_cmp_ptr(), if none is given.
*
* @param allocator the allocator for allocating the list memory
- * (if \c NULL the cxDefaultAllocator will be used)
+ * (if @c NULL, a default stdlib allocator will be used)
* @param comparator the comparator for the elements
- * (if \c NULL, and the list is not storing pointers, sort and find
+ * (if @c NULL, and the list is not storing pointers, sort and find
* functions will not work)
* @param elem_size the size of each element in bytes
* @param initial_capacity the initial number of elements the array can store
* @return the created list
*/
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxListFree, 1)
CxList *cxArrayListCreate(
const CxAllocator *allocator,
cx_compare_func comparator,
);
/**
- * Allocates an array list for storing elements with \p elem_size bytes each.
+ * Allocates an array list for storing elements with @p elem_size bytes each.
*
- * The list will use the cxDefaultAllocator and \em NO compare function.
+ * The list will use the cxDefaultAllocator and @em NO compare function.
* If you want to call functions that need a compare function, you have to
* set it immediately after creation or use cxArrayListCreate().
*
- * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if
* cxListStorePointers() was called immediately after creation and the compare
* function will be automatically set to cx_cmp_ptr().
*
- * @param elem_size the size of each element in bytes
- * @param initial_capacity the initial number of elements the array can store
+ * @param elem_size (@c size_t) the size of each element in bytes
+ * @param initial_capacity (@c size_t) the initial number of elements the array can store
* @return the created list
*/
#define cxArrayListCreateSimple(elem_size, initial_capacity) \
*/
/**
- * \file buffer.h
+ * @file buffer.h
*
- * \brief Advanced buffer implementation.
+ * @brief Advanced buffer implementation.
*
* Instances of CxBuffer can be used to read from or to write to like one
* would do with a stream.
* can be enabled. See the documentation of the macro constants for more
* information.
*
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_BUFFER_H
#include "common.h"
#include "allocator.h"
-#ifdef __cplusplus
+#ifdef __cplusplus
extern "C" {
#endif
/**
* If this flag is enabled, the buffer will automatically free its contents when destroyed.
+ *
+ * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically
+ * set when the copy-on-write operations is performed.
*/
#define CX_BUFFER_FREE_CONTENTS 0x01
/**
- * If this flag is enabled, the buffer will automatically extends its capacity.
+ * If this flag is enabled, the buffer will automatically extend its capacity.
*/
#define CX_BUFFER_AUTO_EXTEND 0x02
+/**
+ * If this flag is enabled, the buffer will allocate new memory when written to.
+ *
+ * The current contents of the buffer will be copied to the new memory and the flag
+ * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically.
+ */
+#define CX_BUFFER_COPY_ON_WRITE 0x04
+
+/**
+ * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation.
+ *
+ * After performing the copy, the flag is automatically cleared.
+ * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why
+ * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled.
+ */
+#define CX_BUFFER_COPY_ON_EXTEND 0x08
+
+/**
+ * Configuration for automatic flushing.
+ */
+struct cx_buffer_flush_config_s {
+ /**
+ * The buffer may not extend beyond this threshold before starting to flush.
+ *
+ * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND.
+ * The threshold will be the maximum capacity the buffer is extended to
+ * before flushing.
+ */
+ size_t threshold;
+ /**
+ * The block size for the elements to flush.
+ */
+ size_t blksize;
+ /**
+ * The maximum number of blocks to flush in one cycle.
+ *
+ * @attention while it is guaranteed that cxBufferFlush() will not flush
+ * more blocks, this is not necessarily the case for cxBufferWrite().
+ * After performing a flush cycle, cxBufferWrite() will retry the write
+ * operation and potentially trigger another flush cycle, until the
+ * flush target accepts no more data.
+ */
+ size_t blkmax;
+
+ /**
+ * The target for write function.
+ */
+ void *target;
+
+ /**
+ * The write-function used for flushing.
+ * If NULL, the flushed content gets discarded.
+ */
+ cx_write_func wfunc;
+};
+
+/**
+ * Type alais for the flush configuration struct.
+ *
+ * @code
+ * struct cx_buffer_flush_config_s {
+ * size_t threshold;
+ * size_t blksize;
+ * size_t blkmax;
+ * void *target;
+ * cx_write_func wfunc;
+ * };
+ * @endcode
+ */
+typedef struct cx_buffer_flush_config_s CxBufferFlushConfig;
+
/** Structure for the UCX buffer data. */
-typedef struct {
+struct cx_buffer_s {
/** A pointer to the buffer contents. */
union {
/**
};
/** The allocator to use for automatic memory management. */
const CxAllocator *allocator;
+ /**
+ * Optional flush configuration
+ *
+ * @see cxBufferEnableFlushing()
+ */
+ CxBufferFlushConfig* flush;
/** Current position of the buffer. */
size_t pos;
/** Current capacity (i.e. maximum size) of the buffer. */
size_t capacity;
/** Current size of the buffer content. */
size_t size;
- /**
- * The buffer may not extend beyond this threshold before starting to flush.
- * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled).
- */
- size_t flush_threshold;
- /**
- * The block size for the elements to flush.
- * Default is 4096 bytes.
- */
- size_t flush_blksize;
- /**
- * The maximum number of blocks to flush in one cycle.
- * Zero disables flushing entirely (this is the default).
- * Set this to \c SIZE_MAX to flush the entire buffer.
- *
- * @attention if the maximum number of blocks multiplied with the block size
- * is smaller than the expected contents written to this buffer within one write
- * operation, multiple flush cycles are performed after that write.
- * That means the total number of blocks flushed after one write to this buffer may
- * be larger than \c flush_blkmax.
- */
- size_t flush_blkmax;
-
- /**
- * The write function used for flushing.
- * If NULL, the flushed content gets discarded.
- */
- cx_write_func flush_func;
-
- /**
- * The target for \c flush_func.
- */
- void *flush_target;
-
/**
* Flag register for buffer features.
* @see #CX_BUFFER_DEFAULT
* @see #CX_BUFFER_FREE_CONTENTS
* @see #CX_BUFFER_AUTO_EXTEND
+ * @see #CX_BUFFER_COPY_ON_WRITE
*/
int flags;
-} cx_buffer_s;
+};
/**
* UCX buffer.
*/
-typedef cx_buffer_s CxBuffer;
+typedef struct cx_buffer_s CxBuffer;
/**
* Initializes a fresh buffer.
*
- * \note You may provide \c NULL as argument for \p space.
+ * You may also provide a read-only @p space, in which case
+ * you will need to cast the pointer, and you should set the
+ * #CX_BUFFER_COPY_ON_WRITE flag.
+ *
+ * You need to set the size manually after initialization, if
+ * you provide @p space which already contains data.
+ *
+ * When you specify stack memory as @p space and decide to use
+ * the auto-extension feature, you @em must use the
+ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
+ * #CX_BUFFER_AUTO_EXTEND flag.
+ *
+ * @note You may provide @c NULL as argument for @p space.
* Then this function will allocate the space and enforce
- * the #CX_BUFFER_FREE_CONTENTS flag.
+ * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying
+ * copy-on-write should be avoided, because the allocated
+ * space will be leaking after the copy-on-write operation.
*
* @param buffer the buffer to initialize
- * @param space pointer to the memory area, or \c NULL to allocate
+ * @param space pointer to the memory area, or @c NULL to allocate
* new memory
* @param capacity the capacity of the buffer
* @param allocator the allocator this buffer shall use for automatic
- * memory management. If \c NULL, the default heap allocator will be used.
+ * memory management
+ * (if @c NULL, a default stdlib allocator will be used)
* @param flags buffer features (see cx_buffer_s.flags)
* @return zero on success, non-zero if a required allocation failed
*/
-__attribute__((__nonnull__(1)))
+cx_attr_nonnull_arg(1)
int cxBufferInit(
CxBuffer *buffer,
void *space,
);
/**
- * Allocates and initializes a fresh buffer.
+ * Configures the buffer for flushing.
*
- * \note You may provide \c NULL as argument for \p space.
- * Then this function will allocate the space and enforce
- * the #CX_BUFFER_FREE_CONTENTS flag.
+ * Flushing can happen automatically when data is written
+ * to the buffer (see cxBufferWrite()) or manually when
+ * cxBufferFlush() is called.
*
- * @param space pointer to the memory area, or \c NULL to allocate
- * new memory
- * @param capacity the capacity of the buffer
- * @param allocator the allocator to use for allocating the structure and the automatic
- * memory management within the buffer. If \c NULL, the default heap allocator will be used.
- * @param flags buffer features (see cx_buffer_s.flags)
- * @return a pointer to the buffer on success, \c NULL if a required allocation failed
+ * @param buffer the buffer
+ * @param config the flush configuration
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cxBufferFlush()
+ * @see cxBufferWrite()
*/
-CxBuffer *cxBufferCreate(
- void *space,
- size_t capacity,
- const CxAllocator *allocator,
- int flags
+cx_attr_nonnull
+int cxBufferEnableFlushing(
+ CxBuffer *buffer,
+ CxBufferFlushConfig config
);
/**
* @param buffer the buffer which contents shall be destroyed
* @see cxBufferInit()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cxBufferDestroy(CxBuffer *buffer);
/**
* Deallocates the buffer.
*
* If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys
- * the contents. If you \em only want to destroy the contents, use cxBufferDestroy().
+ * the contents. If you @em only want to destroy the contents, use cxBufferDestroy().
+ *
+ * @remark As with all free() functions, this accepts @c NULL arguments in which
+ * case it does nothing.
*
* @param buffer the buffer to deallocate
* @see cxBufferCreate()
*/
-__attribute__((__nonnull__))
void cxBufferFree(CxBuffer *buffer);
+/**
+ * Allocates and initializes a fresh buffer.
+ *
+ * You may also provide a read-only @p space, in which case
+ * you will need to cast the pointer, and you should set the
+ * #CX_BUFFER_COPY_ON_WRITE flag.
+ * When you specify stack memory as @p space and decide to use
+ * the auto-extension feature, you @em must use the
+ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
+ * #CX_BUFFER_AUTO_EXTEND flag.
+ *
+ * @note You may provide @c NULL as argument for @p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param space pointer to the memory area, or @c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator to use for allocating the structure and the automatic
+ * memory management within the buffer
+ * (if @c NULL, a default stdlib allocator will be used)
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return a pointer to the buffer on success, @c NULL if a required allocation failed
+ */
+cx_attr_malloc
+cx_attr_dealloc(cxBufferFree, 1)
+cx_attr_nodiscard
+CxBuffer *cxBufferCreate(
+ void *space,
+ size_t capacity,
+ const CxAllocator *allocator,
+ int flags
+);
+
/**
* Shifts the contents of the buffer by the given offset.
*
* are discarded.
*
* If the offset is negative, the contents are shifted to the left where the
- * first \p shift bytes are discarded.
+ * first @p shift 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.
*
- * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and
- * cxBufferShiftRight() functions using a \c size_t as parameter type.
+ * @note For situations where @c off_t is not large enough, there are specialized cxBufferShiftLeft() and
+ * cxBufferShiftRight() functions using a @c size_t as parameter type.
*
- * \attention
- * Security Note: The shifting operation does \em not erase the previously occupied memory cells.
+ * @attention
+ * Security Note: The shifting operation does @em not erase the previously occupied memory cells.
* But you can easily do that manually, e.g. by calling
* <code>memset(buffer->bytes, 0, shift)</code> for a right shift or
* <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code>
*
* @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
+ * @retval zero success
+ * @retval non-zero if a required auto-extension or copy-on-write fails
+ * @see cxBufferShiftLeft()
+ * @see cxBufferShiftRight()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxBufferShift(
CxBuffer *buffer,
off_t shift
*
* @param buffer the buffer
* @param shift the shift offset
- * @return 0 on success, non-zero if a required auto-extension fails
+ * @retval zero success
+ * @retval non-zero if a required auto-extension or copy-on-write fails
* @see cxBufferShift()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxBufferShiftRight(
CxBuffer *buffer,
size_t shift
* Shifts the buffer to the left.
* See cxBufferShift() for details.
*
- * \note Since a left shift cannot fail due to memory allocation problems, this
- * function always returns zero.
- *
* @param buffer the buffer
* @param shift the positive shift offset
- * @return always zero
+ * @retval zero success
+ * @retval non-zero if the buffer uses copy-on-write and the allocation fails
* @see cxBufferShift()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxBufferShiftLeft(
CxBuffer *buffer,
size_t shift
/**
* Moves the position of the buffer.
*
- * The new position is relative to the \p whence argument.
+ * The new position is relative to the @p whence argument.
*
- * \li \c SEEK_SET marks the start of the buffer.
- * \li \c SEEK_CUR marks the current position.
- * \li \c SEEK_END marks the end of the buffer.
+ * @li @c SEEK_SET marks the start of the buffer.
+ * @li @c SEEK_CUR marks the current position.
+ * @li @c SEEK_END marks the end of the buffer.
*
* With an offset of zero, this function sets the buffer position to zero
- * (\c SEEK_SET), the buffer size (\c SEEK_END) or leaves the buffer position
- * unchanged (\c SEEK_CUR).
+ * (@c SEEK_SET), the buffer size (@c SEEK_END) or leaves the buffer position
+ * unchanged (@c SEEK_CUR).
*
* @param buffer the buffer
- * @param offset position offset relative to \p whence
- * @param whence one of \c SEEK_SET, \c SEEK_CUR or \c SEEK_END
- * @return 0 on success, non-zero if the position is invalid
+ * @param offset position offset relative to @p whence
+ * @param whence one of @c SEEK_SET, @c SEEK_CUR or @c SEEK_END
+ * @retval zero success
+ * @retval non-zero if the position is invalid
*
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxBufferSeek(
CxBuffer *buffer,
off_t offset,
* The data is deleted by zeroing it with a call to memset().
* If you do not need that, you can use the faster cxBufferReset().
*
+ * @note If the #CX_BUFFER_COPY_ON_WRITE flag is set, this function
+ * will not erase the data and behave exactly as cxBufferReset().
+ *
* @param buffer the buffer to be cleared
* @see cxBufferReset()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cxBufferClear(CxBuffer *buffer);
/**
* @param buffer the buffer to be cleared
* @see cxBufferClear()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cxBufferReset(CxBuffer *buffer);
/**
* Tests, if the buffer position has exceeded the buffer size.
*
* @param buffer the buffer to test
- * @return non-zero, if the current buffer position has exceeded the last
- * byte of the buffer's contents.
+ * @retval true if the current buffer position has exceeded the last
+ * byte of the buffer's contents
+ * @retval false otherwise
*/
-__attribute__((__nonnull__))
-int cxBufferEof(const CxBuffer *buffer);
+cx_attr_nonnull
+cx_attr_nodiscard
+bool cxBufferEof(const CxBuffer *buffer);
/**
*
* @param buffer the buffer
* @param capacity the minimum required capacity for this buffer
- * @return 0 on success or a non-zero value on failure
+ * @retval zero the capacity was already sufficient or successfully increased
+ * @retval non-zero on allocation failure
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxBufferMinimumCapacity(
CxBuffer *buffer,
size_t capacity
/**
* Writes data to a CxBuffer.
*
+ * If automatic flushing is not enabled, the data is simply written into the
+ * buffer at the current position and the position of the buffer is increased
+ * by the number of bytes written.
+ *
* If flushing is enabled and the buffer needs to flush, the data is flushed to
* the target until the target signals that it cannot take more data by
* returning zero via the respective write function. In that case, the remaining
* newly available space can be used to append as much data as possible. This
* function only stops writing more elements, when the flush target and this
* buffer are both incapable of taking more data or all data has been written.
- * The number returned by this function is the total number of elements that
- * could be written during the process. It does not necessarily mean that those
- * elements are still in this buffer, because some of them could have also be
- * flushed already.
+ * If number of items that shall be written is larger than the buffer can hold,
+ * the first items from @c ptr are directly relayed to the flush target, if
+ * possible.
+ * The number returned by this function is only the number of elements from
+ * @c ptr that could be written to either the flush target or the buffer.
*
- * If automatic flushing is not enabled, the position of the buffer is increased
- * by the number of bytes written.
- *
- * \note The signature is compatible with the fwrite() family of functions.
+ * @note The signature is compatible with the fwrite() family of functions.
*
* @param ptr a pointer to the memory area containing the bytes to be written
* @param size the length of one element
* @param nitems the element count
* @param buffer the CxBuffer to write to
* @return the total count of elements written
+ * @see cxBufferAppend()
+ * @see cxBufferRead()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
size_t cxBufferWrite(
const void *ptr,
size_t size,
CxBuffer *buffer
);
+/**
+ * Appends data to a CxBuffer.
+ *
+ * The data is always appended to current data within the buffer,
+ * regardless of the current position.
+ * This is especially useful when the buffer is primarily meant for reading
+ * while additional data is added to the buffer occasionally.
+ * Consequently, the position of the buffer is unchanged after this operation.
+ *
+ * @note The signature is compatible with the fwrite() family of functions.
+ *
+ * @param ptr a pointer to the memory area containing the bytes to be written
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to write to
+ * @return the total count of elements written
+ * @see cxBufferWrite()
+ * @see cxBufferRead()
+ */
+cx_attr_nonnull
+size_t cxBufferAppend(
+ const void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+);
+
+/**
+ * Performs a single flush-run on the specified buffer.
+ *
+ * Does nothing when the position in the buffer is zero.
+ * Otherwise, the data until the current position minus
+ * one is considered for flushing.
+ * Note carefully that flushing will never exceed the
+ * current @em position, even when the size of the
+ * buffer is larger than the current position.
+ *
+ * One flush run will try to flush @c blkmax many
+ * blocks of size @c blksize until either the @p buffer
+ * has no more data to flush or the write function
+ * used for flushing returns zero.
+ *
+ * The buffer is shifted left for that many bytes
+ * the flush operation has successfully flushed.
+ *
+ * @par Example 1
+ * Assume you have a buffer with size 340 and you are
+ * at position 200. The flush configuration is
+ * @c blkmax=4 and @c blksize=64 .
+ * Assume that the entire flush operation is successful.
+ * All 200 bytes on the left hand-side from the current
+ * position are written.
+ * That means, the size of the buffer is now 140 and the
+ * position is zero.
+ *
+ * @par Example 2
+ * Same as Example 1, but now the @c blkmax is 1.
+ * The size of the buffer is now 276 and the position is 136.
+ *
+ * @par Example 3
+ * Same as Example 1, but now assume the flush target
+ * only accepts 100 bytes before returning zero.
+ * That means, the flush operations manages to flush
+ * one complete block and one partial block, ending
+ * up with a buffer with size 240 and position 100.
+ *
+ * @remark Just returns zero when flushing was not enabled with
+ * cxBufferEnableFlushing().
+ *
+ * @remark When the buffer uses copy-on-write, the memory
+ * is copied first, before attempting any flush.
+ * This is, however, considered an erroneous use of the
+ * buffer, because it does not make much sense to put
+ * readonly data into an UCX buffer for flushing, instead
+ * of writing it directly to the target.
+ *
+ * @param buffer the buffer
+ * @return the number of successfully flushed bytes
+ * @see cxBufferEnableFlushing()
+ */
+cx_attr_nonnull
+size_t cxBufferFlush(CxBuffer *buffer);
+
/**
* Reads data from a CxBuffer.
*
* The position of the buffer is increased by the number of bytes read.
*
- * \note The signature is compatible with the fread() family of functions.
+ * @note The signature is compatible with the fread() family of functions.
*
* @param ptr a pointer to the memory area where to store the read data
* @param size the length of one element
* @param nitems the element count
* @param buffer the CxBuffer to read from
* @return the total number of elements read
+ * @see cxBufferWrite()
+ * @see cxBufferAppend()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
size_t cxBufferRead(
void *ptr,
size_t size,
*
* The least significant byte of the argument is written to the buffer. If the
* end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled,
- * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature is
- * disabled or buffer extension fails, \c EOF is returned.
+ * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature
+ * is disabled or buffer extension fails, @c EOF is returned.
*
* On successful write, the position of the buffer is increased.
*
+ * If you just want to write a null-terminator at the current position, you
+ * should use cxBufferTerminate() instead.
+ *
* @param buffer the buffer to write to
* @param c the character to write
- * @return the byte that has bean written or \c EOF when the end of the stream is
+ * @return the byte that has been written or @c EOF when the end of the stream is
* reached and automatic extension is not enabled or not possible
+ * @see cxBufferTerminate()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxBufferPut(
CxBuffer *buffer,
int c
);
+/**
+ * Writes a terminating zero to a buffer at the current position.
+ *
+ * On successful write, @em neither the position @em nor the size of the buffer is
+ * increased.
+ *
+ * The purpose of this function is to have the written data ready to be used as
+ * a C string.
+ *
+ * @param buffer the buffer to write to
+ * @return zero, if the terminator could be written, non-zero otherwise
+ */
+cx_attr_nonnull
+int cxBufferTerminate(CxBuffer *buffer);
+
/**
* Writes a string to a buffer.
*
+ * This is a convenience function for <code>cxBufferWrite(str, 1, strlen(str), buffer)</code>.
+ *
* @param buffer the buffer
* @param str the zero-terminated string
* @return the number of bytes written
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
size_t cxBufferPutString(
CxBuffer *buffer,
const char *str
* The current position of the buffer is increased after a successful read.
*
* @param buffer the buffer to read from
- * @return the character or \c EOF, if the end of the buffer is reached
+ * @return the character or @c EOF, if the end of the buffer is reached
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxBufferGet(CxBuffer *buffer);
#ifdef __cplusplus
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file collection.h
- * \brief Common definitions for various collection implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file collection.h
+ * @brief Common definitions for various collection implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_COLLECTION_H
/**
* Use this macro to declare common members for a collection structure.
+ *
+ * @par Example Use
+ * @code
+ * struct MyCustomSet {
+ * CX_COLLECTION_BASE;
+ * MySetElements *data;
+ * }
+ * @endcode
*/
#define CX_COLLECTION_BASE struct cx_collection_s collection
/**
* Sets a simple destructor function for this collection.
*
- * @param c the collection
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
* @param destr the destructor function
*/
#define cxDefineDestructor(c, destr) \
/**
* Sets a simple destructor function for this collection.
*
- * @param c the collection
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
* @param destr the destructor function
*/
#define cxDefineAdvancedDestructor(c, destr, data) \
* Usually only used by collection implementations. There should be no need
* to invoke this macro manually.
*
- * @param c the collection
- * @param e the element
+ * When the collection stores pointers, those pointers are directly passed
+ * to the destructor. Otherwise, a pointer to the element is passed.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param e the element (the type is @c void* or @c void** depending on context)
*/
#define cx_invoke_simple_destructor(c, e) \
(c)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e))
* Usually only used by collection implementations. There should be no need
* to invoke this macro manually.
*
- * @param c the collection
- * @param e the element
+ * When the collection stores pointers, those pointers are directly passed
+ * to the destructor. Otherwise, a pointer to the element is passed.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param e the element (the type is @c void* or @c void** depending on context)
*/
#define cx_invoke_advanced_destructor(c, e) \
(c)->collection.advanced_destructor((c)->collection.destructor_data, \
* Usually only used by collection implementations. There should be no need
* to invoke this macro manually.
*
- * @param c the collection
- * @param e the element
+ * When the collection stores pointers, those pointers are directly passed
+ * to the destructor. Otherwise, a pointer to the element is passed.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param e the element (the type is @c void* or @c void** depending on context)
*/
#define cx_invoke_destructor(c, e) \
if ((c)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \
*/
/**
- * \file common.h
+ * @file common.h
*
- * \brief Common definitions and feature checks.
+ * @brief Common definitions and feature checks.
*
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*
- * \mainpage UAP Common Extensions
+ * @mainpage UAP Common Extensions
* Library with common and useful functions, macros and data structures.
* <p>
* Latest available source:<br>
/** Version constant which ensures to increase monotonically. */
#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
-// Common Includes
+// ---------------------------------------------------------------------------
+// Common includes
+// ---------------------------------------------------------------------------
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
-#ifndef UCX_TEST_H
+// ---------------------------------------------------------------------------
+// Architecture Detection
+// ---------------------------------------------------------------------------
+
+#ifndef INTPTR_MAX
+#error Missing INTPTR_MAX definition
+#endif
+#if INTPTR_MAX == INT64_MAX
+/**
+ * The address width in bits on this platform.
+ */
+#define CX_WORDSIZE 64
+#elif INTPTR_MAX == INT32_MAX
+/**
+ * The address width in bits on this platform.
+ */
+#define CX_WORDSIZE 32
+#else
+#error Unknown pointer size or missing size macros!
+#endif
+
+// ---------------------------------------------------------------------------
+// Missing Defines
+// ---------------------------------------------------------------------------
+
+#ifndef SSIZE_MAX // not defined in glibc since C23 and MSVC
+#if CX_WORDSIZE == 64
+/**
+ * The maximum representable value in ssize_t.
+ */
+#define SSIZE_MAX 0x7fffffffffffffffll
+#else
+#define SSIZE_MAX 0x7fffffffl
+#endif
+#endif
+
+
+// ---------------------------------------------------------------------------
+// Attribute definitions
+// ---------------------------------------------------------------------------
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+/**
+ * All pointer arguments must be non-NULL.
+ */
+#define cx_attr_nonnull __attribute__((__nonnull__))
+
+/**
+ * The specified pointer arguments must be non-NULL.
+ */
+#define cx_attr_nonnull_arg(...) __attribute__((__nonnull__(__VA_ARGS__)))
+
+/**
+ * The returned value is guaranteed to be non-NULL.
+ */
+#define cx_attr_returns_nonnull __attribute__((__returns_nonnull__))
+
+/**
+ * The attributed function always returns freshly allocated memory.
+ */
+#define cx_attr_malloc __attribute__((__malloc__))
+
+#ifndef __clang__
+/**
+ * The pointer returned by the attributed function is supposed to be freed
+ * by @p freefunc.
+ *
+ * @param freefunc the function that shall be used to free the memory
+ * @param freefunc_arg the index of the pointer argument in @p freefunc
+ */
+#define cx_attr_dealloc(freefunc, freefunc_arg) \
+ __attribute__((__malloc__(freefunc, freefunc_arg)))
+#else
+/**
+ * Not supported in clang.
+ */
+#define cx_attr_dealloc(...)
+#endif // __clang__
+
+/**
+ * Shortcut to specify #cxFree() as deallocator.
+ */
+#define cx_attr_dealloc_ucx cx_attr_dealloc(cxFree, 2)
+
+/**
+ * Specifies the parameters from which the allocation size is calculated.
+ */
+#define cx_attr_allocsize(...) __attribute__((__alloc_size__(__VA_ARGS__)))
+
+
+#ifdef __clang__
+/**
+ * No support for @c null_terminated_string_arg in clang or GCC below 14.
+ */
+#define cx_attr_cstr_arg(idx)
+/**
+ * No support for access attribute in clang.
+ */
+#define cx_attr_access(mode, ...)
+#else
+#if __GNUC__ < 10
+/**
+ * No support for access attribute in GCC < 10.
+ */
+#define cx_attr_access(mode, ...)
+#else
+/**
+ * Helper macro to define access macros.
+ */
+#define cx_attr_access(mode, ...) __attribute__((__access__(mode, __VA_ARGS__)))
+#endif // __GNUC__ < 10
+#if __GNUC__ < 14
+/**
+ * No support for @c null_terminated_string_arg in clang or GCC below 14.
+ */
+#define cx_attr_cstr_arg(idx)
+#else
+/**
+ * The specified argument is expected to be a zero-terminated string.
+ *
+ * @param idx the index of the argument
+ */
+#define cx_attr_cstr_arg(idx) \
+ __attribute__((__null_terminated_string_arg__(idx)))
+#endif // __GNUC__ < 14
+#endif // __clang__
+
+
+/**
+ * Specifies that the function will only read through the given pointer.
+ *
+ * Takes one or two arguments: the index of the pointer and (optionally) the
+ * index of another argument specifying the maximum number of accessed bytes.
+ */
+#define cx_attr_access_r(...) cx_attr_access(__read_only__, __VA_ARGS__)
+
+/**
+ * Specifies that the function will read and write through the given pointer.
+ *
+ * Takes one or two arguments: the index of the pointer and (optionally) the
+ * index of another argument specifying the maximum number of accessed bytes.
+ */
+#define cx_attr_access_rw(...) cx_attr_access(__read_write__, __VA_ARGS__)
+
+/**
+ * Specifies that the function will only write through the given pointer.
+ *
+ * Takes one or two arguments: the index of the pointer and (optionally) the
+ * index of another argument specifying the maximum number of accessed bytes.
+ */
+#define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__)
+
+#if __STDC_VERSION__ >= 202300L
+
+/**
+ * Do not warn about unused variable.
+ */
+#define cx_attr_unused [[maybe_unused]]
+
+/**
+ * Warn about discarded return value.
+ */
+#define cx_attr_nodiscard [[nodiscard]]
+
+#else // no C23
+
+/**
+ * Do not warn about unused variable.
+ */
+#define cx_attr_unused __attribute__((__unused__))
+
+/**
+ * Warn about discarded return value.
+ */
+#define cx_attr_nodiscard __attribute__((__warn_unused_result__))
+
+#endif // __STDC_VERSION__
+
+// ---------------------------------------------------------------------------
+// Useful function pointers
+// ---------------------------------------------------------------------------
+
/**
* Function pointer compatible with fwrite-like functions.
*/
size_t,
void *
);
-#endif // UCX_TEST_H
/**
* Function pointer compatible with fread-like functions.
void *
);
+// ---------------------------------------------------------------------------
+// Utility macros
+// ---------------------------------------------------------------------------
-// Compiler specific stuff
+/**
+ * Determines the number of members in a static C array.
+ *
+ * @attention never use this to determine the size of a dynamically allocated
+ * array.
+ *
+ * @param arr the array identifier
+ * @return the number of elements
+ */
+#define cx_nmemb(arr) (sizeof(arr)/sizeof((arr)[0]))
-#ifndef __GNUC__
+// ---------------------------------------------------------------------------
+// szmul implementation
+// ---------------------------------------------------------------------------
+
+#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN)
+#define CX_SZMUL_BUILTIN
+#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result)
+#else // no GNUC or clang bultin
/**
- * Removes GNU C attributes where they are not supported.
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * @param a (@c size_t) first operand
+ * @param b (@c size_t) second operand
+ * @param result (@c size_t*) a pointer to a variable, where the result should
+ * be stored
+ * @retval zero success
+ * @retval non-zero the multiplication would overflow
*/
-#define __attribute__(x)
+#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result)
+
+/**
+ * Implementation of cx_szmul() when no compiler builtin is available.
+ *
+ * Do not use in application code.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a variable, where the result should
+ * be stored
+ * @retval zero success
+ * @retval non-zero the multiplication would overflow
+ */
+#if __cplusplus
+extern "C"
#endif
+int cx_szmul_impl(size_t a, size_t b, size_t *result);
+#endif // cx_szmul
-#ifdef _MSC_VER
+// ---------------------------------------------------------------------------
+// Fixes for MSVC incompatibilities
+// ---------------------------------------------------------------------------
+
+#ifdef _MSC_VER
// fix missing ssize_t definition
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
// fix missing _Thread_local support
#define _Thread_local __declspec(thread)
-
-#endif
+#endif // _MSC_VER
#endif // UCX_COMMON_H
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file compare.h
- * \brief A collection of simple compare functions.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file compare.h
+ * @brief A collection of simple compare functions.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_COMPARE_H
extern "C" {
#endif
-#ifndef CX_COMPARE_FUNC_DEFINED
-#define CX_COMPARE_FUNC_DEFINED
/**
- * A comparator function comparing two collection elements.
+ * A comparator function comparing two arbitrary values.
+ *
+ * All functions from compare.h with the cx_cmp prefix are
+ * compatible with this signature and can be used as
+ * compare function for collections, or other implementations
+ * that need to be type-agnostic.
+ *
+ * For simple comparisons the cx_vcmp family of functions
+ * can be used, but they are NOT compatible with this function
+ * pointer.
*/
-typedef int(*cx_compare_func)(
- const void *left,
- const void *right
+cx_attr_nonnull
+cx_attr_nodiscard
+typedef int (*cx_compare_func)(
+ const void *left,
+ const void *right
);
-#endif // CX_COMPARE_FUNC_DEFINED
/**
* Compares two integers of type int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to integer one
* @param i2 pointer to integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_int(const void *i1, const void *i2);
+/**
+ * Compares two ints.
+ *
+ * @param i1 integer one
+ * @param i2 integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_int(int i1, int i2);
+
/**
* Compares two integers of type long int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to long integer one
* @param i2 pointer to long integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_longint(const void *i1, const void *i2);
+/**
+ * Compares two long ints.
+ *
+ * @param i1 long integer one
+ * @param i2 long integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_longint(long int i1, long int i2);
+
/**
* Compares two integers of type long long.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to long long one
* @param i2 pointer to long long two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_longlong(const void *i1, const void *i2);
+/**
+ * Compares twolong long ints.
+ *
+ * @param i1 long long int one
+ * @param i2 long long int two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_longlong(long long int i1, long long int i2);
+
/**
* Compares two integers of type int16_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to int16_t one
* @param i2 pointer to int16_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_int16(const void *i1, const void *i2);
+/**
+ * Compares two integers of type int16_t.
+ *
+ * @param i1 int16_t one
+ * @param i2 int16_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_int16(int16_t i1, int16_t i2);
+
/**
* Compares two integers of type int32_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to int32_t one
* @param i2 pointer to int32_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_int32(const void *i1, const void *i2);
+/**
+ * Compares two integers of type int32_t.
+ *
+ * @param i1 int32_t one
+ * @param i2 int32_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_int32(int32_t i1, int32_t i2);
+
/**
* Compares two integers of type int64_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to int64_t one
* @param i2 pointer to int64_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_int64(const void *i1, const void *i2);
+/**
+ * Compares two integers of type int64_t.
+ *
+ * @param i1 int64_t one
+ * @param i2 int64_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_int64(int64_t i1, int64_t i2);
+
/**
* Compares two integers of type unsigned int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to unsigned integer one
* @param i2 pointer to unsigned integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_uint(const void *i1, const void *i2);
+/**
+ * Compares two unsigned ints.
+ *
+ * @param i1 unsigned integer one
+ * @param i2 unsigned integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_uint(unsigned int i1, unsigned int i2);
+
/**
* Compares two integers of type unsigned long int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to unsigned long integer one
* @param i2 pointer to unsigned long integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_ulongint(const void *i1, const void *i2);
+/**
+ * Compares two unsigned long ints.
+ *
+ * @param i1 unsigned long integer one
+ * @param i2 unsigned long integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2);
+
/**
* Compares two integers of type unsigned long long.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to unsigned long long one
* @param i2 pointer to unsigned long long two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_ulonglong(const void *i1, const void *i2);
+/**
+ * Compares two unsigned long long ints.
+ *
+ * @param i1 unsigned long long one
+ * @param i2 unsigned long long two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2);
+
/**
* Compares two integers of type uint16_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to uint16_t one
* @param i2 pointer to uint16_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_uint16(const void *i1, const void *i2);
+/**
+ * Compares two integers of type uint16_t.
+ *
+ * @param i1 uint16_t one
+ * @param i2 uint16_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_uint16(uint16_t i1, uint16_t i2);
+
/**
* Compares two integers of type uint32_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to uint32_t one
* @param i2 pointer to uint32_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_uint32(const void *i1, const void *i2);
+/**
+ * Compares two integers of type uint32_t.
+ *
+ * @param i1 uint32_t one
+ * @param i2 uint32_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_uint32(uint32_t i1, uint32_t i2);
+
/**
* Compares two integers of type uint64_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to uint64_t one
* @param i2 pointer to uint64_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_uint64(const void *i1, const void *i2);
+/**
+ * Compares two integers of type uint64_t.
+ *
+ * @param i1 uint64_t one
+ * @param i2 uint64_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_uint64(uint64_t i1, uint64_t i2);
+
/**
* Compares two real numbers of type float with precision 1e-6f.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param f1 pointer to float one
* @param f2 pointer to float two
- * @return -1, if *f1 is less than *f2, 0 if both are equal,
- * 1 if *f1 is greater than *f2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-
+cx_attr_nonnull
+cx_attr_nodiscard
int cx_cmp_float(const void *f1, const void *f2);
+/**
+ * Compares two real numbers of type float with precision 1e-6f.
+ *
+ * @param f1 float one
+ * @param f2 float two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_float(float f1, float f2);
+
/**
* Compares two real numbers of type double with precision 1e-14.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param d1 pointer to double one
* @param d2 pointer to double two
- * @return -1, if *d1 is less than *d2, 0 if both are equal,
- * 1 if *d1 is greater than *d2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_double(
- const void *d1,
- const void *d2
-);
+cx_attr_nonnull
+cx_attr_nodiscard
+int cx_cmp_double(const void *d1, const void *d2);
+
+/**
+ * Convenience function
+ *
+ * @param d1 double one
+ * @param d2 double two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_double(double d1, double d2);
/**
* Compares the integer representation of two pointers.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param ptr1 pointer to pointer one (const intptr_t*)
* @param ptr2 pointer to pointer two (const intptr_t*)
- * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
- * 1 if *ptr1 is greater than *ptr2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_intptr(
- const void *ptr1,
- const void *ptr2
-);
+cx_attr_nonnull
+cx_attr_nodiscard
+int cx_cmp_intptr(const void *ptr1, const void *ptr2);
+
+/**
+ * Compares the integer representation of two pointers.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2);
/**
* Compares the unsigned integer representation of two pointers.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param ptr1 pointer to pointer one (const uintptr_t*)
* @param ptr2 pointer to pointer two (const uintptr_t*)
- * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
- * 1 if *ptr1 is greater than *ptr2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_uintptr(
- const void *ptr1,
- const void *ptr2
-);
+cx_attr_nonnull
+cx_attr_nodiscard
+int cx_cmp_uintptr(const void *ptr1, const void *ptr2);
+
+/**
+ * Compares the unsigned integer representation of two pointers.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2);
/**
* Compares the pointers specified in the arguments without de-referencing.
*
* @param ptr1 pointer one
* @param ptr2 pointer two
- * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
- * 1 if ptr1 is greater than ptr2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_ptr(
- const void *ptr1,
- const void *ptr2
-);
+cx_attr_nonnull
+cx_attr_nodiscard
+int cx_cmp_ptr(const void *ptr1, const void *ptr2);
#ifdef __cplusplus
} // extern "C"
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file hash_key.h
- * \brief Interface for map implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file hash_key.h
+ * @brief Interface for map implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#define UCX_HASH_KEY_H
#include "common.h"
+#include "string.h"
#ifdef __cplusplus
extern "C" {
typedef struct cx_hash_key_s CxHashKey;
/**
- * Computes a murmur2 32 bit hash.
+ * Computes a murmur2 32-bit hash.
*
- * You need to initialize \c data and \c len in the key struct.
+ * You need to initialize @c data and @c len in the key struct.
* The hash is then directly written to that struct.
*
- * \note If \c data is \c NULL, the hash is defined as 1574210520.
+ * Usually you should not need this function.
+ * Use cx_hash_key(), instead.
+ *
+ * @note If @c data is @c NULL, the hash is defined as 1574210520.
*
* @param key the key, the hash shall be computed for
+ * @see cx_hash_key()
*/
+cx_attr_nonnull
void cx_hash_murmur(CxHashKey *key);
/**
* @param str the string
* @return the hash key
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
CxHashKey cx_hash_key_str(const char *str);
/**
* @param len the length
* @return the hash key
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
CxHashKey cx_hash_key_bytes(
const unsigned char *bytes,
size_t len
* @param len the length of object in memory
* @return the hash key
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
CxHashKey cx_hash_key(
const void *obj,
size_t len
* @param str the string
* @return the hash key
*/
-#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length)
+cx_attr_nodiscard
+static inline CxHashKey cx_hash_key_cxstr(cxstring str) {
+ return cx_hash_key(str.ptr, str.length);
+}
+
+/**
+ * Computes a hash key from a UCX string.
+ *
+ * @param str (@c cxstring or @c cxmutstr) the string
+ * @return (@c CxHashKey) the hash key
+ */
+#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str))
#ifdef __cplusplus
} // extern "C"
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file hash_map.h
- * \brief Hash map implementation.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file hash_map.h
+ * @brief Hash map implementation.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_HASH_MAP_H
/**
* Creates a new hash map with the specified number of buckets.
*
- * If \p buckets is zero, an implementation defined default will be used.
+ * If @p buckets is zero, an implementation defined default will be used.
*
- * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if
+ * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if
* cxMapStorePointers() was called immediately after creation.
*
* @note Iterators provided by this hash map implementation provide the remove operation.
* The index value of an iterator is incremented when the iterator advanced without removal.
- * In other words, when the iterator is finished, \c index==size .
+ * In other words, when the iterator is finished, @c index==size .
*
* @param allocator the allocator to use
+ * (if @c NULL, a default stdlib allocator will be used)
* @param itemsize the size of one element
* @param buckets the initial number of buckets in this hash map
* @return a pointer to the new hash map
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxMapFree, 1)
CxMap *cxHashMapCreate(
const CxAllocator *allocator,
size_t itemsize,
/**
* Creates a new hash map with a default number of buckets.
*
- * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if
+ * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if
* cxMapStorePointers() was called immediately after creation.
*
* @note Iterators provided by this hash map implementation provide the remove operation.
* The index value of an iterator is incremented when the iterator advanced without removal.
- * In other words, when the iterator is finished, \c index==size .
+ * In other words, when the iterator is finished, @c index==size .
*
- * @param itemsize the size of one element
- * @return a pointer to the new hash map
+ * @param itemsize (@c size_t) the size of one element
+ * @return (@c CxMap*) a pointer to the new hash map
*/
-#define cxHashMapCreateSimple(itemsize) \
- cxHashMapCreate(cxDefaultAllocator, itemsize, 0)
+#define cxHashMapCreateSimple(itemsize) cxHashMapCreate(NULL, itemsize, 0)
/**
* Increases the number of buckets, if necessary.
*
- * The load threshold is \c 0.75*buckets. If the element count exceeds the load
+ * The load threshold is @c 0.75*buckets. If the element count exceeds the load
* threshold, the map will be rehashed. Otherwise, no action is performed and
* this function simply returns 0.
*
* @note If the specified map is not a hash map, the behavior is undefined.
*
* @param map the map to rehash
- * @return zero on success, non-zero if a memory allocation error occurred
+ * @retval zero success
+ * @retval non-zero if a memory allocation error occurred
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxMapRehash(CxMap *map);
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file iterator.h
- * \brief Interface for iterator implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file iterator.h
+ * @brief Interface for iterator implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_ITERATOR_H
#include "common.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Common data for all iterators.
+ */
struct cx_iterator_base_s {
/**
* True iff the iterator points to valid data.
*/
- __attribute__ ((__nonnull__))
+ cx_attr_nonnull
bool (*valid)(const void *);
/**
*
* When valid returns false, the behavior of this function is undefined.
*/
- __attribute__ ((__nonnull__))
+ cx_attr_nonnull
+ cx_attr_nodiscard
void *(*current)(const void *);
/**
* Original implementation in case the function needs to be wrapped.
*/
- __attribute__ ((__nonnull__))
+ cx_attr_nonnull
+ cx_attr_nodiscard
void *(*current_impl)(const void *);
/**
*
* When valid returns false, the behavior of this function is undefined.
*/
- __attribute__ ((__nonnull__))
+ cx_attr_nonnull
void (*next)(void *);
/**
* Indicates whether this iterator may remove elements.
* Internal iterator struct - use CxIterator.
*/
struct cx_iterator_s {
+ /**
+ * Inherited common data for all iterators.
+ */
CX_ITERATOR_BASE;
/**
/**
* May contain the total number of elements, if known.
- * Shall be set to \c SIZE_MAX when the total number is unknown during iteration.
+ * Shall be set to @c SIZE_MAX when the total number is unknown during iteration.
*/
size_t elem_count;
};
* to be "position-aware", which means that they keep track of the current index within the collection.
*
* @note Objects that are pointed to by an iterator are always mutable through that iterator. However,
- * any concurrent mutation of the collection other than by this iterator makes this iterator invalid
+ * any concurrent mutation of the collection other than by this iterator makes this iterator invalid,
* and it must not be used anymore.
*/
typedef struct cx_iterator_s CxIterator;
* This is especially false for past-the-end iterators.
*
* @param iter the iterator
- * @return true iff the iterator points to valid data
+ * @retval true if the iterator points to valid data
+ * @retval false if the iterator already moved past the end
*/
#define cxIteratorValid(iter) (iter).base.valid(&(iter))
*
* @param iter the iterator
* @return a pointer to the current element
+ * @see cxIteratorValid()
*/
#define cxIteratorCurrent(iter) (iter).base.current(&iter)
/**
* Flags the current element for removal, if this iterator is mutating.
*
+ * Does nothing for non-mutating iterators.
+ *
* @param iter the iterator
*/
#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating
* This is useful for APIs that expect some iterator as an argument.
*
* @param iter the iterator
+ * @return (@c CxIterator*) a pointer to the iterator
*/
#define cxIteratorRef(iter) &((iter).base)
/**
* Loops over an iterator.
+ *
* @param type the type of the elements
* @param elem the name of the iteration variable
* @param iter the iterator
/**
* Creates an iterator for the specified plain array.
*
- * The \p array can be \c NULL in which case the iterator will be immediately
- * initialized such that #cxIteratorValid() returns \c false.
+ * The @p array can be @c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns @c false.
*
+ * This iterator yields the addresses of the array elements.
+ * If you want to iterator over an array of pointers, you can
+ * use cxIteratorPtr() to create an iterator which directly
+ * yields the stored pointers.
*
- * @param array a pointer to the array (can be \c NULL)
+ * @param array a pointer to the array (can be @c NULL)
* @param elem_size the size of one array element
* @param elem_count the number of elements in the array
* @return an iterator for the specified array
+ * @see cxIteratorPtr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
CxIterator cxIterator(
const void *array,
size_t elem_size,
* elements through #cxIteratorFlagRemoval(). Every other change to the array
* will bring this iterator to an undefined state.
*
- * When \p remove_keeps_order is set to \c false, removing an element will only
+ * When @p remove_keeps_order is set to @c false, removing an element will only
* move the last element to the position of the removed element, instead of
* moving all subsequent elements by one. Usually, when the order of elements is
- * not important, this parameter should be set to \c false.
+ * not important, this parameter should be set to @c false.
*
- * The \p array can be \c NULL in which case the iterator will be immediately
- * initialized such that #cxIteratorValid() returns \c false.
+ * The @p array can be @c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns @c false.
*
*
- * @param array a pointer to the array (can be \c NULL)
+ * @param array a pointer to the array (can be @c NULL)
* @param elem_size the size of one array element
* @param elem_count the number of elements in the array
- * @param remove_keeps_order \c true if the order of elements must be preserved
+ * @param remove_keeps_order @c true if the order of elements must be preserved
* when removing an element
* @return an iterator for the specified array
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
CxIterator cxMutIterator(
void *array,
size_t elem_size,
bool remove_keeps_order
);
+/**
+ * Creates an iterator for the specified plain pointer array.
+ *
+ * This iterator assumes that every element in the array is a pointer
+ * and yields exactly those pointers during iteration (while in contrast
+ * an iterator created with cxIterator() would return the addresses
+ * of those pointers within the array).
+ *
+ * @param array a pointer to the array (can be @c NULL)
+ * @param elem_count the number of elements in the array
+ * @return an iterator for the specified array
+ * @see cxIterator()
+ */
+cx_attr_nodiscard
+CxIterator cxIteratorPtr(
+ const void *array,
+ size_t elem_count
+);
+
+/**
+ * Creates a mutating iterator for the specified plain pointer array.
+ *
+ * This is the mutating variant of cxIteratorPtr(). See also
+ * cxMutIterator().
+ *
+ * @param array a pointer to the array (can be @c NULL)
+ * @param elem_count the number of elements in the array
+ * @param remove_keeps_order @c true if the order of elements must be preserved
+ * when removing an element
+ * @return an iterator for the specified array
+ * @see cxMutIterator()
+ * @see cxIteratorPtr()
+ */
+cx_attr_nodiscard
+CxIterator cxMutIteratorPtr(
+ void *array,
+ size_t elem_count,
+ bool remove_keeps_order
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
#endif // UCX_ITERATOR_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file json.h
+ * @brief Interface for parsing data from JSON files.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_JSON_H
+#define UCX_JSON_H
+
+#include "common.h"
+#include "allocator.h"
+#include "string.h"
+#include "buffer.h"
+#include "array_list.h"
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * The type of the parsed token.
+ */
+enum cx_json_token_type {
+ /**
+ * No complete token parsed, yet.
+ */
+ CX_JSON_NO_TOKEN,
+ /**
+ * The presumed token contains syntactical errors.
+ */
+ CX_JSON_TOKEN_ERROR,
+ /**
+ * A "begin of array" '[' token.
+ */
+ CX_JSON_TOKEN_BEGIN_ARRAY,
+ /**
+ * A "begin of object" '{' token.
+ */
+ CX_JSON_TOKEN_BEGIN_OBJECT,
+ /**
+ * An "end of array" ']' token.
+ */
+ CX_JSON_TOKEN_END_ARRAY,
+ /**
+ * An "end of object" '}' token.
+ */
+ CX_JSON_TOKEN_END_OBJECT,
+ /**
+ * A colon ':' token separating names and values.
+ */
+ CX_JSON_TOKEN_NAME_SEPARATOR,
+ /**
+ * A comma ',' token separating object entries or array elements.
+ */
+ CX_JSON_TOKEN_VALUE_SEPARATOR,
+ /**
+ * A string token.
+ */
+ CX_JSON_TOKEN_STRING,
+ /**
+ * A number token that can be represented as integer.
+ */
+ CX_JSON_TOKEN_INTEGER,
+ /**
+ * A number token that cannot be represented as integer.
+ */
+ CX_JSON_TOKEN_NUMBER,
+ /**
+ * A literal token.
+ */
+ CX_JSON_TOKEN_LITERAL,
+ /**
+ * A white-space token.
+ */
+ CX_JSON_TOKEN_SPACE
+};
+
+/**
+ * The type of some JSON value.
+ */
+enum cx_json_value_type {
+ /**
+ * Reserved.
+ */
+ CX_JSON_NOTHING, // this allows us to always return non-NULL values
+ /**
+ * A JSON object.
+ */
+ CX_JSON_OBJECT,
+ /**
+ * A JSON array.
+ */
+ CX_JSON_ARRAY,
+ /**
+ * A string.
+ */
+ CX_JSON_STRING,
+ /**
+ * A number that contains an integer.
+ */
+ CX_JSON_INTEGER,
+ /**
+ * A number, not necessarily an integer.
+ */
+ CX_JSON_NUMBER,
+ /**
+ * A literal (true, false, null).
+ */
+ CX_JSON_LITERAL
+};
+
+/**
+ * JSON literal types.
+ */
+enum cx_json_literal {
+ /**
+ * The @c null literal.
+ */
+ CX_JSON_NULL,
+ /**
+ * The @c true literal.
+ */
+ CX_JSON_TRUE,
+ /**
+ * The @c false literal.
+ */
+ CX_JSON_FALSE
+};
+
+/**
+ * Type alias for the token type enum.
+ */
+typedef enum cx_json_token_type CxJsonTokenType;
+/**
+ * Type alias for the value type enum.
+ */
+typedef enum cx_json_value_type CxJsonValueType;
+
+/**
+ * Type alias for the JSON parser interface.
+ */
+typedef struct cx_json_s CxJson;
+
+/**
+ * Type alias for the token struct.
+ */
+typedef struct cx_json_token_s CxJsonToken;
+
+/**
+ * Type alias for the JSON value struct.
+ */
+typedef struct cx_json_value_s CxJsonValue;
+
+/**
+ * Type alias for the JSON array struct.
+ */
+typedef struct cx_json_array_s CxJsonArray;
+/**
+ * Type alias for the JSON object struct.
+ */
+typedef struct cx_json_object_s CxJsonObject;
+/**
+ * Type alias for a JSON string.
+ */
+typedef struct cx_mutstr_s CxJsonString;
+/**
+ * Type alias for a number that can be represented as 64-bit signed integer.
+ */
+typedef int64_t CxJsonInteger;
+/**
+ * Type alias for number that is not an integer.
+ */
+typedef double CxJsonNumber;
+/**
+ * Type alias for a JSON literal.
+ */
+typedef enum cx_json_literal CxJsonLiteral;
+
+/**
+ * Type alias for a key/value pair in a JSON object.
+ */
+typedef struct cx_json_obj_value_s CxJsonObjValue;
+
+/**
+ * JSON array structure.
+ */
+struct cx_json_array_s {
+ /**
+ * The array data.
+ */
+ CX_ARRAY_DECLARE(CxJsonValue*, array);
+};
+
+/**
+ * JSON object structure.
+ */
+struct cx_json_object_s {
+ /**
+ * The key/value entries.
+ */
+ CX_ARRAY_DECLARE(CxJsonObjValue, values);
+ /**
+ * The original indices to reconstruct the order in which the members were added.
+ */
+ size_t *indices;
+};
+
+/**
+ * Structure for a key/value entry in a JSON object.
+ */
+struct cx_json_obj_value_s {
+ /**
+ * The key (or name in JSON terminology) of the value.
+ */
+ cxmutstr name;
+ /**
+ * The value.
+ */
+ CxJsonValue *value;
+};
+
+/**
+ * Structure for a JSON value.
+ */
+struct cx_json_value_s {
+ /**
+ * The allocator with which the value was allocated.
+ *
+ * Required for recursively deallocating memory of objects and arrays.
+ */
+ const CxAllocator *allocator;
+ /**
+ * The type of this value.
+ *
+ * Specifies how the @c value union shall be resolved.
+ */
+ CxJsonValueType type;
+ /**
+ * The value data.
+ */
+ union {
+ /**
+ * The array data if type is #CX_JSON_ARRAY.
+ */
+ CxJsonArray array;
+ /**
+ * The object data if type is #CX_JSON_OBJECT.
+ */
+ CxJsonObject object;
+ /**
+ * The string data if type is #CX_JSON_STRING.
+ */
+ CxJsonString string;
+ /**
+ * The integer if type is #CX_JSON_INTEGER.
+ */
+ CxJsonInteger integer;
+ /**
+ * The number if type is #CX_JSON_NUMBER.
+ */
+ CxJsonNumber number;
+ /**
+ * The literal type if type is #CX_JSON_LITERAL.
+ */
+ CxJsonLiteral literal;
+ } value;
+};
+
+/**
+ * Internally used structure for a parsed token.
+ *
+ * You should never need to use this in your code.
+ */
+struct cx_json_token_s {
+ /**
+ * The token type.
+ */
+ CxJsonTokenType tokentype;
+ /**
+ * True, iff the @c content must be passed to cx_strfree().
+ */
+ bool allocated;
+ /**
+ * The token text, if any.
+ *
+ * This is not necessarily set when the token type already
+ * uniquely identifies the content.
+ */
+ cxmutstr content;
+};
+
+/**
+ * The JSON parser interface.
+ */
+struct cx_json_s {
+ /**
+ * The allocator used for produced JSON values.
+ */
+ const CxAllocator *allocator;
+ /**
+ * The input buffer.
+ */
+ CxBuffer buffer;
+
+ /**
+ * Used internally.
+ *
+ * Remembers the prefix of the last uncompleted token.
+ */
+ CxJsonToken uncompleted;
+
+ /**
+ * A pointer to an intermediate state of the currently parsed value.
+ *
+ * Never access this value manually.
+ */
+ CxJsonValue *parsed;
+
+ /**
+ * A pointer to an intermediate state of a currently parsed object member.
+ *
+ * Never access this value manually.
+ */
+ CxJsonObjValue uncompleted_member;
+
+ /**
+ * State stack.
+ */
+ CX_ARRAY_DECLARE_SIZED(int, states, unsigned);
+
+ /**
+ * Value buffer stack.
+ */
+ CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned);
+
+ /**
+ * Internally reserved memory for the state stack.
+ */
+ int states_internal[8];
+
+ /**
+ * Internally reserved memory for the value buffer stack.
+ */
+ CxJsonValue* vbuf_internal[8];
+
+ /**
+ * Used internally.
+ */
+ bool tokenizer_escape; // TODO: check if it can be replaced with look-behind
+};
+
+/**
+ * Status codes for the json interface.
+ */
+enum cx_json_status {
+ /**
+ * Everything is fine.
+ */
+ CX_JSON_NO_ERROR,
+ /**
+ * The input buffer does not contain more data.
+ */
+ CX_JSON_NO_DATA,
+ /**
+ * The input ends unexpectedly.
+ *
+ * Refill the buffer with cxJsonFill() to complete the json data.
+ */
+ CX_JSON_INCOMPLETE_DATA,
+ /**
+ * Not used as a status and never returned by any function.
+ *
+ * You can use this enumerator to check for all "good" status results
+ * by checking if the status is less than @c CX_JSON_OK.
+ *
+ * A "good" status means, that you can refill data and continue parsing.
+ */
+ CX_JSON_OK,
+ /**
+ * The input buffer has never been filled.
+ */
+ CX_JSON_NULL_DATA,
+ /**
+ * Allocating memory for the internal buffer failed.
+ */
+ CX_JSON_BUFFER_ALLOC_FAILED,
+ /**
+ * Allocating memory for a json value failed.
+ */
+ CX_JSON_VALUE_ALLOC_FAILED,
+ /**
+ * A number value is incorrectly formatted.
+ */
+ CX_JSON_FORMAT_ERROR_NUMBER,
+ /**
+ * The tokenizer found something unexpected.
+ */
+ CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN
+};
+
+/**
+ * Typedef for the json status enum.
+ */
+typedef enum cx_json_status CxJsonStatus;
+
+/**
+ * The JSON writer settings.
+ */
+struct cx_json_writer_s {
+ /**
+ * Set true to enable pretty output.
+ */
+ bool pretty;
+ /**
+ * Set false to output the members in the order in which they were added.
+ */
+ bool sort_members;
+ /**
+ * The maximum number of fractional digits in a number value.
+ */
+ uint8_t frac_max_digits;
+ /**
+ * Set true to use spaces instead of tab characters.
+ * Indentation is only used in pretty output.
+ */
+ bool indent_space;
+ /**
+ * If @c indent_space is true, this is the number of spaces per tab.
+ * Indentation is only used in pretty output.
+ */
+ uint8_t indent;
+};
+
+/**
+ * Typedef for the json writer.
+ */
+typedef struct cx_json_writer_s CxJsonWriter;
+
+/**
+ * Creates a default writer configuration for compact output.
+ *
+ * @return new JSON writer settings
+ */
+cx_attr_nodiscard
+CxJsonWriter cxJsonWriterCompact(void);
+
+/**
+ * Creates a default writer configuration for pretty output.
+ *
+ * @param use_spaces false if you want tabs, true if you want four spaces instead
+ * @return new JSON writer settings
+ */
+cx_attr_nodiscard
+CxJsonWriter cxJsonWriterPretty(bool use_spaces);
+
+/**
+ * Writes a JSON value to a buffer or stream.
+ *
+ * This function blocks until all data is written or an error when trying
+ * to write data occurs.
+ * The write operation is not atomic in the sense that it might happen
+ * that the data is only partially written when an error occurs with no
+ * way to indicate how much data was written.
+ * To avoid this problem, you can use a CxBuffer as @p target which is
+ * unlikely to fail a write operation and either use the buffer's flush
+ * feature to relay the data or use the data in the buffer manually to
+ * write it to the actual target.
+ *
+ * @param target the buffer or stream where to write to
+ * @param value the value that shall be written
+ * @param wfunc the write function to use
+ * @param settings formatting settings (or @c NULL to use a compact default)
+ * @retval zero success
+ * @retval non-zero when no or not all data could be written
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+int cxJsonWrite(
+ void* target,
+ const CxJsonValue* value,
+ cx_write_func wfunc,
+ const CxJsonWriter* settings
+);
+
+/**
+ * Initializes the json interface.
+ *
+ * @param json the json interface
+ * @param allocator the allocator that shall be used for the produced values
+ * @see cxJsonDestroy()
+ */
+cx_attr_nonnull_arg(1)
+void cxJsonInit(CxJson *json, const CxAllocator *allocator);
+
+/**
+ * Destroys the json interface.
+ *
+ * @param json the json interface
+ * @see cxJsonInit()
+ */
+cx_attr_nonnull
+void cxJsonDestroy(CxJson *json);
+
+/**
+ * Destroys and re-initializes the json interface.
+ *
+ * You might want to use this, to reset the parser after
+ * encountering a syntax error.
+ *
+ * @param json the json interface
+ */
+cx_attr_nonnull
+static inline void cxJsonReset(CxJson *json) {
+ const CxAllocator *allocator = json->allocator;
+ cxJsonDestroy(json);
+ cxJsonInit(json, allocator);
+}
+
+/**
+ * Fills the input buffer.
+ *
+ * @remark The JSON interface tries to avoid copying the input data.
+ * When you use this function and cxJsonNext() interleaving,
+ * no copies are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxJsonNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param json the json interface
+ * @param buf the source buffer
+ * @param len the length of the source buffer
+ * @retval zero success
+ * @retval non-zero internal allocation error
+ * @see cxJsonFill()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonFilln(CxJson *json, const char *buf, size_t len);
+
+#ifdef __cplusplus
+} // extern "C"
+
+cx_attr_nonnull
+static inline int cxJsonFill(
+ CxJson *json,
+ cxstring str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+static inline int cxJsonFill(
+ CxJson *json,
+ cxmutstr str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cxJsonFill(
+ CxJson *json,
+ const char *str
+) {
+ return cxJsonFilln(json, str, strlen(str));
+}
+
+extern "C" {
+#else // __cplusplus
+/**
+ * Fills the input buffer.
+ *
+ * The JSON interface tries to avoid copying the input data.
+ * When you use this function and cxJsonNext() interleaving,
+ * no copies are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxJsonNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param json the json interface
+ * @param str the source string
+ * @retval zero success
+ * @retval non-zero internal allocation error
+ * @see cxJsonFilln()
+ */
+#define cxJsonFill(json, str) _Generic((str), \
+ cxstring: cx_json_fill_cxstr, \
+ cxmutstr: cx_json_fill_mutstr, \
+ char*: cx_json_fill_str, \
+ const char*: cx_json_fill_str) \
+ (json, str)
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+static inline int cx_json_fill_cxstr(
+ CxJson *json,
+ cxstring str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+static inline int cx_json_fill_mutstr(
+ CxJson *json,
+ cxmutstr str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cx_json_fill_str(
+ CxJson *json,
+ const char *str
+) {
+ return cxJsonFilln(json, str, strlen(str));
+}
+#endif
+
+/**
+ * Creates a new (empty) JSON object.
+ *
+ * @param allocator the allocator to use
+ * @return the new JSON object or @c NULL if allocation fails
+ * @see cxJsonObjPutObj()
+ * @see cxJsonArrAddValues()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator);
+
+/**
+ * Creates a new (empty) JSON array.
+ *
+ * @param allocator the allocator to use
+ * @return the new JSON array or @c NULL if allocation fails
+ * @see cxJsonObjPutArr()
+ * @see cxJsonArrAddValues()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator);
+
+/**
+ * Creates a new JSON number value.
+ *
+ * @param allocator the allocator to use
+ * @param num the numeric value
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutNumber()
+ * @see cxJsonArrAddNumbers()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num);
+
+/**
+ * Creates a new JSON number value based on an integer.
+ *
+ * @param allocator the allocator to use
+ * @param num the numeric value
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutInteger()
+ * @see cxJsonArrAddIntegers()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num);
+
+/**
+ * Creates a new JSON string.
+ *
+ * @param allocator the allocator to use
+ * @param str the string data
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonCreateString()
+ * @see cxJsonObjPutString()
+ * @see cxJsonArrAddStrings()
+ */
+cx_attr_nodiscard
+cx_attr_nonnull_arg(2)
+cx_attr_cstr_arg(2)
+CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str);
+
+/**
+ * Creates a new JSON string.
+ *
+ * @param allocator the allocator to use
+ * @param str the string data
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonCreateCxString()
+ * @see cxJsonObjPutCxString()
+ * @see cxJsonArrAddCxStrings()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str);
+
+/**
+ * Creates a new JSON literal.
+ *
+ * @param allocator the allocator to use
+ * @param lit the type of literal
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutLiteral()
+ * @see cxJsonArrAddLiterals()
+ */
+cx_attr_nodiscard
+CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit);
+
+/**
+ * Adds number values to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param num the array of values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count);
+
+/**
+ * Adds number values, of which all are integers, to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param num the array of values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count);
+
+/**
+ * Adds strings to a JSON array.
+ *
+ * The strings will be copied with the allocator of the array.
+ *
+ * @param arr the JSON array
+ * @param str the array of strings
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ * @see cxJsonArrAddCxStrings()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count);
+
+/**
+ * Adds strings to a JSON array.
+ *
+ * The strings will be copied with the allocator of the array.
+ *
+ * @param arr the JSON array
+ * @param str the array of strings
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ * @see cxJsonArrAddStrings()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count);
+
+/**
+ * Adds literals to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param lit the array of literal types
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count);
+
+/**
+ * Add arbitrary values to a JSON array.
+ *
+ * @attention In contrast to all other add functions, this function adds the values
+ * directly to the array instead of copying them.
+ *
+ * @param arr the JSON array
+ * @param val the values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count);
+
+/**
+ * Adds or replaces a value within a JSON object.
+ *
+ * The value will be directly added and not copied.
+ *
+ * @note If a value with the specified @p name already exists,
+ * it will be (recursively) freed with its own allocator.
+ *
+ * @param obj the JSON object
+ * @param name the name of the value
+ * @param child the value
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child);
+
+/**
+ * Creates a new JSON object and adds it to an existing object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateObj()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON array and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateArr()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON number and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param num the numeric value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateNumber()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num);
+
+/**
+ * Creates a new JSON number, based on an integer, and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param num the numeric value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateInteger()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num);
+
+/**
+ * Creates a new JSON string and adds it to an object.
+ *
+ * The string data is copied.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param str the string data
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateString()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(3)
+CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str);
+
+/**
+ * Creates a new JSON string and adds it to an object.
+ *
+ * The string data is copied.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param str the string data
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateCxString()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str);
+
+/**
+ * Creates a new JSON literal and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param lit the type of literal
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateLiteral()
+ */
+cx_attr_nonnull
+CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit);
+
+/**
+ * Recursively deallocates the memory of a JSON value.
+ *
+ * @remark The type of each deallocated value will be changed
+ * to #CX_JSON_NOTHING and values of such type will be skipped
+ * by the de-allocation. That means, this function protects
+ * you from double-frees when you are accidentally freeing
+ * a nested value and then the parent value (or vice versa).
+ *
+ * @param value the value
+ */
+void cxJsonValueFree(CxJsonValue *value);
+
+/**
+ * Tries to obtain the next JSON value.
+ *
+ * Before this function can be called, the input buffer needs
+ * to be filled with cxJsonFill().
+ *
+ * When this function returns #CX_JSON_INCOMPLETE_DATA, you can
+ * add the missing data with another invocation of cxJsonFill()
+ * and then repeat the call to cxJsonNext().
+ *
+ * @param json the json interface
+ * @param value a pointer where the next value shall be stored
+ * @retval CX_JSON_NO_ERROR successfully retrieve the @p value
+ * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from
+ * @retval CX_JSON_INCOMPLETE_DATA an incomplete value was read
+ * and more data needs to be filled
+ * @retval CX_JSON_NULL_DATA the buffer was never initialized
+ * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed
+ * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for a CxJsonValue failed
+ * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number
+ * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error
+ */
+cx_attr_nonnull
+cx_attr_access_w(2)
+CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value);
+
+/**
+ * Checks if the specified value is a JSON object.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON object
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsObject(const CxJsonValue *value) {
+ return value->type == CX_JSON_OBJECT;
+}
+
+/**
+ * Checks if the specified value is a JSON array.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON array
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsArray(const CxJsonValue *value) {
+ return value->type == CX_JSON_ARRAY;
+}
+
+/**
+ * Checks if the specified value is a string.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a string
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsString(const CxJsonValue *value) {
+ return value->type == CX_JSON_STRING;
+}
+
+/**
+ * Checks if the specified value is a JSON number.
+ *
+ * This function will return true for both floating point and
+ * integer numbers.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON number
+ * @retval false otherwise
+ * @see cxJsonIsInteger()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsNumber(const CxJsonValue *value) {
+ return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER;
+}
+
+/**
+ * Checks if the specified value is an integer number.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is an integer number
+ * @retval false otherwise
+ * @see cxJsonIsNumber()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsInteger(const CxJsonValue *value) {
+ return value->type == CX_JSON_INTEGER;
+}
+
+/**
+ * Checks if the specified value is a JSON literal.
+ *
+ * JSON literals are @c true, @c false, and @c null.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON literal
+ * @retval false otherwise
+ * @see cxJsonIsTrue()
+ * @see cxJsonIsFalse()
+ * @see cxJsonIsNull()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsLiteral(const CxJsonValue *value) {
+ return value->type == CX_JSON_LITERAL;
+}
+
+/**
+ * Checks if the specified value is a Boolean literal.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is either @c true or @c false
+ * @retval false otherwise
+ * @see cxJsonIsTrue()
+ * @see cxJsonIsFalse()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsBool(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL;
+}
+
+/**
+ * Checks if the specified value is @c true.
+ *
+ * @remark Be advised, that this is not the same as
+ * testing @c !cxJsonIsFalse(v).
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c true
+ * @retval false otherwise
+ * @see cxJsonIsBool()
+ * @see cxJsonIsFalse()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsTrue(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE;
+}
+
+/**
+ * Checks if the specified value is @c false.
+ *
+ * @remark Be advised, that this is not the same as
+ * testing @c !cxJsonIsTrue(v).
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c false
+ * @retval false otherwise
+ * @see cxJsonIsBool()
+ * @see cxJsonIsTrue()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsFalse(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE;
+}
+
+/**
+ * Checks if the specified value is @c null.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c null
+ * @retval false otherwise
+ * @see cxJsonIsLiteral()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsNull(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL;
+}
+
+/**
+ * Obtains a C string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as C string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+static inline char *cxJsonAsString(const CxJsonValue *value) {
+ return value->value.string.ptr;
+}
+
+/**
+ * Obtains a UCX string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as UCX string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+static inline cxstring cxJsonAsCxString(const CxJsonValue *value) {
+ return cx_strcast(value->value.string);
+}
+
+/**
+ * Obtains a mutable UCX string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as mutable UCX string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) {
+ return value->value.string;
+}
+
+/**
+ * Obtains a double-precision floating point value from the given JSON value.
+ *
+ * If the @p value is not a JSON number, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsNumber()
+ */
+cx_attr_nonnull
+static inline double cxJsonAsDouble(const CxJsonValue *value) {
+ if (value->type == CX_JSON_INTEGER) {
+ return (double) value->value.integer;
+ } else {
+ return value->value.number;
+ }
+}
+
+/**
+ * Obtains a 64-bit signed integer from the given JSON value.
+ *
+ * If the @p value is not a JSON number, the behavior is undefined.
+ * If it is a JSON number, but not an integer, the value will be
+ * converted to an integer, possibly losing precision.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsNumber()
+ * @see cxJsonIsInteger()
+ */
+cx_attr_nonnull
+static inline int64_t cxJsonAsInteger(const CxJsonValue *value) {
+ if (value->type == CX_JSON_INTEGER) {
+ return value->value.integer;
+ } else {
+ return (int64_t) value->value.number;
+ }
+}
+
+/**
+ * Obtains a Boolean value from the given JSON value.
+ *
+ * If the @p value is not a JSON literal, the behavior is undefined.
+ * The @c null literal is interpreted as @c false.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsLiteral()
+ */
+cx_attr_nonnull
+static inline bool cxJsonAsBool(const CxJsonValue *value) {
+ return value->value.literal == CX_JSON_TRUE;
+}
+
+/**
+ * Returns the size of a JSON array.
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the size of the array
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+static inline size_t cxJsonArrSize(const CxJsonValue *value) {
+ return value->value.array.array_size;
+}
+
+/**
+ * Returns an element from a JSON array.
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * This function guarantees to return a value. If the index is
+ * out of bounds, the returned value will be of type
+ * #CX_JSON_NOTHING, but never @c NULL.
+ *
+ * @param value the JSON value
+ * @param index the index in the array
+ * @return the value at the specified index
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index);
+
+/**
+ * Returns an iterator over the JSON array elements.
+ *
+ * The iterator yields values of type @c CxJsonValue* .
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return an iterator over the array elements
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+CxIterator cxJsonArrIter(const CxJsonValue *value);
+
+/**
+ * Returns an iterator over the JSON object members.
+ *
+ * The iterator yields values of type @c CxJsonObjValue* which
+ * contain the name and value of the member.
+ *
+ * If the @p value is not a JSON object, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return an iterator over the object members
+ * @see cxJsonIsObject()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+CxIterator cxJsonObjIter(const CxJsonValue *value);
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name);
+
+#ifdef __cplusplus
+} // extern "C"
+
+CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) {
+ return cx_json_obj_get_cxstr(value, name);
+}
+
+CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) {
+ return cx_json_obj_get_cxstr(value, cx_strcast(name));
+}
+
+CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) {
+ return cx_json_obj_get_cxstr(value, cx_str(name));
+}
+
+extern "C" {
+#else
+/**
+ * Returns a value corresponding to a key in a JSON object.
+ *
+ * If the @p value is not a JSON object, the behavior is undefined.
+ *
+ * This function guarantees to return a JSON value. If the
+ * object does not contain @p name, the returned JSON value
+ * will be of type #CX_JSON_NOTHING, but never @c NULL.
+ *
+ * @param value the JSON object
+ * @param name the key to look up
+ * @return the value corresponding to the key
+ * @see cxJsonIsObject()
+ */
+#define cxJsonObjGet(value, name) _Generic((name), \
+ cxstring: cx_json_obj_get_cxstr, \
+ cxmutstr: cx_json_obj_get_mutstr, \
+ char*: cx_json_obj_get_str, \
+ const char*: cx_json_obj_get_str) \
+ (value, name)
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) {
+ return cx_json_obj_get_cxstr(value, cx_strcast(name));
+}
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+cx_attr_cstr_arg(2)
+static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) {
+ return cx_json_obj_get_cxstr(value, cx_str(name));
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_JSON_H */
+
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file linked_list.h
- * \brief Linked list implementation.
- * \details Also provides several low-level functions for custom linked list implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file linked_list.h
+ * @brief Linked list implementation.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_LINKED_LIST_H
/**
* The maximum item size that uses SBO swap instead of relinking.
+ *
*/
-extern unsigned cx_linked_list_swap_sbo_size;
+extern const unsigned cx_linked_list_swap_sbo_size;
/**
- * Allocates a linked list for storing elements with \p elem_size bytes each.
+ * Allocates a linked list for storing elements with @p elem_size bytes each.
*
- * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if
* cxListStorePointers() was called immediately after creation and the compare
* function will be automatically set to cx_cmp_ptr(), if none is given.
*
* @param allocator the allocator for allocating the list nodes
- * (if \c NULL the cxDefaultAllocator will be used)
+ * (if @c NULL, a default stdlib allocator will be used)
* @param comparator the comparator for the elements
- * (if \c NULL, and the list is not storing pointers, sort and find
+ * (if @c NULL, and the list is not storing pointers, sort and find
* functions will not work)
* @param elem_size the size of each element in bytes
* @return the created list
*/
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxListFree, 1)
CxList *cxLinkedListCreate(
const CxAllocator *allocator,
cx_compare_func comparator,
);
/**
- * Allocates a linked list for storing elements with \p elem_size bytes each.
+ * Allocates a linked list for storing elements with @p elem_size bytes each.
*
* The list will use cxDefaultAllocator and no comparator function. If you want
* to call functions that need a comparator, you must either set one immediately
* after list creation or use cxLinkedListCreate().
*
- * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if
* cxListStorePointers() was called immediately after creation and the compare
* function will be automatically set to cx_cmp_ptr().
*
- * @param elem_size the size of each element in bytes
- * @return the created list
+ * @param elem_size (@c size_t) the size of each element in bytes
+ * @return (@c CxList*) the created list
*/
#define cxLinkedListCreateSimple(elem_size) \
cxLinkedListCreate(NULL, NULL, elem_size)
* Finds the node at a certain index.
*
* This function can be used to start at an arbitrary position within the list.
- * If the search index is large than the start index, \p loc_advance must denote
- * the location of some sort of \c next pointer (i.e. a pointer to the next node).
+ * If the search index is large than the start index, @p loc_advance must denote
+ * the location of some sort of @c next pointer (i.e. a pointer to the next node).
* But it is also possible that the search index is smaller than the start index
* (e.g. in cases where traversing a list backwards is faster) in which case
- * \p loc_advance must denote the location of some sort of \c prev pointer
+ * @p loc_advance must denote the location of some sort of @c prev pointer
* (i.e. a pointer to the previous node).
*
* @param start a pointer to the start node
* @param index the search index
* @return the node found at the specified index
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
void *cx_linked_list_at(
const void *start,
size_t start_index,
*
* @param start a pointer to the start node
* @param loc_advance the location of the pointer to advance
- * @param loc_data the location of the \c data pointer within your node struct
- * @param cmp_func a compare function to compare \p elem against the node data
+ * @param loc_data the location of the @c data pointer within your node struct
+ * @param cmp_func a compare function to compare @p elem against the node data
* @param elem a pointer to the element to find
* @return the index of the element or a negative value if it could not be found
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
ssize_t cx_linked_list_find(
const void *start,
ptrdiff_t loc_advance,
/**
* Finds the node containing an element within a linked list.
*
- * @param result a pointer to the memory where the node pointer (or \c NULL if the element
+ * @param result a pointer to the memory where the node pointer (or @c NULL if the element
* could not be found) shall be stored to
* @param start a pointer to the start node
* @param loc_advance the location of the pointer to advance
- * @param loc_data the location of the \c data pointer within your node struct
- * @param cmp_func a compare function to compare \p elem against the node data
+ * @param loc_data the location of the @c data pointer within your node struct
+ * @param cmp_func a compare function to compare @p elem against the node data
* @param elem a pointer to the element to find
* @return the index of the element or a negative value if it could not be found
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
ssize_t cx_linked_list_find_node(
void **result,
const void *start,
/**
* Finds the first node in a linked list.
*
- * The function starts with the pointer denoted by \p node and traverses the list
+ * The function starts with the pointer denoted by @p node and traverses the list
* along a prev pointer whose location within the node struct is
- * denoted by \p loc_prev.
+ * denoted by @p loc_prev.
*
* @param node a pointer to a node in the list
- * @param loc_prev the location of the \c prev pointer
+ * @param loc_prev the location of the @c prev pointer
* @return a pointer to the first node
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_returns_nonnull
void *cx_linked_list_first(
const void *node,
ptrdiff_t loc_prev
/**
* Finds the last node in a linked list.
*
- * The function starts with the pointer denoted by \p node and traverses the list
+ * The function starts with the pointer denoted by @p node and traverses the list
* along a next pointer whose location within the node struct is
- * denoted by \p loc_next.
+ * denoted by @p loc_next.
*
* @param node a pointer to a node in the list
- * @param loc_next the location of the \c next pointer
+ * @param loc_next the location of the @c next pointer
* @return a pointer to the last node
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_returns_nonnull
void *cx_linked_list_last(
const void *node,
ptrdiff_t loc_next
/**
* Finds the predecessor of a node in case it is not linked.
*
- * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined.
+ * @remark If @p node is not contained in the list starting with @p begin, the behavior is undefined.
*
* @param begin the node where to start the search
- * @param loc_next the location of the \c next pointer
+ * @param loc_next the location of the @c next pointer
* @param node the successor of the node to find
- * @return the node or \c NULL if \p node has no predecessor
+ * @return the node or @c NULL if @p node has no predecessor
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void *cx_linked_list_prev(
const void *begin,
ptrdiff_t loc_next,
* Adds a new node to a linked list.
* The node must not be part of any list already.
*
- * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ * @remark One of the pointers @p begin or @p end may be @c NULL, but not both.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param new_node a pointer to the node that shall be appended
*/
-__attribute__((__nonnull__(5)))
+cx_attr_nonnull_arg(5)
void cx_linked_list_add(
void **begin,
void **end,
* Prepends a new node to a linked list.
* The node must not be part of any list already.
*
- * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ * @remark One of the pointers @p begin or @p end may be @c NULL, but not both.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param new_node a pointer to the node that shall be prepended
*/
-__attribute__((__nonnull__(5)))
+cx_attr_nonnull_arg(5)
void cx_linked_list_prepend(
void **begin,
void **end,
/**
* Links two nodes.
*
- * @param left the new predecessor of \p right
- * @param right the new successor of \p left
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param left the new predecessor of @p right
+ * @param right the new successor of @p left
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cx_linked_list_link(
void *left,
void *right,
*
* If right is not the successor of left, the behavior is undefined.
*
- * @param left the predecessor of \p right
- * @param right the successor of \p left
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param left the predecessor of @p right
+ * @param right the successor of @p left
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cx_linked_list_unlink(
void *left,
void *right,
* Inserts a new node after a given node of a linked list.
* The new node must not be part of any list already.
*
- * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
- * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list.
+ * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or
+ * the @p end pointer to determine the start of the list. Then the new node will be prepended to the list.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
- * @param node the node after which to insert (\c NULL if you want to prepend the node to the list)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param node the node after which to insert (@c NULL if you want to prepend the node to the list)
* @param new_node a pointer to the node that shall be inserted
*/
-__attribute__((__nonnull__(6)))
+cx_attr_nonnull_arg(6)
void cx_linked_list_insert(
void **begin,
void **end,
* The chain must not be part of any list already.
*
* If you do not explicitly specify the end of the chain, it will be determined by traversing
- * the \c next pointer.
+ * the @c next pointer.
*
- * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
- * the \p end pointer to determine the start of the list. If only the \p end pointer is specified, you also need
- * to provide a valid \p loc_prev location.
+ * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or
+ * the @p end pointer to determine the start of the list. If only the @p end pointer is specified, you also need
+ * to provide a valid @p loc_prev location.
* Then the chain will be prepended to the list.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
- * @param node the node after which to insert (\c NULL to prepend the chain to the list)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param node the node after which to insert (@c NULL to prepend the chain to the list)
* @param insert_begin a pointer to the first node of the chain that shall be inserted
* @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined)
*/
-__attribute__((__nonnull__(6)))
+cx_attr_nonnull_arg(6)
void cx_linked_list_insert_chain(
void **begin,
void **end,
* Inserts a node into a sorted linked list.
* The new node must not be part of any list already.
*
- * If the list starting with the node pointed to by \p begin is not sorted
+ * If the list starting with the node pointed to by @p begin is not sorted
* already, the behavior is undefined.
*
- * @param begin a pointer to the begin node pointer (required)
+ * @param begin a pointer to the beginning node pointer (required)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param new_node a pointer to the node that shall be inserted
* @param cmp_func a compare function that will receive the node pointers
*/
-__attribute__((__nonnull__(1, 5, 6)))
+cx_attr_nonnull_arg(1, 5, 6)
void cx_linked_list_insert_sorted(
void **begin,
void **end,
* Inserts a chain of nodes into a sorted linked list.
* The chain must not be part of any list already.
*
- * If either the list starting with the node pointed to by \p begin or the list
- * starting with \p insert_begin is not sorted, the behavior is undefined.
+ * If either the list starting with the node pointed to by @p begin or the list
+ * starting with @p insert_begin is not sorted, the behavior is undefined.
*
- * \attention In contrast to cx_linked_list_insert_chain(), the source chain
+ * @attention In contrast to cx_linked_list_insert_chain(), the source chain
* will be broken and inserted into the target list so that the resulting list
- * will be sorted according to \p cmp_func. That means, each node in the source
+ * will be sorted according to @p cmp_func. That means, each node in the source
* chain may be re-linked with nodes from the target list.
*
- * @param begin a pointer to the begin node pointer (required)
+ * @param begin a pointer to the beginning node pointer (required)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param insert_begin a pointer to the first node of the chain that shall be inserted
* @param cmp_func a compare function that will receive the node pointers
*/
-__attribute__((__nonnull__(1, 5, 6)))
+cx_attr_nonnull_arg(1, 5, 6)
void cx_linked_list_insert_sorted_chain(
void **begin,
void **end,
cx_compare_func cmp_func
);
+/**
+ * Removes a chain of nodes from the linked list.
+ *
+ * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end)
+ * addresses are provided, the pointers are adjusted accordingly.
+ *
+ * The following combinations of arguments are valid (more arguments are optional):
+ * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
+ * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance)
+ *
+ * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used
+ * to traverse to a former adjacent node in the list, or within the chain.
+ *
+ * @param begin a pointer to the beginning node pointer (optional)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param node the start node of the chain
+ * @param num the number of nodes to remove
+ * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes)
+ */
+cx_attr_nonnull_arg(5)
+size_t cx_linked_list_remove_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node,
+ size_t num
+);
+
/**
* Removes a node from the linked list.
*
- * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end)
+ * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end)
* addresses are provided, the pointers are adjusted accordingly.
*
* The following combinations of arguments are valid (more arguments are optional):
- * \li \p loc_next and \p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
- * \li \p loc_next and \p begin (ancestor node is determined by list traversal, overall O(n) performance)
+ * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
+ * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance)
*
- * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used
+ * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used
* to traverse to a former adjacent node in the list.
*
- * @param begin a pointer to the begin node pointer (optional)
+ * @param begin a pointer to the beginning node pointer (optional)
* @param end a pointer to the end node pointer (optional)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param node the node to remove
*/
-__attribute__((__nonnull__(5)))
-void cx_linked_list_remove(
+cx_attr_nonnull_arg(5)
+static inline void cx_linked_list_remove(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *node
-);
-
+) {
+ cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1);
+}
/**
- * Determines the size of a linked list starting with \p node.
+ * Determines the size of a linked list starting with @p node.
+ *
* @param node the first node
- * @param loc_next the location of the \c next pointer within the node struct
- * @return the size of the list or zero if \p node is \c NULL
+ * @param loc_next the location of the @c next pointer within the node struct
+ * @return the size of the list or zero if @p node is @c NULL
*/
size_t cx_linked_list_size(
const void *node,
* Sorts a linked list based on a comparison function.
*
* This function can work with linked lists of the following structure:
- * \code
+ * @code
* typedef struct node node;
* struct node {
* node* prev;
* node* next;
* my_payload data;
* }
- * \endcode
+ * @endcode
*
* @note This is a recursive function with at most logarithmic recursion depth.
*
- * @param begin a pointer to the begin node pointer (required)
+ * @param begin a pointer to the beginning node pointer (required)
* @param end a pointer to the end node pointer (optional)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if not present)
- * @param loc_next the location of a \c next pointer within your node struct (required)
- * @param loc_data the location of the \c data pointer within your node struct
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param loc_data the location of the @c data pointer within your node struct
* @param cmp_func the compare function defining the sort order
*/
-__attribute__((__nonnull__(1, 6)))
+cx_attr_nonnull_arg(1, 6)
void cx_linked_list_sort(
void **begin,
void **end,
/**
* Compares two lists element wise.
*
- * \note Both list must have the same structure.
+ * @attention Both list must have the same structure.
*
- * @param begin_left the begin of the left list (\c NULL denotes an empty list)
- * @param begin_right the begin of the right list (\c NULL denotes an empty list)
+ * @param begin_left the beginning of the left list (@c NULL denotes an empty list)
+ * @param begin_right the beginning of the right list (@c NULL denotes an empty list)
* @param loc_advance the location of the pointer to advance
- * @param loc_data the location of the \c data pointer within your node struct
+ * @param loc_data the location of the @c data pointer within your node struct
* @param cmp_func the function to compare the elements
- * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the
+ * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the
* right list, positive if the left list is larger than the right list, zero if both lists are equal.
*/
-__attribute__((__nonnull__(5)))
+cx_attr_nonnull_arg(5)
int cx_linked_list_compare(
const void *begin_left,
const void *begin_right,
/**
* Reverses the order of the nodes in a linked list.
*
- * @param begin a pointer to the begin node pointer (required)
+ * @param begin a pointer to the beginning node pointer (required)
* @param end a pointer to the end node pointer (optional)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
*/
-__attribute__((__nonnull__(1)))
+cx_attr_nonnull_arg(1)
void cx_linked_list_reverse(
void **begin,
void **end,
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file list.h
- * \brief Interface for list implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file list.h
+ * @brief Interface for list implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_LIST_H
* Structure for holding the base data of a list.
*/
struct cx_list_s {
+ /**
+ * Common members for collections.
+ */
CX_COLLECTION_BASE;
/**
* The list class definition.
* Destructor function.
*
* Implementations SHALL invoke the content destructor functions if provided
- * and SHALL deallocate the list memory.
+ * and SHALL deallocate the entire list memory.
*/
- void (*destructor)(struct cx_list_s *list);
+ void (*deallocate)(struct cx_list_s *list);
/**
* Member function for inserting a single element.
/**
* Member function for inserting multiple elements.
* Implementors SHOULD see to performant implementations for corner cases.
+ *
* @see cx_list_default_insert_array()
*/
size_t (*insert_array)(
);
/**
- * Member function for removing an element.
+ * Member function for removing elements.
+ *
+ * Implementations SHALL check if @p targetbuf is set and copy the elements
+ * to the buffer without invoking any destructor.
+ * When @p targetbuf is not set, the destructors SHALL be invoked.
+ *
+ * The function SHALL return the actual number of elements removed, which
+ * might be lower than @p num when going out of bounds.
*/
- int (*remove)(
+ size_t (*remove)(
struct cx_list_s *list,
- size_t index
+ size_t index,
+ size_t num,
+ void *targetbuf
);
/**
/**
* Member function for swapping two elements.
+ *
* @see cx_list_default_swap()
*/
int (*swap)(
);
/**
- * Member function for sorting the list in-place.
+ * Member function for sorting the list.
+ *
* @see cx_list_default_sort()
*/
void (*sort)(struct cx_list_s *list);
/**
* Optional member function for comparing this list
* to another list of the same type.
- * If set to \c NULL, comparison won't be optimized.
+ * If set to @c NULL, comparison won't be optimized.
*/
+ cx_attr_nonnull
int (*compare)(
const struct cx_list_s *list,
const struct cx_list_s *other
* @param n the number of elements to insert
* @return the number of elements actually inserted
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
size_t cx_list_default_insert_array(
struct cx_list_s *list,
size_t index,
* This function uses the array insert function to insert consecutive groups
* of sorted data.
*
- * The source data \em must already be sorted wrt. the list's compare function.
+ * The source data @em must already be sorted wrt. the list's compare function.
*
* Use this in your own list class if you do not want to implement an optimized
* version for your list.
* @param n the number of elements to insert
* @return the number of elements actually inserted
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
size_t cx_list_default_insert_sorted(
struct cx_list_s *list,
const void *sorted_data,
*
* @param list the list that shall be sorted
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cx_list_default_sort(struct cx_list_s *list);
/**
* @param list the list in which to swap
* @param i index of one element
* @param j index of the other element
- * @return zero on success, non-zero when indices are out of bounds or memory
+ * @retval zero success
+ * @retval non-zero when indices are out of bounds or memory
* allocation for the temporary buffer fails
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
/**
* @param list the list
* @see cxListStorePointers()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cxListStoreObjects(CxList *list);
/**
* @param list the list
* @see cxListStoreObjects()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cxListStorePointers(CxList *list);
/**
* @return true, if this list is storing pointers
* @see cxListStorePointers()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline bool cxListIsStoringPointers(const CxList *list) {
return list->collection.store_pointer;
}
* @param list the list
* @return the number of currently stored elements
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxListSize(const CxList *list) {
return list->collection.size;
}
*
* @param list the list
* @param elem a pointer to the element to add
- * @return zero on success, non-zero on memory allocation failure
+ * @retval zero success
+ * @retval non-zero memory allocation failure
* @see cxListAddArray()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListAdd(
CxList *list,
const void *elem
* This method is more efficient than invoking cxListAdd() multiple times.
*
* If there is not enough memory to add all elements, the returned value is
- * less than \p n.
+ * less than @p n.
*
- * If this list is storing pointers instead of objects \p array is expected to
+ * If this list is storing pointers instead of objects @p array is expected to
* be an array of pointers.
*
* @param list the list
* @param n the number of elements to add
* @return the number of added elements
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxListAddArray(
CxList *list,
const void *array,
/**
* Inserts an item at the specified index.
*
- * If \p index equals the list \c size, this is effectively cxListAdd().
+ * If @p index equals the list @c size, this is effectively cxListAdd().
*
* @param list the list
* @param index the index the element shall have
* @param elem a pointer to the element to add
- * @return zero on success, non-zero on memory allocation failure
- * or when the index is out of bounds
+ * @retval zero success
+ * @retval non-zero memory allocation failure or the index is out of bounds
* @see cxListInsertAfter()
* @see cxListInsertBefore()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListInsert(
CxList *list,
size_t index,
*
* @param list the list
* @param elem a pointer to the element to add
- * @return zero on success, non-zero on memory allocation failure
+ * @retval zero success
+ * @retval non-zero memory allocation failure
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListInsertSorted(
CxList *list,
const void *elem
/**
* Inserts multiple items to the list at the specified index.
- * If \p index equals the list size, this is effectively cxListAddArray().
+ * If @p index equals the list size, this is effectively cxListAddArray().
*
* This method is usually more efficient than invoking cxListInsert()
* multiple times.
*
* If there is not enough memory to add all elements, the returned value is
- * less than \p n.
+ * less than @p n.
*
- * If this list is storing pointers instead of objects \p array is expected to
+ * If this list is storing pointers instead of objects @p array is expected to
* be an array of pointers.
*
* @param list the list
* @param n the number of elements to add
* @return the number of added elements
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxListInsertArray(
CxList *list,
size_t index,
* because consecutive chunks of sorted data are inserted in one pass.
*
* If there is not enough memory to add all elements, the returned value is
- * less than \p n.
+ * less than @p n.
*
- * If this list is storing pointers instead of objects \p array is expected to
+ * If this list is storing pointers instead of objects @p array is expected to
* be an array of pointers.
*
* @param list the list
* @param n the number of elements to add
* @return the number of added elements
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxListInsertSortedArray(
CxList *list,
const void *array,
* The used iterator remains operational, but all other active iterators should
* be considered invalidated.
*
- * If \p iter is not a list iterator, the behavior is undefined.
- * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ * If @p iter is not a list iterator, the behavior is undefined.
+ * If @p iter is a past-the-end iterator, the new element gets appended to the list.
*
* @param iter an iterator
* @param elem the element to insert
- * @return zero on success, non-zero on memory allocation failure
+ * @retval zero success
+ * @retval non-zero memory allocation failure
* @see cxListInsert()
* @see cxListInsertBefore()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListInsertAfter(
CxIterator *iter,
const void *elem
* The used iterator remains operational, but all other active iterators should
* be considered invalidated.
*
- * If \p iter is not a list iterator, the behavior is undefined.
- * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ * If @p iter is not a list iterator, the behavior is undefined.
+ * If @p iter is a past-the-end iterator, the new element gets appended to the list.
*
* @param iter an iterator
* @param elem the element to insert
- * @return zero on success, non-zero on memory allocation failure
+ * @retval zero success
+ * @retval non-zero memory allocation failure
* @see cxListInsert()
* @see cxListInsertAfter()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListInsertBefore(
CxIterator *iter,
const void *elem
*
* @param list the list
* @param index the index of the element
- * @return zero on success, non-zero if the index is out of bounds
+ * @retval zero success
+ * @retval non-zero index out of bounds
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListRemove(
CxList *list,
size_t index
) {
- return list->cl->remove(list, index);
+ return list->cl->remove(list, index, 1, NULL) == 0;
}
/**
- * Removes all elements from this list.
+ * Removes and returns the element at the specified index.
+ *
+ * No destructor is called and instead the element is copied to the
+ * @p targetbuf which MUST be large enough to hold the removed element.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @param targetbuf a buffer where to copy the element
+ * @retval zero success
+ * @retval non-zero index out of bounds
+ */
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxListRemoveAndGet(
+ CxList *list,
+ size_t index,
+ void *targetbuf
+) {
+ return list->cl->remove(list, index, 1, targetbuf) == 0;
+}
+
+/**
+ * Removes multiple element starting at the specified index.
*
* If an element destructor function is specified, it is called for each
+ * element. It is guaranteed that the destructor is called before removing
+ * the element, however, due to possible optimizations it is neither guaranteed
+ * that the destructors are invoked for all elements before starting to remove
+ * them, nor that the element is removed immediately after the destructor call
+ * before proceeding to the next element.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @param num the number of elements to remove
+ * @return the actual number of removed elements
+ */
+cx_attr_nonnull
+static inline size_t cxListRemoveArray(
+ CxList *list,
+ size_t index,
+ size_t num
+) {
+ return list->cl->remove(list, index, num, NULL);
+}
+
+/**
+ * Removes and returns multiple element starting at the specified index.
+ *
+ * No destructor is called and instead the elements are copied to the
+ * @p targetbuf which MUST be large enough to hold all removed elements.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @param num the number of elements to remove
+ * @param targetbuf a buffer where to copy the elements
+ * @return the actual number of removed elements
+ */
+cx_attr_nonnull
+cx_attr_access_w(4)
+static inline size_t cxListRemoveArrayAndGet(
+ CxList *list,
+ size_t index,
+ size_t num,
+ void *targetbuf
+) {
+ return list->cl->remove(list, index, num, targetbuf);
+}
+
+/**
+ * Removes all elements from this list.
+ *
+ * If element destructor functions are specified, they are called for each
* element before removing them.
*
* @param list the list
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxListClear(CxList *list) {
list->cl->clear(list);
}
* @param list the list
* @param i the index of the first element
* @param j the index of the second element
- * @return zero on success, non-zero if one of the indices is out of bounds
+ * @retval zero success
+ * @retval non-zero one of the indices is out of bounds
+ * or the swap needed extra memory but allocation failed
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListSwap(
CxList *list,
size_t i,
*
* @param list the list
* @param index the index of the element
- * @return a pointer to the element or \c NULL if the index is out of bounds
+ * @return a pointer to the element or @c NULL if the index is out of bounds
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void *cxListAt(
- CxList *list,
+ const CxList *list,
size_t index
) {
return list->cl->at(list, index);
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListIteratorAt(
const CxList *list,
size_t index
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListBackwardsIteratorAt(
const CxList *list,
size_t index
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
CxIterator cxListMutIteratorAt(
CxList *list,
size_t index
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
CxIterator cxListMutBackwardsIteratorAt(
CxList *list,
size_t index
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListIterator(const CxList *list) {
return list->cl->iterator(list, 0, false);
}
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListMutIterator(CxList *list) {
return cxListMutIteratorAt(list, 0);
}
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListBackwardsIterator(const CxList *list) {
return list->cl->iterator(list, list->collection.size - 1, true);
}
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListMutBackwardsIterator(CxList *list) {
return cxListMutBackwardsIteratorAt(list, list->collection.size - 1);
}
/**
- * Returns the index of the first element that equals \p elem.
+ * Returns the index of the first element that equals @p elem.
*
* Determining equality is performed by the list's comparator function.
*
* @return the index of the element or a negative
* value when the element is not found
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline ssize_t cxListFind(
const CxList *list,
const void *elem
}
/**
- * Removes and returns the index of the first element that equals \p elem.
+ * Removes and returns the index of the first element that equals @p elem.
*
* Determining equality is performed by the list's comparator function.
*
* @return the index of the now removed element or a negative
* value when the element is not found or could not be removed
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline ssize_t cxListFindRemove(
CxList *list,
const void *elem
}
/**
- * Sorts the list in-place.
+ * Sorts the list.
*
- * \remark The underlying sort algorithm is implementation defined.
+ * @remark The underlying sort algorithm is implementation defined.
*
* @param list the list
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxListSort(CxList *list) {
list->cl->sort(list);
}
*
* @param list the list
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxListReverse(CxList *list) {
list->cl->reverse(list);
}
*
* @param list the list
* @param other the list to compare to
- * @return zero, if both lists are equal element wise,
- * negative if the first list is smaller, positive if the first list is larger
- */
-__attribute__((__nonnull__))
+ * @retval zero both lists are equal element wise
+ * @retval negative the first list is smaller
+ * or the first non-equal element in the first list is smaller
+ * @retval positive the first list is larger
+ * or the first non-equal element in the first list is larger
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
int cxListCompare(
const CxList *list,
const CxList *other
/**
* Deallocates the memory of the specified list structure.
*
- * Also calls content a destructor function, depending on the configuration
- * in CxList.content_destructor_type.
- *
- * This function itself is a destructor function for the CxList.
+ * Also calls the content destructor functions for each element, if specified.
*
- * @param list the list which shall be destroyed
+ * @param list the list which shall be freed
*/
-__attribute__((__nonnull__))
-void cxListDestroy(CxList *list);
+void cxListFree(CxList *list);
/**
* A shared instance of an empty list.
*
- * Writing to that list is undefined.
+ * Writing to that list is not allowed.
+ *
+ * You can use this is a placeholder for initializing CxList pointers
+ * for which you do not want to reserve memory right from the beginning.
*/
-extern CxList * const cxEmptyList;
+extern CxList *const cxEmptyList;
#ifdef __cplusplus
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file map.h
- * \brief Interface for map implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file map.h
+ * @brief Interface for map implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_MAP_H
/**
* Deallocates the entire memory.
*/
- __attribute__((__nonnull__))
- void (*destructor)(struct cx_map_s *map);
+ void (*deallocate)(struct cx_map_s *map);
/**
* Removes all elements.
*/
- __attribute__((__nonnull__))
void (*clear)(struct cx_map_s *map);
/**
* Add or overwrite an element.
*/
- __attribute__((__nonnull__))
int (*put)(
CxMap *map,
CxHashKey key,
/**
* Returns an element.
*/
- __attribute__((__nonnull__, __warn_unused_result__))
void *(*get)(
const CxMap *map,
CxHashKey key
/**
* Removes an element.
+ *
+ * Implementations SHALL check if @p targetbuf is set and copy the elements
+ * to the buffer without invoking any destructor.
+ * When @p targetbuf is not set, the destructors SHALL be invoked.
+ *
+ * The function SHALL return zero when the @p key was found and
+ * non-zero, otherwise.
*/
- __attribute__((__nonnull__))
- void *(*remove)(
+ int (*remove)(
CxMap *map,
CxHashKey key,
- bool destroy
+ void *targetbuf
);
/**
* Creates an iterator for this map.
*/
- __attribute__((__nonnull__, __warn_unused_result__))
CxIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type);
};
/**
* A shared instance of an empty map.
*
- * Writing to that map is undefined.
+ * Writing to that map is not allowed.
+ *
+ * You can use this is a placeholder for initializing CxMap pointers
+ * for which you do not want to reserve memory right from the beginning.
*/
extern CxMap *const cxEmptyMap;
* @param map the map
* @see cxMapStorePointers()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxMapStoreObjects(CxMap *map) {
map->collection.store_pointer = false;
}
* @param map the map
* @see cxMapStoreObjects()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxMapStorePointers(CxMap *map) {
map->collection.store_pointer = true;
map->collection.elem_size = sizeof(void *);
* @return true, if this map is storing pointers
* @see cxMapStorePointers()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline bool cxMapIsStoringPointers(const CxMap *map) {
return map->collection.store_pointer;
}
/**
* Deallocates the memory of the specified map.
*
- * @param map the map to be destroyed
+ * Also calls the content destructor functions for each element, if specified.
+ *
+ * @param map the map to be freed
*/
-__attribute__((__nonnull__))
-static inline void cxMapDestroy(CxMap *map) {
- map->cl->destructor(map);
-}
+void cxMapFree(CxMap *map);
/**
* Clears a map by removing all elements.
*
+ * Also calls the content destructor functions for each element, if specified.
+ *
* @param map the map to be cleared
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxMapClear(CxMap *map) {
map->cl->clear(map);
}
* @param map the map
* @return the number of stored elements
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxMapSize(const CxMap *map) {
return map->collection.size;
}
-
-// TODO: set-like map operations (union, intersect, difference)
-
/**
* Creates a value iterator for a map.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored values
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxMapIteratorValues(const CxMap *map) {
return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
}
*
* The elements of the iterator are keys of type CxHashKey.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored keys
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxMapIteratorKeys(const CxMap *map) {
return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
}
*
* The elements of the iterator are key/value pairs of type CxMapEntry.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @see cxMapIteratorKeys()
* @see cxMapIteratorValues()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxMapIterator(const CxMap *map) {
return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
}
/**
* Creates a mutating iterator over the values of a map.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored values
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
CxIterator cxMapMutIteratorValues(CxMap *map);
/**
*
* The elements of the iterator are keys of type CxHashKey.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored keys
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
CxIterator cxMapMutIteratorKeys(CxMap *map);
/**
*
* The elements of the iterator are key/value pairs of type CxMapEntry.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @see cxMapMutIteratorKeys()
* @see cxMapMutIteratorValues()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
CxIterator cxMapMutIterator(CxMap *map);
#ifdef __cplusplus
} // end the extern "C" block here, because we want to start overloading
-
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxMapPut(
CxMap *map,
CxHashKey const &key,
return map->cl->put(map, key, value);
}
-
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxMapPut(
CxMap *map,
cxstring const &key,
return map->cl->put(map, cx_hash_key_cxstr(key), value);
}
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxMapPut(
CxMap *map,
cxmutstr const &key,
return map->cl->put(map, cx_hash_key_cxstr(key), value);
}
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
static inline int cxMapPut(
CxMap *map,
const char *key,
return map->cl->put(map, cx_hash_key_str(key), value);
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxMapGet(
const CxMap *map,
CxHashKey const &key
return map->cl->get(map, key);
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxMapGet(
const CxMap *map,
cxstring const &key
return map->cl->get(map, cx_hash_key_cxstr(key));
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxMapGet(
const CxMap *map,
cxmutstr const &key
return map->cl->get(map, cx_hash_key_cxstr(key));
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(2)
static inline void *cxMapGet(
const CxMap *map,
const char *key
return map->cl->get(map, cx_hash_key_str(key));
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+static inline int cxMapRemove(
CxMap *map,
CxHashKey const &key
) {
- (void) map->cl->remove(map, key, true);
+ return map->cl->remove(map, key, nullptr);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+static inline int cxMapRemove(
CxMap *map,
cxstring const &key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+static inline int cxMapRemove(
CxMap *map,
cxmutstr const &key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cxMapRemove(
CxMap *map,
const char *key
) {
- (void) map->cl->remove(map, cx_hash_key_str(key), true);
+ return map->cl->remove(map, cx_hash_key_str(key), nullptr);
}
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- CxHashKey const &key
+ CxHashKey key,
+ void *targetbuf
) {
- (void) map->cl->remove(map, key, false);
+ return map->cl->remove(map, key, targetbuf);
}
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- cxstring const &key
+ cxstring key,
+ void *targetbuf
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- cxmutstr const &key
+ cxmutstr key,
+ void *targetbuf
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
+cx_attr_nonnull
+cx_attr_access_w(3)
+cx_attr_cstr_arg(2)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- const char *key
+ const char *key,
+ void *targetbuf
) {
- (void) map->cl->remove(map, cx_hash_key_str(key), false);
+ return map->cl->remove(map, cx_hash_key_str(key), targetbuf);
}
-
-
#else // __cplusplus
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cx_map_put(
CxMap *map,
CxHashKey key,
}
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cx_map_put_cxstr(
CxMap *map,
cxstring key,
}
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cx_map_put_mustr(
CxMap *map,
cxmutstr key,
}
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
static inline int cx_map_put_str(
CxMap *map,
const char *key,
/**
* Puts a key/value-pair into the map.
*
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * A possible existing value will be overwritten.
+ *
+ * If this map is storing pointers, the @p value pointer is written
+ * to the map. Otherwise, the memory is copied from @p value with
+ * memcpy().
+ *
+ * The @p key is always copied.
+ *
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @param value (@c void*) the value
+ * @retval zero success
+ * @retval non-zero value on memory allocation failure
*/
#define cxMapPut(map, key, value) _Generic((key), \
CxHashKey: cx_map_put, \
(map, key, value)
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cx_map_get(
const CxMap *map,
CxHashKey key
}
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cx_map_get_cxstr(
const CxMap *map,
cxstring key
}
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cx_map_get_mustr(
const CxMap *map,
cxmutstr key
}
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(2)
static inline void *cx_map_get_str(
const CxMap *map,
const char *key
/**
* Retrieves a value by using a key.
*
- * @param map the map
- * @param key the key
- * @return the value
+ * If this map is storing pointers, the stored pointer is returned.
+ * Otherwise, a pointer to the element within the map's memory
+ * is returned (which is valid as long as the element stays in the map).
+ *
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @return (@c void*) the value
*/
#define cxMapGet(map, key) _Generic((key), \
CxHashKey: cx_map_get, \
(map, key)
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove(
+cx_attr_nonnull
+static inline int cx_map_remove(
CxMap *map,
CxHashKey key
) {
- (void) map->cl->remove(map, key, true);
+ return map->cl->remove(map, key, NULL);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove_cxstr(
+cx_attr_nonnull
+static inline int cx_map_remove_cxstr(
CxMap *map,
cxstring key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), NULL);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove_mustr(
+cx_attr_nonnull
+static inline int cx_map_remove_mustr(
CxMap *map,
cxmutstr key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), NULL);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove_str(
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cx_map_remove_str(
CxMap *map,
const char *key
) {
- (void) map->cl->remove(map, cx_hash_key_str(key), true);
+ return map->cl->remove(map, cx_hash_key_str(key), NULL);
}
/**
* Removes a key/value-pair from the map by using the key.
*
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
+ * Always invokes the destructors functions, if any, on the removed element.
*
- * @param map the map
- * @param key the key
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @retval zero success
+ * @retval non-zero the key was not found
+ *
* @see cxMapRemoveAndGet()
- * @see cxMapDetach()
*/
#define cxMapRemove(map, key) _Generic((key), \
CxHashKey: cx_map_remove, \
(map, key)
/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
- */
-__attribute__((__nonnull__))
-static inline void cx_map_detach(
- CxMap *map,
- CxHashKey key
-) {
- (void) map->cl->remove(map, key, false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
- */
-__attribute__((__nonnull__))
-static inline void cx_map_detach_cxstr(
- CxMap *map,
- cxstring key
-) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
- */
-__attribute__((__nonnull__))
-static inline void cx_map_detach_mustr(
- CxMap *map,
- cxmutstr key
-) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
- */
-__attribute__((__nonnull__))
-static inline void cx_map_detach_str(
- CxMap *map,
- const char *key
-) {
- (void) map->cl->remove(map, cx_hash_key_str(key), false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-#define cxMapDetach(map, key) _Generic((key), \
- CxHashKey: cx_map_detach, \
- cxstring: cx_map_detach_cxstr, \
- cxmutstr: cx_map_detach_mustr, \
- char*: cx_map_detach_str, \
- const char*: cx_map_detach_str) \
- (map, key)
-
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cx_map_remove_and_get(
CxMap *map,
- CxHashKey key
+ CxHashKey key,
+ void *targetbuf
) {
- return map->cl->remove(map, key, !map->collection.store_pointer);
+ return map->cl->remove(map, key, targetbuf);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get_cxstr(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cx_map_remove_and_get_cxstr(
CxMap *map,
- cxstring key
+ cxstring key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get_mustr(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cx_map_remove_and_get_mustr(
CxMap *map,
- cxmutstr key
+ cxmutstr key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get_str(
+cx_attr_nonnull
+cx_attr_access_w(3)
+cx_attr_cstr_arg(2)
+static inline int cx_map_remove_and_get_str(
CxMap *map,
- const char *key
+ const char *key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_str(key), !map->collection.store_pointer);
+ return map->cl->remove(map, cx_hash_key_str(key), targetbuf);
}
/**
* Removes a key/value-pair from the map by using the key.
*
- * This function can be used when the map is storing pointers,
- * in order to retrieve the pointer from the map without invoking
- * any destructor function. Sometimes you do not want the pointer
- * to be returned - in that case (instead of suppressing the "unused
- * result" warning) you can use cxMapDetach().
+ * This function will copy the contents of the removed element
+ * to the target buffer must be guaranteed to be large enough
+ * to hold the element (the map's element size).
+ * The destructor functions, if any, will @em not be called.
*
- * If this map is not storing pointers, this function behaves like
- * cxMapRemove() and returns \c NULL.
+ * If this map is storing pointers, the element is the pointer itself
+ * and not the object it points to.
*
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @param targetbuf (@c void*) the buffer where the element shall be copied to
+ * @retval zero success
+ * @retval non-zero the key was not found
+ *
* @see cxMapStorePointers()
- * @see cxMapDetach()
+ * @see cxMapRemove()
*/
-#define cxMapRemoveAndGet(map, key) _Generic((key), \
+#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \
CxHashKey: cx_map_remove_and_get, \
cxstring: cx_map_remove_and_get_cxstr, \
cxmutstr: cx_map_remove_and_get_mustr, \
char*: cx_map_remove_and_get_str, \
const char*: cx_map_remove_and_get_str) \
- (map, key)
+ (map, key, targetbuf)
#endif // __cplusplus
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file mempool.h
- * \brief Interface for memory pool implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file mempool.h
+ * @brief Interface for memory pool implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_MEMPOOL_H
*/
typedef struct cx_mempool_s CxMempool;
+/**
+ * Deallocates a memory pool and frees the managed memory.
+ *
+ * @param pool the memory pool to free
+ */
+void cxMempoolFree(CxMempool *pool);
+
/**
* Creates an array-based memory pool with a shared destructor function.
*
* This destructor MUST NOT free the memory.
*
* @param capacity the initial capacity of the pool
- * @param destr the destructor function to use for allocated memory
- * @return the created memory pool or \c NULL if allocation failed
+ * @param destr optional destructor function to use for allocated memory
+ * @return the created memory pool or @c NULL if allocation failed
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxMempoolFree, 1)
CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr);
/**
* Creates a basic array-based memory pool.
*
- * @param capacity the initial capacity of the pool
- * @return the created memory pool or \c NULL if allocation failed
- */
-__attribute__((__warn_unused_result__))
-static inline CxMempool *cxBasicMempoolCreate(size_t capacity) {
- return cxMempoolCreate(capacity, NULL);
-}
-
-/**
- * Destroys a memory pool and frees the managed memory.
- *
- * @param pool the memory pool to destroy
+ * @param capacity (@c size_t) the initial capacity of the pool
+ * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed
*/
-__attribute__((__nonnull__))
-void cxMempoolDestroy(CxMempool *pool);
+#define cxBasicMempoolCreate(capacity) cxMempoolCreate(capacity, NULL)
/**
* Sets the destructor function for a specific allocated memory object.
* @param memory the object allocated in the pool
* @param fnc the destructor function
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cxMempoolSetDestructor(
void *memory,
cx_destructor_func fnc
);
+/**
+ * Removes the destructor function for a specific allocated memory object.
+ *
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ * The destructor MUST NOT free the memory.
+ *
+ * @param memory the object allocated in the pool
+ */
+cx_attr_nonnull
+void cxMempoolRemoveDestructor(void *memory);
+
/**
* Registers foreign memory with this pool.
*
* If that allocation fails, this function will return non-zero.
*
* @param pool the pool
- * @param memory the object allocated in the pool
+ * @param memory the object to register (MUST NOT be already allocated in the pool)
* @param destr the destructor function
- * @return zero on success, non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxMempoolRegister(
CxMempool *pool,
void *memory,
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file printf.h
- * \brief Wrapper for write functions with a printf-like interface.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file printf.h
+ * @brief Wrapper for write functions with a printf-like interface.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_PRINTF_H
#include "string.h"
#include <stdarg.h>
+/**
+ * Attribute for printf-like functions.
+ * @param fmt_idx index of the format string parameter
+ * @param arg_idx index of the first formatting argument
+ */
+#define cx_attr_printf(fmt_idx, arg_idx) \
+ __attribute__((__format__(printf, fmt_idx, arg_idx)))
+
#ifdef __cplusplus
extern "C" {
#endif
/**
* The maximum string length that fits into stack memory.
*/
-extern unsigned const cx_printf_sbo_size;
+extern const unsigned cx_printf_sbo_size;
/**
- * A \c fprintf like function which writes the output to a stream by
+ * A @c fprintf like function which writes the output to a stream by
* using a write_func.
*
* @param stream the stream the data is written to
* @param wfc the write function
* @param fmt format string
* @param ... additional arguments
- * @return the total number of bytes written
+ * @return the total number of bytes written or an error code from stdlib printf implementation
*/
-__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4)))
+cx_attr_nonnull_arg(1, 2, 3)
+cx_attr_printf(3, 4)
+cx_attr_cstr_arg(3)
int cx_fprintf(
void *stream,
cx_write_func wfc,
);
/**
- * A \c vfprintf like function which writes the output to a stream by
+ * A @c vfprintf like function which writes the output to a stream by
* using a write_func.
*
* @param stream the stream the data is written to
* @param wfc the write function
* @param fmt format string
* @param ap argument list
- * @return the total number of bytes written
+ * @return the total number of bytes written or an error code from stdlib printf implementation
* @see cx_fprintf()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(3)
int cx_vfprintf(
void *stream,
cx_write_func wfc,
);
/**
- * A \c asprintf like function which allocates space for a string
+ * A @c asprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was an error, in which case the string's pointer
+ * will be @c NULL.
*
* @param allocator the CxAllocator used for allocating the string
* @param fmt format string
* @return the formatted string
* @see cx_strfree_a()
*/
-__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3)))
+cx_attr_nonnull_arg(1, 2)
+cx_attr_printf(2, 3)
+cx_attr_cstr_arg(2)
cxmutstr cx_asprintf_a(
const CxAllocator *allocator,
const char *fmt,
);
/**
- * A \c asprintf like function which allocates space for a string
+ * A @c asprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was an error, in which case the string's pointer
+ * will be @c NULL.
*
- * @param fmt format string
+ * @param fmt (@c char*) format string
* @param ... additional arguments
- * @return the formatted string
+ * @return (@c cxmutstr) the formatted string
* @see cx_strfree()
*/
#define cx_asprintf(fmt, ...) \
cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__)
/**
-* A \c vasprintf like function which allocates space for a string
+* A @c vasprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was an error, in which case the string's pointer
+ * will be @c NULL.
*
* @param allocator the CxAllocator used for allocating the string
* @param fmt format string
* @return the formatted string
* @see cx_asprintf_a()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
cxmutstr cx_vasprintf_a(
const CxAllocator *allocator,
const char *fmt,
);
/**
-* A \c vasprintf like function which allocates space for a string
+* A @c vasprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was in error, in which case the string's pointer
+ * will be @c NULL.
*
- * @param fmt format string
- * @param ap argument list
- * @return the formatted string
+ * @param fmt (@c char*) format string
+ * @param ap (@c va_list) argument list
+ * @return (@c cxmutstr) the formatted string
* @see cx_asprintf()
*/
#define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap)
/**
- * A \c printf like function which writes the output to a CxBuffer.
+ * A @c printf like function which writes the output to a CxBuffer.
*
- * @param buffer a pointer to the buffer the data is written to
- * @param fmt the format string
+ * @param buffer (@c CxBuffer*) a pointer to the buffer the data is written to
+ * @param fmt (@c char*) the format string
* @param ... additional arguments
- * @return the total number of bytes written
- * @see ucx_fprintf()
+ * @return (@c int) the total number of bytes written or an error code from stdlib printf implementation
+ * @see cx_fprintf()
+ * @see cxBufferWrite()
*/
-#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
+#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \
(cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
/**
- * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
*
- * @param str a pointer to the string buffer
- * @param len a pointer to the length of the buffer
- * @param fmt the format string
+ * @param str (@c char**) a pointer to the string buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param fmt (@c char*) the format string
* @param ... additional arguments
- * @return the length of produced string
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
*/
#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
/**
- * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
*
- * \attention The original buffer MUST have been allocated with the same allocator!
+ * @attention The original buffer MUST have been allocated with the same allocator!
*
* @param alloc the allocator to use
* @param str a pointer to the string buffer
* @param len a pointer to the length of the buffer
* @param fmt the format string
* @param ... additional arguments
- * @return the length of produced string
+ * @return the length of produced string or an error code from stdlib printf implementation
*/
-__attribute__((__nonnull__(1, 2, 3, 4), __format__(printf, 4, 5)))
-int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... );
+cx_attr_nonnull_arg(1, 2, 3, 4)
+cx_attr_printf(4, 5)
+cx_attr_cstr_arg(4)
+int cx_sprintf_a(
+ CxAllocator *alloc,
+ char **str,
+ size_t *len,
+ const char *fmt,
+ ...
+);
/**
- * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
*
- * @param str a pointer to the string buffer
- * @param len a pointer to the length of the buffer
- * @param fmt the format string
- * @param ap argument list
- * @return the length of produced string
+ * @param str (@c char**) a pointer to the string buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param fmt (@c char*) the format string
+ * @param ap (@c va_list) argument list
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
*/
#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
/**
- * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated.
*
- * \attention The original buffer MUST have been allocated with the same allocator!
+ * @attention The original buffer MUST have been allocated with the same allocator!
*
* @param alloc the allocator to use
* @param str a pointer to the string buffer
* @param len a pointer to the length of the buffer
* @param fmt the format string
* @param ap argument list
- * @return the length of produced string
+ * @return the length of produced string or an error code from stdlib printf implementation
*/
-__attribute__((__nonnull__))
-int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap);
+cx_attr_nonnull
+cx_attr_cstr_arg(4)
+cx_attr_access_rw(2)
+cx_attr_access_rw(3)
+int cx_vsprintf_a(
+ CxAllocator *alloc,
+ char **str,
+ size_t *len,
+ const char *fmt,
+ va_list ap
+);
/**
- * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * The location of the resulting string will \em always be stored to \p str. When the buffer
- * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
*
- * \remark When a new string needed to be allocated, the contents of \p buf will be
- * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
*
- * @param buf a pointer to the buffer
- * @param len a pointer to the length of the buffer
- * @param str a pointer to the location
- * @param fmt the format string
+ * @param buf (@c char*) a pointer to the buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param str (@c char**) a pointer where the location of the result shall be stored
+ * @param fmt (@c char*) the format string
* @param ... additional arguments
- * @return the length of produced string
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
*/
#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
/**
- * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * The location of the resulting string will \em always be stored to \p str. When the buffer
- * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
*
- * \remark When a new string needed to be allocated, the contents of \p buf will be
- * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
*
* @param alloc the allocator to use
* @param buf a pointer to the buffer
* @param len a pointer to the length of the buffer
- * @param str a pointer to the location
+ * @param str a pointer where the location of the result shall be stored
* @param fmt the format string
* @param ... additional arguments
- * @return the length of produced string
+ * @return the length of produced string or an error code from stdlib printf implementation
*/
-__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6)))
-int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... );
+cx_attr_nonnull_arg(1, 2, 4, 5)
+cx_attr_printf(5, 6)
+cx_attr_cstr_arg(5)
+cx_attr_access_rw(2)
+cx_attr_access_rw(3)
+cx_attr_access_rw(4)
+int cx_sprintf_sa(
+ CxAllocator *alloc,
+ char *buf,
+ size_t *len,
+ char **str,
+ const char *fmt,
+ ...
+);
/**
- * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * The location of the resulting string will \em always be stored to \p str. When the buffer
- * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated.
*
- * \remark When a new string needed to be allocated, the contents of \p buf will be
- * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
*
- * @param buf a pointer to the buffer
- * @param len a pointer to the length of the buffer
- * @param str a pointer to the location
- * @param fmt the format string
- * @param ap argument list
- * @return the length of produced string
+ * @param buf (@c char*) a pointer to the buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param str (@c char**) a pointer where the location of the result shall be stored
+ * @param fmt (@c char*) the format string
+ * @param ap (@c va_list) argument list
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
*/
#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
/**
- * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
*
- * The size of the buffer will be updated in \p len when necessary.
+ * The size of the buffer will be updated in @p len when necessary.
*
- * The location of the resulting string will \em always be stored to \p str. When the buffer
- * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated.
*
- * \remark When a new string needed to be allocated, the contents of \p buf will be
- * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
*
* @param alloc the allocator to use
* @param buf a pointer to the buffer
* @param len a pointer to the length of the buffer
- * @param str a pointer to the location
+ * @param str a pointer where the location of the result shall be stored
* @param fmt the format string
* @param ap argument list
- * @return the length of produced string
+ * @return the length of produced string or an error code from stdlib printf implementation
*/
-__attribute__((__nonnull__))
-int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap);
+cx_attr_nonnull
+cx_attr_cstr_arg(5)
+int cx_vsprintf_sa(
+ CxAllocator *alloc,
+ char *buf,
+ size_t *len,
+ char **str,
+ const char *fmt,
+ va_list ap
+);
#ifdef __cplusplus
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file properties.h
+ * @brief Interface for parsing data from properties files.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define UCX_PROPERTIES_H
+
+#include "common.h"
+#include "string.h"
+#include "map.h"
+#include "buffer.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Configures the expected characters for the properties parser.
+ */
+struct cx_properties_config_s {
+ /**
+ * The key/value delimiter that shall be used.
+ * This is '=' by default.
+ */
+ char delimiter;
+
+ /**
+ * The character, when appearing at the end of a line, continues that line.
+ * This is '\' by default.
+ */
+ // char continuation; // TODO: line continuation in properties
+
+ /**
+ * The first comment character.
+ * This is '#' by default.
+ */
+ char comment1;
+
+ /**
+ * The second comment character.
+ * This is not set by default.
+ */
+ char comment2;
+
+ /**
+ * The third comment character.
+ * This is not set by default.
+ */
+ char comment3;
+};
+
+/**
+ * Typedef for the properties config.
+ */
+typedef struct cx_properties_config_s CxPropertiesConfig;
+
+/**
+ * Default properties configuration.
+ */
+extern const CxPropertiesConfig cx_properties_config_default;
+
+/**
+ * Status codes for the properties interface.
+ */
+enum cx_properties_status {
+ /**
+ * Everything is fine.
+ */
+ CX_PROPERTIES_NO_ERROR,
+ /**
+ * The input buffer does not contain more data.
+ */
+ CX_PROPERTIES_NO_DATA,
+ /**
+ * The input ends unexpectedly.
+ *
+ * This either happens when the last line does not terminate with a line
+ * break, or when the input ends with a parsed key but no value.
+ */
+ CX_PROPERTIES_INCOMPLETE_DATA,
+ /**
+ * Not used as a status and never returned by any function.
+ *
+ * You can use this enumerator to check for all "good" status results
+ * by checking if the status is less than @c CX_PROPERTIES_OK.
+ *
+ * A "good" status means, that you can refill data and continue parsing.
+ */
+ CX_PROPERTIES_OK,
+ /**
+ * Input buffer is @c NULL.
+ */
+ CX_PROPERTIES_NULL_INPUT,
+ /**
+ * The line contains a delimiter, but no key.
+ */
+ CX_PROPERTIES_INVALID_EMPTY_KEY,
+ /**
+ * The line contains data, but no delimiter.
+ */
+ CX_PROPERTIES_INVALID_MISSING_DELIMITER,
+ /**
+ * More internal buffer was needed, but could not be allocated.
+ */
+ CX_PROPERTIES_BUFFER_ALLOC_FAILED,
+ /**
+ * Initializing the properties source failed.
+ *
+ * @see cx_properties_read_init_func
+ */
+ CX_PROPERTIES_READ_INIT_FAILED,
+ /**
+ * Reading from a properties source failed.
+ *
+ * @see cx_properties_read_func
+ */
+ CX_PROPERTIES_READ_FAILED,
+ /**
+ * Sinking a k/v-pair failed.
+ *
+ * @see cx_properties_sink_func
+ */
+ CX_PROPERTIES_SINK_FAILED,
+};
+
+/**
+ * Typedef for the properties status enum.
+ */
+typedef enum cx_properties_status CxPropertiesStatus;
+
+/**
+ * Interface for working with properties data.
+ */
+struct cx_properties_s {
+ /**
+ * The configuration.
+ */
+ CxPropertiesConfig config;
+
+ /**
+ * The text input buffer.
+ */
+ CxBuffer input;
+
+ /**
+ * Internal buffer.
+ */
+ CxBuffer buffer;
+};
+
+/**
+ * Typedef for the properties interface.
+ */
+typedef struct cx_properties_s CxProperties;
+
+
+/**
+ * Typedef for a properties sink.
+ */
+typedef struct cx_properties_sink_s CxPropertiesSink;
+
+/**
+ * A function that consumes a k/v-pair in a sink.
+ *
+ * The sink could be e.g. a map and the sink function would be calling
+ * a map function to store the k/v-pair.
+ *
+ * @param prop the properties interface that wants to sink a k/v-pair
+ * @param sink the sink
+ * @param key the key
+ * @param value the value
+ * @retval zero success
+ * @retval non-zero sinking the k/v-pair failed
+ */
+cx_attr_nonnull
+typedef int(*cx_properties_sink_func)(
+ CxProperties *prop,
+ CxPropertiesSink *sink,
+ cxstring key,
+ cxstring value
+);
+
+/**
+ * Defines a sink for k/v-pairs.
+ */
+struct cx_properties_sink_s {
+ /**
+ * The sink object.
+ */
+ void *sink;
+ /**
+ * Optional custom data.
+ */
+ void *data;
+ /**
+ * A function for consuming k/v-pairs into the sink.
+ */
+ cx_properties_sink_func sink_func;
+};
+
+
+/**
+ * Typedef for a properties source.
+ */
+typedef struct cx_properties_source_s CxPropertiesSource;
+
+/**
+ * A function that reads data from a source.
+ *
+ * When the source is depleted, implementations SHALL provide an empty
+ * string in the @p target and return zero.
+ * A non-zero return value is only permitted in case of an error.
+ *
+ * The meaning of the optional parameters is implementation-dependent.
+ *
+ * @param prop the properties interface that wants to read from the source
+ * @param src the source
+ * @param target a string buffer where the read data shall be stored
+ * @retval zero success
+ * @retval non-zero reading the data failed
+ */
+cx_attr_nonnull
+typedef int(*cx_properties_read_func)(
+ CxProperties *prop,
+ CxPropertiesSource *src,
+ cxstring *target
+);
+
+/**
+ * A function that may initialize additional memory for the source.
+ *
+ * @param prop the properties interface that wants to read from the source
+ * @param src the source
+ * @retval zero initialization was successful
+ * @retval non-zero otherwise
+ */
+cx_attr_nonnull
+typedef int(*cx_properties_read_init_func)(
+ CxProperties *prop,
+ CxPropertiesSource *src
+);
+
+/**
+ * A function that cleans memory initialized by the read_init_func.
+ *
+ * @param prop the properties interface that wants to read from the source
+ * @param src the source
+ */
+cx_attr_nonnull
+typedef void(*cx_properties_read_clean_func)(
+ CxProperties *prop,
+ CxPropertiesSource *src
+);
+
+/**
+ * Defines a properties source.
+ */
+struct cx_properties_source_s {
+ /**
+ * The source object.
+ *
+ * For example a file stream or a string.
+ */
+ void *src;
+ /**
+ * Optional additional data pointer.
+ */
+ void *data_ptr;
+ /**
+ * Optional size information.
+ */
+ size_t data_size;
+ /**
+ * A function that reads data from the source.
+ */
+ cx_properties_read_func read_func;
+ /**
+ * Optional function that may prepare the source for reading data.
+ */
+ cx_properties_read_init_func read_init_func;
+ /**
+ * Optional function that cleans additional memory allocated by the
+ * read_init_func.
+ */
+ cx_properties_read_clean_func read_clean_func;
+};
+
+/**
+ * Initialize a properties interface.
+ *
+ * @param prop the properties interface
+ * @param config the properties configuration
+ * @see cxPropertiesInitDefault()
+ */
+cx_attr_nonnull
+void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config);
+
+/**
+ * Destroys the properties interface.
+ *
+ * @note Even when you are certain that you did not use the interface in a
+ * way that caused a memory allocation, you should call this function anyway.
+ * Future versions of the library might add features that need additional memory
+ * and you really don't want to search the entire code where you might need
+ * add call to this function.
+ *
+ * @param prop the properties interface
+ */
+cx_attr_nonnull
+void cxPropertiesDestroy(CxProperties *prop);
+
+/**
+ * Destroys and re-initializes the properties interface.
+ *
+ * You might want to use this, to reset the parser after
+ * encountering a syntax error.
+ *
+ * @param prop the properties interface
+ */
+cx_attr_nonnull
+static inline void cxPropertiesReset(CxProperties *prop) {
+ CxPropertiesConfig config = prop->config;
+ cxPropertiesDestroy(prop);
+ cxPropertiesInit(prop, config);
+}
+
+/**
+ * Initialize a properties parser with the default configuration.
+ *
+ * @param prop (@c CxProperties*) the properties interface
+ * @see cxPropertiesInit()
+ */
+#define cxPropertiesInitDefault(prop) \
+ cxPropertiesInit(prop, cx_properties_config_default)
+
+/**
+ * Fills the input buffer with data.
+ *
+ * After calling this function, you can parse the data by calling
+ * cxPropertiesNext().
+ *
+ * @remark The properties interface tries to avoid allocations.
+ * When you use this function and cxPropertiesNext() interleaving,
+ * no allocations are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxPropertiesNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param prop the properties interface
+ * @param buf a pointer to the data
+ * @param len the length of the data
+ * @retval zero success
+ * @retval non-zero a memory allocation was necessary but failed
+ * @see cxPropertiesFill()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+int cxPropertiesFilln(
+ CxProperties *prop,
+ const char *buf,
+ size_t len
+);
+
+#ifdef __cplusplus
+} // extern "C"
+cx_attr_nonnull
+static inline int cxPropertiesFill(
+ CxProperties *prop,
+ cxstring str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+static inline int cxPropertiesFill(
+ CxProperties *prop,
+ cxmutstr str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cxPropertiesFill(
+ CxProperties *prop,
+ const char *str
+) {
+ return cxPropertiesFilln(prop, str, strlen(str));
+}
+
+extern "C" {
+#else // __cplusplus
+/**
+ * Fills the input buffer with data.
+ *
+ * After calling this function, you can parse the data by calling
+ * cxPropertiesNext().
+ *
+ * @attention The properties interface tries to avoid allocations.
+ * When you use this function and cxPropertiesNext() interleaving,
+ * no allocations are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxPropertiesNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param prop the properties interface
+ * @param str the text to fill in
+ * @retval zero success
+ * @retval non-zero a memory allocation was necessary but failed
+ * @see cxPropertiesFilln()
+ */
+#define cxPropertiesFill(prop, str) _Generic((str), \
+ cxstring: cx_properties_fill_cxstr, \
+ cxmutstr: cx_properties_fill_mutstr, \
+ char*: cx_properties_fill_str, \
+ const char*: cx_properties_fill_str) \
+ (prop, str)
+
+/**
+ * @copydoc cxPropertiesFill()
+ */
+cx_attr_nonnull
+static inline int cx_properties_fill_cxstr(
+ CxProperties *prop,
+ cxstring str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxPropertiesFill()
+ */
+cx_attr_nonnull
+static inline int cx_properties_fill_mutstr(
+ CxProperties *prop,
+ cxmutstr str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxPropertiesFill()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cx_properties_fill_str(
+ CxProperties *prop,
+ const char *str
+) {
+ return cxPropertiesFilln(prop, str, strlen(str));
+}
+#endif
+
+/**
+ * Specifies stack memory that shall be used as internal buffer.
+ *
+ * @param prop the properties interface
+ * @param buf a pointer to stack memory
+ * @param capacity the capacity of the stack memory
+ */
+cx_attr_nonnull
+void cxPropertiesUseStack(
+ CxProperties *prop,
+ char *buf,
+ size_t capacity
+);
+
+/**
+ * Retrieves the next key/value-pair.
+ *
+ * This function returns zero as long as there are key/value-pairs found.
+ * If no more key/value-pairs are found, #CX_PROPERTIES_NO_DATA is returned.
+ *
+ * When an incomplete line is encountered, #CX_PROPERTIES_INCOMPLETE_DATA is
+ * returned, and you can add more data with #cxPropertiesFill().
+ *
+ * @remark The incomplete line will be stored in an internal buffer, which is
+ * allocated on the heap, by default. If you want to avoid allocations,
+ * you can specify sufficient space with cxPropertiesUseStack() after
+ * initialization with cxPropertiesInit().
+ *
+ * @attention The returned strings will point into a buffer that might not be
+ * available later. It is strongly recommended to copy the strings for further
+ * use.
+ *
+ * @param prop the properties interface
+ * @param key a pointer to the cxstring that shall contain the property name
+ * @param value a pointer to the cxstring that shall contain the property value
+ * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found
+ * @retval CX_PROPERTIES_NO_DATA there is no (more) data in the input buffer
+ * @retval CX_PROPERTIES_INCOMPLETE_DATA the data in the input buffer is incomplete
+ * (fill more data and try again)
+ * @retval CX_PROPERTIES_NULL_INPUT the input buffer was never filled
+ * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key
+ * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter
+ * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+CxPropertiesStatus cxPropertiesNext(
+ CxProperties *prop,
+ cxstring *key,
+ cxstring *value
+);
+
+/**
+ * Creates a properties sink for an UCX map.
+ *
+ * The values stored in the map will be pointers to strings allocated
+ * by #cx_strdup_a().
+ * The default stdlib allocator will be used, unless you specify a custom
+ * allocator in the optional @c data of the sink.
+ *
+ * @param map the map that shall consume the k/v-pairs.
+ * @return the sink
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+CxPropertiesSink cxPropertiesMapSink(CxMap *map);
+
+/**
+ * Creates a properties source based on an UCX string.
+ *
+ * @param str the string
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nodiscard
+CxPropertiesSource cxPropertiesStringSource(cxstring str);
+
+/**
+ * Creates a properties source based on C string with the specified length.
+ *
+ * @param str the string
+ * @param len the length
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
+CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len);
+
+/**
+ * Creates a properties source based on a C string.
+ *
+ * The length will be determined with strlen(), so the string MUST be
+ * zero-terminated.
+ *
+ * @param str the string
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
+CxPropertiesSource cxPropertiesCstrSource(const char *str);
+
+/**
+ * Creates a properties source based on an FILE.
+ *
+ * @param file the file
+ * @param chunk_size how many bytes may be read in one operation
+ *
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_r(1)
+CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size);
+
+
+/**
+ * Loads properties data from a source and transfers it to a sink.
+ *
+ * This function tries to read as much data from the source as possible.
+ * When the source was completely consumed and at least on k/v-pair was found,
+ * the return value will be #CX_PROPERTIES_NO_ERROR.
+ * When the source was consumed but no k/v-pairs were found, the return value
+ * will be #CX_PROPERTIES_NO_DATA.
+ * The other result codes apply, according to their description.
+ *
+ * @param prop the properties interface
+ * @param sink the sink
+ * @param source the source
+ * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found
+ * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed
+ * @retval CX_PROPERTIES_READ_FAILED reading from the source failed
+ * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed
+ * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs
+ * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key
+ * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter
+ * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed
+ */
+cx_attr_nonnull
+CxPropertiesStatus cxPropertiesLoad(
+ CxProperties *prop,
+ CxPropertiesSink sink,
+ CxPropertiesSource source
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_PROPERTIES_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file streams.h
+ *
+ * @brief Utility functions for data streams.
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_STREAMS_H
+#define UCX_STREAMS_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or @c NULL if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if @p buf is @c NULL you can
+ * set this to zero to let the implementation decide
+ * @param n the maximum number of bytes that shall be copied.
+ * If this is larger than @p bufsize, the content is copied over multiple
+ * iterations.
+ * @return the total number of bytes copied
+ */
+cx_attr_nonnull_arg(1, 2, 3, 4)
+cx_attr_access_r(1)
+cx_attr_access_w(2)
+cx_attr_access_w(5)
+size_t cx_stream_bncopy(
+ void *src,
+ void *dest,
+ cx_read_func rfnc,
+ cx_write_func wfnc,
+ char *buf,
+ size_t bufsize,
+ size_t n
+);
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * @param src (@c void*) the source stream
+ * @param dest (@c void*) the destination stream
+ * @param rfnc (@c cx_read_func) the read function
+ * @param wfnc (@c cx_write_func) the write function
+ * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is
+ * @c NULL you can set this to zero to let the implementation decide
+ * @return total number of bytes copied
+ */
+#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \
+ cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX)
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * The data is temporarily stored in a stack allocated buffer.
+ *
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param n the maximum number of bytes that shall be copied.
+ * @return total number of bytes copied
+ */
+cx_attr_nonnull
+cx_attr_access_r(1)
+cx_attr_access_w(2)
+size_t cx_stream_ncopy(
+ void *src,
+ void *dest,
+ cx_read_func rfnc,
+ cx_write_func wfnc,
+ size_t n
+);
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * The data is temporarily stored in a stack allocated buffer.
+ *
+ * @param src (@c void*) the source stream
+ * @param dest (@c void*) the destination stream
+ * @param rfnc (@c cx_read_func) the read function
+ * @param wfnc (@c cx_write_func) the write function
+ * @return total number of bytes copied
+ */
+#define cx_stream_copy(src, dest, rfnc, wfnc) \
+ cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_STREAMS_H
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file string.h
- * \brief Strings that know their length.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file string.h
+ * @brief Strings that know their length.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_STRING_H
/**
* The maximum length of the "needle" in cx_strstr() that can use SBO.
*/
-extern unsigned const cx_strstr_sbo_size;
+extern const unsigned cx_strstr_sbo_size;
/**
* The UCX string structure.
struct cx_mutstr_s {
/**
* A pointer to the string.
- * \note The string is not necessarily \c NULL terminated.
+ * @note The string is not necessarily @c NULL terminated.
* Always use the length.
*/
char *ptr;
struct cx_string_s {
/**
* A pointer to the immutable string.
- * \note The string is not necessarily \c NULL terminated.
+ * @note The string is not necessarily @c NULL terminated.
* Always use the length.
*/
const char *ptr;
/**
* A literal initializer for an UCX string structure.
*
- * The argument MUST be a string (const char*) \em literal.
+ * The argument MUST be a string (const char*) @em literal.
*
* @param literal the string literal
*/
/**
* Wraps a mutable string that must be zero-terminated.
*
- * The length is implicitly inferred by using a call to \c strlen().
+ * The length is implicitly inferred by using a call to @c strlen().
*
- * \note the wrapped string will share the specified pointer to the string.
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a constant string, use cx_str().
*
* @see cx_mutstrn()
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
cxmutstr cx_mutstr(char *cstring);
/**
* Wraps a string that does not need to be zero-terminated.
*
- * The argument may be \c NULL if the length is zero.
+ * The argument may be @c NULL if the length is zero.
*
- * \note the wrapped string will share the specified pointer to the string.
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a constant string, use cx_strn().
*
- * @param cstring the string to wrap (or \c NULL, only if the length is zero)
+ * @param cstring the string to wrap (or @c NULL, only if the length is zero)
* @param length the length of the string
* @return the wrapped string
*
* @see cx_mutstr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_rw(1, 2)
cxmutstr cx_mutstrn(
char *cstring,
size_t length
/**
* Wraps a string that must be zero-terminated.
*
- * The length is implicitly inferred by using a call to \c strlen().
+ * The length is implicitly inferred by using a call to @c strlen().
*
- * \note the wrapped string will share the specified pointer to the string.
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a non-constant string, use cx_mutstr().
*
* @see cx_strn()
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
cxstring cx_str(const char *cstring);
/**
* Wraps a string that does not need to be zero-terminated.
*
- * The argument may be \c NULL if the length is zero.
+ * The argument may be @c NULL if the length is zero.
*
- * \note the wrapped string will share the specified pointer to the string.
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a non-constant string, use cx_mutstrn().
*
- * @param cstring the string to wrap (or \c NULL, only if the length is zero)
+ * @param cstring the string to wrap (or @c NULL, only if the length is zero)
* @param length the length of the string
* @return the wrapped string
*
* @see cx_str()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
cxstring cx_strn(
const char *cstring,
size_t length
);
+#ifdef __cplusplus
+} // extern "C"
+cx_attr_nodiscard
+static inline cxstring cx_strcast(cxmutstr str) {
+ return cx_strn(str.ptr, str.length);
+}
+cx_attr_nodiscard
+static inline cxstring cx_strcast(cxstring str) {
+ return str;
+}
+extern "C" {
+#else
+/**
+ * Internal function, do not use.
+ * @param str
+ * @return
+ * @see cx_strcast()
+ */
+cx_attr_nodiscard
+static inline cxstring cx_strcast_m(cxmutstr str) {
+ return (cxstring) {str.ptr, str.length};
+}
+/**
+ * Internal function, do not use.
+ * @param str
+ * @return
+ * @see cx_strcast()
+ */
+cx_attr_nodiscard
+static inline cxstring cx_strcast_c(cxstring str) {
+ return str;
+}
+
/**
* Casts a mutable string to an immutable string.
*
-* \note This is not seriously a cast. Instead you get a copy
+* Does nothing for already immutable strings.
+*
+* @note This is not seriously a cast. Instead, you get a copy
* of the struct with the desired pointer type. Both structs still
* point to the same location, though!
*
-* @param str the mutable string to cast
-* @return an immutable copy of the string pointer
+* @param str (@c cxstring or @c cxmutstr) the string to cast
+* @return (@c cxstring) an immutable copy of the string pointer
*/
-__attribute__((__warn_unused_result__))
-cxstring cx_strcast(cxmutstr str);
+#define cx_strcast(str) _Generic((str), \
+ cxmutstr: cx_strcast_m, \
+ cxstring: cx_strcast_c) \
+ (str)
+#endif
/**
- * Passes the pointer in this string to \c free().
+ * Passes the pointer in this string to @c free().
*
- * The pointer in the struct is set to \c NULL and the length is set to zero.
+ * The pointer in the struct is set to @c NULL and the length is set to zero.
*
- * \note There is no implementation for cxstring, because it is unlikely that
+ * @note There is no implementation for cxstring, because it is unlikely that
* you ever have a <code>const char*</code> you are really supposed to free.
* If you encounter such situation, you should double-check your code.
*
* @param str the string to free
*/
-__attribute__((__nonnull__))
void cx_strfree(cxmutstr *str);
/**
* Passes the pointer in this string to the allocators free function.
*
- * The pointer in the struct is set to \c NULL and the length is set to zero.
+ * The pointer in the struct is set to @c NULL and the length is set to zero.
*
- * \note There is no implementation for cxstring, because it is unlikely that
+ * @note There is no implementation for cxstring, because it is unlikely that
* you ever have a <code>const char*</code> you are really supposed to free.
* If you encounter such situation, you should double-check your code.
*
* @param alloc the allocator
* @param str the string to free
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull_arg(1)
void cx_strfree_a(
const CxAllocator *alloc,
cxmutstr *str
/**
* Returns the accumulated length of all specified strings.
+ *
+ * If this sum overflows, errno is set to EOVERFLOW.
*
- * \attention if the count argument is larger than the number of the
+ * @attention if the count argument is larger than the number of the
* specified strings, the behavior is undefined.
*
* @param count the total number of specified strings
* @param ... all strings
* @return the accumulated length of all strings
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
size_t cx_strlen(
size_t count,
...
* Concatenates strings.
*
* The resulting string will be allocated by the specified allocator.
- * So developers \em must pass the return value to cx_strfree_a() eventually.
+ * So developers @em must pass the return value to cx_strfree_a() eventually.
*
- * If \p str already contains a string, the memory will be reallocated and
+ * If @p str already contains a string, the memory will be reallocated and
* the other strings are appended. Otherwise, new memory is allocated.
*
- * \note It is guaranteed that there is only one allocation.
+ * If memory allocation fails, the pointer in the returned string will
+ * be @c NULL. Depending on the allocator, @c errno might be set.
+ *
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
* @param alloc the allocator to use
* @param str the string the other strings shall be concatenated to
* @param count the number of the other following strings to concatenate
- * @param ... all other strings
+ * @param ... all other UCX strings
* @return the concatenated string
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
cxmutstr cx_strcat_ma(
const CxAllocator *alloc,
cxmutstr str,
* Concatenates strings and returns a new string.
*
* The resulting string will be allocated by the specified allocator.
- * So developers \em must pass the return value to cx_strfree_a() eventually.
+ * So developers @em must pass the return value to cx_strfree_a() eventually.
+ *
+* If memory allocation fails, the pointer in the returned string will
+ * be @c NULL. Depending on the allocator, @c errno might be set.
*
- * \note It is guaranteed that there is only one allocation.
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
- * @param alloc the allocator to use
- * @param count the number of the other following strings to concatenate
- * @param ... all other strings
- * @return the concatenated string
+ * @param alloc (@c CxAllocator*) the allocator to use
+ * @param count (@c size_t) the number of the other following strings to concatenate
+ * @param ... all other UCX strings
+ * @return (@c cxmutstr) the concatenated string
*/
#define cx_strcat_a(alloc, count, ...) \
cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
/**
* Concatenates strings and returns a new string.
*
- * The resulting string will be allocated by standard \c malloc().
- * So developers \em must pass the return value to cx_strfree() eventually.
+ * The resulting string will be allocated by standard @c malloc().
+ * So developers @em must pass the return value to cx_strfree() eventually.
*
- * \note It is guaranteed that there is only one allocation.
+* If memory allocation fails, the pointer in the returned string will
+ * be @c NULL and @c errno might be set.
+ *
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
- * @param count the number of the other following strings to concatenate
- * @param ... all other strings
- * @return the concatenated string
+ * @param count (@c size_t) the number of the other following strings to concatenate
+ * @param ... all other UCX strings
+ * @return (@c cxmutstr) the concatenated string
*/
#define cx_strcat(count, ...) \
cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
/**
* Concatenates strings.
*
- * The resulting string will be allocated by standard \c malloc().
- * So developers \em must pass the return value to cx_strfree() eventually.
+ * The resulting string will be allocated by standard @c malloc().
+ * So developers @em must pass the return value to cx_strfree() eventually.
*
- * If \p str already contains a string, the memory will be reallocated and
+ * If @p str already contains a string, the memory will be reallocated and
* the other strings are appended. Otherwise, new memory is allocated.
*
- * \note It is guaranteed that there is only one allocation.
+* If memory allocation fails, the pointer in the returned string will
+ * be @c NULL and @c errno might be set.
+ *
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
- * @param str the string the other strings shall be concatenated to
- * @param count the number of the other following strings to concatenate
- * @param ... all other strings
- * @return the concatenated string
+ * @param str (@c cxmutstr) the string the other strings shall be concatenated to
+ * @param count (@c size_t) the number of the other following strings to concatenate
+ * @param ... all other strings
+ * @return (@c cxmutstr) the concatenated string
*/
#define cx_strcat_m(str, count, ...) \
cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
/**
* Returns a substring starting at the specified location.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubsl()
* @see cx_strsubs_m()
* @see cx_strsubsl_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxstring cx_strsubs(
cxstring string,
size_t start
/**
* Returns a substring starting at the specified location.
*
- * The returned string will be limited to \p length bytes or the number
- * of bytes available in \p string, whichever is smaller.
+ * The returned string will be limited to @p length bytes or the number
+ * of bytes available in @p string, whichever is smaller.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
* @param length the maximum length of the returned string
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubs()
* @see cx_strsubs_m()
* @see cx_strsubsl_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxstring cx_strsubsl(
cxstring string,
size_t start,
/**
* Returns a substring starting at the specified location.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubsl_m()
* @see cx_strsubs()
* @see cx_strsubsl()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxmutstr cx_strsubs_m(
cxmutstr string,
size_t start
/**
* Returns a substring starting at the specified location.
*
- * The returned string will be limited to \p length bytes or the number
- * of bytes available in \p string, whichever is smaller.
+ * The returned string will be limited to @p length bytes or the number
+ * of bytes available in @p string, whichever is smaller.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
* @param length the maximum length of the returned string
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubs_m()
* @see cx_strsubs()
* @see cx_strsubsl()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxmutstr cx_strsubsl_m(
cxmutstr string,
size_t start,
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the first location of \p chr
+ * @return a substring starting at the first location of @p chr
*
* @see cx_strchr_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxstring cx_strchr(
cxstring string,
int chr
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the first location of \p chr
+ * @return a substring starting at the first location of @p chr
*
* @see cx_strchr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxmutstr cx_strchr_m(
cxmutstr string,
int chr
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the last location of \p chr
+ * @return a substring starting at the last location of @p chr
*
* @see cx_strrchr_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxstring cx_strrchr(
cxstring string,
int chr
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the last location of \p chr
+ * @return a substring starting at the last location of @p chr
*
* @see cx_strrchr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxmutstr cx_strrchr_m(
cxmutstr string,
int chr
* Returns a substring starting at the location of the first occurrence of the
* specified string.
*
- * If \p haystack does not contain \p needle, an empty string is returned.
+ * If @p haystack does not contain @p needle, an empty string is returned.
*
- * If \p needle is an empty string, the complete \p haystack is
+ * If @p needle is an empty string, the complete @p haystack is
* returned.
*
* @param haystack the string to be scanned
* @param needle string containing the sequence of characters to match
* @return a substring starting at the first occurrence of
- * \p needle, or an empty string, if the sequence is not
+ * @p needle, or an empty string, if the sequence is not
* contained
* @see cx_strstr_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxstring cx_strstr(
cxstring haystack,
cxstring needle
* Returns a substring starting at the location of the first occurrence of the
* specified string.
*
- * If \p haystack does not contain \p needle, an empty string is returned.
+ * If @p haystack does not contain @p needle, an empty string is returned.
*
- * If \p needle is an empty string, the complete \p haystack is
+ * If @p needle is an empty string, the complete @p haystack is
* returned.
*
* @param haystack the string to be scanned
* @param needle string containing the sequence of characters to match
* @return a substring starting at the first occurrence of
- * \p needle, or an empty string, if the sequence is not
+ * @p needle, or an empty string, if the sequence is not
* contained
* @see cx_strstr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxmutstr cx_strstr_m(
cxmutstr haystack,
cxstring needle
/**
* Splits a given string using a delimiter string.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
* @param string the string to split
* @param delim the delimiter
* @param limit the maximum number of split items
- * @param output a pre-allocated array of at least \p limit length
+ * @param output a pre-allocated array of at least @p limit length
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(4, 3)
size_t cx_strsplit(
cxstring string,
cxstring delim,
/**
* Splits a given string using a delimiter string.
*
- * The array pointed to by \p output will be allocated by \p allocator.
+ * The array pointed to by @p output will be allocated by @p allocator.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
- * \attention If allocation fails, the \c NULL pointer will be written to
- * \p output and the number returned will be zero.
+ * @attention If allocation fails, the @c NULL pointer will be written to
+ * @p output and the number returned will be zero.
*
* @param allocator the allocator to use for allocating the resulting array
* @param string the string to split
* written to
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(5)
size_t cx_strsplit_a(
const CxAllocator *allocator,
cxstring string,
/**
* Splits a given string using a delimiter string.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
* @param string the string to split
* @param delim the delimiter
* @param limit the maximum number of split items
- * @param output a pre-allocated array of at least \p limit length
+ * @param output a pre-allocated array of at least @p limit length
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(4, 3)
size_t cx_strsplit_m(
cxmutstr string,
cxstring delim,
/**
* Splits a given string using a delimiter string.
*
- * The array pointed to by \p output will be allocated by \p allocator.
+ * The array pointed to by @p output will be allocated by @p allocator.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
- * \attention If allocation fails, the \c NULL pointer will be written to
- * \p output and the number returned will be zero.
+ * @attention If allocation fails, the @c NULL pointer will be written to
+ * @p output and the number returned will be zero.
*
* @param allocator the allocator to use for allocating the resulting array
* @param string the string to split
* written to
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(5)
size_t cx_strsplit_ma(
const CxAllocator *allocator,
cxmutstr string,
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
int cx_strcmp(
cxstring s1,
cxstring s2
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal ignoring case
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal ignoring case
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
int cx_strcasecmp(
cxstring s1,
cxstring s2
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
int cx_strcmp_p(
const void *s1,
const void *s2
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal ignoring case
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal ignoring case
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
int cx_strcasecmp_p(
const void *s1,
const void *s2
/**
* Creates a duplicate of the specified string.
*
- * The new string will contain a copy allocated by \p allocator.
+ * The new string will contain a copy allocated by @p allocator.
*
- * \note The returned string is guaranteed to be zero-terminated.
+ * @note The returned string is guaranteed to be zero-terminated.
*
* @param allocator the allocator to use
* @param string the string to duplicate
* @return a duplicate of the string
* @see cx_strdup()
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
cxmutstr cx_strdup_a(
const CxAllocator *allocator,
cxstring string
* Creates a duplicate of the specified string.
*
* The new string will contain a copy allocated by standard
- * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ * @c malloc(). So developers @em must pass the return value to cx_strfree().
*
- * \note The returned string is guaranteed to be zero-terminated.
+ * @note The returned string is guaranteed to be zero-terminated.
*
- * @param string the string to duplicate
- * @return a duplicate of the string
+ * @param string (@c cxstring) the string to duplicate
+ * @return (@c cxmutstr) a duplicate of the string
* @see cx_strdup_a()
*/
#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string)
/**
* Creates a duplicate of the specified string.
*
- * The new string will contain a copy allocated by \p allocator.
+ * The new string will contain a copy allocated by @p allocator.
*
- * \note The returned string is guaranteed to be zero-terminated.
+ * @note The returned string is guaranteed to be zero-terminated.
*
- * @param allocator the allocator to use
- * @param string the string to duplicate
- * @return a duplicate of the string
+ * @param allocator (@c CxAllocator*) the allocator to use
+ * @param string (@c cxmutstr) the string to duplicate
+ * @return (@c cxmutstr) a duplicate of the string
* @see cx_strdup_m()
*/
#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string))
* Creates a duplicate of the specified string.
*
* The new string will contain a copy allocated by standard
- * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ * @c malloc(). So developers @em must pass the return value to cx_strfree().
*
- * \note The returned string is guaranteed to be zero-terminated.
+ * @note The returned string is guaranteed to be zero-terminated.
*
- * @param string the string to duplicate
- * @return a duplicate of the string
+ * @param string (@c cxmutstr) the string to duplicate
+ * @return (@c cxmutstr) a duplicate of the string
* @see cx_strdup_ma()
*/
#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string))
/**
* Omits leading and trailing spaces.
*
- * \note the returned string references the same memory, thus you
- * must \em not free the returned memory.
+ * @note the returned string references the same memory, thus you
+ * must @em not free the returned memory.
*
* @param string the string that shall be trimmed
* @return the trimmed string
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxstring cx_strtrim(cxstring string);
/**
* Omits leading and trailing spaces.
*
- * \note the returned string references the same memory, thus you
- * must \em not free the returned memory.
+ * @note the returned string references the same memory, thus you
+ * must @em not free the returned memory.
*
* @param string the string that shall be trimmed
* @return the trimmed string
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
cxmutstr cx_strtrim_m(cxmutstr string);
/**
*
* @param string the string to check
* @param prefix the prefix the string should have
- * @return \c true, if and only if the string has the specified prefix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified prefix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
bool cx_strprefix(
cxstring string,
cxstring prefix
*
* @param string the string to check
* @param suffix the suffix the string should have
- * @return \c true, if and only if the string has the specified suffix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified suffix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
bool cx_strsuffix(
cxstring string,
cxstring suffix
*
* @param string the string to check
* @param prefix the prefix the string should have
- * @return \c true, if and only if the string has the specified prefix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified prefix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
bool cx_strcaseprefix(
cxstring string,
cxstring prefix
*
* @param string the string to check
* @param suffix the suffix the string should have
- * @return \c true, if and only if the string has the specified suffix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified suffix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
bool cx_strcasesuffix(
cxstring string,
cxstring suffix
* Replaces a pattern in a string with another string.
*
* The pattern is taken literally and is no regular expression.
- * Replaces at most \p replmax occurrences.
+ * Replaces at most @p replmax occurrences.
*
- * The returned string will be allocated by \p allocator and is guaranteed
+ * The returned string will be allocated by @p allocator and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
* @param replmax maximum number of replacements
* @return the resulting string after applying the replacements
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
cxmutstr cx_strreplacen_a(
const CxAllocator *allocator,
cxstring str,
* Replaces a pattern in a string with another string.
*
* The pattern is taken literally and is no regular expression.
- * Replaces at most \p replmax occurrences.
+ * Replaces at most @p replmax occurrences.
*
- * The returned string will be allocated by \c malloc() and is guaranteed
+ * The returned string will be allocated by @c malloc() and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
* the returned string will be empty.
*
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @param replmax maximum number of replacements
- * @return the resulting string after applying the replacements
+ * @param str (@c cxstring) the string where replacements should be applied
+ * @param pattern (@c cxstring) the pattern to search for
+ * @param replacement (@c cxstring) the replacement string
+ * @param replmax (@c size_t) maximum number of replacements
+ * @return (@c cxmutstr) the resulting string after applying the replacements
*/
#define cx_strreplacen(str, pattern, replacement, replmax) \
cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax)
*
* The pattern is taken literally and is no regular expression.
*
- * The returned string will be allocated by \p allocator and is guaranteed
+ * The returned string will be allocated by @p allocator and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
* the returned string will be empty.
*
- * @param allocator the allocator to use
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @return the resulting string after applying the replacements
+ * @param allocator (@c CxAllocator*) the allocator to use
+ * @param str (@c cxstring) the string where replacements should be applied
+ * @param pattern (@c cxstring) the pattern to search for
+ * @param replacement (@c cxstring) the replacement string
+ * @return (@c cxmutstr) the resulting string after applying the replacements
*/
#define cx_strreplace_a(allocator, str, pattern, replacement) \
cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX)
* Replaces a pattern in a string with another string.
*
* The pattern is taken literally and is no regular expression.
- * Replaces at most \p replmax occurrences.
+ * Replaces at most @p replmax occurrences.
*
- * The returned string will be allocated by \c malloc() and is guaranteed
+ * The returned string will be allocated by @c malloc() and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
* the returned string will be empty.
*
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @return the resulting string after applying the replacements
+ * @param str (@c cxstring) the string where replacements should be applied
+ * @param pattern (@c cxstring) the pattern to search for
+ * @param replacement (@c cxstring) the replacement string
+ * @return (@c cxmutstr) the resulting string after applying the replacements
*/
#define cx_strreplace(str, pattern, replacement) \
cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX)
* @param limit the maximum number of tokens that shall be returned
* @return a new string tokenization context
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
CxStrtokCtx cx_strtok(
cxstring str,
cxstring delim,
* @param limit the maximum number of tokens that shall be returned
* @return a new string tokenization context
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
CxStrtokCtx cx_strtok_m(
cxmutstr str,
cxstring delim,
* @return true if successful, false if the limit or the end of the string
* has been reached
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_w(2)
bool cx_strtok_next(
CxStrtokCtx *ctx,
cxstring *token
* @return true if successful, false if the limit or the end of the string
* has been reached
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_w(2)
bool cx_strtok_next_m(
CxStrtokCtx *ctx,
cxmutstr *token
* @param delim array of more delimiters
* @param count number of elements in the array
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
void cx_strtok_delim(
CxStrtokCtx *ctx,
const cxstring *delim,
size_t count
);
+/* ------------------------------------------------------------------------- *
+ * string to number conversion functions *
+ * ------------------------------------------------------------------------- */
+
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep);
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a single precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep);
+
+/**
+ * Converts a string to a double precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2)
+int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep);
+
+#ifndef CX_STR_IMPLEMENTATION
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * @copydoc cx_strtouz_lc()
+ */
+#define cx_strtou64_lc(str, output, base, groupsep) cx_strtou64_lc(cx_strcast(str), output, base, groupsep)
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtouz_lc(str, output, base, groupsep) cx_strtouz_lc(cx_strcast(str), output, base, groupsep)
+
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtos(str, output, base) cx_strtos_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoi(str, output, base) cx_strtoi_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtol(str, output, base) cx_strtol_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoll(str, output, base) cx_strtoll_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoi8(str, output, base) cx_strtoi8_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoi16(str, output, base) cx_strtoi16_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoi32(str, output, base) cx_strtoi32_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoi64(str, output, base) cx_strtoi64_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoz(str, output, base) cx_strtoz_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtous(str, output, base) cx_strtous_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtou(str, output, base) cx_strtou_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoul(str, output, base) cx_strtoul_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtoull(str, output, base) cx_strtoull_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtou8(str, output, base) cx_strtou8_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtou16(str, output, base) cx_strtou16_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtou32(str, output, base) cx_strtou32_lc(str, output, base, ",")
+/**
+ * @copydoc cx_strtouz()
+ */
+#define cx_strtou64(str, output, base) cx_strtou64_lc(str, output, base, ",")
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtouz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtouz(str, output, base) cx_strtouz_lc(str, output, base, ",")
+
+/**
+ * Converts a string to a single precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc(cx_strcast(str), output, decsep, groupsep)
+/**
+ * Converts a string to a double precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the double variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc(cx_strcast(str), output, decsep, groupsep)
+
+/**
+ * Converts a string to a single precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtof(str, output) cx_strtof_lc(str, output, '.', ",")
+/**
+ * Converts a string to a double precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the double variable where the result shall be stored
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtod(str, output) cx_strtod_lc(str, output, '.', ",")
+
+#endif
#ifdef __cplusplus
} // extern "C"
*
* **** IN HEADER FILE: ****
*
- * <pre>
+ * <code>
* CX_TEST(function_name);
* CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
- * </pre>
+ * </code>
*
* **** IN SOURCE FILE: ****
- * <pre>
+ * <code>
* CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
* // tests with CX_TEST_ASSERT()
* }
* }
* // cleanup of memory here
* }
- * </pre>
+ * </code>
*
* @attention Do not call own functions within a test, that use
* CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE().
#ifndef UCX_TEST_H
#define UCX_TEST_H
-#include <stdlib.h>
+#include "common.h"
+
#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#define __FUNCTION__ __func__
#endif
-//
#if !defined(__clang__) && __GNUC__ > 3
#pragma GCC diagnostic ignored "-Wclobbered"
#endif
-#ifndef UCX_COMMON_H
-/**
- * Function pointer compatible with fwrite-like functions.
- */
-typedef size_t (*cx_write_func)(
- const void *,
- size_t,
- size_t,
- void *
-);
-#endif // UCX_COMMON_H
-
/** Type for the CxTestSuite. */
typedef struct CxTestSuite CxTestSuite;
* @param name optional name of the suite
* @return a new test suite
*/
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
+cx_attr_malloc
static inline CxTestSuite* cx_test_suite_new(const char *name) {
CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite));
if (suite != NULL) {
}
/**
- * Destroys a test suite.
- * @param suite the test suite to destroy
+ * Deallocates a test suite.
+ *
+ * @param suite the test suite to free
*/
static inline void cx_test_suite_free(CxTestSuite* suite) {
+ if (suite == NULL) return;
CxTestSet *l = suite->tests;
while (l != NULL) {
CxTestSet *e = l;
*
* @param suite the suite, the test function shall be added to
* @param test the test function to register
- * @return zero on success or non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
*/
+cx_attr_nonnull
static inline int cx_test_register(CxTestSuite* suite, CxTest test) {
CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet));
if (t) {
* Runs a test suite and writes the test log to the specified stream.
* @param suite the test suite to run
* @param out_target the target buffer or file to write the output to
- * @param out_writer the write function writing to \p out_target
+ * @param out_writer the write function writing to @p out_target
*/
+cx_attr_nonnull
static inline void cx_test_run(CxTestSuite *suite,
void *out_target, cx_write_func out_writer) {
if (suite->name == NULL) {
/**
* Runs a test suite and writes the test log to the specified FILE stream.
- * @param suite the test suite to run
- * @param file the target file to write the output to
+ * @param suite (@c CxTestSuite*) the test suite to run
+ * @param file (@c FILE*) the target file to write the output to
*/
#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite)
/**
* Runs a test suite and writes the test log to stdout.
- * @param suite the test suite to run
+ * @param suite (@c CxTestSuite*) the test suite to run
*/
#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout)
/**
* Defines the scope of a test.
+ *
+ * @code
+ * CX_TEST(my_test_name) {
+ * // setup code
+ * CX_TEST_DO {
+ * // your tests go here
+ * }
+ * // tear down code
+ * }
+ * @endcode
+ *
* @attention Any CX_TEST_ASSERT() calls must be performed in scope of
* #CX_TEST_DO.
*/
* If the assertion is correct, the test carries on. If the assertion is not
* correct, the specified message (terminated by a dot and a line break) is
* written to the test suites output stream.
- * @param condition the condition to check
- * @param message the message that shall be printed out on failure
+ * @param condition (@c bool) the condition to check
+ * @param message (@c char*) the message that shall be printed out on failure
*/
#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \
const char *_assert_msg_ = message; \
* If the assertion is correct, the test carries on. If the assertion is not
* correct, the specified message (terminated by a dot and a line break) is
* written to the test suites output stream.
- * @param condition the condition to check
+ * @param condition (@c bool) the condition to check
*/
#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed")
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file tree.h
- * \brief Interface for tree implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \copyright 2-Clause BSD License
+ * @file tree.h
+ * @brief Interface for tree implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_TREE_H
*/
size_t depth;
/**
- * The next element in the queue or \c NULL.
+ * The next element in the queue or @c NULL.
*/
struct cx_tree_visitor_queue_s *next;
};
* Releases internal memory of the given tree iterator.
* @param iter the iterator
*/
- __attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxTreeIteratorDispose(CxTreeIterator *iter) {
free(iter->stack);
iter->stack = NULL;
* Releases internal memory of the given tree visitor.
* @param visitor the visitor
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) {
struct cx_tree_visitor_queue_s *q = visitor->queue_next;
while (q != NULL) {
* Advises the iterator to skip the subtree below the current node and
* also continues the current loop.
*
- * @param iterator the iterator
+ * @param iterator (@c CxTreeIterator) the iterator
*/
#define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue
* Advises the visitor to skip the subtree below the current node and
* also continues the current loop.
*
- * @param visitor the visitor
+ * @param visitor (@c CxTreeVisitor) the visitor
*/
#define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor)
* Links a node to a (new) parent.
*
* If the node has already a parent, it is unlinked, first.
- * If the parent has children already, the node is \em appended to the list
+ * If the parent has children already, the node is @em appended to the list
* of all currently existing children.
*
* @param parent the parent node
* @param loc_children offset in the node struct for the children linked list
* @param loc_last_child optional offset in the node struct for the pointer to
* the last child in the linked list (negative if there is no such pointer)
- * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_prev optional offset in the node struct for the prev pointer
* @param loc_next offset in the node struct for the next pointer
* @see cx_tree_unlink()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cx_tree_link(
- void *restrict parent,
- void *restrict node,
+ void *parent,
+ void *node,
ptrdiff_t loc_parent,
ptrdiff_t loc_children,
ptrdiff_t loc_last_child,
* @param loc_children offset in the node struct for the children linked list
* @param loc_last_child optional offset in the node struct for the pointer to
* the last child in the linked list (negative if there is no such pointer)
- * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_prev optional offset in the node struct for the prev pointer
* @param loc_next offset in the node struct for the next pointer
* @see cx_tree_link()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cx_tree_unlink(
void *node,
ptrdiff_t loc_parent,
ptrdiff_t loc_next
);
+/**
+ * Macro that can be used instead of the magic value for infinite search depth.
+ */
+#define CX_TREE_SEARCH_INFINITE_DEPTH 0
+
/**
* Function pointer for a search function.
*
- * A function of this kind shall check if the specified \p node
- * contains the given \p data or if one of the children might contain
+ * A function of this kind shall check if the specified @p node
+ * contains the given @p data or if one of the children might contain
* the data.
*
* The function should use the returned integer to indicate how close the
* match is, where a negative number means that it does not match at all.
+ * Zero means exact match and a positive number is an implementation defined
+ * measure for the distance to an exact match.
*
* For example if a tree stores file path information, a node that is
* describing a parent directory of a filename that is searched, shall
* positive if one of the children might contain the data,
* negative if neither the node, nor the children contains the data
*/
+cx_attr_nonnull
typedef int (*cx_tree_search_data_func)(const void *node, const void *data);
/**
* Function pointer for a search function.
*
- * A function of this kind shall check if the specified \p node
- * contains the same \p data as \p new_node or if one of the children might
+ * A function of this kind shall check if the specified @p node
+ * contains the same @p data as @p new_node or if one of the children might
* contain the data.
*
* The function should use the returned integer to indicate how close the
* match is, where a negative number means that it does not match at all.
+ * Zero means exact match and a positive number is an implementation defined
+ * measure for the distance to an exact match.
*
* For example if a tree stores file path information, a node that is
* describing a parent directory of a filename that is searched, shall
* @param node the node that is currently investigated
* @param new_node a new node with the information which is searched
*
- * @return 0 if \p node contains the same data as \p new_node,
+ * @return 0 if @p node contains the same data as @p new_node,
* positive if one of the children might contain the data,
* negative if neither the node, nor the children contains the data
*/
+cx_attr_nonnull
typedef int (*cx_tree_search_func)(const void *node, const void *new_node);
/**
*
* Depending on the tree structure it is not necessarily guaranteed that the
* "closest" match is uniquely defined. This function will search for a node
- * with the best match according to the \p sfunc (meaning: the return value of
- * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * with the best match according to the @p sfunc (meaning: the return value of
+ * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
* node matching the criteria is returned.
*
* @param root the root node
+ * @param depth the maximum depth (zero=indefinite, one=just root)
* @param data the data to search for
* @param sfunc the search function
* @param result where the result shall be stored
* could contain the node (but doesn't right now), negative if the tree does not
* contain any node that might be related to the searched data
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_access_w(5)
int cx_tree_search_data(
const void *root,
+ size_t depth,
const void *data,
cx_tree_search_data_func sfunc,
void **result,
*
* Depending on the tree structure it is not necessarily guaranteed that the
* "closest" match is uniquely defined. This function will search for a node
- * with the best match according to the \p sfunc (meaning: the return value of
- * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * with the best match according to the @p sfunc (meaning: the return value of
+ * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
* node matching the criteria is returned.
*
* @param root the root node
+* @param depth the maximum depth (zero=indefinite, one=just root)
* @param node the node to search for
* @param sfunc the search function
* @param result where the result shall be stored
* could contain the node (but doesn't right now), negative if the tree does not
* contain any node that might be related to the searched data
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_access_w(5)
int cx_tree_search(
const void *root,
+ size_t depth,
const void *node,
cx_tree_search_func sfunc,
void **result,
* @return the new tree iterator
* @see cxTreeIteratorDispose()
*/
+cx_attr_nodiscard
CxTreeIterator cx_tree_iterator(
void *root,
bool visit_on_exit,
* @return the new tree visitor
* @see cxTreeVisitorDispose()
*/
+cx_attr_nodiscard
CxTreeVisitor cx_tree_visitor(
void *root,
ptrdiff_t loc_children,
* The first argument points to the data the node shall contain and
* the second argument may be used for additional data (e.g. an allocator).
* Functions of this type shall either return a new pointer to a newly
- * created node or \c NULL when allocation fails.
+ * created node or @c NULL when allocation fails.
*
- * \note the function may leave the node pointers in the struct uninitialized.
+ * @note the function may leave the node pointers in the struct uninitialized.
* The caller is responsible to set them according to the intended use case.
*/
+cx_attr_nonnull_arg(1)
typedef void *(*cx_tree_node_create_func)(const void *, void *);
/**
* Once an element cannot be added to the tree, this function returns, leaving
* the iterator in a valid state pointing to the element that could not be
* added.
- * Also, the pointer of the created node will be stored to \p failed.
+ * Also, the pointer of the created node will be stored to @p failed.
* The integer returned by this function denotes the number of elements obtained
- * from the \p iter that have been successfully processed.
- * When all elements could be processed, a \c NULL pointer will be written to
- * \p failed.
+ * from the @p iter that have been successfully processed.
+ * When all elements could be processed, a @c NULL pointer will be written to
+ * @p failed.
*
* The advantage of this function compared to multiple invocations of
* #cx_tree_add() is that the search for the insert locations is not always
* @param loc_children offset in the node struct for the children linked list
* @param loc_last_child optional offset in the node struct for the pointer to
* the last child in the linked list (negative if there is no such pointer)
- * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_prev optional offset in the node struct for the prev pointer
* @param loc_next offset in the node struct for the next pointer
* @return the number of nodes created and added
* @see cx_tree_add()
*/
-__attribute__((__nonnull__(1, 3, 4, 6, 7)))
+cx_attr_nonnull_arg(1, 3, 4, 6, 7)
+cx_attr_access_w(6)
size_t cx_tree_add_iter(
struct cx_iterator_base_s *iter,
size_t num,
* Adds multiple elements efficiently to a tree.
*
* Once an element cannot be added to the tree, this function returns, storing
- * the pointer of the created node to \p failed.
+ * the pointer of the created node to @p failed.
* The integer returned by this function denotes the number of elements from
- * the \p src array that have been successfully processed.
- * When all elements could be processed, a \c NULL pointer will be written to
- * \p failed.
+ * the @p src array that have been successfully processed.
+ * When all elements could be processed, a @c NULL pointer will be written to
+ * @p failed.
*
* The advantage of this function compared to multiple invocations of
* #cx_tree_add() is that the search for the insert locations is not always
* Refer to the documentation of #cx_tree_add() for more details.
*
* @param src a pointer to the source data array
- * @param num the number of elements in the \p src array
- * @param elem_size the size of each element in the \p src array
+ * @param num the number of elements in the @p src array
+ * @param elem_size the size of each element in the @p src array
* @param sfunc a search function
* @param cfunc a node creation function
* @param cdata optional additional data
* @param loc_children offset in the node struct for the children linked list
* @param loc_last_child optional offset in the node struct for the pointer to
* the last child in the linked list (negative if there is no such pointer)
- * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_prev optional offset in the node struct for the prev pointer
* @param loc_next offset in the node struct for the next pointer
* @return the number of array elements successfully processed
* @see cx_tree_add()
*/
-__attribute__((__nonnull__(1, 4, 5, 7, 8)))
+cx_attr_nonnull_arg(1, 4, 5, 7, 8)
+cx_attr_access_w(7)
size_t cx_tree_add_array(
const void *src,
size_t num,
* Adds data to a tree.
*
* An adequate location where to add the new tree node is searched with the
- * specified \p sfunc.
+ * specified @p sfunc.
*
- * When a location is found, the \p cfunc will be invoked with \p cdata.
+ * When a location is found, the @p cfunc will be invoked with @p cdata.
*
- * The node returned by \p cfunc will be linked into the tree.
- * When \p sfunc returned a positive integer, the new node will be linked as a
+ * The node returned by @p cfunc will be linked into the tree.
+ * When @p sfunc returned a positive integer, the new node will be linked as a
* child. The other children (now siblings of the new node) are then checked
- * with \p sfunc, whether they could be children of the new node and re-linked
+ * with @p sfunc, whether they could be children of the new node and re-linked
* accordingly.
*
- * When \p sfunc returned zero and the found node has a parent, the new
+ * When @p sfunc returned zero and the found node has a parent, the new
* node will be added as sibling - otherwise, the new node will be added
* as a child.
*
- * When \p sfunc returned a negative value, the new node will not be added to
+ * When @p sfunc returned a negative value, the new node will not be added to
* the tree and this function returns a non-zero value.
- * The caller should check if \p cnode contains a node pointer and deal with the
+ * The caller should check if @p cnode contains a node pointer and deal with the
* node that could not be added.
*
- * This function also returns a non-zero value when \p cfunc tries to allocate
- * a new node but fails to do so. In that case, the pointer stored to \p cnode
- * will be \c NULL.
+ * This function also returns a non-zero value when @p cfunc tries to allocate
+ * a new node but fails to do so. In that case, the pointer stored to @p cnode
+ * will be @c NULL.
*
* Multiple elements can be added more efficiently with
* #cx_tree_add_array() or #cx_tree_add_iter().
* @param loc_children offset in the node struct for the children linked list
* @param loc_last_child optional offset in the node struct for the pointer to
* the last child in the linked list (negative if there is no such pointer)
- * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_prev optional offset in the node struct for the prev pointer
* @param loc_next offset in the node struct for the next pointer
* @return zero when a new node was created and added to the tree,
* non-zero otherwise
*/
-__attribute__((__nonnull__(1, 2, 3, 5, 6)))
+cx_attr_nonnull_arg(1, 2, 3, 5, 6)
+cx_attr_access_w(5)
int cx_tree_add(
const void *src,
cx_tree_search_func sfunc,
/**
* A pointer to the root node.
*
- * Will be \c NULL when \c size is 0.
+ * Will be @c NULL when @c size is 0.
*/
void *root;
/**
* Macro to roll out the #cx_tree_node_base_s structure with a custom
* node type.
+ *
+ * Must be used as first member in your custom tree struct.
+ *
+ * @param type the data type for the nodes
*/
#define CX_TREE_NODE_BASE(type) \
type *parent; \
/**
* Macro for specifying the layout of a base node tree.
+ *
+ * When your tree uses #CX_TREE_NODE_BASE, you can use this
+ * macro in all tree functions that expect the layout parameters
+ * @c loc_parent, @c loc_children, @c loc_last_child, @c loc_prev,
+ * and @c loc_next.
*/
#define cx_tree_node_base_layout \
offsetof(struct cx_tree_node_base_s, parent),\
offsetof(struct cx_tree_node_base_s, prev), \
offsetof(struct cx_tree_node_base_s, next)
-/**
- * Macro for obtaining the node pointer layout for a specific tree.
- */
-#define cx_tree_node_layout(tree) \
- (tree)->loc_parent,\
- (tree)->loc_children,\
- (tree)->loc_last_child,\
- (tree)->loc_prev, \
- (tree)->loc_next
-
/**
* The class definition for arbitrary trees.
*/
struct cx_tree_class_s {
- /**
- * Destructor function.
- *
- * Implementations SHALL invoke the node destructor functions if provided
- * and SHALL deallocate the tree memory.
- */
- void (*destructor)(struct cx_tree_s *);
-
/**
* Member function for inserting a single element.
*
- * Implementations SHALL NOT simply invoke \p insert_many as this comes
+ * Implementations SHALL NOT simply invoke @p insert_many as this comes
* with too much overhead.
*/
int (*insert_element)(
void *(*find)(
struct cx_tree_s *tree,
const void *subtree,
- const void *data
- );
-
- /**
- * Member function for creating an iterator for the tree.
- */
- CxTreeIterator (*iterator)(
- struct cx_tree_s *tree,
- bool visit_on_exit
+ const void *data,
+ size_t depth
);
-
- /**
- * Member function for creating a visitor for the tree.
- */
- CxTreeVisitor (*visitor)(struct cx_tree_s *tree);
};
/**
*/
typedef struct cx_tree_s CxTree;
+
+/**
+ * Destroys a node and it's subtree.
+ *
+ * It is guaranteed that the simple destructor is invoked before
+ * the advanced destructor, starting with the leaf nodes of the subtree.
+ *
+ * When this function is invoked on the root node of the tree, it destroys the
+ * tree contents, but - in contrast to #cxTreeFree() - not the tree
+ * structure, leaving an empty tree behind.
+ *
+ * @note The destructor function, if any, will @em not be invoked. That means
+ * you will need to free the removed subtree by yourself, eventually.
+ *
+ * @attention This function will not free the memory of the nodes with the
+ * tree's allocator, because that is usually done by the advanced destructor
+ * and would therefore result in a double-free.
+ *
+ * @param tree the tree
+ * @param node the node to remove
+ * @see cxTreeFree()
+ */
+cx_attr_nonnull
+void cxTreeDestroySubtree(CxTree *tree, void *node);
+
+
+/**
+ * Destroys the tree contents.
+ *
+ * It is guaranteed that the simple destructor is invoked before
+ * the advanced destructor, starting with the leaf nodes of the subtree.
+ *
+ * This is a convenience macro for invoking #cxTreeDestroySubtree() on the
+ * root node of the tree.
+ *
+ * @attention Be careful when calling this function when no destructor function
+ * is registered that actually frees the memory of nodes. In that case you will
+ * need a reference to the (former) root node of the tree somewhere or
+ * otherwise you will be leaking memory.
+ *
+ * @param tree the tree
+ * @see cxTreeDestroySubtree()
+ */
+#define cxTreeClear(tree) cxTreeDestroySubtree(tree, tree->root)
+
+/**
+ * Deallocates the tree structure.
+ *
+ * The destructor functions are invoked for each node, starting with the leaf
+ * nodes.
+ * It is guaranteed that for each node the simple destructor is invoked before
+ * the advanced destructor.
+ *
+ * @attention This function will only invoke the destructor functions
+ * on the nodes.
+ * It will NOT additionally free the nodes with the tree's allocator, because
+ * that would cause a double-free in most scenarios where the advanced
+ * destructor is already freeing the memory.
+ *
+ * @param tree the tree to free
+ */
+void cxTreeFree(CxTree *tree);
+
/**
* Creates a new tree structure based on the specified layout.
*
- * The specified \p allocator will be used for creating the tree struct
- * and SHALL be used by \p create_func to allocate memory for the nodes.
+ * The specified @p allocator will be used for creating the tree struct
+ * and SHALL be used by @p create_func to allocate memory for the nodes.
*
- * \note This function will also register an advanced destructor which
+ * @note This function will also register an advanced destructor which
* will free the nodes with the allocator's free() method.
*
* @param allocator the allocator that shall be used
+ * (if @c NULL, a default stdlib allocator will be used)
* @param create_func a function that creates new nodes
* @param search_func a function that compares two nodes
* @param search_data_func a function that compares a node with data
* @param loc_children offset in the node struct for the children linked list
* @param loc_last_child optional offset in the node struct for the pointer to
* the last child in the linked list (negative if there is no such pointer)
- * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_prev optional offset in the node struct for the prev pointer
* @param loc_next offset in the node struct for the next pointer
* @return the new tree
* @see cxTreeCreateSimple()
* @see cxTreeCreateWrapped()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull_arg(2, 3, 4)
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxTreeFree, 1)
CxTree *cxTreeCreate(
const CxAllocator *allocator,
cx_tree_node_create_func create_func,
/**
* Creates a new tree structure based on a default layout.
*
- * Nodes created by \p create_func MUST contain #cx_tree_node_base_s as first
+ * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as first
* member (or at least respect the default offsets specified in the tree
* struct) and they MUST be allocated with the specified allocator.
*
- * \note This function will also register an advanced destructor which
+ * @note This function will also register an advanced destructor which
* will free the nodes with the allocator's free() method.
*
- * @param allocator the allocator that shall be used
- * @param create_func a function that creates new nodes
- * @param search_func a function that compares two nodes
- * @param search_data_func a function that compares a node with data
- * @return the new tree
+ * @param allocator (@c CxAllocator*) the allocator that shall be used
+ * @param create_func (@c cx_tree_node_create_func) a function that creates new nodes
+ * @param search_func (@c cx_tree_search_func) a function that compares two nodes
+ * @param search_data_func (@c cx_tree_search_data_func) a function that compares a node with data
+ * @return (@c CxTree*) the new tree
* @see cxTreeCreate()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxTree *cxTreeCreateSimple(
- const CxAllocator *allocator,
- cx_tree_node_create_func create_func,
- cx_tree_search_func search_func,
- cx_tree_search_data_func search_data_func
-) {
- return cxTreeCreate(
- allocator,
- create_func,
- search_func,
- search_data_func,
- cx_tree_node_base_layout
- );
-}
+#define cxTreeCreateSimple(\
+ allocator, create_func, search_func, search_data_func \
+) cxTreeCreate(allocator, create_func, search_func, search_data_func, \
+cx_tree_node_base_layout)
/**
* Creates a new tree structure based on an existing tree.
*
- * The specified \p allocator will be used for creating the tree struct.
+ * The specified @p allocator will be used for creating the tree struct.
*
- * \attention This function will create an incompletely defined tree structure
+ * @attention This function will create an incompletely defined tree structure
* where neither the create function, the search function, nor a destructor
* will be set. If you wish to use any of this functionality for the wrapped
* tree, you need to specify those functions afterwards.
*
+ * @param allocator the allocator that was used for nodes of the wrapped tree
+ * (if @c NULL, a default stdlib allocator is assumed)
* @param root the root node of the tree that shall be wrapped
* @param loc_parent offset in the node struct for the parent pointer
* @param loc_children offset in the node struct for the children linked list
* @param loc_last_child optional offset in the node struct for the pointer to
* the last child in the linked list (negative if there is no such pointer)
- * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_prev optional offset in the node struct for the prev pointer
* @param loc_next offset in the node struct for the next pointer
* @return the new tree
* @see cxTreeCreate()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull_arg(2)
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxTreeFree, 1)
CxTree *cxTreeCreateWrapped(
const CxAllocator *allocator,
void *root,
ptrdiff_t loc_next
);
-/**
- * Destroys the tree structure.
- *
- * \attention This function will only invoke the destructor functions
- * on the nodes, if specified.
- * It will NOT additionally free the nodes with the tree's allocator, because
- * that would cause a double-free in most scenarios.
- *
- * @param tree the tree to destroy
- */
-__attribute__((__nonnull__))
-static inline void cxTreeDestroy(CxTree *tree) {
- tree->cl->destructor(tree);
-}
-
/**
* Inserts data into the tree.
*
- * \remark For this function to work, the tree needs specified search and
+ * @remark For this function to work, the tree needs specified search and
* create functions, which might not be available for wrapped trees
* (see #cxTreeCreateWrapped()).
*
* @param tree the tree
* @param data the data to insert
- * @return zero on success, non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxTreeInsert(
CxTree *tree,
const void *data
/**
* Inserts elements provided by an iterator efficiently into the tree.
*
- * \remark For this function to work, the tree needs specified search and
+ * @remark For this function to work, the tree needs specified search and
* create functions, which might not be available for wrapped trees
* (see #cxTreeCreateWrapped()).
*
* @param n the maximum number of elements to insert
* @return the number of elements that could be successfully inserted
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxTreeInsertIter(
CxTree *tree,
struct cx_iterator_base_s *iter,
/**
* Inserts an array of data efficiently into the tree.
*
- * \remark For this function to work, the tree needs specified search and
+ * @remark For this function to work, the tree needs specified search and
* create functions, which might not be available for wrapped trees
* (see #cxTreeCreateWrapped()).
*
* @param n the number of elements in the array
* @return the number of elements that could be successfully inserted
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxTreeInsertArray(
CxTree *tree,
const void *data,
/**
* Searches the data in the specified tree.
*
- * \remark For this function to work, the tree needs a specified \c search_data
+ * @remark For this function to work, the tree needs a specified @c search_data
* function, which might not be available wrapped trees
* (see #cxTreeCreateWrapped()).
*
* @param tree the tree
* @param data the data to search for
- * @return the first matching node, or \c NULL when the data cannot be found
+ * @return the first matching node, or @c NULL when the data cannot be found
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxTreeFind(
CxTree *tree,
const void *data
) {
- return tree->cl->find(tree, tree->root, data);
+ return tree->cl->find(tree, tree->root, data, 0);
}
/**
* Searches the data in the specified subtree.
*
- * \note When \p subtree_root is not part of the \p tree, the behavior is
+ * When @p max_depth is zero, the depth is not limited.
+ * The @p subtree_root itself is on depth 1 and its children have depth 2.
+ *
+ * @note When @p subtree_root is not part of the @p tree, the behavior is
* undefined.
*
- * \remark For this function to work, the tree needs a specified \c search_data
+ * @remark For this function to work, the tree needs a specified @c search_data
* function, which might not be the case for wrapped trees
* (see #cxTreeCreateWrapped()).
*
* @param tree the tree
* @param data the data to search for
* @param subtree_root the node where to start
- * @return the first matching node, or \c NULL when the data cannot be found
+ * @param max_depth the maximum search depth
+ * @return the first matching node, or @c NULL when the data cannot be found
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxTreeFindInSubtree(
CxTree *tree,
const void *data,
- void *subtree_root
+ void *subtree_root,
+ size_t max_depth
) {
- return tree->cl->find(tree, subtree_root, data);
+ return tree->cl->find(tree, subtree_root, data, max_depth);
}
/**
* @param subtree_root the root node of the subtree
* @return the number of nodes in the specified subtree
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root);
/**
*
* @param tree the tree
* @param subtree_root the root node of the subtree
- * @return the tree depth including the \p subtree_root
+ * @return the tree depth including the @p subtree_root
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root);
/**
* @param tree the tree
* @return the tree depth, counting the root as one
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
size_t cxTreeDepth(CxTree *tree);
+/**
+ * Creates a depth-first iterator for the specified tree starting in @p node.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * @param tree the tree to iterate
+ * @param node the node where to start
+ * @param visit_on_exit true, if the iterator shall visit a node again when
+ * leaving the subtree
+ * @return a tree iterator (depth-first)
+ * @see cxTreeVisit()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeIterator cxTreeIterateSubtree(
+ CxTree *tree,
+ void *node,
+ bool visit_on_exit
+) {
+ return cx_tree_iterator(
+ node, visit_on_exit,
+ tree->loc_children, tree->loc_next
+ );
+}
+
+/**
+ * Creates a breadth-first iterator for the specified tree starting in @p node.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * @param tree the tree to iterate
+ * @param node the node where to start
+ * @return a tree visitor (a.k.a. breadth-first iterator)
+ * @see cxTreeIterate()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) {
+ return cx_tree_visitor(
+ node, tree->loc_children, tree->loc_next
+ );
+}
+
/**
* Creates a depth-first iterator for the specified tree.
*
* @param tree the tree to iterate
* @param visit_on_exit true, if the iterator shall visit a node again when
- * leaving the sub-tree
+ * leaving the subtree
* @return a tree iterator (depth-first)
- * @see cxTreeVisitor()
+ * @see cxTreeVisit()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxTreeIterator cxTreeIterator(
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeIterator cxTreeIterate(
CxTree *tree,
bool visit_on_exit
) {
- return tree->cl->iterator(tree, visit_on_exit);
+ return cxTreeIterateSubtree(tree, tree->root, visit_on_exit);
}
/**
*
* @param tree the tree to iterate
* @return a tree visitor (a.k.a. breadth-first iterator)
- * @see cxTreeIterator()
+ * @see cxTreeIterate()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxTreeVisitor cxTreeVisitor(CxTree *tree) {
- return tree->cl->visitor(tree);
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeVisitor cxTreeVisit(CxTree *tree) {
+ return cxTreeVisitSubtree(tree, tree->root);
}
+/**
+ * Sets the (new) parent of the specified child.
+ *
+ * If the @p child is not already member of the tree, this function behaves
+ * as #cxTreeAddChildNode().
+ *
+ * @param tree the tree
+ * @param parent the (new) parent of the child
+ * @param child the node to add
+ * @see cxTreeAddChildNode()
+ */
+cx_attr_nonnull
+void cxTreeSetParent(
+ CxTree *tree,
+ void *parent,
+ void *child
+);
+
/**
* Adds a new node to the tree.
*
- * \attention The node may be externally created, but MUST obey the same rules
+ * If the @p child is already member of the tree, the behavior is undefined.
+ * Use #cxTreeSetParent() if you want to move a subtree to another location.
+ *
+ * @attention The node may be externally created, but MUST obey the same rules
* as if it was created by the tree itself with #cxTreeAddChild() (e.g. use
* the same allocator).
*
* @param tree the tree
* @param parent the parent of the node to add
* @param child the node to add
+ * @see cxTreeSetParent()
*/
-__attribute__((__nonnull__))
-static inline void cxTreeAddChildNode(
+cx_attr_nonnull
+void cxTreeAddChildNode(
CxTree *tree,
void *parent,
- void *child) {
- cx_tree_link(parent, child, cx_tree_node_layout(tree));
- tree->size++;
-}
+ void *child
+);
/**
* Creates a new node and adds it to the tree.
* @return zero when the new node was created, non-zero on allocation failure
* @see cxTreeInsert()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
int cxTreeAddChild(
CxTree *tree,
void *parent,
* A function that is invoked when a node needs to be re-linked to a new parent.
*
* When a node is re-linked, sometimes the contents need to be updated.
- * This callback is invoked by #cxTreeRemove() so that those updates can be
- * applied when re-linking the children of the removed node.
+ * This callback is invoked by #cxTreeRemoveNode() and #cxTreeDestroyNode()
+ * so that those updates can be applied when re-linking the children of the
+ * removed node.
*
* @param node the affected node
* @param old_parent the old parent of the node
* @param new_parent the new parent of the node
*/
+cx_attr_nonnull
typedef void (*cx_tree_relink_func)(
void *node,
const void *old_parent,
*
* If the node is not part of the tree, the behavior is undefined.
*
- * \note The destructor function, if any, will \em not be invoked. That means
+ * @note The destructor function, if any, will @em not be invoked. That means
* you will need to free the removed node by yourself, eventually.
*
* @param tree the tree
* @param node the node to remove (must not be the root node)
* @param relink_func optional callback to update the content of each re-linked
* node
- * @return zero on success, non-zero if \p node is the root node of the tree
+ * @return zero on success, non-zero if @p node is the root node of the tree
*/
-__attribute__((__nonnull__(1,2)))
-int cxTreeRemove(
+cx_attr_nonnull_arg(1, 2)
+int cxTreeRemoveNode(
CxTree *tree,
void *node,
cx_tree_relink_func relink_func
*
* If the node is not part of the tree, the behavior is undefined.
*
- * \note The destructor function, if any, will \em not be invoked. That means
+ * @note The destructor function, if any, will @em not be invoked. That means
* you will need to free the removed subtree by yourself, eventually.
*
* @param tree the tree
* @param node the node to remove
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
void cxTreeRemoveSubtree(CxTree *tree, void *node);
+/**
+ * Destroys a node and re-links its children to its former parent.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * It is guaranteed that the simple destructor is invoked before
+ * the advanced destructor.
+ *
+ * @attention This function will not free the memory of the node with the
+ * tree's allocator, because that is usually done by the advanced destructor
+ * and would therefore result in a double-free.
+ *
+ * @param tree the tree
+ * @param node the node to destroy (must not be the root node)
+ * @param relink_func optional callback to update the content of each re-linked
+ * node
+ * @return zero on success, non-zero if @p node is the root node of the tree
+ */
+cx_attr_nonnull_arg(1, 2)
+int cxTreeDestroyNode(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+);
+
#ifdef __cplusplus
} // extern "C"
#endif
unsigned m = 0x5bd1e995;
unsigned r = 24;
- unsigned h = 25 ^ len;
+ unsigned h = 25 ^ (unsigned) len;
unsigned i = 0;
while (len >= 4) {
unsigned k = data[i + 0] & 0xFF;
*/
#include "cx/hash_map.h"
-#include "cx/utils.h"
#include <string.h>
#include <assert.h>
+#include <errno.h>
struct cx_hash_map_element_s {
/** A pointer to the next element in the current bucket. */
static void cx_hash_map_clear(struct cx_map_s *map) {
struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
- cx_for_n(i, hash_map->bucket_count) {
+ for (size_t i = 0; i < hash_map->bucket_count; i++) {
struct cx_hash_map_element_s *elem = hash_map->buckets[i];
if (elem != NULL) {
do {
allocator,
sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
);
- if (e == NULL) {
- return -1;
- }
+ if (e == NULL) return -1;
// write the value
if (map->collection.store_pointer) {
// copy the key
void *kd = cxMalloc(allocator, key.len);
- if (kd == NULL) {
- return -1;
- }
+ if (kd == NULL) return -1;
memcpy(kd, key.data, key.len);
e->key.data = kd;
e->key.len = key.len;
/**
* Helper function to avoid code duplication.
*
+ * If @p remove is true, and @p targetbuf is @c NULL, the element
+ * will be destroyed when found.
+ *
+ * If @p remove is true, and @p targetbuf is set, the element will
+ * be copied to that buffer and no destructor function is called.
+ *
+ * If @p remove is false, @p targetbuf must not be non-null and
+ * either the pointer, when the map is storing pointers, is copied
+ * to the target buffer, or a pointer to the stored object will
+ * be copied to the target buffer.
+ *
* @param map the map
* @param key the key to look up
+ * @param targetbuf see description
* @param remove flag indicating whether the looked up entry shall be removed
- * @param destroy flag indicating whether the destructor shall be invoked
- * @return a pointer to the value corresponding to the key or \c NULL
+ * @return zero, if the key was found, non-zero otherwise
*/
-static void *cx_hash_map_get_remove(
+static int cx_hash_map_get_remove(
CxMap *map,
CxHashKey key,
- bool remove,
- bool destroy
+ void *targetbuf,
+ bool remove
) {
struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
while (elm && elm->key.hash <= hash) {
if (elm->key.hash == hash && elm->key.len == key.len) {
if (memcmp(elm->key.data, key.data, key.len) == 0) {
- void *data = NULL;
- if (destroy) {
- cx_invoke_destructor(map, elm->data);
+ if (remove) {
+ if (targetbuf == NULL) {
+ cx_invoke_destructor(map, elm->data);
+ } else {
+ memcpy(targetbuf, elm->data, map->collection.elem_size);
+ }
+ cx_hash_map_unlink(hash_map, slot, prev, elm);
} else {
+ assert(targetbuf != NULL);
+ void *data = NULL;
if (map->collection.store_pointer) {
data = *(void **) elm->data;
} else {
data = elm->data;
}
+ memcpy(targetbuf, &data, sizeof(void *));
}
- if (remove) {
- cx_hash_map_unlink(hash_map, slot, prev, elm);
- }
- return data;
+ return 0;
}
}
prev = elm;
elm = prev->next;
}
- return NULL;
+ return 1;
}
static void *cx_hash_map_get(
CxHashKey key
) {
// we can safely cast, because we know the map stays untouched
- return cx_hash_map_get_remove((CxMap *) map, key, false, false);
+ void *ptr = NULL;
+ int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false);
+ return found == 0 ? ptr : NULL;
}
-static void *cx_hash_map_remove(
+static int cx_hash_map_remove(
CxMap *map,
CxHashKey key,
- bool destroy
+ void *targetbuf
) {
- return cx_hash_map_get_remove(map, key, true, destroy);
+ return cx_hash_map_get_remove(map, key, targetbuf, true);
}
static void *cx_hash_map_iter_current_entry(const void *it) {
iter.base.current = cx_hash_map_iter_current_value;
break;
default:
- assert(false);
+ assert(false); // LCOV_EXCL_LINE
}
iter.base.valid = cx_hash_map_iter_valid;
size_t itemsize,
size_t buckets
) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+
if (buckets == 0) {
// implementation defined default
buckets = 16;
map->bucket_count = buckets;
map->buckets = cxCalloc(allocator, buckets,
sizeof(struct cx_hash_map_element_s *));
- if (map->buckets == NULL) {
+ if (map->buckets == NULL) { // LCOV_EXCL_START
cxFree(allocator, map);
return NULL;
- }
+ } // LCOV_EXCL_STOP
// initialize base members
map->base.cl = &cx_hash_map_class;
if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) {
size_t new_bucket_count = (map->collection.size * 5) >> 1;
+ if (new_bucket_count < hash_map->bucket_count) {
+ errno = EOVERFLOW;
+ return 1;
+ }
struct cx_hash_map_element_s **new_buckets = cxCalloc(
map->collection.allocator,
new_bucket_count, sizeof(struct cx_hash_map_element_s *)
);
- if (new_buckets == NULL) {
- return 1;
- }
+ if (new_buckets == NULL) return 1;
// iterate through the elements and assign them to their new slots
- cx_for_n(slot, hash_map->bucket_count) {
+ for (size_t slot = 0; slot < hash_map->bucket_count; slot++) {
struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
while (elm != NULL) {
struct cx_hash_map_element_s *next = elm->next;
return iter->elem_handle;
}
+static void *cx_iter_current_ptr(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return *(void**)iter->elem_handle;
+}
+
static void cx_iter_next_fast(void *it) {
struct cx_iterator_s *iter = it;
if (iter->base.remove) {
iter.base.mutating = false;
return iter;
}
+
+CxIterator cxMutIteratorPtr(
+ void *array,
+ size_t elem_count,
+ bool remove_keeps_order
+) {
+ CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order);
+ iter.base.current = cx_iter_current_ptr;
+ return iter;
+}
+
+CxIterator cxIteratorPtr(
+ const void *array,
+ size_t elem_count
+) {
+ CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false);
+ iter.base.mutating = false;
+ return iter;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/json.h"
+#include "cx/compare.h"
+
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <inttypes.h>
+
+/*
+ * RFC 8259
+ * https://tools.ietf.org/html/rfc8259
+ */
+
+static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING};
+
+static int json_cmp_objvalue(const void *l, const void *r) {
+ const CxJsonObjValue *left = l;
+ const CxJsonObjValue *right = r;
+ return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name));
+}
+
+static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) {
+ assert(obj->type == CX_JSON_OBJECT);
+ CxJsonObjValue kv_dummy;
+ kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length);
+ size_t index = cx_array_binary_search(
+ obj->value.object.values,
+ obj->value.object.values_size,
+ sizeof(CxJsonObjValue),
+ &kv_dummy,
+ json_cmp_objvalue
+ );
+ if (index == obj->value.object.values_size) {
+ return NULL;
+ } else {
+ return &obj->value.object.values[index];
+ }
+}
+
+static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) {
+ assert(objv->type == CX_JSON_OBJECT);
+ const CxAllocator * const al = objv->allocator;
+ CxJsonObject *obj = &(objv->value.object);
+
+ // determine the index where we need to insert the new member
+ size_t index = cx_array_binary_search_sup(
+ obj->values,
+ obj->values_size,
+ sizeof(CxJsonObjValue),
+ &member, json_cmp_objvalue
+ );
+
+ // is the name already present?
+ if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) {
+ // free the original value
+ cx_strfree_a(al, &obj->values[index].name);
+ cxJsonValueFree(obj->values[index].value);
+ // replace the item
+ obj->values[index] = member;
+
+ // nothing more to do
+ return 0;
+ }
+
+ // determine the old capacity and reserve for one more element
+ CxArrayReallocator arealloc = cx_array_reallocator(al, NULL);
+ size_t oldcap = obj->values_capacity;
+ if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1;
+
+ // check the new capacity, if we need to realloc the index array
+ size_t newcap = obj->values_capacity;
+ if (newcap > oldcap) {
+ if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) {
+ return 1;
+ }
+ }
+
+ // check if append or insert
+ if (index < obj->values_size) {
+ // move the other elements
+ memmove(
+ &obj->values[index+1],
+ &obj->values[index],
+ (obj->values_size - index) * sizeof(CxJsonObjValue)
+ );
+ // increase indices for the moved elements
+ for (size_t i = 0; i < obj->values_size ; i++) {
+ if (obj->indices[i] >= index) {
+ obj->indices[i]++;
+ }
+ }
+ }
+
+ // insert the element and set the index
+ obj->values[index] = member;
+ obj->indices[obj->values_size] = index;
+ obj->values_size++;
+
+ return 0;
+}
+
+static void token_destroy(CxJsonToken *token) {
+ if (token->allocated) {
+ cx_strfree(&token->content);
+ }
+}
+
+static int num_isexp(const char *content, size_t length, size_t pos) {
+ if (pos >= length) {
+ return 0;
+ }
+
+ int ok = 0;
+ for (size_t i = pos; i < length; i++) {
+ char c = content[i];
+ if (isdigit(c)) {
+ ok = 1;
+ } else if (i == pos) {
+ if (!(c == '+' || c == '-')) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ return ok;
+}
+
+static CxJsonTokenType token_numbertype(const char *content, size_t length) {
+ if (length == 0) return CX_JSON_TOKEN_ERROR;
+
+ if (content[0] != '-' && !isdigit(content[0])) {
+ return CX_JSON_TOKEN_ERROR;
+ }
+
+ CxJsonTokenType type = CX_JSON_TOKEN_INTEGER;
+ for (size_t i = 1; i < length; i++) {
+ if (content[i] == '.') {
+ if (type == CX_JSON_TOKEN_NUMBER) {
+ return CX_JSON_TOKEN_ERROR; // more than one decimal separator
+ }
+ type = CX_JSON_TOKEN_NUMBER;
+ } else if (content[i] == 'e' || content[i] == 'E') {
+ return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR;
+ } else if (!isdigit(content[i])) {
+ return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep
+ }
+ }
+
+ return type;
+}
+
+static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) {
+ cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start);
+ bool allocated = false;
+ if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) {
+ allocated = true;
+ str = cx_strcat_m(json->uncompleted.content, 1, str);
+ if (str.ptr == NULL) { // LCOV_EXCL_START
+ return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}};
+ } // LCOV_EXCL_STOP
+ }
+ json->uncompleted = (CxJsonToken){0};
+ CxJsonTokenType ttype;
+ if (isstring) {
+ ttype = CX_JSON_TOKEN_STRING;
+ } else {
+ cxstring s = cx_strcast(str);
+ if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false"))
+ || !cx_strcmp(s, CX_STR("null"))) {
+ ttype = CX_JSON_TOKEN_LITERAL;
+ } else {
+ ttype = token_numbertype(str.ptr, str.length);
+ }
+ }
+ if (ttype == CX_JSON_TOKEN_ERROR) {
+ if (allocated) {
+ cx_strfree(&str);
+ }
+ return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}};
+ }
+ return (CxJsonToken){ttype, allocated, str};
+}
+
+static CxJsonTokenType char2ttype(char c) {
+ switch (c) {
+ case '[': {
+ return CX_JSON_TOKEN_BEGIN_ARRAY;
+ }
+ case '{': {
+ return CX_JSON_TOKEN_BEGIN_OBJECT;
+ }
+ case ']': {
+ return CX_JSON_TOKEN_END_ARRAY;
+ }
+ case '}': {
+ return CX_JSON_TOKEN_END_OBJECT;
+ }
+ case ':': {
+ return CX_JSON_TOKEN_NAME_SEPARATOR;
+ }
+ case ',': {
+ return CX_JSON_TOKEN_VALUE_SEPARATOR;
+ }
+ case '"': {
+ return CX_JSON_TOKEN_STRING;
+ }
+ default: {
+ if (isspace(c)) {
+ return CX_JSON_TOKEN_SPACE;
+ }
+ }
+ }
+ return CX_JSON_NO_TOKEN;
+}
+
+static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) {
+ // check if there is data in the buffer
+ if (cxBufferEof(&json->buffer)) {
+ return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ?
+ CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA;
+ }
+
+ // current token type and start index
+ CxJsonTokenType ttype = json->uncompleted.tokentype;
+ size_t token_start = json->buffer.pos;
+
+ for (size_t i = json->buffer.pos; i < json->buffer.size; i++) {
+ char c = json->buffer.space[i];
+ if (ttype != CX_JSON_TOKEN_STRING) {
+ // currently non-string token
+ CxJsonTokenType ctype = char2ttype(c); // start of new token?
+ if (ttype == CX_JSON_NO_TOKEN) {
+ if (ctype == CX_JSON_TOKEN_SPACE) {
+ json->buffer.pos++;
+ continue;
+ } else if (ctype == CX_JSON_TOKEN_STRING) {
+ // begin string
+ ttype = CX_JSON_TOKEN_STRING;
+ token_start = i;
+ } else if (ctype != CX_JSON_NO_TOKEN) {
+ // single-char token
+ json->buffer.pos = i + 1;
+ *result = (CxJsonToken){ctype, false, {NULL, 0}};
+ return CX_JSON_NO_ERROR;
+ } else {
+ ttype = CX_JSON_TOKEN_LITERAL; // number or literal
+ token_start = i;
+ }
+ } else {
+ // finish token
+ if (ctype != CX_JSON_NO_TOKEN) {
+ *result = token_create(json, false, token_start, i);
+ if (result->tokentype == CX_JSON_NO_TOKEN) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ if (result->tokentype == CX_JSON_TOKEN_ERROR) {
+ return CX_JSON_FORMAT_ERROR_NUMBER;
+ }
+ json->buffer.pos = i;
+ return CX_JSON_NO_ERROR;
+ }
+ }
+ } else {
+ // currently inside a string
+ if (json->tokenizer_escape) {
+ json->tokenizer_escape = false;
+ } else {
+ if (c == '"') {
+ *result = token_create(json, true, token_start, i + 1);
+ if (result->tokentype == CX_JSON_NO_TOKEN) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ json->buffer.pos = i + 1;
+ return CX_JSON_NO_ERROR;
+ } else if (c == '\\') {
+ json->tokenizer_escape = true;
+ }
+ }
+ }
+ }
+
+ if (ttype != CX_JSON_NO_TOKEN) {
+ // uncompleted token
+ size_t uncompleted_len = json->buffer.size - token_start;
+ if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) {
+ // current token is uncompleted
+ // save current token content
+ CxJsonToken uncompleted = {
+ ttype, true,
+ cx_strdup(cx_strn(json->buffer.space + token_start, uncompleted_len))
+ };
+ if (uncompleted.content.ptr == NULL) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ json->uncompleted = uncompleted;
+ } else {
+ // previously we also had an uncompleted token
+ // combine the uncompleted token with the current token
+ assert(json->uncompleted.allocated);
+ cxmutstr str = cx_strcat_m(json->uncompleted.content, 1,
+ cx_strn(json->buffer.space + token_start, uncompleted_len));
+ if (str.ptr == NULL) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ json->uncompleted.content = str;
+ }
+ // advance the buffer position - we saved the stuff in the uncompleted token
+ json->buffer.pos += uncompleted_len;
+ }
+
+ return CX_JSON_INCOMPLETE_DATA;
+}
+
+static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) {
+ // TODO: support more escape sequences
+ // we know that the unescaped string will be shorter by at least 2 chars
+ cxmutstr result;
+ result.length = 0;
+ result.ptr = cxMalloc(a, str.length - 1);
+ if (result.ptr == NULL) return result; // LCOV_EXCL_LINE
+
+ bool u = false;
+ for (size_t i = 1; i < str.length - 1; i++) {
+ char c = str.ptr[i];
+ if (u) {
+ u = false;
+ if (c == 'n') {
+ c = '\n';
+ } else if (c == 't') {
+ c = '\t';
+ }
+ result.ptr[result.length++] = c;
+ } else {
+ if (c == '\\') {
+ u = true;
+ } else {
+ result.ptr[result.length++] = c;
+ }
+ }
+ }
+ result.ptr[result.length] = 0;
+
+ return result;
+}
+
+static CxJsonValue* create_json_value(CxJson *json, CxJsonValueType type) {
+ CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue));
+ if (v == NULL) return NULL; // LCOV_EXCL_LINE
+
+ // initialize the value
+ v->type = type;
+ v->allocator = json->allocator;
+ if (type == CX_JSON_ARRAY) {
+ cx_array_initialize_a(json->allocator, v->value.array.array, 16);
+ if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ } else if (type == CX_JSON_OBJECT) {
+ cx_array_initialize_a(json->allocator, v->value.object.values, 16);
+ v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t));
+ if (v->value.object.values == NULL ||
+ v->value.object.indices == NULL)
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+
+ // add the new value to a possible parent
+ if (json->vbuf_size > 0) {
+ CxJsonValue *parent = json->vbuf[json->vbuf_size - 1];
+ assert(parent != NULL);
+ if (parent->type == CX_JSON_ARRAY) {
+ CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL);
+ if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) {
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+ } else if (parent->type == CX_JSON_OBJECT) {
+ // the member was already created after parsing the name
+ assert(json->uncompleted_member.name.ptr != NULL);
+ json->uncompleted_member.value = v;
+ if (json_add_objvalue(parent, json->uncompleted_member)) {
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+ json->uncompleted_member.name = (cxmutstr) {NULL, 0};
+ } else {
+ assert(false); // LCOV_EXCL_LINE
+ }
+ }
+
+ // add the new value to the stack, if it is an array or object
+ if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) {
+ CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal);
+ if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) {
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+ }
+
+ // if currently no value is parsed, this is now the value of interest
+ if (json->parsed == NULL) {
+ json->parsed = v;
+ }
+
+ return v;
+ // LCOV_EXCL_START
+create_json_value_exit_error:
+ cxJsonValueFree(v);
+ return NULL;
+ // LCOV_EXCL_STOP
+}
+
+#define JP_STATE_VALUE_BEGIN 0
+#define JP_STATE_VALUE_END 10
+#define JP_STATE_VALUE_BEGIN_OBJ 1
+#define JP_STATE_OBJ_SEP_OR_CLOSE 11
+#define JP_STATE_VALUE_BEGIN_AR 2
+#define JP_STATE_ARRAY_SEP_OR_CLOSE 12
+#define JP_STATE_OBJ_NAME_OR_CLOSE 5
+#define JP_STATE_OBJ_NAME 6
+#define JP_STATE_OBJ_COLON 7
+
+void cxJsonInit(CxJson *json, const CxAllocator *allocator) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+
+ memset(json, 0, sizeof(CxJson));
+ json->allocator = allocator;
+
+ json->states = json->states_internal;
+ json->states_capacity = cx_nmemb(json->states_internal);
+ json->states[0] = JP_STATE_VALUE_BEGIN;
+ json->states_size = 1;
+
+ json->vbuf = json->vbuf_internal;
+ json->vbuf_capacity = cx_nmemb(json->vbuf_internal);
+}
+
+void cxJsonDestroy(CxJson *json) {
+ cxBufferDestroy(&json->buffer);
+ if (json->states != json->states_internal) {
+ free(json->states);
+ }
+ if (json->vbuf != json->vbuf_internal) {
+ free(json->vbuf);
+ }
+ cxJsonValueFree(json->parsed);
+ json->parsed = NULL;
+ if (json->uncompleted_member.name.ptr != NULL) {
+ cx_strfree_a(json->allocator, &json->uncompleted_member.name);
+ json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL};
+ }
+}
+
+int cxJsonFilln(CxJson *json, const char *buf, size_t size) {
+ if (cxBufferEof(&json->buffer)) {
+ // reinitialize the buffer
+ cxBufferDestroy(&json->buffer);
+ cxBufferInit(&json->buffer, (char*) buf, size,
+ NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE);
+ json->buffer.size = size;
+ return 0;
+ } else {
+ return size != cxBufferAppend(buf, 1, size, &json->buffer);
+ }
+}
+
+static void json_add_state(CxJson *json, int state) {
+ // we have guaranteed the necessary space with cx_array_simple_reserve()
+ // therefore, we can safely add the state in the simplest way possible
+ json->states[json->states_size++] = state;
+}
+
+#define return_rec(code) \
+ token_destroy(&token); \
+ return code
+
+static enum cx_json_status json_parse(CxJson *json) {
+ // Reserve a pointer for a possibly read value
+ CxJsonValue *vbuf = NULL;
+
+ // grab the next token
+ CxJsonToken token;
+ {
+ enum cx_json_status ret = token_parse_next(json, &token);
+ if (ret != CX_JSON_NO_ERROR) {
+ return ret;
+ }
+ }
+
+ // pop the current state
+ assert(json->states_size > 0);
+ int state = json->states[--json->states_size];
+
+ // guarantee that at least two more states fit on the stack
+ CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal);
+ if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+
+
+ // 0 JP_STATE_VALUE_BEGIN value begin
+ // 10 JP_STATE_VALUE_END expect value end
+ // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object)
+ // 11 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose
+ // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array)
+ // 12 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose
+ // 5 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose
+ // 6 JP_STATE_OBJ_NAME object, expect name
+ // 7 JP_STATE_OBJ_COLON object, expect ':'
+
+ if (state < 3) {
+ // push expected end state to the stack
+ json_add_state(json, 10 + state);
+ switch (token.tokentype) {
+ case CX_JSON_TOKEN_BEGIN_ARRAY: {
+ if (create_json_value(json, CX_JSON_ARRAY) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ json_add_state(json, JP_STATE_VALUE_BEGIN_AR);
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_BEGIN_OBJECT: {
+ if (create_json_value(json, CX_JSON_OBJECT) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE);
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_STRING: {
+ if ((vbuf = create_json_value(json, CX_JSON_STRING)) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ cxmutstr str = unescape_string(json->allocator, token.content);
+ if (str.ptr == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ vbuf->value.string = str;
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_INTEGER:
+ case CX_JSON_TOKEN_NUMBER: {
+ int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER;
+ if (NULL == (vbuf = create_json_value(json, type))) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ if (type == CX_JSON_INTEGER) {
+ if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) {
+ return_rec(CX_JSON_FORMAT_ERROR_NUMBER);
+ }
+ } else {
+ if (cx_strtod(token.content, &vbuf->value.number)) {
+ return_rec(CX_JSON_FORMAT_ERROR_NUMBER);
+ }
+ }
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_LITERAL: {
+ if ((vbuf = create_json_value(json, CX_JSON_LITERAL)) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) {
+ vbuf->value.literal = CX_JSON_TRUE;
+ } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) {
+ vbuf->value.literal = CX_JSON_FALSE;
+ } else {
+ vbuf->value.literal = CX_JSON_NULL;
+ }
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ default: {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ }
+ } else if (state == JP_STATE_ARRAY_SEP_OR_CLOSE) {
+ // expect ',' or ']'
+ if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) {
+ json_add_state(json, JP_STATE_VALUE_BEGIN_AR);
+ return_rec(CX_JSON_NO_ERROR);
+ } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) {
+ // discard the array from the value buffer
+ json->vbuf_size--;
+ return_rec(CX_JSON_NO_ERROR);
+ } else {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) {
+ if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) {
+ // discard the obj from the value buffer
+ json->vbuf_size--;
+ return_rec(CX_JSON_NO_ERROR);
+ } else {
+ // expect string
+ if (token.tokentype != CX_JSON_TOKEN_STRING) {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+
+ // add new entry
+ cxmutstr name = unescape_string(json->allocator, token.content);
+ if (name.ptr == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ assert(json->uncompleted_member.name.ptr == NULL);
+ json->uncompleted_member.name = name;
+ assert(json->vbuf_size > 0);
+
+ // next state
+ json_add_state(json, JP_STATE_OBJ_COLON);
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ } else if (state == JP_STATE_OBJ_COLON) {
+ // expect ':'
+ if (token.tokentype != CX_JSON_TOKEN_NAME_SEPARATOR) {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ // next state
+ json_add_state(json, JP_STATE_VALUE_BEGIN_OBJ);
+ return_rec(CX_JSON_NO_ERROR);
+ } else if (state == JP_STATE_OBJ_SEP_OR_CLOSE) {
+ // expect ',' or '}'
+ if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) {
+ json_add_state(json, JP_STATE_OBJ_NAME);
+ return_rec(CX_JSON_NO_ERROR);
+ } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) {
+ // discard the obj from the value buffer
+ json->vbuf_size--;
+ return_rec(CX_JSON_NO_ERROR);
+ } else {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ } else {
+ // should be unreachable
+ assert(false);
+ return_rec(-1);
+ }
+}
+
+CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) {
+ // check if buffer has been filled
+ if (json->buffer.space == NULL) {
+ return CX_JSON_NULL_DATA;
+ }
+
+ // initialize output value
+ *value = &cx_json_value_nothing;
+
+ // parse data
+ CxJsonStatus result;
+ do {
+ result = json_parse(json);
+ if (result == CX_JSON_NO_ERROR && json->states_size == 1) {
+ // final state reached
+ assert(json->states[0] == JP_STATE_VALUE_END);
+ assert(json->vbuf_size == 0);
+
+ // write output value
+ *value = json->parsed;
+ json->parsed = NULL;
+
+ // re-initialize state machine
+ json->states[0] = JP_STATE_VALUE_BEGIN;
+
+ return CX_JSON_NO_ERROR;
+ }
+ } while (result == CX_JSON_NO_ERROR);
+
+ // the parser might think there is no data
+ // but when we did not reach the final state,
+ // we know that there must be more to come
+ if (result == CX_JSON_NO_DATA && json->states_size > 1) {
+ return CX_JSON_INCOMPLETE_DATA;
+ }
+
+ return result;
+}
+
+void cxJsonValueFree(CxJsonValue *value) {
+ if (value == NULL || value->type == CX_JSON_NOTHING) return;
+ switch (value->type) {
+ case CX_JSON_OBJECT: {
+ CxJsonObject obj = value->value.object;
+ for (size_t i = 0; i < obj.values_size; i++) {
+ cxJsonValueFree(obj.values[i].value);
+ cx_strfree_a(value->allocator, &obj.values[i].name);
+ }
+ cxFree(value->allocator, obj.values);
+ cxFree(value->allocator, obj.indices);
+ break;
+ }
+ case CX_JSON_ARRAY: {
+ CxJsonArray array = value->value.array;
+ for (size_t i = 0; i < array.array_size; i++) {
+ cxJsonValueFree(array.array[i]);
+ }
+ cxFree(value->allocator, array.array);
+ break;
+ }
+ case CX_JSON_STRING: {
+ cxFree(value->allocator, value->value.string.ptr);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ cxFree(value->allocator, value);
+}
+
+CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) {
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_OBJECT;
+ cx_array_initialize_a(allocator, v->value.object.values, 16);
+ if (v->value.object.values == NULL) { // LCOV_EXCL_START
+ cxFree(allocator, v);
+ return NULL;
+ // LCOV_EXCL_STOP
+ }
+ v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t));
+ if (v->value.object.indices == NULL) { // LCOV_EXCL_START
+ cxFree(allocator, v->value.object.values);
+ cxFree(allocator, v);
+ return NULL;
+ // LCOV_EXCL_STOP
+ }
+ return v;
+}
+
+CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) {
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_ARRAY;
+ cx_array_initialize_a(allocator, v->value.array.array, 16);
+ if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) {
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_NUMBER;
+ v->value.number = num;
+ return v;
+}
+
+CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) {
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_INTEGER;
+ v->value.integer = num;
+ return v;
+}
+
+CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) {
+ return cxJsonCreateCxString(allocator, cx_str(str));
+}
+
+CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) {
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_STRING;
+ cxmutstr s = cx_strdup_a(allocator, str);
+ if (s.ptr == NULL) { cxFree(allocator, v); return NULL; }
+ v->value.string = s;
+ return v;
+}
+
+CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) {
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_LITERAL;
+ v->value.literal = lit;
+ return v;
+}
+
+// LCOV_EXCL_START
+// never called as long as malloc() does not return NULL
+static void cx_json_arr_free_temp(CxJsonValue** values, size_t count) {
+ for (size_t i = 0; i < count; i++) {
+ if (values[i] == NULL) break;
+ cxJsonValueFree(values[i]);
+ }
+ free(values);
+}
+// LCOV_EXCL_STOP
+
+int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) {
+ CxJsonValue** values = calloc(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateNumber(arr->allocator, num[i]);
+ if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ free(values);
+ return ret;
+}
+
+int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) {
+ CxJsonValue** values = calloc(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateInteger(arr->allocator, num[i]);
+ if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ free(values);
+ return ret;
+}
+
+int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) {
+ CxJsonValue** values = calloc(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateString(arr->allocator, str[i]);
+ if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ free(values);
+ return ret;
+}
+
+int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) {
+ CxJsonValue** values = calloc(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateCxString(arr->allocator, str[i]);
+ if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ free(values);
+ return ret;
+}
+
+int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) {
+ CxJsonValue** values = calloc(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]);
+ if (values[i] == NULL) { cx_json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ free(values);
+ return ret;
+}
+
+int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) {
+ CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL);
+ assert(arr->type == CX_JSON_ARRAY);
+ return cx_array_simple_copy_a(&value_realloc,
+ arr->value.array.array,
+ arr->value.array.array_size,
+ val, count
+ );
+}
+
+int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) {
+ cxmutstr k = cx_strdup_a(obj->allocator, name);
+ if (k.ptr == NULL) return -1;
+ CxJsonObjValue kv = {k, child};
+ if (json_add_objvalue(obj, kv)) {
+ cx_strfree_a(obj->allocator, &k);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) {
+ CxJsonValue* v = cxJsonCreateObj(obj->allocator);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) {
+ CxJsonValue* v = cxJsonCreateArr(obj->allocator);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) {
+ CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) {
+ CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) {
+ CxJsonValue* v = cxJsonCreateString(obj->allocator, str);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) {
+ CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) {
+ CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;}
+ return v;
+}
+
+CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) {
+ if (index >= value->value.array.array_size) {
+ return &cx_json_value_nothing;
+ }
+ return value->value.array.array[index];
+}
+
+CxIterator cxJsonArrIter(const CxJsonValue *value) {
+ return cxIteratorPtr(
+ value->value.array.array,
+ value->value.array.array_size
+ );
+}
+
+CxIterator cxJsonObjIter(const CxJsonValue *value) {
+ return cxIterator(
+ value->value.object.values,
+ sizeof(CxJsonObjValue),
+ value->value.object.values_size
+ );
+}
+
+CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) {
+ CxJsonObjValue *member = json_find_objvalue(value, name);
+ if (member == NULL) {
+ return &cx_json_value_nothing;
+ } else {
+ return member->value;
+ }
+}
+
+static const CxJsonWriter cx_json_writer_default = {
+ false,
+ true,
+ 255,
+ false,
+ 4
+};
+
+CxJsonWriter cxJsonWriterCompact(void) {
+ return cx_json_writer_default;
+}
+
+CxJsonWriter cxJsonWriterPretty(bool use_spaces) {
+ return (CxJsonWriter) {
+ true,
+ true,
+ 255,
+ use_spaces,
+ 4
+ };
+}
+
+static int cx_json_writer_indent(
+ void *target,
+ cx_write_func wfunc,
+ const CxJsonWriter *settings,
+ unsigned int depth
+) {
+ if (depth == 0) return 0;
+
+ // determine the width and characters to use
+ const char* indent; // for 32 prepared chars
+ size_t width = depth;
+ if (settings->indent_space) {
+ if (settings->indent == 0) return 0;
+ width *= settings->indent;
+ indent = " ";
+ } else {
+ indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+ }
+
+ // calculate the number of write calls and write
+ size_t full = width / 32;
+ size_t remaining = width % 32;
+ for (size_t i = 0; i < full; i++) {
+ if (32 != wfunc(indent, 1, 32, target)) return 1;
+ }
+ if (remaining != wfunc(indent, 1, remaining, target)) return 1;
+
+ return 0;
+}
+
+
+int cx_json_write_rec(
+ void *target,
+ const CxJsonValue *value,
+ cx_write_func wfunc,
+ const CxJsonWriter *settings,
+ unsigned int depth
+) {
+ // keep track of written items
+ // the idea is to reduce the number of jumps for error checking
+ size_t actual = 0, expected = 0;
+
+ // small buffer for number to string conversions
+ char numbuf[32];
+
+ // recursively write the values
+ switch (value->type) {
+ case CX_JSON_OBJECT: {
+ const char *begin_obj = "{\n";
+ if (settings->pretty) {
+ actual += wfunc(begin_obj, 1, 2, target);
+ expected += 2;
+ } else {
+ actual += wfunc(begin_obj, 1, 1, target);
+ expected++;
+ }
+ depth++;
+ size_t elem_count = value->value.object.values_size;
+ for (size_t look_idx = 0; look_idx < elem_count; look_idx++) {
+ // get the member either via index array or directly
+ size_t elem_idx = settings->sort_members
+ ? look_idx
+ : value->value.object.indices[look_idx];
+ CxJsonObjValue *member = &value->value.object.values[elem_idx];
+ if (settings->sort_members) {
+ depth++;depth--;
+ }
+
+ // possible indentation
+ if (settings->pretty) {
+ if (cx_json_writer_indent(target, wfunc, settings, depth)) {
+ return 1; // LCOV_EXCL_LINE
+ }
+ }
+
+ // the name
+ actual += wfunc("\"", 1, 1, target);
+ // TODO: escape the string
+ actual += wfunc(member->name.ptr, 1,
+ member->name.length, target);
+ actual += wfunc("\"", 1, 1, target);
+ const char *obj_name_sep = ": ";
+ if (settings->pretty) {
+ actual += wfunc(obj_name_sep, 1, 2, target);
+ expected += 4 + member->name.length;
+ } else {
+ actual += wfunc(obj_name_sep, 1, 1, target);
+ expected += 3 + member->name.length;
+ }
+
+ // the value
+ if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1;
+
+ // end of object-value
+ if (look_idx < elem_count - 1) {
+ const char *obj_value_sep = ",\n";
+ if (settings->pretty) {
+ actual += wfunc(obj_value_sep, 1, 2, target);
+ expected += 2;
+ } else {
+ actual += wfunc(obj_value_sep, 1, 1, target);
+ expected++;
+ }
+ } else {
+ if (settings->pretty) {
+ actual += wfunc("\n", 1, 1, target);
+ expected ++;
+ }
+ }
+ }
+ depth--;
+ if (settings->pretty) {
+ if (cx_json_writer_indent(target, wfunc, settings, depth)) return 1;
+ }
+ actual += wfunc("}", 1, 1, target);
+ expected++;
+ break;
+ }
+ case CX_JSON_ARRAY: {
+ actual += wfunc("[", 1, 1, target);
+ expected++;
+ CxIterator iter = cxJsonArrIter(value);
+ cx_foreach(CxJsonValue*, element, iter) {
+ if (cx_json_write_rec(
+ target, element,
+ wfunc, settings, depth)
+ ) return 1;
+
+ if (iter.index < iter.elem_count - 1) {
+ const char *arr_value_sep = ", ";
+ if (settings->pretty) {
+ actual += wfunc(arr_value_sep, 1, 2, target);
+ expected += 2;
+ } else {
+ actual += wfunc(arr_value_sep, 1, 1, target);
+ expected++;
+ }
+ }
+ }
+ actual += wfunc("]", 1, 1, target);
+ expected++;
+ break;
+ }
+ case CX_JSON_STRING: {
+ actual += wfunc("\"", 1, 1, target);
+ // TODO: escape the string
+ actual += wfunc(value->value.string.ptr, 1,
+ value->value.string.length, target);
+ actual += wfunc("\"", 1, 1, target);
+ expected += 2 + value->value.string.length;
+ break;
+ }
+ case CX_JSON_NUMBER: {
+ // TODO: locale bullshit
+ // TODO: formatting settings
+ snprintf(numbuf, 32, "%g", value->value.number);
+ size_t len = strlen(numbuf);
+ actual += wfunc(numbuf, 1, len, target);
+ expected += len;
+ break;
+ }
+ case CX_JSON_INTEGER: {
+ snprintf(numbuf, 32, "%" PRIi64, value->value.integer);
+ size_t len = strlen(numbuf);
+ actual += wfunc(numbuf, 1, len, target);
+ expected += len;
+ break;
+ }
+ case CX_JSON_LITERAL: {
+ if (value->value.literal == CX_JSON_TRUE) {
+ actual += wfunc("true", 1, 4, target);
+ expected += 4;
+ } else if (value->value.literal == CX_JSON_FALSE) {
+ actual += wfunc("false", 1, 5, target);
+ expected += 5;
+ } else {
+ actual += wfunc("null", 1, 4, target);
+ expected += 4;
+ }
+ break;
+ }
+ case CX_JSON_NOTHING: {
+ // deliberately supported as an empty string!
+ // users might want to just write the result
+ // of a get operation without testing the value
+ // and therefore this should not blow up
+ break;
+ }
+ default: assert(false); // LCOV_EXCL_LINE
+ }
+
+ return expected != actual;
+}
+
+int cxJsonWrite(
+ void *target,
+ const CxJsonValue *value,
+ cx_write_func wfunc,
+ const CxJsonWriter *settings
+) {
+ if (settings == NULL) {
+ settings = &cx_json_writer_default;
+ }
+ assert(target != NULL);
+ assert(value != NULL);
+ assert(wfunc != NULL);
+
+ return cx_json_write_rec(target, value, wfunc, settings, 0);
+}
*/
#include "cx/linked_list.h"
-#include "cx/utils.h"
#include "cx/compare.h"
#include <string.h>
#include <assert.h>
do {
void *current = ll_data(node);
if (cmp_func(current, elem) == 0) {
- *result = (void*) node;
+ *result = (void *) node;
return index;
}
node = ll_advance(node);
}
}
-void cx_linked_list_remove(
+size_t cx_linked_list_remove_chain(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
- void *node
+ void *node,
+ size_t num
) {
assert(node != NULL);
assert(loc_next >= 0);
assert(loc_prev >= 0 || begin != NULL);
+ // easy exit
+ if (num == 0) return 0;
+
// find adjacent nodes
- void *next = ll_next(node);
void *prev;
if (loc_prev >= 0) {
prev = ll_prev(node);
prev = cx_linked_list_prev(*begin, loc_next, node);
}
+ void *next = ll_next(node);
+ size_t removed = 1;
+ for (; removed < num && next != NULL ; removed++) {
+ next = ll_next(next);
+ }
+
// update next pointer of prev node, or set begin
if (prev == NULL) {
if (begin != NULL) {
} else if (loc_prev >= 0) {
ll_prev(next) = prev;
}
+
+ return removed;
}
size_t cx_linked_list_size(
// Update pointer
if (loc_prev >= 0) ll_prev(sorted[0]) = NULL;
- cx_for_n (i, length - 1) {
+ for (size_t i = 0 ; i < length - 1; i++) {
cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next);
}
ll_next(sorted[length - 1]) = NULL;
*begin = sorted[0];
- *end = sorted[length-1];
+ *end = sorted[length - 1];
if (sorted != sbo) {
free(sorted);
}
cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
// perform first insert
- if (0 != cx_ll_insert_at(list, node, array)) {
- return 1;
- }
+ if (0 != cx_ll_insert_at(list, node, array)) return 1;
// is there more?
if (n == 1) return 1;
const char *source = array;
for (size_t i = 1; i < n; i++) {
source += list->collection.elem_size;
- if (0 != cx_ll_insert_at(list, node, source)) {
- return i;
- }
+ if (0 != cx_ll_insert_at(list, node, source)) return i;
node = node->next;
}
return n;
return inserted;
}
-static int cx_ll_remove(
+static size_t cx_ll_remove(
struct cx_list_s *list,
- size_t index
+ size_t index,
+ size_t num,
+ void *targetbuf
) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_node *node = cx_ll_node_at(ll, index);
// out-of-bounds check
- if (node == NULL) return 1;
-
- // element destruction
- cx_invoke_destructor(list, node->payload);
+ if (node == NULL) return 0;
// remove
- cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
- CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+ size_t removed = cx_linked_list_remove_chain(
+ (void **) &ll->begin,
+ (void **) &ll->end,
+ CX_LL_LOC_PREV,
+ CX_LL_LOC_NEXT,
+ node,
+ num
+ );
// adjust size
- list->collection.size--;
-
- // free and return
- cxFree(list->collection.allocator, node);
+ list->collection.size -= removed;
+
+ // copy or destroy the removed chain
+ if (targetbuf == NULL) {
+ cx_linked_list_node *n = node;
+ for (size_t i = 0; i < removed; i++) {
+ // element destruction
+ cx_invoke_destructor(list, n->payload);
+
+ // free the node and advance
+ void *next = n->next;
+ cxFree(list->collection.allocator, n);
+ n = next;
+ }
+ } else {
+ char *dest = targetbuf;
+ cx_linked_list_node *n = node;
+ for (size_t i = 0; i < removed; i++) {
+ // copy payload
+ memcpy(dest, n->payload, list->collection.elem_size);
+
+ // advance target buffer
+ dest += list->collection.elem_size;
+
+ // free the node and advance
+ void *next = n->next;
+ cxFree(list->collection.allocator, n);
+ n = next;
+ }
+ }
- return 0;
+ return removed;
}
static void cx_ll_clear(struct cx_list_s *list) {
#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
#define CX_LINKED_LIST_SWAP_SBO_SIZE 128
#endif
-unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE;
+const unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE;
static int cx_ll_swap(
struct cx_list_s *list,
left = j;
right = i;
}
- cx_linked_list_node *nleft, *nright;
+ cx_linked_list_node *nleft = NULL, *nright = NULL;
if (left < mid && right < mid) {
// case 1: both items left from mid
nleft = cx_ll_node_at(ll, left);
}
static void cx_pl_destructor(struct cx_list_s *list) {
- list->climpl->destructor(list);
+ list->climpl->deallocate(list);
}
static int cx_pl_insert_element(
return list->climpl->insert_iter(iter, &elem, prepend);
}
-static int cx_pl_remove(
+static size_t cx_pl_remove(
struct cx_list_s *list,
- size_t index
+ size_t index,
+ size_t num,
+ void *targetbuf
) {
- return list->climpl->remove(list, index);
+ return list->climpl->remove(list, index, num, targetbuf);
}
static void cx_pl_clear(struct cx_list_s *list) {
// <editor-fold desc="empty list implementation">
-static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) {
+static void cx_emptyl_noop(cx_attr_unused CxList *list) {
// this is a noop, but MUST be implemented
}
static void *cx_emptyl_at(
- __attribute__((__unused__)) const struct cx_list_s *list,
- __attribute__((__unused__)) size_t index
+ cx_attr_unused const struct cx_list_s *list,
+ cx_attr_unused size_t index
) {
return NULL;
}
static ssize_t cx_emptyl_find_remove(
- __attribute__((__unused__)) struct cx_list_s *list,
- __attribute__((__unused__)) const void *elem,
- __attribute__((__unused__)) bool remove
+ cx_attr_unused struct cx_list_s *list,
+ cx_attr_unused const void *elem,
+ cx_attr_unused bool remove
) {
return -1;
}
-static bool cx_emptyl_iter_valid(__attribute__((__unused__)) const void *iter) {
+static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) {
return false;
}
static CxIterator cx_emptyl_iterator(
const struct cx_list_s *list,
size_t index,
- __attribute__((__unused__)) bool backwards
+ cx_attr_unused bool backwards
) {
CxIterator iter = {0};
iter.src_handle.c = list;
const char *src = data;
size_t i = 0;
for (; i < n; i++) {
- if (0 != invoke_list_func(insert_element,
- list, index + i, src + (i * elem_size))) {
- return i;
- }
+ if (0 != invoke_list_func(
+ insert_element, list, index + i,
+ src + (i * elem_size))) return i;
}
return i;
}
// insert the elements at location si
if (ins == 1) {
- if (0 != invoke_list_func(insert_element,
- list, di, src))
- return inserted;
+ if (0 != invoke_list_func(
+ insert_element, list, di, src)) return inserted;
} else {
size_t r = invoke_list_func(insert_array, list, di, src, ins);
if (r < ins) return inserted + r;
return 0;
}
-void cxListDestroy(CxList *list) {
- list->cl->destructor(list);
-}
-
int cxListCompare(
const CxList *list,
const CxList *other
it.base.mutating = true;
return it;
}
+
+void cxListFree(CxList *list) {
+ if (list == NULL) return;
+ list->cl->deallocate(list);
+}
\r
// <editor-fold desc="empty map implementation">\r
\r
-static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) {\r
+static void cx_empty_map_noop(cx_attr_unused CxMap *map) {\r
// this is a noop, but MUST be implemented\r
}\r
\r
static void *cx_empty_map_get(\r
- __attribute__((__unused__)) const CxMap *map,\r
- __attribute__((__unused__)) CxHashKey key\r
+ cx_attr_unused const CxMap *map,\r
+ cx_attr_unused CxHashKey key\r
) {\r
return NULL;\r
}\r
\r
-static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) {\r
+static bool cx_empty_map_iter_valid(cx_attr_unused const void *iter) {\r
return false;\r
}\r
\r
static CxIterator cx_empty_map_iterator(\r
const struct cx_map_s *map,\r
- __attribute__((__unused__)) enum cx_map_iterator_type type\r
+ cx_attr_unused enum cx_map_iterator_type type\r
) {\r
CxIterator iter = {0};\r
iter.src_handle.c = map;\r
it.base.mutating = true;\r
return it;\r
}\r
+\r
+void cxMapFree(CxMap *map) {\r
+ if (map == NULL) return;\r
+ map->cl->deallocate(map);\r
+}\r
*/
#include "cx/mempool.h"
-#include "cx/utils.h"
+
#include <string.h>
+#include <errno.h>
struct cx_mempool_memory_s {
/** The destructor. */
if (pool->size >= pool->capacity) {
size_t newcap = pool->capacity - (pool->capacity % 16) + 16;
- struct cx_mempool_memory_s **newdata = realloc(pool->data, newcap*sizeof(struct cx_mempool_memory_s*));
- if (newdata == NULL) {
+ size_t newmsize;
+ if (pool->capacity > newcap || cx_szmul(newcap,
+ sizeof(struct cx_mempool_memory_s*), &newmsize)) {
+ errno = EOVERFLOW;
return NULL;
}
+ struct cx_mempool_memory_s **newdata = realloc(pool->data, newmsize);
+ if (newdata == NULL) return NULL;
pool->data = newdata;
pool->capacity = newcap;
}
struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n);
- if (mem == NULL) {
- return NULL;
- }
+ if (mem == NULL) return NULL;
mem->destructor = pool->auto_destr;
pool->data[pool->size] = mem;
) {
size_t msz;
if (cx_szmul(nelem, elsize, &msz)) {
+ errno = EOVERFLOW;
return NULL;
}
void *ptr = cx_mempool_malloc(p, msz);
- if (ptr == NULL) {
- return NULL;
- }
+ if (ptr == NULL) return NULL;
memset(ptr, 0, nelem * elsize);
return ptr;
}
mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func));
newm = realloc(mem, n + sizeof(cx_destructor_func));
- if (newm == NULL) {
- return NULL;
- }
+ if (newm == NULL) return NULL;
if (mem != newm) {
- cx_for_n(i, pool->size) {
+ for (size_t i = 0; i < pool->size; i++) {
if (pool->data[i] == mem) {
pool->data[i] = newm;
return ((char*)newm) + sizeof(cx_destructor_func);
}
}
- abort();
+ abort(); // LCOV_EXCL_LINE
} else {
return ptr;
}
struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *)
((char *) ptr - sizeof(cx_destructor_func));
- cx_for_n(i, pool->size) {
+ for (size_t i = 0; i < pool->size; i++) {
if (mem == pool->data[i]) {
if (mem->destructor) {
mem->destructor(mem->c);
return;
}
}
- abort();
+ abort(); // LCOV_EXCL_LINE
}
-void cxMempoolDestroy(CxMempool *pool) {
+void cxMempoolFree(CxMempool *pool) {
+ if (pool == NULL) return;
struct cx_mempool_memory_s *mem;
- cx_for_n(i, pool->size) {
+ for (size_t i = 0; i < pool->size; i++) {
mem = pool->data[i];
if (mem->destructor) {
mem->destructor(mem->c);
*(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
}
+void cxMempoolRemoveDestructor(void *ptr) {
+ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL;
+}
+
struct cx_mempool_foreign_mem_s {
cx_destructor_func destr;
void* mem;
) {
size_t poolsize;
if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) {
+ errno = EOVERFLOW;
return NULL;
}
struct cx_mempool_s *pool =
malloc(sizeof(struct cx_mempool_s));
- if (pool == NULL) {
- return NULL;
- }
+ if (pool == NULL) return NULL;
CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
- if (provided_allocator == NULL) {
+ if (provided_allocator == NULL) { // LCOV_EXCL_START
free(pool);
return NULL;
- }
+ } // LCOV_EXCL_STOP
provided_allocator->cl = &cx_mempool_allocator_class;
provided_allocator->data = pool;
pool->allocator = provided_allocator;
pool->data = malloc(poolsize);
- if (pool->data == NULL) {
+ if (pool->data == NULL) { // LCOV_EXCL_START
free(provided_allocator);
free(pool);
return NULL;
- }
+ } // LCOV_EXCL_STOP
pool->size = 0;
pool->capacity = capacity;
pool->auto_destr = destr;
- return (CxMempool *) pool;
+ return pool;
}
#ifndef CX_PRINTF_SBO_SIZE
#define CX_PRINTF_SBO_SIZE 512
#endif
-unsigned const cx_printf_sbo_size = CX_PRINTF_SBO_SIZE;
+const unsigned cx_printf_sbo_size = CX_PRINTF_SBO_SIZE;
int cx_fprintf(
void *stream,
} else {
int len = ret + 1;
char *newbuf = malloc(len);
- if (!newbuf) {
+ if (!newbuf) { // LCOV_EXCL_START
va_end(ap2);
return -1;
- }
+ } // LCOV_EXCL_STOP
ret = vsnprintf(newbuf, len, fmt, ap2);
va_end(ap2);
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/properties.h"
+
+#include <assert.h>
+
+const CxPropertiesConfig cx_properties_config_default = {
+ '=',
+ //'\\',
+ '#',
+ '\0',
+ '\0'
+};
+
+void cxPropertiesInit(
+ CxProperties *prop,
+ CxPropertiesConfig config
+) {
+ memset(prop, 0, sizeof(CxProperties));
+ prop->config = config;
+}
+
+void cxPropertiesDestroy(CxProperties *prop) {
+ cxBufferDestroy(&prop->input);
+ cxBufferDestroy(&prop->buffer);
+}
+
+int cxPropertiesFilln(
+ CxProperties *prop,
+ const char *buf,
+ size_t len
+) {
+ 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;
+}
+
+void cxPropertiesUseStack(
+ CxProperties *prop,
+ char *buf,
+ size_t capacity
+) {
+ cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND);
+}
+
+CxPropertiesStatus cxPropertiesNext(
+ CxProperties *prop,
+ cxstring *key,
+ cxstring *value
+) {
+ // check if we have a text buffer
+ 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 (!cxBufferEof(&prop->buffer)) {
+ // check if we can now get a complete line
+ 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 - input.ptr) + 1;
+
+ if (cxBufferAppend(input.ptr, 1,
+ len_until_nl, &prop->buffer) < len_until_nl) {
+ return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+ }
+
+ // advance the position in the input buffer
+ prop->input.pos += len_until_nl;
+
+ // we now want to read from the rescue buffer
+ current_buffer = &prop->buffer;
+ } else {
+ // 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;
+ }
+ }
+
+ char comment1 = prop->config.comment1;
+ char comment2 = prop->config.comment2;
+ char comment3 = prop->config.comment3;
+ char delimiter = prop->config.delimiter;
+
+ // get one line and parse it
+ 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
+ * delimiter and comment chars
+ */
+ size_t delimiter_index = 0;
+ size_t comment_index = 0;
+ bool has_comment = false;
+
+ size_t i = 0;
+ char c = 0;
+ for (; i < len; i++) {
+ c = buf[i];
+ if (c == comment1 || c == comment2 || c == comment3) {
+ if (comment_index == 0) {
+ comment_index = i;
+ has_comment = true;
+ }
+ } else if (c == delimiter) {
+ if (delimiter_index == 0 && !has_comment) {
+ delimiter_index = i;
+ }
+ } else if (c == '\n') {
+ break;
+ }
+ }
+
+ if (c != '\n') {
+ // 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;
+ }
+
+ cxstring line = has_comment ?
+ cx_strn(buf, comment_index) :
+ cx_strn(buf, i);
+ // check line
+ if (delimiter_index == 0) {
+ // if line is not blank ...
+ line = cx_strtrim(line);
+ // ... either no delimiter found, or key is empty
+ if (line.length > 0) {
+ if (line.ptr[0] == delimiter) {
+ return CX_PROPERTIES_INVALID_EMPTY_KEY;
+ } 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);
+ cxstring val = cx_strn(
+ buf + delimiter_index + 1,
+ line.length - delimiter_index - 1);
+ k = cx_strtrim(k);
+ val = cx_strtrim(val);
+ if (k.length > 0) {
+ *key = k;
+ *value = val;
+ current_buffer->pos += i + 1;
+ assert(current_buffer->pos <= current_buffer->size);
+ return CX_PROPERTIES_NO_ERROR;
+ } else {
+ return CX_PROPERTIES_INVALID_EMPTY_KEY;
+ }
+ }
+ // unreachable - either we returned or skipped a blank line
+ assert(false);
+ }
+
+ // when we come to this point, all data must have been read
+ assert(cxBufferEof(&prop->buffer));
+ assert(cxBufferEof(&prop->input));
+
+ return CX_PROPERTIES_NO_DATA;
+}
+
+static int cx_properties_sink_map(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSink *sink,
+ cxstring key,
+ cxstring value
+) {
+ CxMap *map = sink->sink;
+ CxAllocator *alloc = sink->data;
+ cxmutstr v = cx_strdup_a(alloc, value);
+ int r = cx_map_put_cxstr(map, key, v.ptr);
+ if (r != 0) cx_strfree_a(alloc, &v);
+ return r;
+}
+
+CxPropertiesSink cxPropertiesMapSink(CxMap *map) {
+ CxPropertiesSink sink;
+ sink.sink = map;
+ sink.data = cxDefaultAllocator;
+ sink.sink_func = cx_properties_sink_map;
+ return sink;
+}
+
+static int cx_properties_read_string(
+ CxProperties *prop,
+ CxPropertiesSource *src,
+ cxstring *target
+) {
+ if (prop->input.space == src->src) {
+ // when the input buffer already contains the string
+ // we have nothing more to provide
+ target->length = 0;
+ } else {
+ target->ptr = src->src;
+ target->length = src->data_size;
+ }
+ return 0;
+}
+
+static int cx_properties_read_file(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSource *src,
+ cxstring *target
+) {
+ target->ptr = src->data_ptr;
+ target->length = fread(src->data_ptr, 1, src->data_size, src->src);
+ return ferror(src->src);
+}
+
+static int cx_properties_read_init_file(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSource *src
+) {
+ src->data_ptr = malloc(src->data_size);
+ if (src->data_ptr == NULL) return 1;
+ return 0;
+}
+
+static void cx_properties_read_clean_file(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSource *src
+) {
+ free(src->data_ptr);
+}
+
+CxPropertiesSource cxPropertiesStringSource(cxstring str) {
+ 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;
+ return src;
+}
+
+CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) {
+ 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;
+ return src;
+}
+
+CxPropertiesSource cxPropertiesCstrSource(const char *str) {
+ 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;
+ return src;
+}
+
+CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) {
+ 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;
+ return src;
+}
+
+CxPropertiesStatus cxPropertiesLoad(
+ CxProperties *prop,
+ CxPropertiesSink sink,
+ CxPropertiesSource source
+) {
+ assert(source.read_func != NULL);
+ assert(sink.sink_func != NULL);
+
+ // initialize reader
+ if (source.read_init_func != NULL) {
+ if (source.read_init_func(prop, &source)) {
+ return CX_PROPERTIES_READ_INIT_FAILED;
+ }
+ }
+
+ // transfer the data from the source to the sink
+ CxPropertiesStatus status;
+ bool found = false;
+ while (true) {
+ // read input
+ cxstring input;
+ if (source.read_func(prop, &source, &input)) {
+ status = CX_PROPERTIES_READ_FAILED;
+ break;
+ }
+
+ // no more data - break
+ if (input.length == 0) {
+ status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA;
+ break;
+ }
+
+ // set the input buffer and read the k/v-pairs
+ cxPropertiesFill(prop, input);
+
+ CxPropertiesStatus kv_status;
+ do {
+ cxstring key, value;
+ kv_status = cxPropertiesNext(prop, &key, &value);
+ if (kv_status == CX_PROPERTIES_NO_ERROR) {
+ found = true;
+ if (sink.sink_func(prop, &sink, key, value)) {
+ kv_status = CX_PROPERTIES_SINK_FAILED;
+ }
+ }
+ } while (kv_status == CX_PROPERTIES_NO_ERROR);
+
+ if (kv_status > CX_PROPERTIES_OK) {
+ status = kv_status;
+ break;
+ }
+ }
+
+ if (source.read_clean_func != NULL) {
+ source.read_clean_func(prop, &source);
+ }
+
+ return status;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/streams.h"
+
+#ifndef CX_STREAM_BCOPY_BUF_SIZE
+#define CX_STREAM_BCOPY_BUF_SIZE 8192
+#endif
+
+#ifndef CX_STREAM_COPY_BUF_SIZE
+#define CX_STREAM_COPY_BUF_SIZE 1024
+#endif
+
+size_t cx_stream_bncopy(
+ void *src,
+ void *dest,
+ cx_read_func rfnc,
+ cx_write_func wfnc,
+ char *buf,
+ size_t bufsize,
+ size_t n
+) {
+ if (n == 0) {
+ return 0;
+ }
+
+ char *lbuf;
+ size_t ncp = 0;
+
+ if (buf) {
+ if (bufsize == 0) return 0;
+ lbuf = buf;
+ } else {
+ if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE;
+ lbuf = malloc(bufsize);
+ if (lbuf == NULL) return 0;
+ }
+
+ size_t r;
+ size_t rn = bufsize > n ? n : bufsize;
+ while ((r = rfnc(lbuf, 1, rn, src)) != 0) {
+ r = wfnc(lbuf, 1, r, dest);
+ ncp += r;
+ n -= r;
+ rn = bufsize > n ? n : bufsize;
+ if (r == 0 || n == 0) {
+ break;
+ }
+ }
+
+ if (lbuf != buf) {
+ free(lbuf);
+ }
+
+ return ncp;
+}
+
+size_t cx_stream_ncopy(
+ void *src,
+ void *dest,
+ cx_read_func rfnc,
+ cx_write_func wfnc,
+ size_t n
+) {
+ char buf[CX_STREAM_COPY_BUF_SIZE];
+ return cx_stream_bncopy(src, dest, rfnc, wfnc,
+ buf, CX_STREAM_COPY_BUF_SIZE, n);
+}
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
-
+#define CX_STR_IMPLEMENTATION
#include "cx/string.h"
-#include "cx/utils.h"
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <float.h>
-#ifndef _WIN32
-
-#include <strings.h> // for strncasecmp()
-
-#endif // _WIN32
+#ifdef _WIN32
+#define cx_strcasecmp_impl _strnicmp
+#else
+#include <strings.h>
+#define cx_strcasecmp_impl strncasecmp
+#endif
cxmutstr cx_mutstr(char *cstring) {
return (cxmutstr) {cstring, strlen(cstring)};
return (cxstring) {cstring, length};
}
-cxstring cx_strcast(cxmutstr str) {
- return (cxstring) {str.ptr, str.length};
-}
-
void cx_strfree(cxmutstr *str) {
+ if (str == NULL) return;
free(str->ptr);
str->ptr = NULL;
str->length = 0;
const CxAllocator *alloc,
cxmutstr *str
) {
+ if (str == NULL) return;
cxFree(alloc, str->ptr);
str->ptr = NULL;
str->length = 0;
va_list ap;
va_start(ap, count);
size_t size = 0;
- cx_for_n(i, count) {
+ for (size_t i = 0; i < count; i++) {
cxstring str = va_arg(ap, cxstring);
+ if (size > SIZE_MAX - str.length) errno = EOVERFLOW;
size += str.length;
}
va_end(ap);
) {
if (count == 0) return str;
- cxstring *strings = calloc(count, sizeof(cxstring));
- if (!strings) abort();
+ cxstring strings_stack[8];
+ cxstring *strings;
+ if (count > 8) {
+ strings = calloc(count, sizeof(cxstring));
+ if (strings == NULL) {
+ return (cxmutstr) {NULL, 0};
+ }
+ } else {
+ strings = strings_stack;
+ }
va_list ap;
va_start(ap, count);
// get all args and overall length
+ bool overflow = false;
size_t slen = str.length;
- cx_for_n(i, count) {
+ for (size_t i = 0; i < count; i++) {
cxstring s = va_arg (ap, cxstring);
strings[i] = s;
+ if (slen > SIZE_MAX - str.length) overflow = true;
slen += s.length;
}
va_end(ap);
+ // abort in case of overflow
+ if (overflow) {
+ errno = EOVERFLOW;
+ if (strings != strings_stack) {
+ free(strings);
+ }
+ return (cxmutstr) { NULL, 0 };
+ }
+
// reallocate or create new string
+ char *newstr;
if (str.ptr == NULL) {
- str.ptr = cxMalloc(alloc, slen + 1);
+ newstr = cxMalloc(alloc, slen + 1);
} else {
- str.ptr = cxRealloc(alloc, str.ptr, slen + 1);
+ newstr = cxRealloc(alloc, str.ptr, slen + 1);
+ }
+ if (newstr == NULL) {
+ if (strings != strings_stack) {
+ free(strings);
+ }
+ return (cxmutstr) {NULL, 0};
}
- if (str.ptr == NULL) abort();
+ str.ptr = newstr;
// concatenate strings
size_t pos = str.length;
str.length = slen;
- cx_for_n(i, count) {
+ for (size_t i = 0; i < count; i++) {
cxstring s = strings[i];
memcpy(str.ptr + pos, s.ptr, s.length);
pos += s.length;
str.ptr[str.length] = '\0';
// free temporary array
- free(strings);
+ if (strings != strings_stack) {
+ free(strings);
+ }
return str;
}
) {
chr = 0xFF & chr;
// TODO: improve by comparing multiple bytes at once
- cx_for_n(i, string.length) {
+ for (size_t i = 0; i < string.length; i++) {
if (string.ptr[i] == chr) {
return cx_strsubs(string, i);
}
#ifndef CX_STRSTR_SBO_SIZE
#define CX_STRSTR_SBO_SIZE 512
#endif
-unsigned const cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
+const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
cxstring cx_strstr(
cxstring haystack,
cxstring s2
) {
if (s1.length == s2.length) {
- return memcmp(s1.ptr, s2.ptr, s1.length);
+ return strncmp(s1.ptr, s2.ptr, s1.length);
} else if (s1.length > s2.length) {
+ int r = strncmp(s1.ptr, s2.ptr, s2.length);
+ if (r != 0) return r;
return 1;
} else {
+ int r = strncmp(s1.ptr, s2.ptr, s1.length);
+ if (r != 0) return r;
return -1;
}
}
cxstring s2
) {
if (s1.length == s2.length) {
-#ifdef _WIN32
- return _strnicmp(s1.ptr, s2.ptr, s1.length);
-#else
- return strncasecmp(s1.ptr, s2.ptr, s1.length);
-#endif
+ return cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
} else if (s1.length > s2.length) {
+ int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s2.length);
+ if (r != 0) return r;
return 1;
} else {
+ int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
+ if (r != 0) return r;
return -1;
}
}
}
void cx_strlower(cxmutstr string) {
- cx_for_n(i, string.length) {
+ for (size_t i = 0; i < string.length; i++) {
string.ptr[i] = (char) tolower(string.ptr[i]);
}
}
void cx_strupper(cxmutstr string) {
- cx_for_n(i, string.length) {
+ for (size_t i = 0; i < string.length; i++) {
string.ptr[i] = (char) toupper(string.ptr[i]);
}
}
// if more delimiters are specified, check them now
if (ctx->delim_more_count > 0) {
- cx_for_n(i, ctx->delim_more_count) {
+ for (size_t i = 0; i < ctx->delim_more_count; i++) {
cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
delim.ptr = d.ptr;
ctx->delim_more = delim;
ctx->delim_more_count = count;
}
+
+#define cx_strtoX_signed_impl(rtype, rmin, rmax) \
+ long long result; \
+ if (cx_strtoll_lc(str, &result, base, groupsep)) { \
+ return -1; \
+ } \
+ if (result < rmin || result > rmax) { \
+ errno = ERANGE; \
+ return -1; \
+ } \
+ *output = (rtype) result; \
+ return 0
+
+int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX);
+}
+
+int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int, INT_MIN, INT_MAX);
+}
+
+int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX);
+}
+
+int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep) {
+ // strategy: parse as unsigned, check range, negate if required
+ bool neg = false;
+ size_t start_unsigned = 0;
+
+ // trim already, to search for a sign character
+ str = cx_strtrim(str);
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // test if we have a negative sign character
+ if (str.ptr[start_unsigned] == '-') {
+ neg = true;
+ start_unsigned++;
+ // must not be followed by positive sign character
+ if (str.length == 1 || str.ptr[start_unsigned] == '+') {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ // now parse the number with strtoull
+ unsigned long long v;
+ cxstring ustr = start_unsigned == 0 ? str
+ : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned);
+ int ret = cx_strtoull_lc(ustr, &v, base, groupsep);
+ if (ret != 0) return ret;
+ if (neg) {
+ if (v - 1 > LLONG_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ *output = -(long long) v;
+ return 0;
+ } else {
+ if (v > LLONG_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ *output = (long long) v;
+ return 0;
+ }
+}
+
+int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX);
+}
+
+int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX);
+}
+
+int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX);
+}
+
+int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep) {
+ assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms
+ return cx_strtoll_lc(str, (long long*) output, base, groupsep);
+}
+
+int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep) {
+#if SSIZE_MAX == INT32_MAX
+ return cx_strtoi32_lc(str, (int32_t*) output, base, groupsep);
+#elif SSIZE_MAX == INT64_MAX
+ return cx_strtoll_lc(str, (long long*) output, base, groupsep);
+#else
+#error "unsupported ssize_t size"
+#endif
+}
+
+#define cx_strtoX_unsigned_impl(rtype, rmax) \
+ uint64_t result; \
+ if (cx_strtou64_lc(str, &result, base, groupsep)) { \
+ return -1; \
+ } \
+ if (result > rmax) { \
+ errno = ERANGE; \
+ return -1; \
+ } \
+ *output = (rtype) result; \
+ return 0
+
+int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX);
+}
+
+int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(unsigned int, UINT_MAX);
+}
+
+int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX);
+}
+
+int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep) {
+ // some sanity checks
+ str = cx_strtrim(str);
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!(base == 2 || base == 8 || base == 10 || base == 16)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (groupsep == NULL) groupsep = "";
+
+ // find the actual start of the number
+ if (str.ptr[0] == '+') {
+ str.ptr++;
+ str.length--;
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+ size_t start = 0;
+
+ // if base is 2 or 16, some leading stuff may appear
+ if (base == 2) {
+ if ((str.ptr[0] | 32) == 'b') {
+ start = 1;
+ } else if (str.ptr[0] == '0' && str.length > 1) {
+ if ((str.ptr[1] | 32) == 'b') {
+ start = 2;
+ }
+ }
+ } else if (base == 16) {
+ if ((str.ptr[0] | 32) == 'x' || str.ptr[0] == '#') {
+ start = 1;
+ } else if (str.ptr[0] == '0' && str.length > 1) {
+ if ((str.ptr[1] | 32) == 'x') {
+ start = 2;
+ }
+ }
+ }
+
+ // check if there are digits left
+ if (start >= str.length) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // now parse the number
+ unsigned long long result = 0;
+ for (size_t i = start; i < str.length; i++) {
+ // ignore group separators
+ if (strchr(groupsep, str.ptr[i])) continue;
+
+ // determine the digit value of the character
+ unsigned char c = str.ptr[i];
+ if (c >= 'a') c = 10 + (c - 'a');
+ else if (c >= 'A') c = 10 + (c - 'A');
+ else if (c >= '0') c = c - '0';
+ else c = 255;
+ if (c >= base) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // now combine the digit with what we already have
+ unsigned long right = (result & 0xff) * base + c;
+ unsigned long long left = (result >> 8) * base + (right >> 8);
+ if (left > (ULLONG_MAX >> 8)) {
+ errno = ERANGE;
+ return -1;
+ }
+ result = (left << 8) + (right & 0xff);
+ }
+
+ *output = result;
+ return 0;
+}
+
+int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX);
+}
+
+int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX);
+}
+
+int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX);
+}
+
+int cx_strtou64_lc(cxstring str, uint64_t *output, int base, const char *groupsep) {
+ assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms
+ return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep);
+}
+
+int cx_strtouz_lc(cxstring str, size_t *output, int base, const char *groupsep) {
+#if SIZE_MAX == UINT32_MAX
+ return cx_strtou32_lc(str, (uint32_t*) output, base, groupsep);
+#elif SIZE_MAX == UINT64_MAX
+ return cx_strtoull_lc(str, (unsigned long long *) output, base, groupsep);
+#else
+#error "unsupported size_t size"
+#endif
+}
+
+int cx_strtof_lc(cxstring str, float *output, char decsep, const char *groupsep) {
+ // use string to double and add a range check
+ double d;
+ int ret = cx_strtod_lc(str, &d, decsep, groupsep);
+ if (ret != 0) return ret;
+ // note: FLT_MIN is the smallest POSITIVE number that can be represented
+ double test = d < 0 ? -d : d;
+ if (test < FLT_MIN || test > FLT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ *output = (float) d;
+ return 0;
+}
+
+int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep) {
+ // TODO: overflow check
+ // TODO: increase precision
+
+ // trim and check
+ str = cx_strtrim(str);
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ double result = 0.;
+ int sign = 1;
+
+ // check if there is a sign
+ if (str.ptr[0] == '-') {
+ sign = -1;
+ str.ptr++;
+ str.length--;
+ } else if (str.ptr[0] == '+') {
+ str.ptr++;
+ str.length--;
+ }
+
+ // there must be at least one char to parse
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // parse all digits until we find the decsep
+ size_t pos = 0;
+ do {
+ if (isdigit(str.ptr[pos])) {
+ result = result * 10 + (str.ptr[pos] - '0');
+ } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
+ break;
+ }
+ } while (++pos < str.length);
+
+ // already done?
+ if (pos == str.length) {
+ *output = result * sign;
+ return 0;
+ }
+
+ // is the next char the decsep?
+ if (str.ptr[pos] == decsep) {
+ pos++;
+ // it may end with the decsep, if it did not start with it
+ if (pos == str.length) {
+ if (str.length == 1) {
+ errno = EINVAL;
+ return -1;
+ } else {
+ *output = result * sign;
+ return 0;
+ }
+ }
+ // parse everything until exponent or end
+ double factor = 1.;
+ do {
+ if (isdigit(str.ptr[pos])) {
+ factor *= 0.1;
+ result = result + factor * (str.ptr[pos] - '0');
+ } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
+ break;
+ }
+ } while (++pos < str.length);
+ }
+
+ // no exponent?
+ if (pos == str.length) {
+ *output = result * sign;
+ return 0;
+ }
+
+ // now the next separator MUST be the exponent separator
+ // and at least one char must follow
+ if ((str.ptr[pos] | 32) != 'e' || str.length <= pos + 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ pos++;
+
+ // check if we have a sign for the exponent
+ double factor = 10.;
+ if (str.ptr[pos] == '-') {
+ factor = .1;
+ pos++;
+ } else if (str.ptr[pos] == '+') {
+ pos++;
+ }
+
+ // at least one digit must follow
+ if (pos == str.length) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // parse the exponent
+ unsigned int exp = 0;
+ do {
+ if (isdigit(str.ptr[pos])) {
+ exp = 10 * exp + (str.ptr[pos] - '0');
+ } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ } while (++pos < str.length);
+
+ // apply the exponent by fast exponentiation
+ do {
+ if (exp & 1) {
+ result *= factor;
+ }
+ factor *= factor;
+ } while ((exp >>= 1) > 0);
+
+ // store the result and exit
+ *output = result * sign;
+ return 0;
+}
* POSSIBILITY OF SUCH DAMAGE.
*/
+#include "cx/common.h"
+
+#ifndef CX_SZMUL_BUILTIN
int cx_szmul_impl(
size_t a,
size_t b,
return 1;
}
}
+#endif // CX_SZMUL_BUILTIN
#define cx_tree_ptr_locations \
loc_parent, loc_children, loc_last_child, loc_prev, loc_next
+#define cx_tree_node_layout(tree) \
+ (tree)->loc_parent,\
+ (tree)->loc_children,\
+ (tree)->loc_last_child,\
+ (tree)->loc_prev, \
+ (tree)->loc_next
+
static void cx_tree_zero_pointers(
void *node,
ptrdiff_t loc_parent,
ptrdiff_t loc_next
) {
tree_parent(node) = NULL;
- tree_prev(node) = NULL;
+ if (loc_prev >= 0) {
+ tree_prev(node) = NULL;
+ }
tree_next(node) = NULL;
tree_children(node) = NULL;
if (loc_last_child >= 0) {
}
void cx_tree_link(
- void *restrict parent,
- void *restrict node,
+ void *parent,
+ void *node,
ptrdiff_t loc_parent,
ptrdiff_t loc_children,
ptrdiff_t loc_last_child,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
) {
+ assert(loc_parent >= 0);
+ assert(loc_children >= 0);
+ assert(loc_next >= 0);
+
void *current_parent = tree_parent(node);
if (current_parent == parent) return;
if (current_parent != NULL) {
tree_last_child(parent) = node;
}
} else {
+ void *child;
if (loc_last_child >= 0) {
- void *child = tree_last_child(parent);
- tree_prev(node) = child;
- tree_next(child) = node;
+ child = tree_last_child(parent);
tree_last_child(parent) = node;
} else {
- void *child = tree_children(parent);
+ child = tree_children(parent);
void *next;
while ((next = tree_next(child)) != NULL) {
child = next;
}
+ }
+ if (loc_prev >= 0) {
tree_prev(node) = child;
- tree_next(child) = node;
}
+ tree_next(child) = node;
}
tree_parent(node) = parent;
}
+static void *cx_tree_node_prev(
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next,
+ const void *node
+) {
+ void *parent = tree_parent(node);
+ void *begin = tree_children(parent);
+ if (begin == node) return NULL;
+ const void *cur = begin;
+ const void *next;
+ while (1) {
+ next = tree_next(cur);
+ if (next == node) return (void *) cur;
+ cur = next;
+ }
+}
+
void cx_tree_unlink(
void *node,
ptrdiff_t loc_parent,
) {
if (tree_parent(node) == NULL) return;
- void *left = tree_prev(node);
+ assert(loc_children >= 0);
+ assert(loc_next >= 0);
+ assert(loc_parent >= 0);
+ void *left;
+ if (loc_prev >= 0) {
+ left = tree_prev(node);
+ } else {
+ left = cx_tree_node_prev(loc_parent, loc_children, loc_next, node);
+ }
void *right = tree_next(node);
void *parent = tree_parent(node);
assert(left == NULL || tree_children(parent) != node);
tree_last_child(parent) = left;
}
} else {
- tree_prev(right) = left;
+ if (loc_prev >= 0) {
+ tree_prev(right) = left;
+ }
}
tree_parent(node) = NULL;
- tree_prev(node) = NULL;
tree_next(node) = NULL;
+ if (loc_prev >= 0) {
+ tree_prev(node) = NULL;
+ }
}
int cx_tree_search(
const void *root,
+ size_t depth,
const void *node,
cx_tree_search_func sfunc,
void **result,
ptrdiff_t loc_children,
ptrdiff_t loc_next
) {
- int ret;
+ // help avoiding bugs due to uninitialized memory
+ assert(result != NULL);
*result = NULL;
- // shortcut: compare root before doing anything else
- ret = sfunc(root, node);
+ // remember return value for best match
+ int ret = sfunc(root, node);
if (ret < 0) {
- return ret;
- } else if (ret == 0 || tree_children(root) == NULL) {
- *result = (void*)root;
- return ret;
+ // not contained, exit
+ return -1;
+ }
+ *result = (void*) root;
+ // if root is already exact match, exit
+ if (ret == 0) {
+ return 0;
}
- // create a working stack
- CX_ARRAY_DECLARE(const void *, work);
- cx_array_initialize(work, 32);
-
- // add the children of root to the working stack
- {
- void *c = tree_children(root);
- while (c != NULL) {
- cx_array_simple_add(work, c);
- c = tree_next(c);
- }
+ // when depth is one, we are already done
+ if (depth == 1) {
+ return ret;
}
- // remember a candidate for adding the data
- // also remember the exact return code from sfunc
- void *candidate = (void *) root;
- int ret_candidate = ret;
+ // special case: indefinite depth
+ if (depth == 0) {
+ depth = SIZE_MAX;
+ }
- // process the working stack
- while (work_size > 0) {
- // pop element
- const void *elem = work[--work_size];
+ // create an iterator
+ CxTreeIterator iter = cx_tree_iterator(
+ (void*) root, false, loc_children, loc_next
+ );
- // apply the search function
- ret = sfunc(elem, node);
+ // skip root, we already handled it
+ cxIteratorNext(iter);
- if (ret == 0) {
+ // loop through the remaining tree
+ cx_foreach(void *, elem, iter) {
+ // investigate the current node
+ int ret_elem = sfunc(elem, node);
+ if (ret_elem == 0) {
// if found, exit the search
*result = (void *) elem;
- work_size = 0;
+ ret = 0;
break;
- } else if (ret > 0) {
- // if children might contain the data, add them to the stack
- void *c = tree_children(elem);
- while (c != NULL) {
- cx_array_simple_add(work, c);
- c = tree_next(c);
- }
+ } else if (ret_elem > 0 && ret_elem < ret) {
+ // new distance is better
+ *result = elem;
+ ret = ret_elem;
+ } else {
+ // not contained or distance is worse, skip entire subtree
+ cxTreeIteratorContinue(iter);
+ }
- // remember this node in case no child is suitable
- if (ret < ret_candidate) {
- candidate = (void *) elem;
- ret_candidate = ret;
- }
+ // when we reached the max depth, skip the subtree
+ if (iter.depth == depth) {
+ cxTreeIteratorContinue(iter);
}
}
- // not found, but was there a candidate?
- if (ret != 0 && candidate != NULL) {
- ret = ret_candidate;
- *result = candidate;
- }
+ // dispose the iterator as we might have exited the loop early
+ cxTreeIteratorDispose(&iter);
- // free the working queue and return
- free(work);
+ assert(ret < 0 || *result != NULL);
return ret;
}
int cx_tree_search_data(
const void *root,
+ size_t depth,
const void *data,
cx_tree_search_data_func sfunc,
void **result,
) {
// it is basically the same implementation
return cx_tree_search(
- root, data,
+ root, depth, data,
(cx_tree_search_func) sfunc,
result,
loc_children, loc_next);
return iter->node;
}
-__attribute__((__nonnull__))
+cx_attr_nonnull
static void cx_tree_visitor_enqueue_siblings(
struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) {
node = tree_next(node);
void *match = NULL;
int result = cx_tree_search(
root,
+ 0,
*cnode,
sfunc,
&match,
cx_tree_add_look_around_retry:
result = cx_tree_search(
current_node,
+ 0,
new_node,
sfunc,
&match,
loc_prev, loc_next);
}
-static void cx_tree_default_destructor(CxTree *tree) {
- if (tree->simple_destructor != NULL || tree->advanced_destructor != NULL) {
- CxTreeIterator iter = tree->cl->iterator(tree, true);
- cx_foreach(void *, node, iter) {
- if (iter.exiting) {
- if (tree->simple_destructor) {
- tree->simple_destructor(node);
- }
- if (tree->advanced_destructor) {
- tree->advanced_destructor(tree->destructor_data, node);
- }
- }
- }
- }
- cxFree(tree->allocator, tree);
-}
-
-static CxTreeIterator cx_tree_default_iterator(
- CxTree *tree,
- bool visit_on_exit
-) {
- return cx_tree_iterator(
- tree->root, visit_on_exit,
- tree->loc_children, tree->loc_next
- );
-}
-
-static CxTreeVisitor cx_tree_default_visitor(CxTree *tree) {
- return cx_tree_visitor(tree->root, tree->loc_children, tree->loc_next);
-}
-
static int cx_tree_default_insert_element(
CxTree *tree,
const void *data
static void *cx_tree_default_find(
CxTree *tree,
const void *subtree,
- const void *data
+ const void *data,
+ size_t depth
) {
if (tree->root == NULL) return NULL;
void *found;
if (0 == cx_tree_search_data(
subtree,
+ depth,
data,
tree->search_data,
&found,
}
static cx_tree_class cx_tree_default_class = {
- cx_tree_default_destructor,
cx_tree_default_insert_element,
cx_tree_default_insert_many,
- cx_tree_default_find,
- cx_tree_default_iterator,
- cx_tree_default_visitor
+ cx_tree_default_find
};
CxTree *cxTreeCreate(
ptrdiff_t loc_prev,
ptrdiff_t loc_next
) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ assert(create_func != NULL);
+ assert(search_func != NULL);
+ assert(search_data_func != NULL);
+
CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
if (tree == NULL) return NULL;
tree->node_create = create_func;
tree->search = search_func;
tree->search_data = search_data_func;
+ tree->simple_destructor = NULL;
tree->advanced_destructor = (cx_destructor_func2) cxFree;
tree->destructor_data = (void *) allocator;
tree->loc_parent = loc_parent;
return tree;
}
+void cxTreeFree(CxTree *tree) {
+ if (tree == NULL) return;
+ if (tree->root != NULL) {
+ cxTreeClear(tree);
+ }
+ cxFree(tree->allocator, tree);
+}
+
CxTree *cxTreeCreateWrapped(
const CxAllocator *allocator,
void *root,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ assert(root != NULL);
+
CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
if (tree == NULL) return NULL;
return tree;
}
+void cxTreeSetParent(
+ CxTree *tree,
+ void *parent,
+ void *child
+) {
+ size_t loc_parent = tree->loc_parent;
+ if (tree_parent(child) == NULL) {
+ tree->size++;
+ }
+ cx_tree_link(parent, child, cx_tree_node_layout(tree));
+}
+
+void cxTreeAddChildNode(
+ CxTree *tree,
+ void *parent,
+ void *child
+) {
+ cx_tree_link(parent, child, cx_tree_node_layout(tree));
+ tree->size++;
+}
+
int cxTreeAddChild(
CxTree *tree,
void *parent,
}
size_t cxTreeDepth(CxTree *tree) {
- CxTreeVisitor visitor = tree->cl->visitor(tree);
+ CxTreeVisitor visitor = cx_tree_visitor(
+ tree->root, tree->loc_children, tree->loc_next
+ );
while (cxIteratorValid(visitor)) {
cxIteratorNext(visitor);
}
return visitor.depth;
}
-int cxTreeRemove(
+int cxTreeRemoveNode(
CxTree *tree,
void *node,
cx_tree_relink_func relink_func
cx_tree_unlink(node, cx_tree_node_layout(tree));
tree->size -= subtree_size;
}
+
+int cxTreeDestroyNode(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+) {
+ int result = cxTreeRemoveNode(tree, node, relink_func);
+ if (result == 0) {
+ if (tree->simple_destructor) {
+ tree->simple_destructor(node);
+ }
+ if (tree->advanced_destructor) {
+ tree->advanced_destructor(tree->destructor_data, node);
+ }
+ return 0;
+ } else {
+ return result;
+ }
+}
+
+void cxTreeDestroySubtree(CxTree *tree, void *node) {
+ cx_tree_unlink(node, cx_tree_node_layout(tree));
+ CxTreeIterator iter = cx_tree_iterator(
+ node, true,
+ tree->loc_children, tree->loc_next
+ );
+ cx_foreach(void *, child, iter) {
+ if (iter.exiting) {
+ if (tree->simple_destructor) {
+ tree->simple_destructor(child);
+ }
+ if (tree->advanced_destructor) {
+ tree->advanced_destructor(tree->destructor_data, child);
+ }
+ }
+ }
+ tree->size -= iter.counter;
+ if (node == tree->root) {
+ tree->root = NULL;
+ }
+}
int y;
int colspan;
int rowspan;
+ int preferred_width;
+ int preferred_height;
BOOL hexpand;
BOOL vexpand;
BOOL hfill;
int size;
int pos;
int preferred_size;
- BOOL extend;
+ BOOL expand;
} GridDef;
@interface GridLayout : NSView<Container>
int ncols = _cols+1;
int nrows = _rows+1;
- GridDef *coldef = calloc(ncols, sizeof(GridDef));
- GridDef *rowdef = calloc(nrows, sizeof(GridDef));
+ GridDef *cols = calloc(ncols, sizeof(GridDef));
+ GridDef *rows = calloc(nrows, sizeof(GridDef));
NSRect viewFrame = self.frame;
int colspacing = _columnspacing;
int rowspacing = _rowspacing;
- CxIterator i = cxListIterator(_children);
- cx_foreach(GridElm *, elm, i) {
- NSSize size = elm->view.intrinsicContentSize;
- NSEdgeInsets alignment = elm->view.alignmentRectInsets;
- if(size.width != NSViewNoIntrinsicMetric) {
- CGFloat width = size.width + alignment.left + alignment.right;
- if(width > coldef[elm->x].preferred_size) {
- coldef[elm->x].preferred_size = width;
+ int span_max = 1;
+ for(int r=0;r<2;r++) {
+ CxIterator i = cxListIterator(_children);
+ cx_foreach(GridElm *, elm, i) {
+ int x = elm->x;
+ int y = elm->y;
+ GridDef *col = &cols[x];
+ GridDef *row = &rows[y];
+
+ NSSize size = elm->view.intrinsicContentSize;
+ NSEdgeInsets alignment = elm->view.alignmentRectInsets;
+ if(size.width != NSViewNoIntrinsicMetric) {
+ CGFloat width = size.width + alignment.left + alignment.right;
+ if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) {
+ cols[elm->x].preferred_size = width;
+ }
+ elm->preferred_width = width;
}
- }
- if(size.height != NSViewNoIntrinsicMetric) {
- CGFloat height = size.height + alignment.top + alignment.right;
- //CGFloat height = size.height;
- if(height > rowdef[elm->y].preferred_size) {
- rowdef[elm->y].preferred_size = height;
+ if(size.height != NSViewNoIntrinsicMetric) {
+ CGFloat height = size.height + alignment.top + alignment.right;
+ //CGFloat height = size.height;
+ if(height > rows[elm->y].preferred_size && elm->rowspan <= 1 && span_max == 1) {
+ rows[elm->y].preferred_size = height;
+ }
+ elm->preferred_height = height;
+ }
+
+ if(elm->rowspan > span_max || elm->colspan > span_max) {
+ continue;
+ }
+
+ int end_col = x+elm->colspan;
+ if(end_col > ncols) {
+ end_col = ncols;
+ }
+ int end_row = y+elm->rowspan;
+ if(end_row > nrows) {
+ end_row = nrows;
+ }
+
+ // are all columns in the span > preferred_width?
+ if(elm->colspan > 1) {
+ int span_width = 0;
+ GridDef *last_col = col;
+ for(int c=x;c<end_col;c++) {
+ span_width += cols[c].size;
+ last_col = &cols[c];
+ }
+ if(span_width < elm->preferred_width) {
+ last_col->size += elm->preferred_width - span_width;
+ }
+ }
+ // are all rows in the span > preferred_height?
+ if(elm->rowspan > 1) {
+ int span_height = 0;
+ GridDef *last_row = row;
+ for(int c=x;c<end_row;c++) {
+ span_height += rows[c].size;
+ last_row = &rows[c];
+ }
+ if(span_height < elm->preferred_height) {
+ last_row->size += elm->preferred_height - span_height;
+ }
+ }
+
+ if(elm->hexpand) {
+ if(elm->colspan > 1) {
+ // check if any column in the span is expanding
+ // if not, make the last column expanding
+ GridDef *last_col = col;
+ for(int c=x;c<end_col;c++) {
+ last_col = &cols[c];
+ if(last_col->expand) {
+ break;
+ }
+ }
+ last_col->expand = TRUE;
+ } else {
+ col->expand = TRUE;
+ }
+ }
+ if(elm->vexpand) {
+ if(elm->rowspan > 1) {
+ // same as colspan
+ GridDef *last_row = row;
+ for(int c=x;c<nrows;c++) {
+ last_row = &rows[c];
+ if(last_row->expand) {
+ break;
+ }
+ }
+ last_row->expand = TRUE;
+ } else {
+ row->expand = TRUE;
+ }
}
}
-
- if(elm->hexpand) {
- coldef[elm->x].extend = TRUE;
- }
- if(elm->vexpand) {
- rowdef[elm->y].extend = TRUE;
- }
+ span_max = 50000; // not sure if this is unreasonable low or high
}
+
int col_ext = 0;
int row_ext = 0;
int preferred_width = 0;
int preferred_height = 0;
for(int j=0;j<ncols;j++) {
- preferred_width += coldef[j].preferred_size + colspacing;
- if(coldef[j].extend) {
+ preferred_width += cols[j].preferred_size + colspacing;
+ if(cols[j].expand) {
col_ext++;
}
}
for(int j=0;j<nrows;j++) {
- preferred_height += rowdef[j].preferred_size + rowspacing;
- if(rowdef[j].extend) {
+ preferred_height += rows[j].preferred_size + rowspacing;
+ if(rows[j].expand) {
row_ext++;
}
}
int vext = vremaining/row_ext;
for(int j=0;j<ncols;j++) {
- GridDef *col = &coldef[j];
- if(col->extend) {
+ GridDef *col = &cols[j];
+ if(col->expand) {
col->size = col->preferred_size + hext;
} else {
col->size = col->preferred_size;
}
}
for(int j=0;j<nrows;j++) {
- GridDef *row = &rowdef[j];
- if(row->extend) {
+ GridDef *row = &rows[j];
+ if(row->expand) {
row->size = row->preferred_size + vext;
} else {
row->size = row->preferred_size;
int pos = 0;
for(int j=0;j<ncols;j++) {
- coldef[j].pos = pos;
- pos += coldef[j].size + colspacing;
+ cols[j].pos = pos;
+ pos += cols[j].size + colspacing;
}
pos = 0;
for(int j=0;j<nrows;j++) {
- rowdef[j].pos = pos;
- pos += rowdef[j].size + rowspacing;
+ rows[j].pos = pos;
+ pos += rows[j].size + rowspacing;
}
- i = cxListIterator(_children);
+ CxIterator i = cxListIterator(_children);
cx_foreach(GridElm *, elm, i) {
//NSSize size = elm->view.intrinsicContentSize;
- GridDef *col = &coldef[elm->x];
- GridDef *row = &rowdef[elm->y];
+ GridDef *col = &cols[elm->x];
+ GridDef *row = &rows[elm->y];
NSEdgeInsets alignment = elm->view.alignmentRectInsets;
NSRect frame;
- frame.size.width = col->size;
- frame.size.height = row->size;
+ if(elm->hfill) {
+ if(elm->colspan > 1) {
+ int cwidth = 0;
+ int end_col = elm->x + elm->colspan;
+ if(end_col > ncols) {
+ end_col = ncols;
+ }
+ for(int c=elm->x;c<end_col;c++) {
+ cwidth += cols[c].size;
+ }
+ frame.size.width = cwidth;
+ } else {
+ frame.size.width = col->size;
+ }
+ } else {
+ frame.size.width = elm->preferred_width;
+ }
+ if(elm->vfill) {
+ if(elm->rowspan > 1) {
+ int rheight = 0;
+ int end_row = elm->y + elm->rowspan;
+ if(end_row > nrows) {
+ end_row = nrows;
+ }
+ for(int r=elm->y;r<end_row;r++) {
+ rheight += rows[r].size;
+ }
+ frame.size.height = rheight;
+ }
+ frame.size.height = row->size;
+ } else {
+ frame.size.height = elm->preferred_height;
+ }
frame.origin.x = col->pos - (alignment.left+alignment.right)/2;
frame.origin.y = viewFrame.size.height - row->pos - frame.size.height + ((alignment.top+alignment.right)/2);
elm->view.frame = frame;
}
- free(coldef);
- free(rowdef);
+ free(cols);
+ free(rows);
}
}
- (void) dealloc {
- cxListDestroy(_children);
+ cxListFree(_children);
}
@end
if(var_ctx->vars_unbound && cxMapSize(var_ctx->vars_unbound) > 0) {
CxIterator i = cxMapIterator(var_ctx->vars_unbound);
cx_foreach(CxMapEntry*, entry, i) {
+ printf("attach %s\n", entry->key->data);
UiVar *var = entry->value;
UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
if(docvar) {
ctx->detach_document2(ctx, doc);
}
- cxListDestroy(ls);
+ cxListFree(ls);
}
static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) {
}
UIEXPORT void ui_context_destroy(UiContext *ctx) {
- cxMempoolDestroy(ctx->mp);
+ cxMempoolFree(ctx->mp);
}
uic_add_group_widget(ctx, widget, enable, groups);
- cxListDestroy(groups);
+ cxListFree(groups);
}
size_t uic_group_array_size(const int *groups) {
if(ctx->close_callback) {
ctx->close_callback(&ev, ctx->close_data);
}
- cxMempoolDestroy(ctx->mp);
+ cxMempoolFree(ctx->mp);
}
}
item->callback = args.onselect;
item->userdata = args.onselectdata;
item->varname = nl_strdup(args.varname);
+ item->addseparator = args.addseparator;
add_item((UiMenuItemI*)item);
}
free_menuitem(m);
m = next;
}
- cxListDestroy(builder->current);
+ cxListFree(builder->current);
free(builder);
}
ui_callback callback;
void *userdata;
char *varname;
+ UiBool addseparator;
};
ev.intval = 0;
obj->ctx->close_callback(&ev, obj->ctx->close_data);
}
- cxMempoolDestroy(obj->ctx->mp);
+ cxMempoolFree(obj->ctx->mp);
}
UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
}
void ui_list_free(UiList *list) {
- cxListDestroy(list->data);
+ cxListFree(list->data);
free(list);
}
info->titles[i] = c->name;
i++;
}
- cxListDestroy(cols);
+ cxListFree(cols);
return info;
}
for (int i = 0; i < model->columns; i++) {
newmodel->titles[i] = model->titles[i] ? cx_strdup_a(a, cx_str(model->titles[i])).ptr : NULL;
}
+ newmodel->columnsize = cxCalloc(a, model->columns, sizeof(int));
+ memcpy(newmodel->columnsize, model->columnsize, model->columns*sizeof(int));
return newmodel;
}
const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
cxFree(a, mi->types);
cxFree(a, mi->titles);
+ cxFree(a, mi->columnsize);
cxFree(a, mi);
}
return 1;
}
+UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ UIWIDGET widget = create_widget(obj, args, userdata);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, widget, FALSE);
+
+ return widget;
+}
+
GtkWidget* ui_gtk_vbox_new(int spacing) {
#if GTK_MAJOR_VERSION >= 3
return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
+/* -------------------- ItemList Container -------------------- */
+
+static void remove_item(void *data, void *item) {
+ UiGtkItemListContainer *ct = data;
+ UiObject *obj = item;
+ if(ct->remove_items) {
+ BOX_REMOVE(ct->widget, obj->widget);
+ }
+ uic_object_destroy(obj);
+}
+
+static void update_itemlist(UiList *list, int c) {
+ UiGtkItemListContainer *ct = list->obj;
+
+ CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS);
+ new_items->collection.advanced_destructor = remove_item;
+ new_items->collection.destructor_data = ct;
+
+ // only create new widgets for new elements, so at first we have
+ // to find which elements are new
+ // check which elements in the list are already in the container
+ void *elm = list->first(list);
+ int j = 0;
+ while(elm) {
+ CxHashKey key = cx_hash_key(&elm, sizeof(void*));
+ UiObject *item_obj = NULL;
+ cxMapRemoveAndGet(ct->current_items, key, &item_obj);
+ if(item_obj) {
+ g_object_ref(G_OBJECT(item_obj->widget));
+ BOX_REMOVE(ct->widget, item_obj->widget);
+ cxMapPut(new_items, key, item_obj);
+ }
+ elm = list->next(list);
+ j++;
+ }
+
+ // ct->current_items only contains elements, that are not in the list
+ cxMapFree(ct->current_items); // calls destructor remove_item
+ ct->current_items = new_items;
+
+ // add all items
+ int index = 0;
+ elm = list->first(list);
+ while(elm) {
+ CxHashKey key = cx_hash_key(&elm, sizeof(void*));
+ UiObject *item_obj = cxMapGet(ct->current_items, key);
+ if(item_obj) {
+ // re-add previously created widget
+ ui_box_container_add(ct->container, item_obj->widget, FALSE);
+ } else {
+ // create new widget and object for this list element
+ CxMempool *mp = cxBasicMempoolCreate(256);
+ const CxAllocator *a = mp->allocator;
+ UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
+ obj->ctx = uic_context(obj, mp);
+ obj->window = NULL;
+ obj->widget = ui_subcontainer_create(
+ ct->subcontainer,
+ obj,
+ ct->spacing,
+ ct->columnspacing,
+ ct->rowspacing,
+ ct->margin);
+ ui_box_container_add(ct->container, obj->widget, FALSE);
+ if(ct->create_ui) {
+ ct->create_ui(obj, index, elm, ct->userdata);
+ }
+ cxMapPut(new_items, key, obj);
+ }
+ elm = list->next(list);
+ index++;
+ }
+}
+
+static void destroy_itemlist_container(GtkWidget *w, UiGtkItemListContainer *container) {
+ container->remove_items = FALSE;
+ cxMapFree(container->current_items);
+ free(container);
+}
+
+UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args) {
+ UiObject *current = uic_current_obj(obj);
+ UiContainer *ct = current->container;
+ UI_APPLY_LAYOUT1(current, args);
+
+ GtkWidget *box = args.container == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing);
+ ui_set_name_and_style(box, args.name, args.style_class);
+ GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box;
+ ct->add(ct, widget, TRUE);
+
+ UiGtkItemListContainer *container = malloc(sizeof(UiGtkItemListContainer));
+ container->parent = obj;
+ container->widget = box;
+ container->container = ui_box_container(current, box, args.container);
+ container->create_ui = args.create_ui;
+ container->userdata = args.userdata;
+ container->subcontainer = args.subcontainer;
+ container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS);
+ container->current_items->collection.advanced_destructor = remove_item;
+ container->current_items->collection.destructor_data = container;
+ container->margin = args.sub_margin;
+ container->spacing = args.sub_spacing;
+ container->columnspacing = args.sub_columnspacing;
+ container->rowspacing = args.sub_rowspacing;
+ container->remove_items = TRUE;
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_LIST);
+ if(var) {
+ UiList *list = var->value;
+ list->obj = container;
+ list->update = update_itemlist;
+ update_itemlist(list, 0);
+ }
+ g_signal_connect(
+ box,
+ "destroy",
+ G_CALLBACK(destroy_itemlist_container),
+ container);
+
+ return box;
+}
#include <string.h>
#include <cx/allocator.h>
+#include <cx/hash_map.h>
#ifdef __cplusplus
extern "C" {
UiHeaderbarAlternative alternative; /* only used by fallback headerbar */
} UiHeaderbarContainer;
+typedef struct UiGtkItemListContainer {
+ UiObject *parent;
+ GtkWidget *widget;
+ UiContainer *container;
+ void (*create_ui)(UiObject *, int, void *, void *);
+ void *userdata;
+ UiSubContainerType subcontainer;
+ CxMap *current_items;
+ int margin;
+ int spacing;
+ int columnspacing;
+ int rowspacing;
+ bool remove_items;
+} UiGtkItemListContainer;
+
GtkWidget* ui_gtk_vbox_new(int spacing);
GtkWidget* ui_gtk_hbox_new(int spacing);
}
void ui_dnd_free(UiDnD *dnd) {
- cxListDestroy(dnd->providers);
+ cxListFree(dnd->providers);
free(dnd);
}
return column == 0 ? elm : NULL;
}
+/*
+static GtkTargetEntry targetentries[] =
+ {
+ { "STRING", 0, 0 },
+ { "text/plain", 0, 1 },
+ { "text/uri-list", 0, 2 },
+ };
+*/
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+
+/* BEGIN GObject wrapper for generic pointers */
+
+typedef struct _ObjWrapper {
+ GObject parent_instance;
+ void *data;
+} ObjWrapper;
+
+typedef struct _ObjWrapperClass {
+ GObjectClass parent_class;
+} ObjWrapperClass;
+
+G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT)
+
+static void obj_wrapper_class_init(ObjWrapperClass *klass) {
+
+}
+
+static void obj_wrapper_init(ObjWrapper *self) {
+ self->data = NULL;
+}
+
+ObjWrapper* obj_wrapper_new(void* data) {
+ ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL);
+ obj->data = data;
+ return obj;
+}
+
+/* END GObject wrapper for generic pointers */
+
+static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
+ UiColData *col = userdata;
+ UiModel *model = col->listview->model;
+ UiModelType type = model->types[col->model_column];
+ if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) {
+ GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ GtkWidget *image = gtk_image_new();
+ GtkWidget *label = gtk_label_new(NULL);
+ BOX_ADD(hbox, image);
+ BOX_ADD(hbox, label);
+ gtk_list_item_set_child(item, hbox);
+ g_object_set_data(G_OBJECT(hbox), "image", image);
+ g_object_set_data(G_OBJECT(hbox), "label", label);
+ } else if(type == UI_ICON) {
+ GtkWidget *image = gtk_image_new();
+ gtk_list_item_set_child(item, image);
+ } else {
+ GtkWidget *label = gtk_label_new(NULL);
+ gtk_label_set_xalign(GTK_LABEL(label), 0);
+ gtk_list_item_set_child(item, label);
+ }
+}
+
+static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
+ UiColData *col = userdata;
+
+ ObjWrapper *obj = gtk_list_item_get_item(item);
+ UiModel *model = col->listview->model;
+ UiModelType type = model->types[col->model_column];
+
+ void *data = model->getvalue(obj->data, col->data_column);
+ GtkWidget *child = gtk_list_item_get_child(item);
+
+ bool freevalue = TRUE;
+ switch(type) {
+ case UI_STRING: {
+ freevalue = FALSE;
+ }
+ case UI_STRING_FREE: {
+ gtk_label_set_label(GTK_LABEL(child), data);
+ if(freevalue) {
+ free(data);
+ }
+ break;
+ }
+ case UI_INTEGER: {
+ intptr_t intvalue = (intptr_t)data;
+ char buf[32];
+ snprintf(buf, 32, "%d", (int)intvalue);
+ gtk_label_set_label(GTK_LABEL(child), buf);
+ break;
+ }
+ case UI_ICON: {
+ UiIcon *icon = data;
+ if(icon) {
+ gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info));
+ }
+ break;
+ }
+ case UI_ICON_TEXT: {
+ freevalue = FALSE;
+ }
+ case UI_ICON_TEXT_FREE: {
+ void *data2 = model->getvalue(obj->data, col->data_column+1);
+ GtkWidget *image = g_object_get_data(G_OBJECT(child), "image");
+ GtkWidget *label = g_object_get_data(G_OBJECT(child), "label");
+ if(data && image) {
+ UiIcon *icon = data;
+ gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info));
+ }
+ if(data2 && label) {
+ gtk_label_set_label(GTK_LABEL(label), data2);
+ }
+ if(freevalue) {
+ free(data2);
+ }
+ break;
+ }
+ }
+}
+
+static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) {
+ GtkSelectionModel *selection_model;
+ if(multiselection) {
+ selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore)));
+ } else {
+ selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore)));
+ }
+ g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview);
+ return selection_model;
+}
+
+static UiListView* create_listview(UiObject *obj, UiListArgs args) {
+ UiListView *tableview = malloc(sizeof(UiListView));
+ memset(tableview, 0, sizeof(UiListView));
+ tableview->obj = obj;
+ tableview->model = args.model;
+ tableview->onactivate = args.onactivate;
+ tableview->onactivatedata = args.onactivatedata;
+ tableview->onselection = args.onselection;
+ tableview->onselectiondata = args.onselectiondata;
+ tableview->ondragstart = args.ondragstart;
+ tableview->ondragstartdata = args.ondragstartdata;
+ tableview->ondragcomplete = args.ondragcomplete;
+ tableview->ondragcompletedata = args.ondragcompletedata;
+ tableview->ondrop = args.ondrop;
+ tableview->ondropdata = args.ondropsdata;
+ tableview->selection.count = 0;
+ tableview->selection.rows = NULL;
+ return tableview;
+}
+
+UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ // to simplify things and share code with ui_table_create, we also
+ // use a UiModel for the listview
+ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+ model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+ args.model = model;
+
+ GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
+ UiListView *listview = create_listview(obj, args);
+
+ listview->columns = malloc(sizeof(UiColData));
+ listview->columns->listview = listview;
+ listview->columns->data_column = 0;
+ listview->columns->model_column = 0;
+
+ GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
+ g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns);
+ g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns);
+
+ GtkSelectionModel *selection_model = create_selection_model(listview, ls, args.multiselection);
+ GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory);
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ // init listview
+ listview->widget = view;
+ listview->var = var;
+ listview->liststore = ls;
+ listview->selectionmodel = selection_model;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_listview_destroy),
+ listview);
+
+ // bind listview to list
+ if(var && var->value) {
+ UiList *list = var->value;
+
+ list->obj = listview;
+ list->update = ui_listview_update2;
+ list->getselection = ui_listview_getselection2;
+ list->setselection = ui_listview_setselection2;
+
+ ui_update_liststore(ls, list);
+ }
+
+ // event handling
+ if(args.onactivate) {
+ // columnview and listview can use the same callback function, because
+ // the first parameter (which is technically a different pointer type)
+ // is ignored
+ g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview);
+ }
+
+ // add widget to parent
+ GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+ gtk_scrolled_window_set_policy(
+ GTK_SCROLLED_WINDOW(scroll_area),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
+ SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, scroll_area, FALSE);
+
+ // ct->current should point to view, not scroll_area, to make it possible
+ // to add a context menu
+ current->container->current = view;
+
+ return scroll_area;
+}
+
+UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ // to simplify things and share code with ui_tableview_create, we also
+ // use a UiModel for the listview
+ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+ model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+ args.model = model;
+
+ GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
+ UiListView *listview = create_listview(obj, args);
+
+ listview->columns = malloc(sizeof(UiColData));
+ listview->columns->listview = listview;
+ listview->columns->data_column = 0;
+ listview->columns->model_column = 0;
+
+ GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
+ g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns);
+ g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns);
+
+ GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL);
+ gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory);
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ // init listview
+ listview->widget = view;
+ listview->var = var;
+ listview->liststore = ls;
+ listview->selectionmodel = NULL;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_listview_destroy),
+ listview);
+
+ // bind listview to list
+ if(var && var->value) {
+ UiList *list = var->value;
+
+ list->obj = listview;
+ list->update = ui_listview_update2;
+ list->getselection = ui_combobox_getselection;
+ list->setselection = ui_combobox_setselection;
+
+ ui_update_liststore(ls, list);
+ }
+
+ // event handling
+ if(args.onactivate) {
+ g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview);
+ }
+
+ // add widget to parent
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, view, FALSE);
+ return view;
+}
+
+UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
+ //g_list_store_append(ls, v1);
+
+ // create obj to store all relevant data we need for handling events
+ // and list updates
+ UiListView *tableview = create_listview(obj, args);
+
+ GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args.multiselection);
+ GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model));
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ // init tableview
+ tableview->widget = view;
+ tableview->var = var;
+ tableview->liststore = ls;
+ tableview->selectionmodel = selection_model;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_listview_destroy),
+ tableview);
+
+
+ // create columns from UiModel
+ UiModel *model = args.model;
+ int columns = model ? model->columns : 0;
+
+ tableview->columns = calloc(columns, sizeof(UiColData));
+
+ int addi = 0;
+ for(int i=0;i<columns;i++) {
+ tableview->columns[i].listview = tableview;
+ tableview->columns[i].model_column = i;
+ tableview->columns[i].data_column = i+addi;
+
+ if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) {
+ // icon+text has 2 data columns
+ addi++;
+ }
+
+ GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
+ UiColData *col = &tableview->columns[i];
+ g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col);
+ g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col);
+
+ GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory);
+ gtk_column_view_column_set_resizable(column, true);
+ gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column);
+
+ int size = model->columnsize[i];
+ if(size > 0) {
+ gtk_column_view_column_set_fixed_width(column, size);
+ } else if(size < 0) {
+ gtk_column_view_column_set_expand(column, TRUE);
+ }
+ }
+
+ // bind listview to list
+ if(var && var->value) {
+ UiList *list = var->value;
+
+ list->obj = tableview;
+ list->update = ui_listview_update2;
+ list->getselection = ui_listview_getselection2;
+ list->setselection = ui_listview_setselection2;
+
+ ui_update_liststore(ls, list);
+ }
+
+ // event handling
+ if(args.onactivate) {
+ g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview);
+ }
+
+ // add widget to parent
+ GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+ gtk_scrolled_window_set_policy(
+ GTK_SCROLLED_WINDOW(scroll_area),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
+ SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, scroll_area, FALSE);
+
+ // ct->current should point to view, not scroll_area, to make it possible
+ // to add a context menu
+ current->container->current = view;
+
+ return scroll_area;
+}
+
+static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) {
+ UiListSelection sel = { 0, NULL };
+ GtkBitset *bitset = gtk_selection_model_get_selection(model);
+ int n = gtk_bitset_get_size(bitset);
+ printf("bitset %d\n", n);
+
+ gtk_bitset_unref(bitset);
+ return sel;
+}
+
+static void listview_event(ui_callback cb, void *cbdata, UiListView *view) {
+ UiEvent event;
+ event.obj = view->obj;
+ event.document = event.obj->ctx->document;
+ event.window = event.obj->window;
+ event.intval = view->selection.count;
+ event.eventdata = &view->selection;
+ if(cb) {
+ cb(&event, cbdata);
+ }
+}
+
+void ui_columnview_activate(void *ignore, guint position, gpointer userdata) {
+ UiListView *view = userdata;
+ listview_event(view->onactivate, view->onactivatedata, view);
+}
+
+void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) {
+ UiListView *view = userdata;
+ free(view->selection.rows);
+ view->selection.count = 0;
+ view->selection.rows = NULL;
+
+ CX_ARRAY_DECLARE(int, newselection);
+ cx_array_initialize(newselection, 8);
+
+ size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore));
+
+ for(size_t i=0;i<nitems;i++) {
+ if(gtk_selection_model_is_selected(view->selectionmodel, i)) {
+ int s = (int)i;
+ cx_array_simple_add(newselection, s);
+ }
+ }
+
+ if(newselection_size > 0) {
+ view->selection.count = newselection_size;
+ view->selection.rows = newselection;
+ } else {
+ free(newselection);
+ }
+
+ listview_event(view->onselection, view->onselectiondata, view);
+}
+
+void ui_dropdown_activate(GtkDropDown* self, gpointer userdata) {
+ UiListView *view = userdata;
+ guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget));
+ UiListSelection sel = { 0, NULL };
+ int sel2 = (int)selection;
+ if(selection != GTK_INVALID_LIST_POSITION) {
+ sel.count = 1;
+ sel.rows = &sel2;
+ }
+
+ if(view->onactivate) {
+ UiEvent event;
+ event.obj = view->obj;
+ event.document = event.obj->ctx->document;
+ event.window = event.obj->window;
+ event.intval = view->selection.count;
+ event.eventdata = &view->selection;
+ view->onactivate(&event, view->onactivatedata);
+ }
+}
+
+void ui_update_liststore(GListStore *liststore, UiList *list) {
+ g_list_store_remove_all(liststore);
+ void *elm = list->first(list);
+ while(elm) {
+ ObjWrapper *obj = obj_wrapper_new(elm);
+ g_list_store_append(liststore, obj);
+ elm = list->next(list);
+ }
+}
+
+void ui_listview_update2(UiList *list, int i) {
+ UiListView *view = list->obj;
+ ui_update_liststore(view->liststore, view->var->value);
+}
+
+UiListSelection ui_listview_getselection2(UiList *list) {
+ UiListView *view = list->obj;
+ UiListSelection selection;
+ selection.count = view->selection.count;
+ selection.rows = calloc(selection.count, sizeof(int));
+ memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int));
+ return selection;
+}
+
+void ui_listview_setselection2(UiList *list, UiListSelection selection) {
+ UiListView *view = list->obj;
+ UiListSelection newselection;
+ newselection.count = view->selection.count;
+ if(selection.count > 0) {
+ newselection.rows = calloc(newselection.count, sizeof(int));
+ memcpy(newselection.rows, selection.rows, selection.count*sizeof(int));
+ } else {
+ newselection.rows = NULL;
+ }
+ free(view->selection.rows);
+ view->selection = newselection;
+
+ gtk_selection_model_unselect_all(view->selectionmodel);
+ if(selection.count > 0) {
+ for(int i=0;i<selection.count;i++) {
+ gtk_selection_model_select_item(view->selectionmodel, selection.rows[i], FALSE);
+ }
+ }
+}
+
+UiListSelection ui_combobox_getselection(UiList *list) {
+ UiListView *view = list->obj;
+ guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget));
+ UiListSelection sel = { 0, NULL };
+ if(selection != GTK_INVALID_LIST_POSITION) {
+ sel.count = 1;
+ sel.rows = malloc(sizeof(int));
+ sel.rows[0] = (int)selection;
+ }
+ return sel;
+}
+
+void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+ UiListView *view = list->obj;
+ if(selection.count > 0) {
+ gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]);
+ } else {
+ gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION);
+ }
+}
+
+#else
+
static GtkListStore* create_list_store(UiList *list, UiModel *model) {
int columns = model->columns;
GType types[2*columns];
}
case UI_INTEGER: {
g_value_init(&value, G_TYPE_INT);
- int *intptr = data;
- g_value_set_int(&value, *intptr);
+ intptr_t intptr = (intptr_t)data;
+ g_value_set_int(&value, (int)intptr);
break;
}
case UI_ICON: {
listview->widget = view;
listview->var = var;
listview->model = model;
+ listview->selection.count = 0;
+ listview->selection.rows = NULL;
g_signal_connect(
view,
"destroy",
return scroll_area;
}
-/*
-static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
- printf("drag begin\n");
-
-}
-
-static void drag_end(
- GtkWidget *widget,
- GdkDragContext *context,
- guint time,
- gpointer udata)
-{
- printf("drag end\n");
-
-}
-*/
-
-/*
-static GtkTargetEntry targetentries[] =
- {
- { "STRING", 0, 0 },
- { "text/plain", 0, 1 },
- { "text/uri-list", 0, 2 },
- };
-*/
-
UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
UiObject* current = uic_current_obj(obj);
tableview->ondragcompletedata = args.ondragcompletedata;
tableview->ondrop = args.ondrop;
tableview->ondropdata = args.ondropsdata;
+ tableview->selection.count = 0;
+ tableview->selection.rows = NULL;
g_signal_connect(
view,
"destroy",
UI_APPLY_LAYOUT1(current, args);
current->container->add(current->container, scroll_area, FALSE);
- // ct->current should point to view, not scroll_area, to make it possible
- // to add a context menu
- current->container->current = view;
+ // ct->current should point to view, not scroll_area, to make it possible
+ // to add a context menu
+ current->container->current = view;
+
+ return scroll_area;
+}
+
+
+
+void ui_listview_update(UiList *list, int i) {
+ UiListView *view = list->obj;
+ GtkListStore *store = create_list_store(list, view->model);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
+ g_object_unref(G_OBJECT(store));
+}
+
+UiListSelection ui_listview_getselection(UiList *list) {
+ UiListView *view = list->obj;
+ UiListSelection selection = ui_listview_selection(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)),
+ NULL);
+ return selection;
+}
+
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+ UiListView *view = list->obj;
+ GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
+ GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
+ gtk_tree_selection_select_path(sel, path);
+ //g_object_unref(path);
+}
+
+
+
+/* --------------------------- ComboBox --------------------------- */
+
+UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+ model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata);
+ ui_set_name_and_style(combobox, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, combobox, args.groups);
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, combobox, FALSE);
+ current->container->current = combobox;
+ return combobox;
+}
+
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) {
+ GtkWidget *combobox = gtk_combo_box_new();
+
+ UiListView *uicbox = malloc(sizeof(UiListView));
+ uicbox->obj = obj;
+ uicbox->widget = combobox;
+
+ UiList *list = var ? var->value : NULL;
+ GtkListStore *listmodel = create_list_store(list, model);
+
+ if(listmodel) {
+ gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel));
+ g_object_unref(listmodel);
+ }
+
+ uicbox->var = var;
+ uicbox->model = model;
+
+ g_signal_connect(
+ combobox,
+ "destroy",
+ G_CALLBACK(ui_combobox_destroy),
+ uicbox);
+
+ // bind var
+ if(list) {
+ list->update = ui_combobox_modelupdate;
+ list->getselection = ui_combobox_getselection;
+ list->setselection = ui_combobox_setselection;
+ list->obj = uicbox;
+ }
+
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
+ gtk_cell_layout_set_attributes(
+ GTK_CELL_LAYOUT(combobox),
+ renderer,
+ "text",
+ 0,
+ NULL);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
+
+ // add callback
+ if(f) {
+ UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = udata;
+ event->callback = f;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ combobox,
+ "changed",
+ G_CALLBACK(ui_combobox_change_event),
+ event);
+ }
+
+ return combobox;
+}
+
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
+ UiEvent event;
+ event.obj = e->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = NULL;
+ event.intval = gtk_combo_box_get_active(widget);
+ e->callback(&event, e->userdata);
+}
+
+void ui_combobox_modelupdate(UiList *list, int i) {
+ UiListView *view = list->obj;
+ GtkListStore *store = create_list_store(view->var->value, view->model);
+ gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store));
+ g_object_unref(store);
+}
+
+UiListSelection ui_combobox_getselection(UiList *list) {
+ UiListView *combobox = list->obj;
+ UiListSelection ret;
+ ret.rows = malloc(sizeof(int*));
+ ret.count = 1;
+ ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget));
+ return ret;
+}
+
+void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+ UiListView *combobox = list->obj;
+ if(selection.count > 0) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
+ }
+}
+
+
+
+
+void ui_listview_activate_event(
+ GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ UiTreeEventData *event)
+{
+ UiListSelection selection = ui_listview_selection(
+ gtk_tree_view_get_selection(treeview),
+ event);
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = &selection;
+ e.intval = selection.count > 0 ? selection.rows[0] : -1;
+ event->activate(&e, event->activatedata);
+
+ if(selection.count > 0) {
+ free(selection.rows);
+ }
+}
+
+void ui_listview_selection_event(
+ GtkTreeSelection *treeselection,
+ UiTreeEventData *event)
+{
+ UiListSelection selection = ui_listview_selection(treeselection, event);
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = &selection;
+ e.intval = selection.count > 0 ? selection.rows[0] : -1;
+ event->selection(&e, event->selectiondata);
+
+ if(selection.count > 0) {
+ free(selection.rows);
+ }
+}
+
+UiListSelection ui_listview_selection(
+ GtkTreeSelection *selection,
+ UiTreeEventData *event)
+{
+ GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
- return scroll_area;
+ UiListSelection ls;
+ ls.count = g_list_length(rows);
+ ls.rows = calloc(ls.count, sizeof(int));
+ GList *r = rows;
+ int i = 0;
+ while(r) {
+ GtkTreePath *path = r->data;
+ ls.rows[i] = ui_tree_path_list_index(path);
+ r = r->next;
+ i++;
+ }
+ return ls;
+}
+
+int ui_tree_path_list_index(GtkTreePath *path) {
+ int depth = gtk_tree_path_get_depth(path);
+ if(depth == 0) {
+ fprintf(stderr, "UiError: treeview selection: depth == 0\n");
+ return -1;
+ }
+ int *indices = gtk_tree_path_get_indices(path);
+ return indices[depth - 1];
}
+
+#endif
+
+
#if GTK_MAJOR_VERSION >= 4
static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) {
free(t);
}
*/
-
-void ui_listview_update(UiList *list, int i) {
- UiListView *view = list->obj;
- GtkListStore *store = create_list_store(list, view->model);
- gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
- g_object_unref(G_OBJECT(store));
-}
-
-UiListSelection ui_listview_getselection(UiList *list) {
- UiListView *view = list->obj;
- UiListSelection selection = ui_listview_selection(
- gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)),
- NULL);
- return selection;
-}
-
-void ui_listview_setselection(UiList *list, UiListSelection selection) {
- UiListView *view = list->obj;
- GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
- GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
- gtk_tree_selection_select_path(sel, path);
- //g_object_unref(path);
-}
void ui_listview_destroy(GtkWidget *w, UiListView *v) {
//gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
ui_destroy_boundvar(v->obj->ctx, v->var);
+#if GTK_CHECK_VERSION(4, 10, 0)
+ free(v->columns);
+#endif
+ free(v->selection.rows);
free(v);
}
}
-void ui_listview_activate_event(
- GtkTreeView *treeview,
- GtkTreePath *path,
- GtkTreeViewColumn *column,
- UiTreeEventData *event)
-{
- UiListSelection selection = ui_listview_selection(
- gtk_tree_view_get_selection(treeview),
- event);
-
- UiEvent e;
- e.obj = event->obj;
- e.window = event->obj->window;
- e.document = event->obj->ctx->document;
- e.eventdata = &selection;
- e.intval = selection.count > 0 ? selection.rows[0] : -1;
- event->activate(&e, event->activatedata);
-
- if(selection.count > 0) {
- free(selection.rows);
- }
-}
-
-void ui_listview_selection_event(
- GtkTreeSelection *treeselection,
- UiTreeEventData *event)
-{
- UiListSelection selection = ui_listview_selection(treeselection, event);
-
- UiEvent e;
- e.obj = event->obj;
- e.window = event->obj->window;
- e.document = event->obj->ctx->document;
- e.eventdata = &selection;
- e.intval = selection.count > 0 ? selection.rows[0] : -1;
- event->selection(&e, event->selectiondata);
-
- if(selection.count > 0) {
- free(selection.rows);
- }
-}
-
-UiListSelection ui_listview_selection(
- GtkTreeSelection *selection,
- UiTreeEventData *event)
-{
- GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
-
- UiListSelection ls;
- ls.count = g_list_length(rows);
- ls.rows = calloc(ls.count, sizeof(int));
- GList *r = rows;
- int i = 0;
- while(r) {
- GtkTreePath *path = r->data;
- ls.rows[i] = ui_tree_path_list_index(path);
- r = r->next;
- i++;
- }
- return ls;
-}
-
-int ui_tree_path_list_index(GtkTreePath *path) {
- int depth = gtk_tree_path_get_depth(path);
- if(depth == 0) {
- fprintf(stderr, "UiError: treeview selection: depth == 0\n");
- return -1;
- }
- int *indices = gtk_tree_path_get_indices(path);
- return indices[depth - 1];
-}
-
-
-/* --------------------------- ComboBox --------------------------- */
-
-UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
- UiObject* current = uic_current_obj(obj);
-
- UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
- model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
-
- UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
-
- GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata);
- ui_set_name_and_style(combobox, args.name, args.style_class);
- ui_set_widget_groups(obj->ctx, combobox, args.groups);
- UI_APPLY_LAYOUT1(current, args);
- current->container->add(current->container, combobox, FALSE);
- current->container->current = combobox;
- return combobox;
-}
-
-GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) {
- GtkWidget *combobox = gtk_combo_box_new();
-
- UiListView *uicbox = malloc(sizeof(UiListView));
- uicbox->obj = obj;
- uicbox->widget = combobox;
-
- UiList *list = var ? var->value : NULL;
- GtkListStore *listmodel = create_list_store(list, model);
-
- if(listmodel) {
- gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel));
- g_object_unref(listmodel);
- }
-
- uicbox->var = var;
- uicbox->model = model;
-
- g_signal_connect(
- combobox,
- "destroy",
- G_CALLBACK(ui_combobox_destroy),
- uicbox);
-
- // bind var
- if(list) {
- list->update = ui_combobox_modelupdate;
- list->getselection = ui_combobox_getselection;
- list->setselection = ui_combobox_setselection;
- list->obj = uicbox;
- }
-
- GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
- gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
- gtk_cell_layout_set_attributes(
- GTK_CELL_LAYOUT(combobox),
- renderer,
- "text",
- 0,
- NULL);
- gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
-
- // add callback
- if(f) {
- UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
- event->obj = obj;
- event->userdata = udata;
- event->callback = f;
- event->value = 0;
- event->customdata = NULL;
-
- g_signal_connect(
- combobox,
- "changed",
- G_CALLBACK(ui_combobox_change_event),
- event);
- }
-
- return combobox;
-}
-
-void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
- UiEvent event;
- event.obj = e->obj;
- event.window = event.obj->window;
- event.document = event.obj->ctx->document;
- event.eventdata = NULL;
- event.intval = gtk_combo_box_get_active(widget);
- e->callback(&event, e->userdata);
-}
-
-void ui_combobox_modelupdate(UiList *list, int i) {
- UiListView *view = list->obj;
- GtkListStore *store = create_list_store(view->var->value, view->model);
- gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store));
- g_object_unref(store);
-}
-
-UiListSelection ui_combobox_getselection(UiList *list) {
- UiListView *combobox = list->obj;
- UiListSelection ret;
- ret.rows = malloc(sizeof(int*));
- ret.count = 1;
- ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget));
- return ret;
-}
-
-void ui_combobox_setselection(UiList *list, UiListSelection selection) {
- UiListView *combobox = list->obj;
- if(selection.count > 0) {
- gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
- }
-}
-
-
/* ------------------------------ Source List ------------------------------ */
static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) {
- cxListDestroy(v->sublists);
+ cxListFree(v->sublists);
free(v);
}
static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) {
free(sublist->header);
ui_destroy_boundvar(obj->ctx, sublist->var);
- cxListDestroy(sublist->widgets);
+ cxListFree(sublist->widgets);
}
static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) {
}
UiListBoxSubList *sublist = data->customdata0;
+ UiSubListEventData eventdata;
+ eventdata.list = sublist->var->value;
+ eventdata.sublist_index = sublist->index;
+ eventdata.row_index = data->value0;
+ eventdata.sublist_userdata = sublist->userdata;
+ eventdata.row_data = ui_list_get(eventdata.list, eventdata.row_index);
+ eventdata.event_data = data->customdata2;
+
UiEvent event;
event.obj = data->obj;
event.window = event.obj->window;
event.document = event.obj->ctx->document;
- event.eventdata = data->customdata2;
+ event.eventdata = &eventdata;
event.intval = data->value0;
if(data->callback) {
#ifdef __cplusplus
extern "C" {
#endif
+
+typedef struct UiColData UiColData;
typedef struct UiListView {
- UiObject *obj;
- GtkWidget *widget;
- UiVar *var;
- UiModel *model;
- ui_callback ondragstart;
- void *ondragstartdata;
- ui_callback ondragcomplete;
- void *ondragcompletedata;
- ui_callback ondrop;
- void *ondropdata;
+ UiObject *obj;
+ GtkWidget *widget;
+ UiVar *var;
+ UiModel *model;
+#if GTK_CHECK_VERSION(4, 10, 0)
+ GListStore *liststore;
+ GtkSelectionModel *selectionmodel;
+ UiColData *columns;
+#endif
+ ui_callback onactivate;
+ void *onactivatedata;
+ ui_callback onselection;
+ void *onselectiondata;
+ ui_callback ondragstart;
+ void *ondragstartdata;
+ ui_callback ondragcomplete;
+ void *ondragcompletedata;
+ ui_callback ondrop;
+ void *ondropdata;
+ UiListSelection selection;
} UiListView;
+struct UiColData {
+ UiListView *listview;
+ int model_column;
+ int data_column;
+};
+
typedef struct UiTreeEventData {
UiObject *obj;
ui_callback activate;
GtkListBoxRow *first_row;
};
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+void ui_update_liststore(GListStore *liststore, UiList *list);
+
+void ui_listview_update2(UiList *list, int i);
+UiListSelection ui_listview_getselection2(UiList *list);
+void ui_listview_setselection2(UiList *list, UiListSelection selection);
+
+void ui_columnview_activate(void *ignore, guint position, gpointer userdata);
+void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer user_data);
+
+void ui_dropdown_activate(GtkDropDown* self, gpointer userdata);
+
+#endif
+
void* ui_strmodel_getvalue(void *elm, int column);
UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
void ui_combobox_destroy(GtkWidget *w, UiListView *v);
void ui_listview_destroy(GtkWidget *w, UiListView *v);
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+#else
void ui_listview_activate_event(
GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeSelection *selection,
UiTreeEventData *event);
int ui_tree_path_list_index(GtkTreePath *path);
+#endif
void ui_listview_add_dnd(UiListView *listview, UiListArgs *args);
void ui_listview_enable_drop(UiListView *listview, UiListArgs *args);
CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
cxListAddArray(groups, i->groups, i->ngroups);
uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
- cxListDestroy(groups);
+ cxListFree(groups);
}
}
#if GTK_MAJOR_VERSION >= 4
+GtkWidget *ui_create_menubar(UiObject *obj) {
+ UiMenu *menus_begin = uic_get_menu_list();
+ if(menus_begin == NULL) {
+ return NULL;
+ }
+
+ GMenu *menu = g_menu_new();
+ UiMenu *ls = menus_begin;
+ while(ls) {
+ GMenu *sub_menu = g_menu_new();
+ ui_gmenu_add_menu_items(sub_menu, 0, ls, obj);
+ g_menu_append_submenu(menu, ls->label, G_MENU_MODEL(sub_menu));
+ ls = (UiMenu*)ls->item.next;
+ }
+
+ // Create a menubar from the menu model
+ return gtk_popover_menu_bar_new_from_model(G_MENU_MODEL(menu));
+}
static ui_gmenu_add_f createMenuItem[] = {
/* UI_MENU */ ui_gmenu_add_menu,
CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
cxListAddArray(groups, i->groups, i->ngroups);
uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups);
- cxListDestroy(groups);
+ cxListFree(groups);
}
if(i->callback != NULL) {
UIMENU ui_create_menu(UiMenuBuilder *builder, UiObject *obj);
void ui_widget_set_contextmenu(GtkWidget *widget, UIMENU menu);
+GtkWidget *ui_create_menubar(UiObject *obj);
+
#if GTK_MAJOR_VERSION <= 3
typedef struct UiActiveMenuItemList UiActiveMenuItemList;
void *userdata;
};
-GtkWidget *ui_create_menubar(UiObject *obj);
-
void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj);
void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
#define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child)
#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child)
#define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child)
+#define BOX_REMOVE(box, child) gtk_box_remove(GTK_BOX(box), child)
#define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text)
#define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry))
#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new()
#define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child)
#define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0)
#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0)
+#define BOX_REMOVE(box, child) gtk_container_remove(GTK_CONTAINER(box), child)
#define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text)
#define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry))
#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL)
void *userdata;
} UiVarEventData;
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
#ifndef UI_GTK4
struct UiSelection {
GtkSelectionData *data;
};
#endif
-typedef enum UiOrientation UiOrientation;
-enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
-
#ifdef UI_APPLICATION
void ui_app_quit();
GtkApplication* ui_get_application();
GtkWidget *content_box = ui_gtk_vbox_new(0);
BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+ GtkWidget *sidebar_headerbar = NULL;
if(sidebar) {
GtkWidget *splitview = adw_overlay_split_view_new();
adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview);
GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new();
adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view);
- GtkWidget *sidebar_headerbar = adw_header_bar_new();
+ sidebar_headerbar = adw_header_bar_new();
adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), sidebar_headerbar);
adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), toolbar_view);
adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
}
-
GtkWidget *headerbar = adw_header_bar_new();
+
+ const char *show_title = ui_get_property("ui.gtk.window.showtitle");
+ if(show_title) {
+ if(!strcmp(show_title, "main") && sidebar) {
+ adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE);
+ } else if(!strcmp(show_title, "sidebar")) {
+ adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE);
+ } else if(!strcmp(show_title, "false")) {
+ adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE);
+ adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE);
+ } else {
+ fprintf(stderr, "Unknown value '%s' for property ui.gtk.window.showtitle\n", show_title);
+ adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE);
+ }
+ } else {
+ adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE);
+ }
+
adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar);
g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar);
#elif GTK_MAJOR_VERSION >= 4
GtkWidget *content_box = ui_gtk_vbox_new(0);
WINDOW_SET_CONTENT(obj->widget, vbox);
+ if(!simple) {
+ if(uic_get_menu_list()) {
+ GtkWidget *mb = ui_create_menubar(obj);
+ if(mb) {
+ BOX_ADD(vbox, mb);
+ }
+ }
+ }
if(sidebar) {
GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0);
<EnterWindow>: getfocus()\n\
<LeaveWindow>: loosefocus()\n";
+static XtResource resources[] =
+{
+ {
+ gridColumnSpacing,
+ gridColumnSpacing,
+ XmRDimension,
+ sizeof (Dimension),
+ XtOffsetOf( GridRec,
+ mywidget.columnspacing),
+ XmRImmediate,
+ (XtPointer) 0
+ },
+ {
+ gridRowSpacing,
+ gridRowSpacing,
+ XmRDimension,
+ sizeof (Dimension),
+ XtOffsetOf( GridRec,
+ mywidget.rowspacing),
+ XmRImmediate,
+ (XtPointer) 0
+ },
+ {
+ gridMargin,
+ gridMargin,
+ XmRDimension,
+ sizeof (Dimension),
+ XtOffsetOf( GridRec,
+ mywidget.margin),
+ XmRImmediate,
+ (XtPointer) 0
+ }
+};
///*
static XtResource constraints[] =
grid.vfill),
XmRImmediate,
(XtPointer) 0
+ },
+ {
+ gridMinWidth,
+ gridMinWidth,
+ XmRDimension,
+ sizeof (Dimension),
+ XtOffsetOf( GridConstraintRec,
+ grid.min_width),
+ XmRImmediate,
+ (XtPointer) 0
}
-
};
//*/
-//static XtResource constraints[] = {};
GridClassRec gridClassRec = {
// Core Class
//(WidgetClass)&constraintClassRec, // superclass
(WidgetClass)&xmManagerClassRec,
"Grid", // class_name
- sizeof(GridRec), // widget_size
- grid_class_initialize, // class_initialize
+ sizeof(GridRec), // widget_size
+ grid_class_initialize, // class_initialize
NULL, // class_part_initialize
FALSE, // class_inited
- (XtInitProc)grid_initialize, // initialize
+ (XtInitProc)grid_initialize, // initialize
NULL, // initialize_hook
- grid_realize, // realize
- actionslist, // actions
- XtNumber(actionslist), // num_actions
- NULL, // resources
- 0, // num_resources
+ grid_realize, // realize
+ actionslist, // actions
+ XtNumber(actionslist), // num_actions
+ resources, // resources
+ XtNumber(resources), // num_resources
NULLQUARK, // xrm_class
True, // compress_motion
True, // compress_exposure
// XmManager Class
///*
{
- NULL,
+ XtInheritTranslations,
NULL,
0,
NULL,
0,
- NULL,
+ XmInheritParentProcess,
NULL
},
//*/
int req_width = 0;
int req_height = 0;
+ //printf("container width: %d\n", (int)w->core.width);
+
// calculate the minimum size requirements for all columns and rows
// we need to run this 2 times: for widgets without colspan/rowspan first
// and then again for colspan/rowspan > 1
for(int i=0;i<w->composite.num_children;i++) {
Widget child = w->composite.children[i];
GridConstraintRec *constraints = child->core.constraints;
+ if(constraints->grid.pref_width == 0) {
+ constraints->grid.pref_width = child->core.width;
+ }
+ if(constraints->grid.pref_height == 0) {
+ constraints->grid.pref_height = child->core.height;
+ }
+ if(constraints->grid.pref_width < constraints->grid.min_width) {
+ constraints->grid.pref_width = constraints->grid.min_width;
+ }
if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) {
continue;
span_max = 50000; // not sure if this is unreasonable low or high
}
-
+ // calc required size
for(int i=0;i<ncols;i++) {
if(cols[i].expand) {
num_cols_expanding++;
}
if(req_width > 0 && req_height > 0) {
+ // add col/row spacing
+ req_width += (ncols-1)*w->mywidget.columnspacing;
+ req_height += (nrows-1)*w->mywidget.rowspacing;
+
Widget parent = w->core.parent;
Dimension rwidth = req_width;
Dimension rheight = req_height;
if(!w->mywidget.sizerequest) {
Dimension actual_width, actual_height;
w->mywidget.sizerequest = TRUE;
+
+ //XtWidgetGeometry request;
+ //request.width = req_width;
+ //request.request_mode = CWWidth;
+ //XtWidgetGeometry reply;
+ //XtGeometryResult result = XtMakeGeometryRequest((Widget)w, &request, &reply);
+
XtMakeResizeRequest((Widget)w, req_width, req_height, &actual_width, &actual_height);
w->mywidget.sizerequest = FALSE;
//printf("size request: %d %d\n", (int)actual_width, (int)actual_height);
}
+ // how much space can we add to each expanding col/row
int hexpand = 0;
int width_diff = (int)w->core.width - req_width;
int hexpand2 = 0;
if(cols[i].expand) {
cols[i].size += hexpand + hexpand2;
}
- x += cols[i].size;
+ x += cols[i].size + w->mywidget.columnspacing;
hexpand2 = 0;
}
if(rows[i].expand) {
rows[i].size += vexpand + vexpand2;
}
- y += rows[i].size;
+ y += rows[i].size += w->mywidget.rowspacing;
vexpand2 = 0;
}
Dimension cwidth = 0;
for(int j=0;j<constraints->grid.colspan;j++) {
if(constraints->grid.x+j < ncols) {
- cwidth += cols[constraints->grid.x+j].size;
+ cwidth += cols[constraints->grid.x+j].size + (j > 0 ? w->mywidget.columnspacing : 0);
}
}
width = cwidth;
} else {
- width = c.size;
+ width = c.size - w->mywidget.columnspacing;
}
}
if(constraints->grid.vfill) {
Dimension cheight = 0;
for(int j=0;j<constraints->grid.rowspan;j++) {
if(constraints->grid.y+j < nrows) {
- cheight += rows[constraints->grid.y+j].size;
+ cheight += rows[constraints->grid.y+j].size + (j > 0 ? w->mywidget.rowspacing : 0);
}
}
height = cheight;
} else {
- height = r.size;
+ height = r.size - w->mywidget.rowspacing;
}
}
- XtConfigureWidget(child, x, y, width, height, child->core.border_width);
+ if(width > 0 && height > 0) {
+ XtConfigureWidget(child, x, y, width, height, child->core.border_width);
+ }
//printf("child %d %d - %d %d\n", (int)child->core.x, (int)child->core.y, (int)child->core.width, (int)child->core.height);
}
extern "C" {
#endif
+// resources
+#define gridColumnSpacing "gridColumnSpacing"
+#define gridRowSpacing "gridRowSpacing"
+#define gridMargin "gridMargin"
+
+// constraints
#define gridColumn "gridColumn"
#define gridRow "gridRow"
#define gridColspan "gridColspan"
#define gridMarginRight "gridMarginRight"
#define gridMarginTop "gridMarginTop"
#define gridMarginBottom "gridMarginBottom"
+#define gridMinWidth "gridMinWidth"
typedef struct GridDef {
int margin_bottom;
int max_col;
int max_row;
+ Dimension columnspacing;
+ Dimension rowspacing;
+ Dimension margin;
Boolean sizerequest;
} GridPart;
Dimension rowspan;
Dimension pref_width;
Dimension pref_height;
+ Dimension min_width;
} GridContraintPart;
typedef struct GridConstraintRec {
UI_APPLY_LAYOUT(ctn->layout, args);
Widget parent = ctn->prepare(ctn, xargs, &n);
-
+
XmString label = NULL;
if(args.label) {
label = XmStringCreateLocalized((char*)args.label);
}
static void destroy_list(Widget w, CxList *list, XtPointer d) {
- cxListDestroy(list);
+ cxListFree(list);
}
static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) {
#include "../common/context.h"
#include "../common/object.h"
+#include <cx/array_list.h>
+
#include "Grid.h"
+
+UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) {
+ Arg xargs[64];
+ int n = 0;
+
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ UI_APPLY_LAYOUT(ctn->layout, args);
+
+ Widget parent = ctn->prepare(ctn, xargs, &n);
+ Widget widget = create_widget(obj, args, userdata, parent, xargs, n);
+ XtManageChild(widget);
+ ctn->add(ctn, widget);
+
+ return widget;
+}
+
/* ---------------------------- Box Container ---------------------------- */
static UIWIDGET box_create(UiObject *obj, UiContainerArgs args, UiBoxOrientation orientation) {
Arg xargs[16];
int n = 0;
- XtSetArg(xargs[n], XmNbackground, 0); n++;
-
UiContainerPrivate *ctn = ui_obj_container(obj);
UI_APPLY_LAYOUT(ctn->layout, args);
Widget parent = ctn->prepare(ctn, xargs, &n);
+ XtSetArg(xargs[n], gridMargin, args.margin); n++;
+ XtSetArg(xargs[n], gridColumnSpacing, args.columnspacing); n++;
+ XtSetArg(xargs[n], gridRowSpacing, args.rowspacing); n++;
Widget grid = XtCreateManagedWidget(args.name ? args.name : "gridcontainer", gridClass, parent, xargs, n);
ctn->add(ctn, grid);
}
+/* -------------------------- TabView Container -------------------------- */
+
+static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) {
+ printf("ui_tabbar_resize\n");
+
+ UiMotifTabView *tabview = udata;
+
+ int width = 0;
+ int height = 0;
+ XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
+ int button_width = width / 4;
+ int x = 0;
+
+ printf("width: %d\n", (int)width);
+
+ CxIterator i = cxListIterator(tabview->tabs);
+ cx_foreach(UiTab *, tab, i) {
+ XtVaSetValues(
+ tab->tab_button,
+ XmNx, x,
+ XmNy, 0,
+ XmNwidth,
+ button_width,
+
+ NULL);
+ x += button_width;
+ }
+
+ if(height <= tabview->height) {
+ XtVaSetValues(widget, XmNheight, tabview->height + 4, NULL);
+ }
+}
+
+static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) {
+ printf("ui_tabbar_expose\n");
+
+ UiMotifTabView *tabview = udata;
+ CxIterator i = cxListIterator(tabview->tabs);
+ cx_foreach(UiTab *, tab, i) {
+ printf("y: %d\n", (int)tab->tab_button->core.y);
+ }
+
+ XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata;
+ XEvent *event = cbs->event;
+ Display *dpy = XtDisplay(widget);
+
+ printf("width: %d\n", (int)widget->core.width);
+}
+
+UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs args) {
+ Arg xargs[16];
+ int n = 0;
+
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ UI_APPLY_LAYOUT(ctn->layout, args);
+
+ // create widgets
+ // form
+ // - tabbar (Drawing Area)
+ // - content (Frame)
+ UiMotifTabView *tabview = malloc(sizeof(UiMotifTabView));
+ memset(tabview, 0, sizeof(UiMotifTabView));
+ char *name = args.name ? (char*)args.name : "tabview";
+ XtSetArg(xargs[n], XmNuserData, tabview); n++;
+ Widget parent = ctn->prepare(ctn, xargs, &n);
+ Widget form = XmCreateForm(parent, name, xargs, n);
+ XtManageChild(form);
+
+ n = 0;
+ XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++;
+ XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++;
+ XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_FORM); n++;
+ XtSetArg(xargs[n], XmNorientation, XmHORIZONTAL); n++;
+ XtSetArg(xargs[n], XmNpacking, XmPACK_TIGHT); n++;
+ XtSetArg(xargs[n], XmNspacing, 1); n++;
+ XtSetArg(xargs[n], XmNmarginWidth, 0); n++;
+ XtSetArg(xargs[n], XmNmarginHeight, 0); n++;
+ Widget tabbar = XmCreateDrawingArea(form, "ui_test", xargs, n);
+ XtManageChild(tabbar);
+ XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabview);
+ XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabview);
+
+ n = 0;
+ XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++;
+ XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++;
+ XtSetArg(xargs[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+ XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
+ XtSetArg(xargs[n], XmNtopWidget, tabbar); n++;
+ Widget content = XmCreateFrame(form, "tabviewcontent", xargs, n);
+
+ // setup tabview object, that holds all relevant objects
+ tabview->form = form;
+ tabview->tabbar = tabbar;
+ tabview->content = content;
+ tabview->tabview = args.tabview;
+ tabview->subcontainer = args.subcontainer;
+ tabview->select = ui_motif_tabview_select;
+ tabview->add = ui_motif_tabview_add_tab;
+ tabview->remove = ui_motif_tabview_remove;
+ tabview->tabs = cxArrayListCreateSimple(sizeof(UiTab), 8);
+
+ UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer));
+ ct->container.widget = form;
+ ct->container.type = UI_CONTAINER_TABVIEW;
+ ct->container.prepare = ui_tabview_container_prepare;
+ ct->container.add = ui_tabview_container_add;
+ ct->tabview = tabview;
+
+ uic_object_push_container(obj, (UiContainerX*)ct);
+
+ return form;
+}
+
+void ui_tab_create(UiObject *obj, const char* title) {
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ if(ctn->type != UI_CONTAINER_TABVIEW) {
+ fprintf(stderr, "UI Error: container is not a tabview\n");
+ return;
+ }
+
+ UiMotifTabView *tabview = NULL;
+ XtVaGetValues(ctn->widget, XmNuserData, &tabview, NULL);
+ if(!tabview) {
+ fprintf(stderr, "UI Error: no tabview\n");
+ return;
+ }
+
+
+ Widget child = ui_vbox_create(obj, (UiContainerArgs) { 0 });
+ if(tabview->current) {
+ XtUnmanageChild(child);
+ } else {
+ tabview->current = child;
+ }
+
+ tabview->add(tabview, -1, title, child);
+}
+
+void ui_motif_tabview_select(UiMotifTabView *tabview, int tab) {
+
+}
+
+void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child) {
+ UiTab tab;
+
+ Arg args[16];
+ int n = 0;
+
+ XmString label = XmStringCreateLocalized((char*)name);
+ XtSetArg(args[n], XmNlabelString, label); n++;
+ XtSetArg(args[n], XmNshadowThickness, 0); n++;
+ XtSetArg(args[n], XmNhighlightThickness, 0); n++;
+
+ Widget button = XmCreatePushButton(tabview->tabbar, "tab_button", args, 3);
+ XtManageChild(button);
+
+ if(tabview->height == 0) {
+ Dimension h;
+ XtVaGetValues(
+ button,
+ XmNarmColor,
+ &tabview->bg1,
+ XmNbackground,
+ &tabview->bg2,
+ XmNheight,
+ &h,
+ NULL);
+ tabview->height = h + 2; // border
+ }
+
+ tab.tab_button = button;
+ tab.child = child;
+ cxListAdd(tabview->tabs, &tab);
+}
+
+void ui_motif_tabview_remove(UiMotifTabView *tabview, int index) {
+
+}
+
+Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
+ UiTabViewContainer *ct = (UiTabViewContainer*)ctn;
+ UiMotifTabView *tabview = ct->tabview;
+ int a = *n;
+ XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++;
+ XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++;
+ XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++;
+ XtSetArg(args[a], XmNtopAttachment, XmATTACH_WIDGET); a++;
+ XtSetArg(args[a], XmNtopWidget, tabview->tabbar); a++;
+ *n = a;
+ return tabview->form;
+}
+
+void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget) {
+ ui_reset_layout(ctn->layout);
+}
+
+
+
/* -------------------- Container Helper Functions -------------------- */
void ui_container_begin_close(UiObject *obj) {
layout.rowspan = args.rowspan
typedef enum UiBoxOrientation UiBoxOrientation;
+
+enum UiContainerType {
+ UI_CONTAINER_GENERIC = 0,
+ UI_CONTAINER_TABVIEW
+};
+typedef enum UiContainerType UiContainerType;
#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
struct UiContainerPrivate {
- UiContainerX container;
- Widget (*prepare)(UiContainerPrivate*, Arg *, int*);
- void (*add)(UiContainerPrivate*, Widget);
- Widget widget;
- UiLayout layout;
+ UiContainerX container;
+ Widget (*prepare)(UiContainerPrivate*, Arg *, int*);
+ void (*add)(UiContainerPrivate*, Widget);
+ Widget widget;
+ UiContainerType type;
+ UiLayout layout;
};
typedef struct UiBoxContainer {
Dimension y;
} UiGridContainer;
+typedef struct UiMotifTabView UiMotifTabView;
+struct UiMotifTabView {
+ Widget form;
+ Widget tabbar;
+ Widget content;
+ Widget current;
+ int height;
+ Pixel bg1;
+ Pixel bg2;
+ UiTabViewType tabview;
+ UiSubContainerType subcontainer;
+ CxList *tabs;
+ void (*select)(UiMotifTabView *tabview, int tab);
+ void (*add)(UiMotifTabView *tabview, int index, const char *name, Widget child);
+ void (*remove)(UiMotifTabView *tabview, int index);
+};
+
+typedef struct UiTab {
+ Widget tab_button;
+ Widget child;
+} UiTab;
+
+typedef struct UiTabViewContainer {
+ UiContainerPrivate container;
+ UiMotifTabView *tabview;
+} UiTabViewContainer;
+
+void ui_motif_tabview_select(UiMotifTabView *tabview, int tab);
+void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child);
+void ui_motif_tabview_remove(UiMotifTabView *tabview, int index);
+
+Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
+void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget);
+
UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation);
Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n);
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
#include "../common/context.h"
#include "../common/object.h"
+#include "Grid.h"
+
+static UIWIDGET label_create(UiObject *obj, UiLabelArgs args, int align) {
+ Arg xargs[16];
+ int n = 0;
+
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ UI_APPLY_LAYOUT(ctn->layout, args);
+
+ Widget parent = ctn->prepare(ctn, xargs, &n);
+
+ XtSetArg(xargs[n], XmNalignment, align); n++;
+ XmString label = NULL;
+ if(args.label) {
+ label = XmStringCreateLocalized((char*)args.label);
+ XtSetArg(xargs[n], XmNlabelString, label); n++;
+ }
+
+ char *name = args.name ? (char*)args.name : "label";
+ Widget w = XmCreateLabel(parent, name, xargs, n);
+ XtManageChild(w);
+ ctn->add(ctn, w);
+
+ XmStringFree(label);
+ return w;
+}
+
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) {
+ return label_create(obj, args, XmALIGNMENT_CENTER);
+}
+
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {
+ return label_create(obj, args, XmALIGNMENT_BEGINNING);
+}
+
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {
+ return label_create(obj, args, XmALIGNMENT_END);
+}
+
+
+/* -------------------------- progressbar/spiner -------------------------- */
+
+static void ui_destroy_progressbar(Widget w, UiProgressBar *pb, XtPointer d) {
+ // TODO: free other stuff
+ free(pb);
+}
+
+static void ui_progressbar_expose(Widget widget, UiProgressBar *pb, XtPointer c) {
+ Display *dp = XtDisplay(widget);
+ Window w = XtWindow(widget);
+ if(w == 0) {
+ return;
+ }
+ if(!pb->gc) {
+ XGCValues gcvals;
+ gcvals.foreground = pb->color;
+ pb->gc = XCreateGC(dp, w, (GCForeground), &gcvals);
+ }
+
+ Dimension width = widget->core.width;
+ Dimension height = widget->core.height;
+
+ double value = (pb->value - pb->min) / (pb->max - pb->min);
+ Dimension valueW = (double)width * value;
+
+ XClearArea(dp, w, 0, 0, width, height, False);
+ XFillRectangle(dp, w, pb->gc, 0, 0, valueW, widget->core.height);
+}
+
+UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args) {
+ Arg xargs[16];
+ int n = 0;
+
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ UI_APPLY_LAYOUT(ctn->layout, args);
+
+ Widget parent = ctn->prepare(ctn, xargs, &n);
+
+ char *name = args.name ? (char*)args.name : "progressbar";
+ Widget frame = XmCreateFrame(parent, name, xargs, n);
+
+ // create a button and get some informations about the height, shadow, highlight, ....
+ // we want the frame to have the same dimensions as a normal button
+ Widget test = XmCreatePushButton(frame, "button", NULL, 0);
+ XtManageChild(test);
+ Dimension h, highlightThickness, shadowThickness;
+ Pixel highlightColor;
+ XtVaGetValues(test, XmNheight, &h, XmNhighlightThickness, &highlightThickness,
+ XmNshadowThickness, &shadowThickness, XmNhighlightColor, &highlightColor, NULL);
+ XtDestroyWidget(test);
+
+ // adjust frame
+ XtVaSetValues(frame, XmNshadowThickness, shadowThickness, gridMarginLeft, highlightThickness,
+ gridMarginRight, highlightThickness, gridMarginTop, highlightThickness,
+ gridMarginBottom, highlightThickness, NULL);
+
+ // create drawing area
+ Dimension da_height = h - 2*highlightThickness - 2*shadowThickness;
+ n = 0;
+ XtSetArg(xargs[n], XmNheight, da_height); n++;
+ Widget drawingArea = XmCreateDrawingArea(frame, "progressbar_drawingarea", xargs, n);
+ XtManageChild(drawingArea);
+
+ UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_DOUBLE);
+
+ UiProgressBar *progressbarData = malloc(sizeof(UiProgressBar));
+ progressbarData->widget = drawingArea;
+ progressbarData->min = args.min;
+ progressbarData->max = args.max == 0 ? 100 : args.max;
+ progressbarData->value = 50;
+ progressbarData->var = var;
+ progressbarData->color = highlightColor;
+ progressbarData->gc = NULL; // initialize on first expose
+
+ if(var) {
+ UiDouble *d = var->value;
+ progressbarData->value = d->value;
+ d->obj = progressbarData;
+ d->get = ui_progressbar_get;
+ d->set = ui_progressbar_set;
+ }
+
+ XtAddCallback(
+ drawingArea,
+ XmNexposeCallback,
+ (XtCallbackProc)ui_progressbar_expose,
+ progressbarData);
+ XtAddCallback(
+ drawingArea,
+ XmNresizeCallback,
+ (XtCallbackProc)ui_progressbar_expose,
+ progressbarData);
+
+
+ XtManageChild(frame);
+ return frame;
+}
+
+double ui_progressbar_get(UiDouble *d) {
+ UiProgressBar *pb = d->obj;
+ d->value = pb->value;
+ return d->value;
+}
+
+void ui_progressbar_set(UiDouble *d, double value) {
+ UiProgressBar *pb = d->obj;
+ d->value = value;
+ pb->value = value;
+ ui_progressbar_expose(pb->widget, pb, NULL);
+}
+
+
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {
+ Arg xargs[16];
+ int n = 0;
+
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ UI_APPLY_LAYOUT(ctn->layout, args);
+
+ Widget parent = ctn->prepare(ctn, xargs, &n);
+
+ XmString label = XmStringCreateSimple("");
+ XtSetArg(xargs[n], XmNlabelString, label); n++;
+ XtSetArg(xargs[n], XmNalignment, XmALIGNMENT_END); n++;
+ XtSetArg(xargs[n], gridMinWidth, 40); n++;
+
+ char *name = args.name ? (char*)args.name : "progresss_spinner";
+ Widget w = XmCreateLabel(parent, name, xargs, n);
+ XtManageChild(w);
+ ctn->add(ctn, w);
+
+ UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER);
+ if(var) {
+ UiInteger *value = var->value;
+ value->obj = w;
+ value->get = ui_progressspinner_get;
+ value->set = ui_progressspinner_set;
+
+ if(value->value) {
+ ui_progressspinner_set(value, 1);
+ }
+ }
+
+
+ XmStringFree(label);
+ return w;
+}
+
+int64_t ui_progressspinner_get(UiInteger *i) {
+ return i->value;
+}
+
+void ui_progressspinner_set(UiInteger *i, int64_t value) {
+ Widget w = i->obj;
+ XmString label;
+ if(value) {
+ char str[4];
+ snprintf(str, 4, "%c", 150);
+ label = XmStringCreateSimple(str);
+ } else {
+ label = XmStringCreateSimple("");
+ }
+ XtVaSetValues(w, XmNlabelString, label, NULL);
+ XmStringFree(label);
+ i->value = value;
+}
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
#ifndef LABEL_H
#define LABEL_H
+#include "../ui/display.h"
+#include "../common/context.h"
+
#ifdef __cplusplus
extern "C" {
#endif
+typedef struct UiProgressBar {
+ Widget widget;
+ GC gc;
+ UiVar *var;
+ double min;
+ double max;
+ double value;
+ Pixel color;
+} UiProgressBar;
+
+double ui_progressbar_get(UiDouble *d);
+void ui_progressbar_set(UiDouble *d, double value);
+int64_t ui_progressspinner_get(UiInteger *i);
+void ui_progressspinner_set(UiInteger *i, int64_t value);
#ifdef __cplusplus
}
#include "list.h"
#include "../common/object.h"
+UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) {
+ Arg xargs[16];
+ int n = 0;
+
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ UI_APPLY_LAYOUT(ctn->layout, args);
+
+ if(args.multiselection) {
+ XtSetArg(xargs[n], XmNselectionPolicy, XmEXTENDED_SELECT); n++;
+ } else {
+ XtSetArg(xargs[n], XmNselectionPolicy, XmSINGLE_SELECT); n++;
+ }
+
+ char *name = args.name ? (char*)args.name : "listview";
+ Widget parent = ctn->prepare(ctn, xargs, &n);
+ Widget widget = XmCreateScrolledList(parent, name, xargs, n);
+ XtManageChild(widget);
+
+ UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ UiListView *listview = malloc(sizeof(UiListView));
+ memset(listview, 0, sizeof(UiListView));
+ listview->obj = obj;
+ listview->widget = widget;
+ listview->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+ listview->var = var;
+ listview->onactivate = args.onactivate;
+ listview->onactivatedata = args.onactivatedata;
+ listview->onselection = args.onselection;
+ listview->onselectiondata = args.onselectiondata;
+
+ if(var) {
+ UiList *list = var->value;
+ list->obj = listview;
+ list->update = ui_listview_update;
+ list->getselection = ui_listview_getselection;
+ list->setselection = ui_listview_setselection;
+ ui_listview_update(list, 0);
+ }
+
+ XtAddCallback(
+ widget,
+ XmNdestroyCallback,
+ (XtCallbackProc)ui_listview_destroy,
+ listview);
+
+ XtAddCallback(
+ widget,
+ XmNdefaultActionCallback,
+ (XtCallbackProc)ui_listview_activate,
+ listview);
+ XtAddCallback(
+ widget,
+ XmNextendedSelectionCallback,
+ (XtCallbackProc)ui_listview_selection,
+ listview);
+ XtAddCallback(
+ widget,
+ XmNsingleSelectionCallback,
+ (XtCallbackProc)ui_listview_selection,
+ listview);
+
+ return widget;
+}
+
+void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d) {
+ // TODO
+}
+
+static void list_callback(UiObject *obj, UiListSelection sel, ui_callback callback, void *userdata) {
+ UiEvent event;
+ event.obj = obj;
+ event.window = obj->window;
+ event.document = obj->ctx->document;
+ event.eventdata = &sel;
+ event.intval = sel.count > 0 ? sel.rows[0] : -1;
+ callback(&event, userdata);
+}
+
+static void listview_save_selection(UiListView *listview, XmListCallbackStruct *cb) {
+ UiListSelection sel = { cb->selected_item_count, NULL };
+ if(sel.count > 0) {
+ sel.rows = calloc(sel.count, sizeof(int));
+ for(int i=0;i<sel.count;i++) {
+ sel.rows[i] = cb->selected_item_positions[i]-1;
+ }
+ }
+ free(listview->current_selection.rows);
+ listview->current_selection = sel;
+}
+
+void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb) {
+ listview_save_selection(listview, cb);
+ if(listview->onactivate) {
+ list_callback(listview->obj, listview->current_selection, listview->onactivate, listview->onactivatedata);
+ }
+}
+
+void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb) {
+ listview_save_selection(listview, cb);
+ if(listview->onselection) {
+ list_callback(listview->obj, listview->current_selection, listview->onselection, listview->onselectiondata);
+ }
+}
+
+static XmStringTable create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count) {
+ int num = list->count(list);
+ XmStringTable items = (XmStringTable)XtMalloc(num * sizeof(XmString));
+ void *data = list->first(list);
+ for(int i=0;i<num;i++) {
+ char *s = getvalue(data, 0);
+ items[i] = XmStringCreateLocalized(s ? s : "");
+ data = list->next(list);
+ }
+
+ *count = num;
+ return items;
+}
+
+void ui_listview_update(UiList *list, int i) {
+ UiListView *listview = list->obj;
+
+ int count;
+ XmStringTable items = create_stringlist(
+ list,
+ listview->getvalue,
+ &count);
+
+ XtVaSetValues(
+ listview->widget,
+ XmNitems, count == 0 ? NULL : items,
+ XmNitemCount,
+ count,
+ NULL);
+
+ for (int i=0;i<count;i++) {
+ XmStringFree(items[i]);
+ }
+ XtFree((char *)items);
+}
+
+UiListSelection ui_listview_getselection(UiList *list) {
+ UiListView *listview = list->obj;
+ UiListSelection sel = { listview->current_selection.count, NULL };
+ if(sel.count > 0) {
+ sel.rows = calloc(sel.count, sizeof(int));
+ memcpy(sel.rows, listview->current_selection.rows, sel.count*sizeof(int));
+ }
+ return sel;
+}
+
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+ UiListView *listview = list->obj;
+ XmListDeselectAllItems(listview->widget);
+ for(int i=0;i<selection.count;i++) {
+ XmListSelectPos(listview->widget, selection.rows[i]+1, False);
+ }
+}
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+ return column == 0 ? elm : NULL;
+}
extern "C" {
#endif
+typedef struct UiListView {
+ UiObject *obj;
+ Widget widget;
+ UiVar *var;
+ UiModel* model;
+ ui_getvaluefunc getvalue;
+ UiListSelection current_selection;
+
+ ui_callback onactivate;
+ void* onactivatedata;
+ ui_callback onselection;
+ void* onselectiondata;
+ ui_callback ondragstart;
+ void* ondragstartdata;
+ ui_callback ondragcomplete;
+ void* ondragcompletedata;
+ ui_callback ondrop;
+ void* ondropsdata;
+ UiBool multiselection;
+} UiListView;
+
+void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d);
+
+void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb);
+void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb);
+
+void ui_listview_update(UiList *list, int i);
+UiListSelection ui_listview_getselection(UiList *list);
+void ui_listview_setselection(UiList *list, UiListSelection selection);
+
+void* ui_strmodel_getvalue(void *elm, int column);
#ifdef __cplusplus
}
#include "container.h"
#include "../common/context.h"
#include "../common/menu.h"
+#include "../common/types.h"
#include "../ui/window.h"
#include <cx/linked_list.h>
}
void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) {
+ UiMenuItemList *il = (UiMenuItemList*)item;
+ const CxAllocator *a = obj->ctx->allocator;
+ UiActiveMenuItemList *ls = cxMalloc(
+ a,
+ sizeof(UiActiveMenuItemList));
+ ls->object = obj;
+ ls->menu = p;
+ ls->index = i;
+ ls->oldcount = 0;
+ ls->getvalue = il->getvalue;
+ ls->callback = il->callback;
+ ls->userdata = il->userdata;
+ ls->addseparator = il->addseparator;
+
+ ls->var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); //uic_widget_var(obj->ctx, obj->ctx, NULL, il->varname, UI_VAR_LIST);
+ UiList *list = ls->var->value;
+
+ UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls);
+ list->observers = ui_obsvlist_add(list->observers, observer);
+ uic_list_register_observer_destructor(obj->ctx, list, observer);
+
+ ui_update_menuitem_list(NULL, ls);
+}
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {
+ XmString s = NULL;
+ Arg args[4];
+ int n;
+
+ UiList *ls;
+ if(list->var && list->var->value) {
+ ls = list->var->value;
+ } else {
+ return;
+ }
+
+ if(list->oldcount > 0) {
+ Widget *children;
+ int nc;
+
+ XtVaGetValues(
+ list->menu,
+ XmNchildren,
+ &children,
+ XmNnumChildren,
+ &nc,
+ NULL);
+
+ for(int i=0;i<list->oldcount;i++) {
+ XtDestroyWidget(children[list->index + i]);
+ }
+ }
+
+ void* elm = ui_list_first(ls);
+ int i = 0;
+ if(elm && list->addseparator) {
+ XtSetArg(args[0], XmNpositionIndex, list->index);
+ Widget s = XmCreateSeparatorGadget(list->menu, "menuseparator", args, 1);
+ XtManageChild(s);
+ i++;
+ }
+
+ ui_getvaluefunc getvalue = list->getvalue;
+ int pos = list->index;
+ while(elm) {
+ n = 0;
+ char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+ if(label) {
+ s = XmStringCreateLocalized(label);
+ XtSetArg(args[n], XmNlabelString, s); n++;
+ }
+ XtSetArg(args[n], XmNpositionIndex, pos+i); n++;
+
+ Widget mitem = XtCreateManagedWidget(
+ "menubutton",
+ xmPushButtonWidgetClass,
+ list->menu,
+ args,
+ n);
+ if(s) {
+ XmStringFree(s);
+ }
+
+ if(list->callback) {
+ UiEventData *eventdata = malloc(sizeof(UiEventData));
+ eventdata->callback = list->callback;
+ eventdata->userdata = list->userdata;
+ eventdata->obj = list->object;
+ eventdata->value = 0;
+ XtAddCallback(
+ mitem,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_push_button_callback,
+ eventdata);
+ XtAddCallback(
+ mitem,
+ XmNdestroyCallback,
+ (XtCallbackProc)ui_destroy_eventdata,
+ eventdata);
+ }
+
+ elm = ui_list_next(ls);
+ i++;
+ }
+
+ list->oldcount = i;
}
#include "../ui/menu.h"
#include "../common/menu.h"
+#include "../common/context.h"
#ifdef __cplusplus
extern "C" {
#endif
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+struct UiActiveMenuItemList {
+ UiObject *object;
+ Widget menu;
+ int index;
+ int oldcount;
+ UiVar *var;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+ bool addseparator;
+};
+
typedef void(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*);
void ui_create_menubar(UiObject *obj, Widget window);
void add_checkitemnv_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj);
void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj);
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
+
#ifdef __cplusplus
}
#endif
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
#include <cx/string.h>
+/* ------------------------------ Text Area ------------------------------ */
+
+UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
+ Arg xargs[16];
+ int n = 0;
+
+ XtSetArg(xargs[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
+
+ UiContainerPrivate *ctn = ui_obj_container(obj);
+ UI_APPLY_LAYOUT(ctn->layout, args);
+
+ Widget parent = ctn->prepare(ctn, xargs, &n);
+ char *name = args.name ? (char*)args.name : "textarea";
+ Widget widget = XmCreateScrolledText(parent, name, xargs, n);
+ XtManageChild(widget);
+
+ UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_TEXT);
+
+ UiTextArea *textarea = malloc(sizeof(UiTextArea));
+ memset(textarea, 0, sizeof(UiTextArea));
+ textarea->obj = obj;
+ textarea->var = var;
+
+ if(var) {
+ UiText *value = var->value;
+ if(value->value.ptr) {
+ XmTextSetString(widget, value->value.ptr);
+ value->value.free(value->value.ptr);
+ value->value.ptr = NULL;
+ }
+
+ value->set = ui_textarea_set;
+ value->get = ui_textarea_get;
+ value->getsubstr = ui_textarea_getsubstr;
+ value->insert = ui_textarea_insert;
+ value->setposition = ui_textarea_setposition;
+ value->position = ui_textarea_position;
+ value->selection = ui_textarea_selection;
+ value->length = ui_textarea_length;
+ value->value.ptr = NULL;
+ value->obj = widget;
+
+ if(!value->undomgr) {
+ value->undomgr = ui_create_undomgr();
+ }
+
+ XtAddCallback(
+ widget,
+ XmNmodifyVerifyCallback,
+ (XtCallbackProc)ui_text_modify_callback,
+ var);
+ }
+
+ return widget;
+}
+
+char* ui_textarea_get(UiText *text) {
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ char *str = XmTextGetString(text->obj);
+ text->value.ptr = str;
+ text->value.free = (ui_freefunc)XtFree;
+ return str;
+}
+
+void ui_textarea_set(UiText *text, const char *str) {
+ XmTextSetString(text->obj, (char*)str);
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ text->value.ptr = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ int length = end - begin;
+ char *str = XtMalloc(length + 1);
+ XmTextGetSubstring(text->obj, begin, length, length + 1, str);
+ text->value.ptr = str;
+ text->value.free = (ui_freefunc)XtFree;
+ return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+ text->value.ptr = NULL;
+ XmTextInsert(text->obj, pos, str);
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+ XmTextSetInsertionPosition(text->obj, pos);
+}
+
+int ui_textarea_position(UiText *text) {
+ long begin;
+ long end;
+ XmTextGetSelectionPosition(text->obj, &begin, &end);
+ text->pos = begin;
+ return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+ XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
+}
+
+int ui_textarea_length(UiText *text) {
+ return (int)XmTextGetLastPosition(text->obj);
+}
+
+
+
+UiUndoMgr* ui_create_undomgr() {
+ UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+ mgr->begin = NULL;
+ mgr->end = NULL;
+ mgr->cur = NULL;
+ mgr->length = 0;
+ mgr->event = 1;
+ return mgr;
+}
+
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+ UiTextBufOp *op = mgr->begin;
+ while(op) {
+ UiTextBufOp *nextOp = op->next;
+ if(op->text) {
+ free(op->text);
+ }
+ free(op);
+ op = nextOp;
+ }
+ free(mgr);
+}
+
+void ui_text_selection_callback(
+ Widget widget,
+ UiTextArea *textarea,
+ XtPointer data)
+{
+ long left = 0;
+ long right = 0;
+ XmTextGetSelectionPosition(widget, &left, &right);
+ int sel = left < right ? 1 : 0;
+ if(sel != textarea->last_selection_state) {
+ if(sel) {
+ ui_set_group(textarea->obj->ctx, UI_GROUP_SELECTION);
+ } else {
+ ui_unset_group(textarea->obj->ctx, UI_GROUP_SELECTION);
+ }
+ }
+ textarea->last_selection_state = sel;
+}
+
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
+ UiText *value = var->value;
+ if(!value->obj) {
+ // TODO: bug, fix
+ return;
+ }
+ if(!value->undomgr) {
+ value->undomgr = ui_create_undomgr();
+ }
+
+ XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
+ int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
+ UiUndoMgr *mgr = value->undomgr;
+ if(!mgr->event) {
+ return;
+ }
+
+ char *text = txv->text->ptr;
+ int length = txv->text->length;
+
+ if(mgr->cur) {
+ UiTextBufOp *elm = mgr->cur->next;
+ if(elm) {
+ mgr->cur->next = NULL;
+ mgr->end = mgr->cur;
+ while(elm) {
+ elm->prev = NULL;
+ UiTextBufOp *next = elm->next;
+ ui_free_textbuf_op(elm);
+ elm = next;
+ }
+ }
+
+ UiTextBufOp *last_op = mgr->cur;
+ if(
+ last_op->type == UI_TEXTBUF_INSERT &&
+ ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
+ {
+ // append text to last op
+ int ln = last_op->len;
+ char *newtext = malloc(ln + length + 1);
+ memcpy(newtext, last_op->text, ln);
+ memcpy(newtext+ln, text, length);
+ newtext[ln+length] = '\0';
+
+ last_op->text = newtext;
+ last_op->len = ln + length;
+ last_op->end += length;
+
+ return;
+ }
+ }
+
+ char *str;
+ if(type == UI_TEXTBUF_INSERT) {
+ str = malloc(length + 1);
+ memcpy(str, text, length);
+ str[length] = 0;
+ } else {
+ length = txv->endPos - txv->startPos;
+ str = malloc(length + 1);
+ XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
+ }
+
+ UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+ op->prev = NULL;
+ op->next = NULL;
+ op->type = type;
+ op->start = txv->startPos;
+ op->end = txv->endPos + 1;
+ op->len = length;
+ op->text = str;
+
+ cx_linked_list_add(
+ (void**)&mgr->begin,
+ (void**)&mgr->end,
+ offsetof(UiTextBufOp, prev),
+ offsetof(UiTextBufOp, next),
+ op);
+
+ mgr->cur = op;
+}
+
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
+ // return 1 if oldstr + newstr are one word
+
+ int has_space = 0;
+ for(int i=0;i<oldlen;i++) {
+ if(oldstr[i] < 33) {
+ has_space = 1;
+ break;
+ }
+ }
+
+ for(int i=0;i<newlen;i++) {
+ if(has_space && newstr[i] > 32) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void ui_free_textbuf_op(UiTextBufOp *op) {
+ if(op->text) {
+ free(op->text);
+ }
+ free(op);
+}
+
+
+void ui_text_undo(UiText *value) {
+ UiUndoMgr *mgr = value->undomgr;
+
+ if(mgr->cur) {
+ UiTextBufOp *op = mgr->cur;
+ mgr->event = 0;
+ switch(op->type) {
+ case UI_TEXTBUF_INSERT: {
+ XmTextReplace(value->obj, op->start, op->end, "");
+ break;
+ }
+ case UI_TEXTBUF_DELETE: {
+ XmTextInsert(value->obj, op->start, op->text);
+ break;
+ }
+ }
+ mgr->event = 1;
+ mgr->cur = mgr->cur->prev;
+ }
+}
+
+void ui_text_redo(UiText *value) {
+ UiUndoMgr *mgr = value->undomgr;
+
+ UiTextBufOp *elm = NULL;
+ if(mgr->cur) {
+ if(mgr->cur->next) {
+ elm = mgr->cur->next;
+ }
+ } else if(mgr->begin) {
+ elm = mgr->begin;
+ }
+
+ if(elm) {
+ UiTextBufOp *op = elm;
+ mgr->event = 0;
+ switch(op->type) {
+ case UI_TEXTBUF_INSERT: {
+ XmTextInsert(value->obj, op->start, op->text);
+ break;
+ }
+ case UI_TEXTBUF_DELETE: {
+ XmTextReplace(value->obj, op->start, op->end, "");
+ break;
+ }
+ }
+ mgr->event = 1;
+ mgr->cur = elm;
+ }
+}
+
+
/* ------------------------------ Text Field ------------------------------ */
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2014 Olaf Wintermann. All rights reserved.
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
#ifdef __cplusplus
extern "C" {
#endif
+
+#define UI_TEXTBUF_INSERT 0
+#define UI_TEXTBUF_DELETE 1
+typedef struct UiTextBufOp UiTextBufOp;
+struct UiTextBufOp {
+ UiTextBufOp *prev;
+ UiTextBufOp *next;
+ int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
+ int start;
+ int end;
+ int len;
+ char *text;
+};
+
+typedef struct UiUndoMgr {
+ UiTextBufOp *begin;
+ UiTextBufOp *end;
+ UiTextBufOp *cur;
+ int length;
+ int event;
+} UiUndoMgr;
+
+typedef struct UiTextArea {
+ UiObject *obj;
+ UiVar *var;
+ int last_selection_state;
+} UiTextArea;
+
+char* ui_textarea_get(UiText *text);
+void ui_textarea_set(UiText *text, const char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+
+UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
+void ui_text_selection_callback(
+ Widget widget,
+ UiTextArea *textarea,
+ XtPointer data);
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data);
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
+void ui_free_textbuf_op(UiTextBufOp *op);
char* ui_textfield_get(UiString *str);
void ui_textfield_set(UiString *str, const char *value);
static String fallback[] = {
- //"*fontList: -dt-interface system-medium-r-normal-s*utf*:",
- "*text_area*renderTable: f1",
- "*f1*fontType: FONT_IS_XFT",
- "*f1*fontName: Monospace",
- "*f1*fontSize: 11",
+ //"*fontList: -dt-interface system-medium-r-normal-s*utf*:",
"*renderTable: rt",
"*rt*fontType: FONT_IS_XFT",
"*rt*fontName: Sans",
"*rt*fontSize: 11",
+ "*progresss_spinner*renderTable*fontType: FONT_IS_FONT",
+ "*progresss_spinner*renderTable*fontName: Cursor",
+
"*window_frame.shadowType: SHADOW_ETCHED_OUT",
"*window_frame.shadowThickness: 1",
"*togglebutton.shadowThickness: 1",
"*togglebutton.highlightThickness: 2",
+
+ "*ui_test.background: red",
NULL
};
4);
}
-void ui_destroy_eventdata(Widget w, XtPointer *data, XtPointer d) {
+void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d) {
free(data);
}
ui_callback callback;
void *userdata;
int value;
+ void *customdata;
} UiEventData;
typedef struct UiEventDataExt {
void ui_secondary_event_loop(int *loop);
void ui_window_dark_theme(Display *dp, Window window);
-void ui_destroy_eventdata(Widget w, XtPointer *data, XtPointer d);
+void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d);
void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) ;
void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups);
Widget frame = XmCreateFrame(window, "window_frame", args, n);
XtManageChild(frame);
- Widget vbox = XtCreateManagedWidget("window_vbox", gridClass, frame, NULL, 0);
+ Widget form = XmCreateForm(frame, "window_form", args, 0);
+ XtManageChild(form);
+
+ n = 0;
+ XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+ XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+ XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+ XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+ Widget vbox = XtCreateManagedWidget("window_vbox", gridClass, form, args, n);
UiContainerX *container = ui_box_container(obj, vbox, UI_BOX_VERTICAL);
uic_object_push_container(obj, container);
UI_HEADERBAR_ALTERNATIVE_BOX
} UiHeaderbarAlternative;
+typedef struct UiWidgetArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+} UiWidgetArgs;
+
typedef struct UiContainerArgs {
UiTri fill;
UiBool hexpand;
int spacing;
} UiSidebarArgs;
+typedef struct UiItemListContainerArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ int margin;
+ int spacing;
+
+ int sub_margin;
+ int sub_spacing;
+ int sub_columnspacing;
+ int sub_rowspacing;
+
+ UiList *value;
+ const char *varname;
+ /*
+ * void create_ui(UiObject *obj, int index, void *elm, void *userdata)
+ *
+ * UI constructor for each list element
+ *
+ * This callback is executed for each list element. A new UiObject is
+ * created for every item.
+ */
+ void (*create_ui)(UiObject *, int, void *, void *);
+ void *userdata;
+
+ /*
+ * ItemList container type
+ * Only UI_CONTAINER_VBOX or UI_CONTAINER_HBOX are supported
+ */
+ UiSubContainerType container;
+
+ /*
+ * item root container
+ */
+ UiSubContainerType subcontainer;
+} UiItemListContainerArgs;
struct UiContainerX {
void *container;
#define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
#define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_itemlist(obj, ...) ui_itemlist_create(obj, (UiItemListContainerArgs) { __VA_ARGS__} )
+
UIEXPORT void ui_end(UiObject *obj); // deprecated
UIEXPORT void ui_end_new(UiObject *obj); // TODO: rename to ui_end
UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args);
+UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args);
UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO
UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO
UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view);
+#ifdef UI_GTK
+typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
+#elif defined(UI_MOTIF)
+typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata, Widget parent, Arg *a, int n);
+#elif defined(UI_COCOA)
+typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs args, void *userdata);
+#endif
+UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args);
+
+#define ui_customwidget(obj, create_widget, userdata, ...) ui_customwidget_create(obj, create_widget, userdata, (UiWidgetArgs) { __VA_ARGS__ })
+
+
/* used for macro */
UIEXPORT void ui_container_begin_close(UiObject *obj);
UIEXPORT int ui_container_finish(UiObject *obj);
UiBool vfill;
int colspan;
int rowspan;
+ const char *name;
+ const char *style_class;
const char* label;
UiAlignment align;
int colspan;
int rowspan;
int width;
+ const char *name;
+ const char *style_class;
double min;
double max;
UiBool vfill;
int colspan;
int rowspan;
+ const char *name;
+ const char *style_class;
UiInteger* value;
const char* varname;
ui_getvaluefunc getvalue;
ui_callback onselect;
void* onselectdata;
+ UiBool addseparator;
} UiMenuItemListArgs;
#define ui_menu(label) for(ui_menu_create(label);ui_menu_is_open();ui_menu_close())
typedef void* UIMENU; // NSMenu*
#elif UI_GTK2 || UI_GTK3 || UI_GTK4
+#define UI_GTK
#include <gtk/gtk.h>
#define UIWIDGET GtkWidget*
void *userdata;
};
+typedef struct UiSubListEventData {
+ UiList *list;
+ int sublist_index;
+ int row_index;
+ void *row_data;
+ void *sublist_userdata;
+ void *event_data;
+} UiSubListEventData;
+
/*
* list item members must be filled by the sublist getvalue func
* all members must be allocated (by malloc, strdup, ...) the pointer