From 5f2e901a458d4d69beeb46735879389b37d238b5 Mon Sep 17 00:00:00 2001 From: Israel Date: Mon, 13 Oct 2025 09:53:43 -0600 Subject: [PATCH 001/361] Checkpoint: Switching to DM UI project. --- .gitignore | 1 + centrallix-lib/Makefile.in | 4 +- centrallix-lib/include/clusters.h | 93 + centrallix-lib/include/util.h | 109 +- centrallix-lib/include/xhash.h | 4 +- centrallix-lib/src/clusters.c | 989 +++++ centrallix-lib/src/util.c | 170 +- centrallix-lib/src/xhash.c | 70 + centrallix-os/cluster-schema.cluster | 176 + centrallix-os/file.cluster | 64 + centrallix-os/file2.cluster | 42 + centrallix-sysdoc/OSDriver_Authoring.md | 99 +- centrallix-sysdoc/string_comparison.md | 12 +- centrallix/Makefile.in | 3 + centrallix/centrallix.c | 2 +- centrallix/etc/types.cfg | 1 + centrallix/expression/exp_compiler.c | 17 +- centrallix/expression/exp_double_metaphone.c | 1517 ++++++++ centrallix/expression/exp_functions.c | 1819 ++++++++- centrallix/include/cxss/policy.h | 2 +- centrallix/include/expression.h | 1 + centrallix/include/stparse.h | 2 +- centrallix/osdrivers/objdrv_cluster.c | 3345 +++++++++++++++++ .../tests/test_expfn_double_metaphone_00.cmp | 140 + .../tests/test_expfn_double_metaphone_00.to | 161 + 25 files changed, 8751 insertions(+), 92 deletions(-) create mode 100644 centrallix-lib/include/clusters.h create mode 100644 centrallix-lib/src/clusters.c create mode 100644 centrallix-os/cluster-schema.cluster create mode 100644 centrallix-os/file.cluster create mode 100644 centrallix-os/file2.cluster create mode 100644 centrallix/expression/exp_double_metaphone.c create mode 100644 centrallix/osdrivers/objdrv_cluster.c create mode 100644 centrallix/tests/test_expfn_double_metaphone_00.cmp create mode 100644 centrallix/tests/test_expfn_double_metaphone_00.to diff --git a/.gitignore b/.gitignore index cbfe20f1d..bddd6b099 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ perf.data.old .idea/ .vscode/ centrallix-os/tmp/* +centrallix-os/datasets/ diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index a7197622b..20c57c11f 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -63,10 +63,10 @@ CFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PRO MTCFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PROFILE) $(COVERAGE) -g -O0 TCFLAGS=$(patsubst -DNDEBUG,,$(CFLAGS)) -XSTATICFILES=mtask.o mtlexer.o memstr.o xarray.o xhash.o xstring.o mtsession.o newmalloc.o xhashqueue.o bdqs_transport.o xhandle.o xringqueue.o cxsec.o smmalloc.o qprintf.o strtcpy.o util.o +XSTATICFILES=mtask.o mtlexer.o memstr.o xarray.o xhash.o xstring.o mtsession.o newmalloc.o xhashqueue.o bdqs_transport.o xhandle.o xringqueue.o cxsec.o smmalloc.o clusters.o qprintf.o strtcpy.o util.o STATICFILES=$(patsubst %,src/%,$(XSTATICFILES)) -XDYNAMICFILES=mtask.lo mtlexer.lo memstr.lo xarray.lo xhash.lo xstring.lo mtsession.lo newmalloc.lo xhashqueue.lo bdqs_transport.lo xhandle.lo xringqueue.lo cxsec.lo smmalloc.lo qprintf.lo strtcpy.lo util.lo +XDYNAMICFILES=mtask.lo mtlexer.lo memstr.lo xarray.lo xhash.lo xstring.lo mtsession.lo newmalloc.lo xhashqueue.lo bdqs_transport.lo xhandle.lo xringqueue.lo cxsec.lo smmalloc.lo clusters.o qprintf.lo strtcpy.lo util.lo DYNAMICFILES=$(patsubst %,src/%,$(XDYNAMICFILES)) INCLUDEFILES:=$(wildcard include/*.h) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h new file mode 100644 index 000000000..2605b4314 --- /dev/null +++ b/centrallix-lib/include/clusters.h @@ -0,0 +1,93 @@ + +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: lib_cluster.c */ +/* Author: Israel Fuller */ +/* Creation: September 29, 2025 */ +/* Description: Internal algorithms for the cluster object driver. */ +/* See centrallix-sysdoc/EAV_Pivot.md for more information. */ +/************************************************************************/ + +#include + +#ifdef CXLIB_INTERNAL +#include "xarray.h" +#else +#include "cxlib/xarray.h" +#endif + +#define CA_NUM_DIMS 251 /* aka. The vector table size. */ + +/** The character used to create a pair with the first and last characters of a string. **/ +#define CA_BOUNDARY_CHAR ('a' - 1) + +/** Types. **/ +typedef int* pVector; /* Sparse vector. */ +typedef double* pCentroid; /* Dense centroid. */ +#define pCentroidSize CA_NUM_DIMS * sizeof(double) + +/** Duplocate information. **/ +typedef struct + { + unsigned int id1; + unsigned int id2; + double similarity; + } + Dup, *pDup; + +pVector ca_build_vector(const char* str); +unsigned int ca_sparse_len(const pVector vector); +void ca_free_vector(pVector sparse_vector); +void ca_kmeans( + pVector* vectors, + const unsigned int num_vectors, + unsigned int* labels, + const unsigned int num_clusters, + const unsigned int max_iter, + const double improvement_threshold +); +pXArray ca_search( + pVector* vectors, + const unsigned int num_vectors, + const unsigned int* labels, + const double dupe_threshold +); +pXArray ca_lightning_search( + pVector* vectors, + const unsigned int num_vectors, + const double dupe_threshold +); +unsigned int ca_edit_dist( + const char* str1, + const char* str2, + const size_t str1_length, + const size_t str2_length +); +pXArray ca_phone_search( + char dataset[][10u], + const unsigned int dataset_size, + const double dupe_threshold +); +void ca_init(); diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index df4ba0d58..2b9d7b26f 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -21,14 +21,119 @@ extern "C" { #endif - int strtoi(const char *nptr, char **endptr, int base); unsigned int strtoui(const char *nptr, char **endptr, int base); + char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes); + void fprint_mem(FILE* out); + + typedef struct + { + double start, end; + } + Timer, *pTimer; + + pTimer timer_init(pTimer timer); + pTimer timer_new(void); + pTimer timer_start(pTimer timer); + pTimer timer_stop(pTimer timer); + double timer_get(pTimer timer); + void timer_de_init(pTimer timer); + void timer_free(pTimer timer); #ifdef __cplusplus } #endif -#endif /* UTILITY_H */ +#ifndef __cplusplus + +/** TODO: Greg, is the __typeof__ syntax from GCC a portability concern? **/ + +/*** @brief Returns the smaller of two values. + *** + *** @param a The first value. + *** @param b The second value. + *** @return The smaller of the two values. + *** + *** @note This macro uses GCC extensions to enusre type safety. + ***/ +#define min(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a < _b) ? _a : _b; \ + }) + +/*** @brief Returns the larger of two values. + *** + *** @param a The first value. + *** @param b The second value. + *** @return The larger of the two values. + *** + *** @note This macro uses GCC extensions to enusre type safety. + ***/ +#define max(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a > _b) ? _a : _b; \ + }) + +/** Error Handling. **/ +void fail(const char* function_name, int code); +/*** Helper function for compact error handling on library & system function calls. + *** Any non-zero value is treated as an error, exiting the program. + *** + *** @param result The result of the function we're checking. + *** @returns result + ***/ +#define check(result) \ + ({ \ + __typeof__ (result) _r = (result); \ + if (_r != 0) fail(#result, _r); \ + _r; \ + }) + +/*** Helper function for compact error handling on library & system function calls. + *** Any negative is treated as an error, exiting the program. + *** + *** @param result The result of the function we're checking. + *** @returns result + ***/ +#define check_neg(result) \ + ({ \ + __typeof__ (result) _r = (result); \ + if (_r < 0) fail(#result, _r); \ + _r; \ + }) + +/*** Helper function for compact error handling on library & system function calls. + *** Any value of -1 is treated as an error, exiting the program. + *** + *** @param result The result of the function we're checking. + *** @returns result + ***/ +#define check_strict(result) \ + ({ \ + __typeof__ (result) _r = (result); \ + if (_r == -1) fail(#result, _r); \ + _r; \ + }) + +/*** Helper function for compact error handling on library & system function calls. + *** Any null value is treated as an error, exiting the program. + *** + *** @param result The result of the function we're checking + *** @returns result + ***/ +#define check_ptr(result) \ + ({ \ + __typeof__ (result) _r = (result); \ + if (_r == NULL) fail(#result, 0); \ + _r; \ + }) + +#endif /* __cplusplus */ + +#endif /* UTILITY_H */ diff --git a/centrallix-lib/include/xhash.h b/centrallix-lib/include/xhash.h index 1b5d8459a..65b900570 100644 --- a/centrallix-lib/include/xhash.h +++ b/centrallix-lib/include/xhash.h @@ -1,7 +1,6 @@ #ifndef _XHASH_H #define _XHASH_H - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Base Library */ @@ -55,6 +54,7 @@ int xhAdd(pXHashTable this, char* key, char* data); int xhRemove(pXHashTable this, char* key); char* xhLookup(pXHashTable this, char* key); int xhClear(pXHashTable this, int (*free_fn)(), void* free_arg); +int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); +int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg); #endif /* _XHASH_H */ - diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c new file mode 100644 index 000000000..4e41d449d --- /dev/null +++ b/centrallix-lib/src/clusters.c @@ -0,0 +1,989 @@ + +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: lib_cluster.c */ +/* Author: Israel Fuller */ +/* Creation: September 29, 2025 */ +/* Description: Internal algorithms for the cluster object driver. */ +/* See centrallix-sysdoc/EAV_Pivot.md for more information. */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clusters.h" +#include "newmalloc.h" +#include "util.h" +#include "xarray.h" + +/*** Gets the hash, representing a pair of ASCII characters, represented by unsigned ints. + *** Thank you to professor John Delano for this hashing algorithm. + *** + *** @param num1 The first character in the pair. + *** @param num1 The second character in the pair. + *** @returns The resulting hash. + ***/ +static unsigned int hash_char_pair(const unsigned int num1, const unsigned int num2) + { + if (num1 == CA_BOUNDARY_CHAR && num2 == CA_BOUNDARY_CHAR) + { + // fprintf(stderr, + // "hash_char_pair(%u, %u) - Warning: Pair of boundary characters.\n", + // num1, num2 + // ); + } + const double sum = (num1 * num1 * num1) + (num2 * num2 * num2); + const double scale = ((double)num1 + 1.0) / ((double)num2 + 1.0); + const unsigned int hash = (unsigned int)round(sum * scale) - 1u; + return hash % CA_NUM_DIMS; + } + +/*** Builds a vector using a string. + *** + *** Vectors are based on the frequencies of character pairs in the string. + *** Space characters and punctuation characters (see code for list) are ignored, + *** and all characters are converted to lowercase. Character 96, which is just + *** before 'a' in the ASCII table (and maps to '`') is used to make pairs on the + *** start and end of strings. The only supported characters for the passed char* + *** are spaces, punctuation, uppercase and lowercase letters, and numbers. + *** + *** This results in the following modified ASCII table: + *** ```csv + *** #, char, #, char, #, char + *** 97, a, 109, m, 121, y + *** 98, b, 110, n, 122, z + *** 99, c, 111, o, 123, 0 + *** 100, d, 112, p, 124, 1 + *** 101, e, 113, q, 125, 2 + *** 102, f, 114, r, 126, 3 + *** 103, g, 115, s, 127, 4 + *** 104, h, 116, t, 128, 5 + *** 105, i, 117, u, 129, 6 + *** 106, j, 118, v, 130, 7 + *** 107, k, 119, w, 131, 8 + *** 108, l, 120, x, 132, 9 + *** ``` + *** Thus, any number from 96 (the start/end character) to 132 ('9') is a valid + *** input to get_char_pair_hash(). + *** + *** After hashing each character pair, we add some number from 1 to 13 to the + *** coresponding dimention. However, for most names, this results in a lot of + *** zeros and a FEW positive numbers. Thus, after creating the dense vector, + *** we convert it to a sparse vector in which a negative number replaces a run + *** of that many zeros. Consider the following example: + *** + *** Dense pVector: `[1,0,0,0,3,0]` + *** + *** Sparse pVector: `[1,-3,3,-1]` + *** + *** Using these sparse vectors greatly reduces the required memory and gives + *** aproximately an x5 boost to performance when traversing vectors, at the + *** cost of more algorithmically complex code. + *** + *** @param str The string to be divided into pairs and hashed to make the vector. + *** @returns The sparse vector built using the hashed character pairs. + ***/ +pVector ca_build_vector(const char* str) + { + /** Allocate space for a dense vector. **/ + unsigned int dense_vector[CA_NUM_DIMS] = {0u}; + + /** j is the former character, i is the latter. **/ + const unsigned int num_chars = (unsigned int)strlen(str); + for (unsigned int j = 65535u, i = 0u; i <= num_chars; i++) + { + /** isspace: space, \n, \v, \f, \r **/ + if (isspace(str[i])) continue; + + /** ispunct: !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ **/ + if (ispunct(str[i]) && str[i] != CA_BOUNDARY_CHAR) continue; + + /*** iscntrl (0-8): NULL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS + *** (14-31): SO, SI, DLE, DC1-4, NAK, SYN, ETB, CAN EM, + *** SUB, ESC, FS, GS, RS, US + ***/ + if (iscntrl(str[i]) && i != num_chars) + { + fprintf(stderr, + "ca_build_vector(%s) - Warning: Skipping unknown character #%u.\n", + str, (unsigned int)str[i] + ); + continue; + } + + /** First and last character should fall one before 'a' in the ASCII table. **/ + unsigned int temp1 = (j == 65535u) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[j]); + unsigned int temp2 = (i == num_chars) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[i]); + + /** Shift numbers to the end of the lowercase letters. **/ + if ('0' <= temp1 && temp1 <= '9') temp1 += 75u; + if ('0' <= temp2 && temp2 <= '9') temp2 += 75u; + + /** Hash the character pair into an index (dimension). **/ + /** Note that temp will be between 97 ('a') and 132 ('9'). **/ + unsigned int dim = hash_char_pair(temp1, temp2); + + /** Increment the dimension of the dense vector by a number from 1 to 13. **/ + dense_vector[dim] += (temp1 + temp2) % 13u + 1u; + + j = i; + } + + /** Count how much space is needed for a sparse vector. **/ + bool zero_prev = false; + size_t size = 0u; + for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) + { + if (dense_vector[dim] == 0u) + { + size += (zero_prev) ? 0u : 1u; + zero_prev = true; + } + else + { + size++; + zero_prev = false; + } + } + + /*** Check compression size. + *** If this check fails, I doubt anything will break. However, the longest + *** word I know (supercalifragilisticexpialidocious) has only 35 character + *** pairs, so it shouldn't reach half this size (and it'd be even shorter + *** if the hash generates at least one collision). + *** + *** Bad vector compression will result in degraded performace and increased + *** memory usage. This indicates a likely bug in the code. Thus, if this + *** warning is ever generated, it is definitely worth investigating. + ***/ + const size_t expected_max_size = 64u; + if (size > expected_max_size) + { + fprintf(stderr, + "cli_build_vector(%s) - Warning: Sparse vector larger than expected.\n" + " > Size: %lu\n" + " > #Dims: %u\n", + str, + size, + CA_NUM_DIMS + ); + } + + /** Allocate space for sparse vector. **/ + const size_t sparse_vector_size = size * sizeof(int); + pVector sparse_vector = (pVector)nmSysMalloc(sparse_vector_size); + if (sparse_vector == NULL) + { + fprintf(stderr, + "cli_build_vector(%s) - nmSysMalloc(%lu) failed.\n", + str, sparse_vector_size + ); + return NULL; + } + + /** Convert the dense vector above to a sparse vector. **/ + unsigned int j = 0u, sparse_idx = 0u; + while (j < CA_NUM_DIMS) + { + if (dense_vector[j] == 0u) + { + /*** Count and store consecutive zeros, except the first one, + *** which we already know is zero. + ***/ + unsigned int zero_count = 1u; + j++; + while (j < CA_NUM_DIMS && dense_vector[j] == 0u) + { + zero_count++; + j++; + } + sparse_vector[sparse_idx++] = (int)-zero_count; + } + else + { + /** Store the value. **/ + sparse_vector[sparse_idx++] = (int)dense_vector[j++]; + } + } + + return sparse_vector; + } + +/*** Free memory allocated to store a sparse vector. + *** + *** @param sparse_vector The sparse vector being freed. + ***/ +void ca_free_vector(pVector sparse_vector) + { + nmSysFree(sparse_vector); + } + +/*** Compute the magnitude of a sparsely allocated vector. + *** + *** @param vector The vector. + *** @returns The computed magnitude. + ***/ +static double magnitude_sparse(const pVector vector) + { + unsigned int magnitude = 0u; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = vector[i++]; + + /** Negative val represents -val 0s in the array, so skip that many values. **/ + if (val < 0) dim += (unsigned)(-val); + + /** We have a param_value, so square it and add it to the magnitude. **/ + else { magnitude += (unsigned)(val * val); dim++; } + } + return sqrt((double)magnitude); + } + +/*** Compute the length of a sparsely allocated vector. + *** + *** @param vector The vector. + *** @returns The computed length. + ***/ +unsigned int ca_sparse_len(const pVector vector) + { + unsigned int i = 0u; + for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = vector[i++]; + + /** Negative val represents -val 0s in the array, so skip that many values. **/ + if (val < 0) dim += (unsigned)(-val); + + /** We have a param_value, but we don't need to do anything with it. **/ + else dim++; + } + return i; + } + +/*** Compute the magnitude of a densely allocated centroid. + *** + *** @param centroid The centroid. + *** @returns The computed magnitude. + ***/ +static double magnitude_dense(const pCentroid centroid) + { + double magnitude = 0.0; + for (int i = 0; i < CA_NUM_DIMS; i++) + magnitude += centroid[i] * centroid[i]; + return sqrt(magnitude); + } + +/*** Parse a token from a sparsely allocated vector and write the param_value and + *** number of remaining values to the passed locations. + *** + *** @param token The sparse vector token being parsed. + *** @param remaining The location to save the remaining number of characters. + *** @param param_value The location to save the param_value of the token. + ***/ +static void parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) + { + if (token < 0) + { + /** This run contains -token zeros. **/ + *remaining = (unsigned)(-token); + *param_value = 0u; + } + else + { + /** This run contains one param_value. **/ + *remaining = 1u; + *param_value = (unsigned)(token); + } + } + +/*** Calculate the similarity on sparcely allocated vectors. Comparing + *** any string to an empty string should always return 0.5 (untested). + *** + *** @param v1 Sparse vector #1. + *** @param v2 Sparse vector #2. + *** @returns Similarity between 0 and 1 where + *** 1 indicates identical and + *** 0 indicates completely different. + ***/ +static double sparse_similarity(const pVector v1, const pVector v2) + { + /** Calculate dot product. **/ + unsigned int vec1_remaining = 0u, vec2_remaining = 0u; + unsigned int dim = 0u, i1 = 0u, i2 = 0u, dot_product = 0u; + while (dim < CA_NUM_DIMS) + { + unsigned int val1 = 0u, val2 = 0u; + if (vec1_remaining == 0u) parse_vector_token(v1[i1++], &vec1_remaining, &val1); + if (vec2_remaining == 0u) parse_vector_token(v2[i2++], &vec2_remaining, &val2); + + /*** Accumulate the dot_product. If either vector is 0 here, + *** the total is 0 and this statement does nothing. + ***/ + dot_product += val1 * val2; + + /** Consume overlap from both runs. **/ + unsigned int overlap = min(vec1_remaining, vec2_remaining); + vec1_remaining -= overlap; + vec2_remaining -= overlap; + dim += overlap; + } + + /** Optional optimization to speed up nonsimilar vectors. **/ + if (dot_product == 0u) return 0.0; + + /** Return the difference score. **/ + return (double)dot_product / (magnitude_sparse(v1) * magnitude_sparse(v2)); + } + +/*** Calculate the difference on sparcely allocated vectors. Comparing + *** any string to an empty string should always return 0.5 (untested). + *** + *** @param v1 Sparse vector #1. + *** @param v2 Sparse vector #2. + *** @returns Similarity between 0 and 1 where + *** 1 indicates completely different and + *** 0 indicates identical. + ***/ +#define sparse_dif(v1, v2) (1.0 - sparse_similarity(v1, v2)) + +/*** Calculate the similarity between a sparsely allocated vector + *** and a densely allocated centroid. Comparing any string to an + *** empty string should always return 0.5 (untested). + *** + *** @param v1 Sparse vector #1. + *** @param c1 Dense centroid #2. + *** @returns Similarity between 0 and 1 where + *** 1 indicates identical and + *** 0 indicates completely different. + ***/ +static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) + { + /** Calculate dot product. **/ + double dot_product = 0.0; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = v1[i++]; + + /** Negative val represents -val 0s in the array, so skip that many values. **/ + if (val < 0) dim += (unsigned)(-val); + + /** We have a param_value, so square it and add it to the magnitude. **/ + else dot_product += (double)val * c2[dim++]; + } + + /** Return the difference score. **/ + return dot_product / (magnitude_sparse(v1) * magnitude_dense(c2)); + } + +/*** Calculate the difference between a sparsely allocated vector + *** and a densely allocated centroid. Comparing any string to an + *** empty string should always return 0.5 (untested). + *** + *** @param v1 Sparse vector #1. + *** @param c1 Dense centroid #2. + *** @returns Difference between 0 and 1 where + *** 1 indicates completely different and + *** 0 indicates identical. + ***/ +#define sparse_dif_to_centroid(v1, c2) (1.0 - sparse_similarity_to_centroid(v1, c2)) + +/*** Calculate the average size of all clusters in a set of vectors. + *** + *** @param vectors The vectors of the dataset (allocated sparsely). + *** @param num_vectors The number of vectors in the dataset. + *** @param labels The clusters to which vectors are assigned. + *** @param centroids The locations of the centroids (allocated densely). + *** @param num_clusters The number of centroids (k). + *** @returns The average cluster size. + ***/ +static double get_cluster_size( + pVector* vectors, + const unsigned int num_vectors, + unsigned int* labels, + pCentroid* centroids, + const unsigned int num_clusters) + { + /** Could be up to around 1KB on the stack, but I think that's fine. **/ + double cluster_sums[num_clusters]; + unsigned int cluster_counts[num_clusters]; + for (unsigned int i = 0u; i < num_clusters; i++) + cluster_sums[i] = 0.0; + memset(cluster_counts, 0, sizeof(cluster_counts)); + + /** Sum the difference from each vector to its cluster centroid. **/ + for (unsigned int i = 0u; i < num_vectors; i++) + { + const unsigned int label = labels[i]; + cluster_sums[label] += sparse_dif_to_centroid(vectors[i], centroids[label]); + cluster_counts[label]++; + } + + /** Add up the average cluster size. **/ + double cluster_total = 0.0; + unsigned int num_valid_clusters = 0u; + for (unsigned int label = 0u; label < num_clusters; label++) + { + const unsigned int cluster_count = cluster_counts[label]; + if (cluster_count == 0u) continue; + + cluster_total += cluster_sums[label] / cluster_count; + num_valid_clusters++; + } + + /** Return average sizes. **/ + return cluster_total / num_valid_clusters; + } + +/*** Compute the param_value for `k` (number of clusters), given a dataset of with + *** a size of `n`. + *** + *** The following table shows data sizes vs.selected cluster size. In testing, + *** these numbers tended to givea good balance of accuracy and dulocates detected. + *** + *** ```csv + *** Data Size, Actual + *** 10k, 12 + *** 100k, 33 + *** 1M, 67 + *** 4M, 93 + *** ``` + *** + *** This function is not intended for datasets smaller than (`n < ~2000`). + *** These should be handled using complete search. + *** + *** LaTeX Notation: \log_{36}\left(n\right)^{3.1}-8 + *** + *** @param n The size of the dataset. + *** @returns k, the number of clusters to use. + *** + *** Complexity: `O(1)` + ***/ +unsigned int compute_k(const unsigned int n) + { + return (unsigned)max(2, pow(log(n) / log(36), 3.2) - 8); + } + +/*** Executes the k-means clustering algorithm. Selects NUM_CLUSTERS random + *** vectors as initial centroids. Then points are assigned to the nearest + *** centroid, after which centroids are moved to the center of their points. + *** + *** @param vectors The vectors to cluster. + *** @param num_vectors The number of vectors to cluster. + *** @param labels Stores the final cluster identities of the vectors after + *** clustering is completed. + *** @param centroids Stores the locations of the centroids used for the clusters + *** of the data. + *** @param iterations The number of iterations that actually executed is stored + *** here. Leave this NULL if you don't care. + *** @param max_iter The max number of iterations. + *** @param num_clusters The number of clusters to generate. + *** + *** @attention - Assumes: num_vectors is the length of vectors. + *** @attention - Assumes: num_clusters is the length of labels. + *** + *** @attention - Issue: At larger numbers of clustering iterations, some + *** clusters have a size of negative infinity. In this implementation, + *** the bug is mitigated by setting a small number of max iterations, + *** such as 16 instead of 100. + *** @attention - Issue: Clusters do not apear to improve much after the first + *** iteration, which puts the efficacy of the algorithm into question. This + *** may be due to the uneven density of a typical dataset. However, the + *** clusters still offer useful information. + *** + *** Complexity: + *** + *** - `O(kd + k + i*(k + n*(k+d) + kd))` + *** + *** - `O(kd + k + ik + ink + ind + ikd)` + *** + *** - `O(nk + nd)` + ***/ +void ca_kmeans( + pVector* vectors, + const unsigned int num_vectors, + unsigned int* labels, + const unsigned int num_clusters, + const unsigned int max_iter, + const double improvement_threshold) + { + /** Ensure labels is clean. **/ + memset(labels, 0, num_clusters * sizeof(unsigned int)); + + /** Allocate space to store centroids and new_centroids. **/ + /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ + pCentroid* centroids = (pCentroid*)nmMalloc(num_clusters * sizeof(pCentroid)); + if (centroids == NULL) + { + fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", num_clusters * sizeof(pCentroid)); + assert(false); + } + pCentroid* new_centroids = (pCentroid*)nmMalloc(num_clusters * sizeof(pCentroid)); + if (new_centroids == NULL) + { + fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", num_clusters * sizeof(pCentroid)); + assert(false); + } + for (unsigned int i = 0u; i < num_clusters; i++) + { + /** Malloc each centroid. **/ + centroids[i] = (pCentroid)nmMalloc(pCentroidSize); + if (centroids[i] == NULL) + { + fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", pCentroidSize); + assert(false); + } + memset(centroids[i], 0, pCentroidSize); + + /** Malloc each new centroid. **/ + new_centroids[i] = (pCentroid)nmMalloc(pCentroidSize); + if (new_centroids[i] == NULL) + { + fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", pCentroidSize); + assert(false); + } + memset(new_centroids[i], 0, pCentroidSize); + } + + /** Select random vectors to use as the initial centroids. **/ + srand(time(NULL)); + for (unsigned int i = 0u; i < num_clusters; i++) + { + // Pick a random vector. + const pVector vector = vectors[rand() % num_vectors]; + + // Sparse copy the vector to expand it into a densely allocated centroid. + pCentroid centroid = centroids[i]; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int token = vector[i++]; + if (token > 0) centroid[dim++] = (double)token; + else for (unsigned int j = 0u; j < -token; j++) centroid[dim++] = 0.0; + } + } + + /** Main kmeans loop. **/ + double old_average_cluster_size = 1.0; + unsigned int cluster_counts[num_clusters]; + for (unsigned int iter = 0u; iter < max_iter; iter++) + { + bool changed = false; + + /** Reset new centroids. **/ + for (unsigned int i = 0u; i < num_clusters; i++) + { + cluster_counts[i] = 0u; + for (unsigned int dim = 0; dim < CA_NUM_DIMS; dim++) + new_centroids[i][dim] = 0.0; + } + + /** Assign each point to the nearest centroid. **/ + for (unsigned int i = 0u; i < num_vectors; i++) + { + const pVector vector = vectors[i]; + double min_dist = DBL_MAX; + unsigned int best_centroid_label = 0u; + + // Find nearest centroid. + for (unsigned int j = 0u; j < num_clusters; j++) + { + const double dist = sparse_dif_to_centroid(vector, centroids[j]); + if (dist < min_dist) + { + min_dist = dist; + best_centroid_label = j; + } + } + + /** Update label to new centroid, if necessary. **/ + if (labels[i] != best_centroid_label) + { + labels[i] = best_centroid_label; + changed = true; + } + + /** Accumulate values for new centroid calculation. **/ + pCentroid best_centroid = new_centroids[best_centroid_label]; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = vector[i++]; + if (val < 0) dim += (unsigned)(-val); + else best_centroid[dim++] += (double)val; + } + cluster_counts[best_centroid_label]++; + } + + /** Stop if centroids didn't change. **/ + if (!changed) break; + + /** Update centroids. **/ + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (cluster_counts[i] == 0u) continue; + pCentroid centroid = centroids[i]; + const pCentroid new_centroid = new_centroids[i]; + const unsigned int cluster_count = cluster_counts[i]; + for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) + centroid[dim] = new_centroid[dim] / cluster_count; + } + + /** Is there enough improvement? **/ + const double average_cluster_size = get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters); + const double improvement = old_average_cluster_size - average_cluster_size; + if (improvement < improvement_threshold) break; + old_average_cluster_size = average_cluster_size; + } + + /** Clean up. **/ + for (unsigned int i = 0u; i < num_clusters; i++) + { + nmFree(centroids[i], pCentroidSize); + nmFree(new_centroids[i], pCentroidSize); + } + nmFree(centroids, num_clusters * sizeof(pCentroid)); + nmFree(new_centroids, num_clusters * sizeof(pCentroid)); + } + +pXArray ca_search( + pVector* vectors, + const unsigned int num_vectors, + const unsigned int* labels, + const double dupe_threshold) + { + /** Allocate space for dups. **/ + pXArray dups = xaNew(num_vectors); + if (dups == NULL) + { + fprintf(stderr, "ca_search() - xaNew(%u) failed.\n", num_vectors); + return NULL; + } + + unsigned int a = 0, b = 0, c = 0, d = 0; + for (unsigned int i = 0u; i < num_vectors; i++) + { + const pVector v1 = vectors[i]; + const unsigned int label = labels[i]; + for (unsigned int j = i + 1u; j < num_vectors; j++) + { + if (b++ % 100 == 0) printf("."); + if (labels[j] != label) continue; + if (c++ % 100 == 0) printf(":"); + const pVector v2 = vectors[j]; + const double similarity = sparse_similarity(v1, v2); + if (similarity > dupe_threshold) /* Dup found! */ + { + Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); + if (dup == NULL) + { + fprintf(stderr, + "ca_search() - nmMalloc(%lu) failed.\n", + sizeof(Dup) + ); + goto err_free_dups; + } + + dup->id1 = i; + dup->id2 = j; + dup->similarity = similarity; + xaAddItem(dups, (void*)dup); + if (d++ % 4 == 0) printf("!"); + } + } + if (a++ % 4 == 0) printf("\n"); + } + + return dups; + + /** Free dups. **/ + err_free_dups:; + const size_t num_dups = dups->nItems; + for (unsigned int i = 0u; i < num_dups; i++) + { + nmFree(dups->Items[i], sizeof(Dup)); + dups->Items[i] = NULL; + } + xaDeInit(dups); + return NULL; + } + +/*** Runs complete search to find duplocates if `num_vectors < MAX_COMPLETE_SEARCH` + *** and runs a search using k-means clustering on larger amounts of data. + *** + *** @param vectors Array of precomputed frequency vectors for all dataset strings. + *** @param num_vectors The number of vectors to be scanned. + *** @param dupe_threshold The similarity threshold, below which dups are ignored. + *** @returns The duplicates in pDup structs. + ***/ +pXArray ca_lightning_search(pVector* vectors, const unsigned int num_vectors, const double dupe_threshold) + { + /** Allocate space for dups. **/ + const size_t guess_size = num_vectors * 2u; + pXArray dups = xaNew(guess_size); + if (dups == NULL) + { + fprintf(stderr, "ca_lightning_search() - xaNew(%lu) failed.\n", guess_size); + return NULL; + } + + /** Descide which algorithm to use. **/ + if (num_vectors <= 50 * 1000) + { /** Do a complete search. **/ + for (unsigned int i = 0u; i < num_vectors; i++) + { + const pVector v1 = vectors[i]; + for (unsigned int j = i + 1u; j < num_vectors; j++) + { + const pVector v2 = vectors[j]; + const double similarity = sparse_similarity(v1, v2); + if (similarity > dupe_threshold) // Dup found! + { + Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); + if (dup == NULL) + { + fprintf(stderr, "ca_lightning_search() - nmMalloc(%lu) failed.\n", sizeof(Dup)); + goto err_free_dups; + } + + dup->id1 = i; + dup->id2 = j; + dup->similarity = similarity; + xaAddItem(dups, (void*)dup); + } + } + } + } + else + { /** Do a k-means search. **/ + /** Define constants for the algorithm. **/ + const unsigned int max_iter = 64u; /** Hardcode value because idk. **/ + const unsigned int num_clusters = compute_k(num_vectors); + + /** Allocate static memory for finding clusters. **/ + unsigned int labels[num_vectors]; + memset(labels, 0u, sizeof(labels)); + + /** Execute kmeans clustering. **/ + ca_kmeans(vectors, num_vectors, labels, num_clusters, max_iter, 0.0002); + + /** Find duplocates in clusters. **/ + for (unsigned int i = 0u; i < num_vectors; i++) + { + const pVector v1 = vectors[i]; + const unsigned int label = labels[i]; + for (unsigned int j = i + 1u; j < num_vectors; j++) + { + if (labels[j] != label) continue; + const pVector v2 = vectors[j]; + const double similarity = sparse_similarity(v1, v2); + if (similarity > dupe_threshold) /* Dup found! */ + { + Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); + if (dup == NULL) + { + fprintf(stderr, + "ca_lightning_search() - nmMalloc(%lu) failed.\n", + sizeof(Dup) + ); + goto err_free_dups; + } + + dup->id1 = i; + dup->id2 = j; + dup->similarity = similarity; + xaAddItem(dups, (void*)dup); + } + } + } + } + + /** Done **/ + return dups; + + /** Free dups. **/ + err_free_dups:; + const size_t num_dups = dups->nItems; + for (unsigned int i = 0u; i < num_dups; i++) + { + nmFree(dups->Items[i], sizeof(Dup)); + dups->Items[i] = NULL; + } + xaDeInit(dups); + return NULL; + } + +/*** Computes Levenshtein distance between two strings. + *** + *** @param str1 The first string. + *** @param str2 The second string. + *** @param length1 The length of the first string. + *** @param length1 The length of the first string. + *** + *** @attention - Tip: Pass 0 for the length of either string to infer it + *** using the null terminating character. Thus, strings with no null + *** terminator are supported if you pass explicit lengths. + *** + *** Complexity: O(length1 * length2). + *** + *** @see centrallix-sysdoc/string_comparison.md + ***/ +unsigned int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) + { + /*** lev_matrix: + *** For all i and j, d[i][j] will hold the Levenshtein distance between + *** the first i characters of s and the first j characters of t. + *** + *** As they say, no dynamic programming algorithm is complete without a + *** matrix that you fill out and it has the answer in the final location. + ***/ + const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; + const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; + unsigned int lev_matrix[str1_len + 1][str2_len + 1]; + + /*** Base case #0: + *** Transforming an empty string into an empty string has 0 cost. + ***/ + lev_matrix[0][0] = 0u; + + /*** Base case #1: + *** Any source prefixe can be transformed into an empty string by + *** dropping each character. + ***/ + for (unsigned int i = 1u; i <= str1_len; i++) + lev_matrix[i][0] = i; + + /*** Base case #2: + *** Any target prefixes can be transformed into an empty string by + *** inserting each character. + ***/ + for (unsigned int j = 1u; j <= str2_len; j++) + lev_matrix[0][j] = j; + + /** General Case **/ + for (unsigned int i = 1u; i <= str1_len; i++) + { + for (unsigned int j = 1u; j <= str2_len; j++) + { + /** Equal characters need no changes. **/ + if (str1[i - 1] == str2[j - 1]) + lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; + + /*** We need to make a change, so use the opereration with the + *** lowest cost out of delete, insert, replace, or swap. + ***/ + else + { + unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; + unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; + unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; + + /** If a swap is possible, calculate the cost. **/ + bool can_swap = ( + i > 1 && j > 1 && + str1[i - 1] == str2[j - 2] && + str1[i - 2] == str2[j - 1] + ); + unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; + + // Find the best operation. + lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); + } + } + } + + return lev_matrix[str1_len][str2_len]; + } + +/*** Runs complete search to find duplocates in phone numbers using the + *** levenshtein min edit distance algorithm. + *** + *** @param dataset An array of characters for all dataset strings. + *** @param dataset_size The number of phone numbers to be scanned. + *** @param dupe_threshold The similarity threshold, below which dups are ignored. + *** @returns The duplicates in pDup structs. + ***/ +pXArray ca_phone_search(char dataset[][10u], const unsigned int dataset_size, const double dupe_threshold) + { + /** Allocate space for dups. **/ + const size_t guess_size = dataset_size * 2u; + pXArray dups = xaNew(guess_size); + if (dups == NULL) + { + fprintf(stderr, "ca_phone_search() - xaNew(%lu) failed.\n", guess_size); + return NULL; + } + + /** Search for dups using edit distance. **/ + for (unsigned int i = 0u; i < dataset_size; i++) + { + const char* v1 = dataset[i]; + for (unsigned int j = i + 1u; j < dataset_size; j++) + { + const char* v2 = dataset[j]; + const unsigned int dist = ca_edit_dist(v1, v2, 10u, 10u); + const double similarity = (double)dist / 10.0; + if (similarity > dupe_threshold) /* Dup found! */ + { + Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); + if (dup == NULL) + { + fprintf(stderr, "ca_phone_search() - nmMalloc(%lu) failed.\n", sizeof(Dup)); + + /** Free data before returning. **/ + const size_t num_dups = dups->nItems; + for (unsigned int i = 0u; i < num_dups; i++) + { + void* dup = dups->Items[i]; + nmFree(dup, sizeof(Dup)); + } + xaDeInit(dups); + return NULL; + } + + dup->id1 = i; + dup->id2 = j; + dup->similarity = similarity; + xaAddItem(dups, (void*)dup); + } + } + } + + return dups; + } + +void ca_init() + { + nmRegister(sizeof(Dup), "Dup"); + } + +/** Scope cleanup. **/ +#undef sparse_dif +#undef sparse_dif_to_centroid diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 629b59c79..ec1d87bcf 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -14,11 +14,17 @@ /* Description: Collection of utilities */ /************************************************************************/ +#include +#include +#include +#include +#include #include - #include -#include -#include +#include +#include + +#include "newmalloc.h" #include "util.h" /** @@ -77,3 +83,161 @@ unsigned int strtoui(const char *nptr, char **endptr, int base){ //return as tmp; return (unsigned int)tmp; } + +/*** Detects the optimal number of threads to use on this system. + *** Note: Multithreading is not currently supported, so this funciton + *** will always return 1, for now. + *** + *** @returns The number of threads that should be used on this system. + ***/ +int util_detect_num_threads(void) + { + /** Centrallix does not support multithreading. **/ + return 1; + + long num_procs = sysconf(_SC_NPROCESSORS_ONLN); + if (num_procs < 1 || INT_MAX < num_procs) + { + fprintf(stderr, "Warning: Detected strange number of processors (assuming 1): %ld\n", num_procs); + return 1; + } + else return (int)num_procs; + } + +/*** snprint_bytes() allows one to pick between CS units, where the kibibyte + *** (KiB) is 1024 bytes, and metric units where the kilobyte (KB) is 1000 bytes. + *** Fun Fact: Windows uses kibibytes, but displays them as KB. + ***/ +#define USE_METRIC false +#define nUnits 6u +static char* units_cs[nUnits] = {"bytes", "KiB", "MiB", "GiB", "TiB", "PiB"}; +static char* units_metric[nUnits] = {"bytes", "KB", "MB", "GB", "TB", "PB"}; +/*** Displays a size in bytes using the largest unit where the result would be + *** at least 1.0. + *** + *** @param buf The buffer to which new text will be written, using snprintf(). + *** @param buf_size The amount of space in the buffer, passed to snprintf(). + *** It is recomended to have at least 12 characters available. + *** @param bytes The number of bytes, which will be formatted and written + *** to the buffer.. + *** @returns buf, for chaining. + ***/ +char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) + { + char** units = (USE_METRIC) ? units_metric : units_cs; + const double unit_size = (USE_METRIC) ? 1000.0 : 1024.0; + + /** Search for the largest unit where the value would be at least 1. **/ + const double size = (double)bytes; + for (unsigned char i = nUnits; i >= 1u; i--) + { + const double denominator = pow(unit_size, i); + if (size >= denominator) + { + const double converted_size = size / denominator; + if (converted_size >= 100.0) + snprintf(buf, buf_size, "%.5g %s", converted_size, units[i]); + else if (converted_size >= 10.0) + snprintf(buf, buf_size, "%.4g %s", converted_size, units[i]); + else /* if (converted_size >= 1.0) - Always true. */ + snprintf(buf, buf_size, "%.3g %s", converted_size, units[i]); + return buf; + } + } + + /** None of the larger units work, so we just use bytes. **/ + snprintf(buf, buf_size, "%u %s", bytes, units[0]); + + return buf; + } +#undef nUints + +void fprint_mem(FILE* out) + { + FILE* fp = fopen("/proc/self/statm", "r"); + if (fp == NULL) { perror("fopen()"); return; } + + long size, resident, share, text, lib, data, dt; + if (fscanf(fp, "%ld %ld %ld %ld %ld %ld %ld", + &size, &resident, &share, &text, &lib, &data, &dt) != 7) + { + fprintf(stderr, "Failed to read memory info\n"); + fclose(fp); + return; + } + fclose(fp); + + long page_size = sysconf(_SC_PAGESIZE); // in bytes + long resident_bytes = resident * page_size; + + const size_t buf_siz = 16u; + char buf[buf_siz]; + snprint_bytes(buf, buf_siz, (unsigned int)resident_bytes); + + fprintf(out, "Memory used: %ld bytes (%s)\n", resident_bytes, buf); + fprintf(out, "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", share, text, lib, data); + } + +static double get_time(void) + { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9f; + } + +pTimer timer_init(pTimer timer) + { + if (timer == NULL) return NULL; + timer->start = NAN; + timer->end = NAN; + return timer; + } + +pTimer timer_new(void) + { + return timer_init(nmMalloc(sizeof(Timer))); + } + +pTimer timer_start(pTimer timer) + { + if (!timer) return timer; + timer->start = get_time(); + return timer; + } + +pTimer timer_stop(pTimer timer) + { + if (!timer) return timer; + timer->end = get_time(); + return timer; + } + +double timer_get(pTimer timer) + { + return (timer) ? timer->end - timer->start : NAN; + } + +void timer_de_init(pTimer timer) {} + +void timer_free(pTimer timer) + { + timer_de_init(timer); + nmFree(timer, sizeof(Timer)); + } + +/*** Function for failing on error, assuming the error came from a library or + *** system function call, so that the error buffer is set to a valid value. + ***/ +void fail(const char* function_name, int code) + { + /** Create the most descriptive error message we can. **/ + char error_buf[BUFSIZ]; + snprintf(error_buf, sizeof(error_buf), "kmeans.c: Fail - %s", function_name); + if (errno != 0) perror(error_buf); + else if (code != 0) fprintf(stderr, "%s (error code %d)\n", error_buf, code); + else fprintf(stderr, "%s", error_buf); + + /** Throw error for easier locating in a debugger. **/ + fprintf(stderr, "Program will now crash.\n"); + raise(SIGSEGV); + } diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index afeb432b5..32a4a35eb 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -290,4 +290,74 @@ xhClear(pXHashTable this, int (*free_fn)(), void* free_arg) return 0; } +/*** Executes an operation on each entry of the hash table entry. + *** + *** @param this The affected hash table. + *** @param callback_fn A callback function to be called on each hash table + *** entry. It takes 2 parameters: the current hash table entry and a void* + *** argument specified using each_arg. If any invokation of the callback + *** function returns a value other than 0, xhForEach() will immediately + *** fail, returning that value as the error code. + *** @param each_arg An aditional argument which will be passed to each + *** invokation of the callback function. + *** @returns 0 if the function executes successfully. + *** 1 if the callback function is NULL. + *** n (where n != 0) if the callback function returns n. + ***/ +int +xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg) + { + if (callback_fn == NULL) return 1; + + for (int row = 0; row < this->nRows; row++) + { + pXHashEntry entry = (pXHashEntry)(this->Rows.Items[row]); + while (entry != NULL) + { + pXHashEntry next = entry->Next; + const int ret = callback_fn(entry, each_arg); + if (ret != 0) return ret; + entry = next; + } + } + + return 0; + } + +static int +xhiFreeEntry(pXHashEntry entry, void* arg) + { + /*** The passed void* actually points to a void* array with 2 elements. + *** The first element is a function pointer to the free function, which + *** we invoke using the provided entry and the free_arg, specified as the + *** second element of the array. + *** + *** Interestingly, you can write this code in one line like this: + *** ((void (*)(pXHashEntry, void*))((void**)arg)[0])(entry, ((void**)arg)[1]); + *** But I value code readability, so fortunately, I can't be THAT cleaver... + ***/ + void** args = (void**)arg; + void (*free_fn)(pXHashEntry, void*) = args[0]; + free_fn(entry, args[1]); + + /** Free the entry. **/ + nmFree(entry, sizeof(XHashEntry)); + + return 0; + } +int +xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg) + { + /** Free each row. **/ + void* args[2] = {free_fn, free_arg}; + const int ret = xhForEach(this, xhiFreeEntry, args); + + /** Mark all rows as empty. **/ + for (int i = 0; i < this->nRows; i++) + this->Rows.Items[i] = NULL; + this->nItems = 0; + + /** We are successful only if the free function didn't fail. **/ + return ret; + } diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster new file mode 100644 index 000000000..201c41255 --- /dev/null +++ b/centrallix-os/cluster-schema.cluster @@ -0,0 +1,176 @@ +// Input schema +$Version=2$ +file_name "system/cluster" + { + name "cluster/parameter" + { + type : DATA_T // See datatypes.h + ?default : type + ?name : String // Overrides the name above. + ?style : StyleObj // idk where to find docs for this. + } + // Access with :parameters:name. Accessing dynamic data (e.g. parameters) + // should be managed within a runserver() call. + ... + + source : DataSourcePath + attr_name : string ⊂ DataSourcePath/columns + + cluster_name "cluster/cluster" + { + algorithm : "none" | "sliding-window" | "k-means" + | "k-means++" | "k-medoids" |"db-scan" // dbscan not implemented + similarity_measure : "cosine" | "levenshtein" // levenshtein not implemented. + num_clusters : uint > 1 // (probably a parameter) + ?min_improvement : double && 0.0 < x < 1.0 | "none" // default: 0.0001 + ?max_iterations : uint // default: 64 + + // Not implemented + sub_cluster_name "cluster/cluster" + { + // Same as above. + } + } + ... + + search_name "system/search" + { + source : string ⊂ [cluster_name, ...] + threshold : double && 0.0 < x < 1.0 // optimization. + similarity_measure : "cosine" | "levenshtein" // levenshtein not implemented. + } + ... + } + +// Output schema + +- /{arbitrary uint} + ? /sub_cluster_name + ? /{arbitrary uint} + ... + - /average_similarity : double && 0.0 < x < 1.0 + - /size = average_similarity + - /{arbitrary uint} + - /val : typeof(attr_name) // The value of the data point. + - /label : uint < num_clusters // id of the cluster to which this data point belongs. + - /sim : double && 0.0 < x <= threshold // Similarity to cluster centroid. +... +/search_name +- /{arbitrary uint} + - /id1 : uint // The id of the first data point. + - /id2 : uint // The id of the second data point. + - /val1 : typeof(attr_name) // The value of the first data point. + - /val2 : typeof(attr_name) // The value of the second data point. + - /sim : double && 0.0 < x <= threshold // The similarity of the two data points. +... + +// Other notes + +// This means centrallix scripts will have to chose when to switch +// from complete search to clustered search. I think this is a good +// thing, because that feels like a higher-level responsibility. + +// Invoke file: +// select * from /file.cl + +// Driver-authoring.md +// Comprehend stparse.c (lib vs. centrallix?) +// Design what a .cluster file looks like. +// +// Figure out how to invoke the object system. + +// Random queries + +// Names +SELECT CONCAT(p_given_name, ' ', p_surname) AS full_name, + COUNT(*) AS num_dups +FROM p_partner +WHERE p_given_name is not null +AND p_surname is not null +AND p_given_name != "" +AND p_surname != "" +AND p_given_name != " " +AND p_surname != " " +GROUP BY full_name +ORDER BY num_dups DESC +LIMIT 1; +// Result: Ine Bradley with 4 dups + +// Phone Numbers +SELECT CONCAT(ci.p_phone_country, ci.p_phone_area_city, ci.p_contact_data) AS phone_number, + COUNT(*) AS num_dups +FROM p_partner AS p +JOIN p_contact_info AS ci + ON p.p_partner_key = ci.p_partner_key +WHERE ci.p_contact_data != ' ' +AND ci.p_contact_data != '' +AND (ci.p_contact_type = 'P' OR ci.p_contact_type = 'C') +GROUP BY phone_number +ORDER BY num_dups DESC +LIMIT 1; +// Result: 1813762-2274 with 2 dups + +// Emails and Addresses +SELECT CONCAT(ci.p_contact_data, ' ', + l.p_in_care_of, ' ', + l.p_address_1, ' ', + l.p_address_2, ' ', + l.p_address_3, ' ', + l.p_city, ' ', + l.p_state_province, ' ', + l.p_country_code, ' ', + l.p_postal_code) AS email_and_address, + COUNT(*) AS duplicate_count +FROM p_partner AS p +JOIN p_contact_info AS ci + ON p.p_partner_key = ci.p_partner_key +JOIN p_location AS l + ON p.p_partner_key = l.p_partner_key +WHERE ci.p_contact_type = 'E' +GROUP BY email_and_address +ORDER BY duplicate_count DESC +LIMIT 1; +// Result: richard.aypofblcsg@iipr.yeen with 2 dups + +// Email +SELECT ci.p_contact_data AS email, + COUNT(*) AS duplicate_count +FROM p_partner AS p +JOIN p_contact_info AS ci + ON p.p_partner_key = ci.p_partner_key +WHERE ci.p_contact_type = 'E' +GROUP BY email +ORDER BY duplicate_count DESC +LIMIT 1; + +// Result: uoehtbtjvqh20@ltirs.zese with 2 dups + +// Address +SELECT CONCAT(l.p_in_care_of, ' ', + l.p_address_1, ' ', + l.p_address_2, ' ', + l.p_address_3, ' ', + l.p_city, ' ', + l.p_state_province, ' ', + l.p_country_code, ' ', + l.p_postal_code) AS address, + COUNT(*) AS duplicate_count +FROM p_partner AS p +JOIN p_location AS l + ON p.p_partner_key = l.p_partner_key +WHERE l.p_address_1 != ' ' +GROUP BY address +ORDER BY duplicate_count DESC +LIMIT 1; +// Result: "742 1ben Sc E Adams FL US 49152" with 4 + + +// Output to dataset +INTO OUTFILE '/var/lib/mysql/db_output.csv' +LINES TERMINATED BY '|' + +// Output to CSV +INTO OUTFILE '/var/lib/mysql/db_output.csv' +FIELDS TERMINATED BY ',' +ENCLOSED BY '"' +LINES TERMINATED BY '\n'; diff --git a/centrallix-os/file.cluster b/centrallix-os/file.cluster new file mode 100644 index 000000000..929efdd03 --- /dev/null +++ b/centrallix-os/file.cluster @@ -0,0 +1,64 @@ +$Version=2$ +file_name "system/cluster" + { + // Developer can specify parameters to improve file reuseability. + // TIP: Improve performance by declairing frequently used parameters first. + k "cluster/parameter" { type = integer; style=notnull; } + str "cluster/parameter" { type = string; } + int "cluster/parameter" { type = integer; default = runserver(:parameters:k); } + dbl "cluster/parameter" { type = double; default=4.2; } + // conversion "cluster/parameter" { type=double; default=4; } + + null_str "cluster/parameter" { type = string; default = null; } + null_int "cluster/parameter" { type = integer; default = null; } + null_dbl "cluster/parameter" { type = double; default = null; } + + // We calculate k in a centrallix script using: + // k = max(2, pow(log(n) / log(36), 3.2) - 8) + // where n is the number of records passed. + + // Specify the data source at the top of the file. + // How do we pass distinct data? Should the driver + // handle that for us? + source = "/apps/kardia/data/Kardia_DB/p_partner/rows"; + attr_name = p_given_name; // runserver(:parameters:str) + + // Clustering object specifies properties for clustering. + kmeans_cluster "cluster/cluster" + { + algorithm = "k-means"; + similarity_measure = "cosine"; + num_clusters = runserver(:parameters:k); + min_improvement = 0.0001; + max_iterations = 48; + + // Create subclusters. (Not implemented) + sub_cluster "cluster/cluster" + { + algorithm = "none"; + similarity_measure = "cosine"; + num_clusters = 7; + min_improvement = "max"; + } + } + + // Complete search. + no_clustering "cluster/cluster" + { + algorithm = "none"; + } + + dups "cluster/search" + { + source = kmeans_cluster; + threshold = 0.75; + similarity_measure = "cosine"; + } + + dups2 "cluster/search" + { + source = no_clustering; + threshold = 0.75; + similarity_measure = "cosine"; + } + } diff --git a/centrallix-os/file2.cluster b/centrallix-os/file2.cluster new file mode 100644 index 000000000..a55c37f85 --- /dev/null +++ b/centrallix-os/file2.cluster @@ -0,0 +1,42 @@ +$Version=2$ +file_name "system/cluster" + { + // Developer can specify parameters to improve file reuseability. + // TIP: Improve performance by declairing frequently used parameters first. + k "cluster/parameter" { type = integer; style=notnull; } + str "cluster/parameter" { type = string; default="k-means"; } + int "cluster/parameter" { type=integer; default=:parameters:k; } + dbl "cluster/parameter" { type=double; default=4.2; } + // conversion "cluster/parameter" { type=double; default=4; } + + null_str "cluster/parameter" { type = string; default = null; } + null_int "cluster/parameter" { type = integer; default = null; } + null_dbl "cluster/parameter" { type = double; default = null; } + + // We calculate k in a centrallix script using: + // k = max(2, pow(log(n) / log(36), 3.2) - 8) + // where n is the number of records passed. + + // Specify the data source at the top of the file. + // How do we pass distinct data? Should the driver + // handle that for us? + source = "/apps/kardia/data/Kardia_DB/p_partner/rows"; + attr_name = "p_given_name"; + + // Clustering object specifies properties for clustering. + kmeans_cluster "cluster/cluster" + { + algorithm = "k-means"; + similarity_measure = "cosine"; + num_clusters = :parameters:k; + min_improvement = 0.0001; + max_iterations = 48; + } + + dups "cluster/search" + { + source = kmeans_cluster; + threshold = 0.75; + similarity_measure = "cosine"; + } + } diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index c167fce26..5755d15c5 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -166,9 +166,11 @@ Within the initialization function, the driver should initialize all necessary g To register with the OSML, the driver must first allocate an ObjDriver structure and fill in its contents. +```c pObjDriver drv; drv = (pObjDriver)nmMalloc(sizeof(ObjDriver)); +``` This involves setting a large number of fields to the appropriate entry points within the OS Driver, as well as telling the OSML what object type(s) are handled by the driver and giving the OSML a description of the driver. A list of the required entry point functions / fields follows: @@ -208,14 +210,17 @@ Another field in the driver structure is the Capabilities field. This field is a The 'Name' field should be filled in with a description of the OS driver, with a maximum length of 63 characters (plus the string null terminator). Normally, the 2-4 letter prefix of the driver is included at the beginning of 'Name', such as "UXD - UNIX filesystem driver". Finally, the 'RootContentTypes' field is an XArray containing a list of strings, each of which specifies the node object types that the driver will handle. Such types are added to this XArray using the normal XArray utility functions, such as: - +```c xaInit(&drv->RootContentTypes, 16); xaAddItem(&drv->RootContentTypes, "system/file"); xaAddItem(&drv->RootContentTypes, "system/directory"); +``` When the structure has been filled out, the os driver should call the OSML to register itself, using the objRegisterDriver function: +```c objRegisterDriver(drv); +``` The initialization function should return 0 to indicate success, or -1 on failure. Currently, initialization success/failure is not verified by lsmain.c. @@ -234,54 +239,58 @@ As an overview, the normal procedure for the open routine to follow is this: The first basic part of the OS driver consists of the Open and Close routines, normally named 'xxxOpen' and 'xxxClose' within the driver, where 'xxx' is the driver's prefix. The Close routine is normally fairly simple, but the Open routine is one of the most complicated routines in a typical OS driver, for the Open routine must parse the subtree pathname beneath the node object. For example, if the node object had a pathname like: +```sh /datasources/OMSS_DB +``` and the user opened an object called: +```sh /datasources/OMSS_DB/JNetHelp/rows/1 +``` the OS driver would have to determine what the subtree pathname 'JNetHelp/rows/1' means, since this path will mean different things to different os drivers. -The Open routine also must determine whether the object already exists or not, and if not, whether to create a new object. This logic is largely dependent on the obj->Mode flags, as if O_CREAT is included, the driver must attempt to create the object if it does not already exist, and if O_EXCL is included, the driver must refuse to open the object if it already exists, as with the UNIX open() system call semantics. +The Open routine also must determine whether the object already exists or not, and if not, whether to create a new object. This logic is largely dependent on the `obj->Mode` flags, as if `O_CREAT` is included, the driver must attempt to create the object if it does not already exist, and if `O_EXCL` is included, the driver must refuse to open the object if it already exists, as with the UNIX `open()` system call semantics. -Finally, if the os driver specified a capability of OBJDRV_C_TRANS, it must pay attention to the current state of the end-user's trans- action. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). The transaction layer will be discussed in depth later in this document. +Finally, if the os driver specified a capability of `OBJDRV_C_TRANS`, it must pay attention to the current state of the end-user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). The transaction layer will be discussed in depth later in this document. -As a part of the Open process, the OS driver will normally allocate an internal structure to represent the current open object, and will return that structure as a void* data type in the return value. This pointer will be then passed to each of the other driver entry point functions, with the exception of QueryFetch, QueryDelete, and Query- Close, which will be discussed later. +As a part of the Open process, the OS driver will normally allocate an internal structure to represent the current open object, and will return that structure as a `void*` data type in the return value. This pointer will be then passed to each of the other driver entry point functions, with the exception of QueryFetch, QueryDelete, and Query- Close, which will be discussed later. The Open() routine is called with five parameters: -- obj (pObject) +- `obj` (pObject) This is a pointer to the Object sturcture maintained by the OSML. This structure will contain some important fields for processing the open() request. - obj->Mode is a bitmask of the O_* flags, which include O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, and O_EXCL. + - `obj->Mode` is a bitmask of the O_* flags, which include `O_RDONLY`, `O_WRONLY`, `O_RDWR`, `O_CREAT`, `O_TRUNC`, and `O_EXCL`. - obj->Pathname is a Pathname structure which contains the complete parsed pathname for the object. This structure is defined in the file include/obj.h, and has a buffer for the pathname as well as an array of pointers to the pathname's components. The function obj_internal_PathPart() can be used to obtain at will any component or series of components of the pathname. + - `obj->Pathname` is a Pathname structure which contains the complete parsed pathname for the object. This structure is defined in the file `include/obj.h`, and has a buffer for the pathname as well as an array of pointers to the pathname's components. The function `obj_internal_PathPart()` can be used to obtain at will any component or series of components of the pathname. - obj->Pathname->OpenCtl[] contains parameters to the open() operation. Frequently these params provide additional information on how to open the object. The use of these parameters is determined by the author of the objectsystem driver. The parameters are those passed in normal URL fasion (?param=value, etc.). Typically, the only OpenCtl of interest is going to be obj->Pathname->OpenCtl[obj->SubPtr] (see below for SubPtr meaning). + - `obj->Pathname->OpenCtl[]` contains parameters to the open() operation. Frequently these params provide additional information on how to open the object. The use of these parameters is determined by the author of the objectsystem driver. The parameters are those passed in normal URL fasion (?param=value, etc.). Typically, the only OpenCtl of interest is going to be `obj->Pathname->OpenCtl[obj->SubPtr]` (see below for SubPtr meaning). - obj->SubPtr is the number of components in the path that are a part of the node object's path. For example, in the above path of '/datasources/OMSS_DB', the path would be internally represented as './datasources/ OMSS_DB', and the SubPtr would be 3. + - `obj->SubPtr` is the number of components in the path that are a part of the node object's path. For example, in the above path of '/datasources/OMSS_DB', the path would be internally represented as './datasources/ OMSS_DB', and the SubPtr would be 3. - obj->SubCnt reflects the number of components of the path which are under the control of the current driver. This includes the node object, so SubCnt will always be at least 1. For example, when opening '/data/file.csv/rows/1', and the driver in question is the CSV driver, SubPtr would be 3 (includes an "invisible" first component), from '/data/file.csv', and SubCnt would be 3, from 'file.csv/rows/1'. The driver will need to SET THE SUBCNT value in its Open function. SubPtr is already set. + - `obj->SubCnt` reflects the number of components of the path which are under the control of the current driver. This includes the node object, so SubCnt will always be at least 1. For example, when opening '/data/file.csv/rows/1', and the driver in question is the CSV driver, SubPtr would be 3 (includes an "invisible" first component), from '/data/file.csv', and SubCnt would be 3, from 'file.csv/rows/1'. The driver will need to SET THE SUBCNT value in its Open function. SubPtr is already set. - obj->Prev is the underlying object as opened by the next-lower-level driver. It is the duty of this driver to parse the content of that object and do something meaningful with it. + - `obj->Prev` is the underlying object as opened by the next-lower-level driver. It is the duty of this driver to parse the content of that object and do something meaningful with it. - obj->Prev->Flags contains some critical infor- mation about the underlying object. If it contains the flag OBJ_F_CREATED, then the underlying object was just created by this open() operation. In that case, this driver is expected to create the node with snNewNode() (see later in this document) as long as obj->Mode contains O_CREAT. + - `obj->Prev->Flags` contains some critical information about the underlying object. If it contains the flag `OBJ_F_CREATED`, then the underlying object was just created by this open() operation. In that case, this driver is expected to create the node with snNewNode() (see later in this document) as long as obj->Mode contains O_CREAT. -- mask (int) +- `mask` (int) Indicates the security mask to be given to the object if it is being created. Typically, this will only apply to files and directories. The values are the same as UNIX chmod() type values. -- systype (pContentType) +- `systype` (pContentType) This param indicates the content type of the node object as determined by the OSML. The ContentType structure is defined in include/ obj.h, and includes among other things the name of the content type. For example, for the reporting driver, this type would be "system/report". -- usrtype (char*) +- `usrtype` (char*) This param is the requested object type by the user and is normally used when creating a new object, though under some circumstances it may change the way the open operates on an existing object. For example, the reporting driver can change whether it generates HTML report text or plaintext reports based on usrtype being either "text/html" or "text/plain". -- oxt (pObjTrxTree*) +- `oxt` (pObjTrxTree*) This param is only used by object drivers that specified a capability of OBJDRV_C_TRANS. More on this field later. For non-transaction-aware drivers, this field can be safely ignored. Yes, this param *is* a pointer to a pointer. Essentially, a pointer passed by reference. -The Open routine should return its internal structure pointer on success, or NULL on failure. It is normal to allocate one such structure per Open call, and for the structure to point, among other things, to shared data describing the node object. Accessing the node object is described later in this document. +The Open routine should return its internal structure pointer on success, or `NULL` on failure. It is normal to allocate one such structure per Open call, and for the structure to point, among other things, to shared data describing the node object. Accessing the node object is described later in this document. It is important to know what kinds of fields normally are placed in the allocated data structure returned by Open. These fields are all determined by the driver author, but here are a few typical ones that are helpful to have ("inf" is the pointer to the structure here): @@ -307,7 +316,7 @@ Before exiting, the Close routine should make sure it decrements the Open Count ### C. Creating and Deleting Objects. The Create and Delete functions are used for creating and deleting objects. Normally, the os driver will process the Pathname in the same manner for Create and Delete as for Open, thus such functionality could be placed in another function. -As a side note, within Centrallix, the standard function naming convention is to use xxx_internal_FunctionName for functions that are more or less internal to the module and not a part of any standard interface. +As a side note, within Centrallix, the standard function naming convention is to use `xxx_internal_FunctionName()` for functions that are more or less internal to the module and not a part of any standard interface. The Create routine has parameters identical to the Open routine. It should return 0 on success and -1 on error. @@ -349,19 +358,19 @@ The query mechanism can also be used to delete a set of child objects, optionall The first main function for handling queries is OpenQuery. This function is passed three arguments: -- inf_v (void*) The value returned from Open for this object. +- `inf_v` (void*) The value returned from Open for this object. -- query (pObjQuery) The query structure setup by the OSML. It will contain several key fields: +- `query` (pObjQuery) The query structure setup by the OSML. It will contain several key fields: - query->QyText: the text of the criteria (i.e., the WHERE clause, in Centrallix SQL syntax) + - `query->QyText`: the text of the criteria (i.e., the WHERE clause, in Centrallix SQL syntax) - query->Tree: the compiled expression tree, which evaluates to nonzero for true or zero for false as the WHERE clause condition. + - `query->Tree`: the compiled expression tree, which evaluates to nonzero for true or zero for false as the WHERE clause condition. - query->SortBy[]: an array of expressions giving the various components of the sorting criteria. + - `query->SortBy[]`: an array of expressions giving the various components of the sorting criteria. - query->Flags: the driver should set and/or clear the flags OBJ_QY_F_FULLQUERY and OBJ_QY_F_FULLSORT if need be. The former indicates that the driver is willing to handle the full WHERE clause (the query->Tree). The latter indicates that the driver is willing to handle the sorting of the data as well (in query->SortBy[]). If the driver can easily have the sorting/selection done (as when querying an RDBMS), it should set these flags. Otherwise, it should let the OSML take care of the ORDER BY and WHERE conditions. + - `query->Flags`: the driver should set and/or clear the flags `OBJ_QY_F_FULLQUERY` and `OBJ_QY_F_FULLSORT` if need be. The former indicates that the driver is willing to handle the full WHERE clause (the query->Tree). The latter indicates that the driver is willing to handle the sorting of the data as well (in query->SortBy[]). If the driver can easily have the sorting/selection done (as when querying an RDBMS), it should set these flags. Otherwise, it should let the OSML take care of the ORDER BY and WHERE conditions. -- oxt (pObjTrxTree*) The transaction tree pointer. +- `oxt` (pObjTrxTree*) The transaction tree pointer. The OpenQuery function should return a void* value, which will within the driver point to a structure used for managing the query. This structure will normally have a pointer to the inf_v value returned by Open as well, since inf_v is never passed to QueryFetch, QueryDelete or QueryClose. OpenQuery should return NULL if the object does not support queries or if some other error condition occurs that will prevent the execution of the query. @@ -378,6 +387,7 @@ The QueryFetch routine should return an inf_v pointer to the child object, or NU All object drivers will need to add an element to the obj->Pathname structure to indicate the path to the child object being returned. This will involve a process somewhat like this: (given that new_name is the new object's name, qy is the current query structure, which contains a field 'Parent' that points to the inf_v originally returned by Open, and where the inf_v contains a field Obj that points to the Object structure containing a Pathname structure) +```c int cnt; pObject obj; char* new_name; @@ -389,6 +399,7 @@ All object drivers will need to add an element to the obj->Pathname structure to if (cnt < 0 || cnt >= 256) return NULL; obj->Pathname->Elements[obj->Pathname->nElements++] = strrchr(obj->Pathname->Pathbuf,'/')+1; +``` QueryDelete is passed the qy_v void* parameter, and an oxt parameter. It should return 0 on successful deletion, and -1 on failure. @@ -456,22 +467,28 @@ The driver's first course of action to obtain node object data is to open the no ### pSnNode snReadNode(pObject obj) This function reads a Structure File from the already-open node object which is passed in the "obj" parameter in the xxxOpen() routine. The "obj" parameter has an element, obj->Prev, which is a link to the node object as opened by the previous driver in the OSML's chain of drivers for handling this open(). All you need to know to get the parsed node object is the following: +```c pSnNode node; node = snReadNode(obj->Prev); +``` The returned node structure is managed by the SN module and need not be nmFree()ed. The only thing that must be done is that the driver should increment the node structure's link count like this: +```c node->OpenCnt++; +``` When closing an object (and thus releasing a reference to the Node structure), the driver should decrement the link count. ### pSnNode snNewNode(pObject obj, char* content_type) This function creates a new node object with a given content type. The open link count should be incremented as appropriate, as before with snReadNode(). +```c pSnNode node; node = snNewNode(obj->Prev, "system/structure"); +``` The "system/structure" argument is the type that will be assigned to the newly created node object. Note that the underlying object must already exist in order for this to create a node object as that object's content. Normally the OSML does this for you by commanding the previous driver (handling obj->Prev) to create the underlying object in question. @@ -512,6 +529,7 @@ This function adds a node of type ST_T_SUBGROUP to either a ST_T_SUBGROUP or ST_ ### int stAddValue(pStructInf inf, char* strval, int intval) This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If 'strval' is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. If the string is non-static, and has a lifetime less than the ST_T_ATTRIB tree node, then the following procedure must be used: +```c char* ptr; char* nptr; pStructInf attr_inf; @@ -522,6 +540,7 @@ This function adds a value to an attribute, and can be called multiple times on strcpy(nptr, ptr); stAddValue(attr_inf, nptr, 0); attr_inf->StrAlloc[0] = 1; +``` By following this method (making a copy of the string and then setting the StrAlloc value for that string), when the StructInf tree node is freed by the stparse module, the string will auto- matically be freed as well. @@ -533,6 +552,7 @@ This function returns the value of the given attribute in an ST_T_ATTRIB tree no It is common practice to use the stLookup and stAttrValue functions together to retrieve values, and search for an attribute StructInf and retrieve its value in one operation: +```c pStructInf inf; char* ptr; @@ -540,12 +560,14 @@ It is common practice to use the stLookup and stAttrValue functions together to { printf("%s is the value\n", ptr); } +``` ### int stFreeInf(pStructInf this) This function is used to free a StructInf tree node. It will free any sub-nodes first, so if that is not desired, be sure to disconnect them by removing them from the SubInf array and appropriately adjusting the nSubInf counter, and setting the SubInf array position to NULL. This function also disconnects the tree node from its parent, if any, so if the parent is already free()'d, be sure to set the node's Parent pointer to NULL. Any strings marked allocated with the StrAlloc flags will be free()'d. It is also common practice to bypass the stXxx() functions entirely and access the elements of the StructInf structures themselves. This is not forbidden, and may be done. See the file stparse.h for a description of the structure. For example, +```c pStructInf inf; int i; @@ -556,6 +578,7 @@ It is also common practice to bypass the stXxx() functions entirely and access t /** do stuff with attribute... **/ } } +``` ## IV Memory Management in Centrallix Centrallix has its own memory manager that caches freshly-deallocated blocks of memory in lists according to size so that they can be quickly reallocated. This memory manager also catches double-freeing of blocks, making debugging of memory problems a little easier. @@ -625,6 +648,7 @@ This adds an item to the xarray, and keeps the array sorted. The value for sort #### xaFindItem(pXArray this, void* item) This returns the offset into the array's items of the given value. An exact match is required. The array's items are given below: +```c XArray xa; pStructInf inf; int item_id; @@ -639,6 +663,7 @@ This returns the offset into the array's items of the given value. An exact matc item_id = xaFindItem(&xa, inf); inf == xa.Items[item_id]; +``` #### xaRemoveItem(pXArray this, int index) This function removes an item from the xarray at the given index. @@ -682,22 +707,27 @@ Copies the string 'text' into the XString. Like xsConcatenate, except that the #### char* xsStringEnd(pXString this) Returns a pointer to the end of the string. Useful for finding the end of the string without performing: +```c pXString xs; xs->String + strlen(xs->String) +``` since the xs module already knows the string length and does not have to search for the null terminator. Furthermore, since the string can contain nulls, the above statement could produce incorrect results in those situations. The contents of the XString can be easily referenced via: +```c pXString xs; printf("This string is %s\n", xs->String); +``` IMPORTANT NOTE: Do not store pointers to values within the string while you are still adding text to the end of the string. If the string ends up realloc()ing, your pointers will be incorrect. Instead, if data in the middle of the string needs to be pointed to, store offsets from the beginning of the string, not pointers to the string. For example, this is WRONG: +```c pXString xs; char* ptr; @@ -706,9 +736,11 @@ For example, this is WRONG: ptr = xsStringEnd(&xs); xsConcatenate(&xs, "This is the second sentence.", -1); printf("A pointer to the second sentence is '%s'\n", ptr); +``` Instead, use pointer aritmetic and do this: +```c pXString xs; int offset; @@ -717,6 +749,7 @@ Instead, use pointer aritmetic and do this: offset = xsStringEnd(&xs) - xs->String; xsConcatenate(&xs, "This is the second sentence.", -1); printf("A pointer to the second sentence is '%s'\n",xs->String+offset); +``` ### D. Expression (EXP) - Expression Trees @@ -726,7 +759,9 @@ Expressions can be stand-alone expression trees, or they can take parameter obje Expression evaluation results in the top-level expression tree node having the final value of the expression, which may be NULL, and may be an integer, string, datetime, money, or double data type. For example, the final value of +``` :myobject:oneattribute == 'yes' +``` would be integer 1 (true) if the attribute's value is indeed 'yes'. @@ -777,8 +812,10 @@ Frees a parameter object list. #### int expAddParamToList(pParamObjects this, char* name, pObject obj, int flags) Adds a parameter to the parameter object list. The 'obj' pointer may be left NULL during the expCompileExpression state of operation but must be set to a value before expEvalTree is called. Otherwise the attributes that reference that parameter object will result in NULL values in the expression (it's technically not an error). Flags can be EXPR_O_CURRENT if the object is to be marked as the current one, or EXPR_O_PARENT if it is to be marked as the parent object. Current and Parent objects can be referenced in an expression like this: +``` :currentobjattr ::parentobjattr +``` and is thus a shortcut to typing the full object name. @@ -851,15 +888,21 @@ drivers. Most of them are named obj_internal_XxxYyy or similar. #### char* obj_internal_PathPart(pPathname path, int start, int length) The Pathname structure breaks down a pathname into path elements, which are text strings separated by the directory separator '/'. This function takes the given Pathname structure, and returns the number of path elements requested. For instance, if you have a path: +``` /apps/kardia/data/Kardia_DB/p_partner/rows/1 +``` that path would be stored internally in Centrallix as: +``` ./apps/kardia/data/Kardia_DB/p_partner/rows/1 +``` To just return "Kardia_DB/p_partner", you could call: +``` obj_internal_PathPart(pathstruct, 4, 2); +``` Note that return values from obj_internal_PathPart are only valid until the next call to PathPart on the given pathname structure. @@ -886,9 +929,9 @@ This function closes a network connection, and optionally waits up to 'linger_ms ### int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags) This function writes data to a file descriptor, from a given buffer and length, and to an optional seek offset and with some optional flags. Flags can be the following: -- FD_U_NOBLOCK - If the write can't be performed immediately, don't perform it at all. -- FD_U_SEEK - The 'offset' value is valid. Seek to it before writing. Not valid for network connections. -- FD_U_PACKET - ALL of the data of 'length' in 'buffer' must be written. Normal write() semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. +- `FD_U_NOBLOCK` - If the write can't be performed immediately, don't perform it at all. +- `FD_U_SEEK` - The 'offset' value is valid. Seek to it before writing. Not valid for network connections. +- `FD_U_PACKET` - ALL of the data of 'length' in 'buffer' must be written. Normal write() semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. #### int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags) The complement to the above routine. Takes the same flags as the above routine, except FD_U_PACKET means that all of 'maxlen' bytes must be read before returning. This is good for reading a packet that is known to be exactly 'maxlen' bytes long, but which might be broken up into fragments by the network (TCP/IP has a maximum frame transmission size of about 1450 bytes). diff --git a/centrallix-sysdoc/string_comparison.md b/centrallix-sysdoc/string_comparison.md index 222e3e6d9..dac13d544 100644 --- a/centrallix-sysdoc/string_comparison.md +++ b/centrallix-sysdoc/string_comparison.md @@ -31,9 +31,9 @@ int exp_fn_fuzzy_compare(pExpression tree, pParamObjects objlist, pExpression i0 ``` Returns a value between 0.0 (complete match) and 1.0 (complete difference) between strings a and b, based on the (levenshtein distance) / (max len of input strings). Some alterations to the calculation are as follows: -- matching an empty string against anything returns 0.5. -- a string that only required insertions to become the other string has its (lev_dist)/(strlen) value halved before returning -The parameter max_field_width is required, but not used. +- Matching an empty string against anything returns 0.5. +- A string that only required insertions to become the other string has its `(lev_dist)/(strlen)` value halved before returning. +- The parameter `max_field_width` is required, but not used. ## Cosine Similarity @@ -46,6 +46,7 @@ const char *CHAR_SET ... `CHAR_SET` represents all of the characters that should be considered during the calculation of similarity. `CHAR_SET` can be extended to include additional characters, as necessary. ### Frequency Table + ```c int exp_fn_i_frequency_table(double *table, char *term) ``` @@ -70,6 +71,7 @@ Helper function for similarity(). Creates a TF x IDF vector from a frequency tab The `frequency_table` parameter must have been created using the `exp_fn_i_frequency_table` function above. ### Dot Product + ```c int exp_fn_i_dot_product(double *dot_product, double *r_freq_table1, double *r_freq_table2) ``` @@ -78,6 +80,7 @@ Helper function for similarity(). Calculates the dot product of two relative fre The `dot_product` parameter should be initialized to 0 before calling the function. The table parameters must contain relative frequency tables that are generated from the `exp_fn_i_relative_frequency_table` function. The lengths of both tables must equal the length of `CHAR_SET`. ### Magnitude + ```c int exp_fn_i_magnitude(double *magnitude, double *r_freq_table) ``` @@ -86,6 +89,7 @@ Helper function for similarity(). Calculates the magnitude of a relative frequen The `magnitude` parameter should be initialized to 0 before calling the function. The table parameter must contain a relative frequency table that was generated from the `exp_fn_i_relative_frequency_table` function. The length of the frequency table must equal the length of `CHAR_SET`. ### Similarity + ```c int exp_fn_similarity(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) ``` @@ -95,5 +99,3 @@ Returns a value between 0.0 (completely different) and 1.0 (complete match) refl ### Inverse Document Frequency (IDF) In text mining, the most common metric to use in the cosine similarity function is the [TF x IDF](https://en.wikipedia.org/wiki/Tf%E2%80%93idf) metric. Our approach uses only TF (term frequency). Inverse document frequency calculates a weighting factor for each character. This could increase precision a small amount by weighting characters that appear on many records as less important in distinguishing matches, and weighting characters that appear on only certain records as more important. IDF could be calculated by iterating through the entire partner dataset each time. The current approach uses the relative frequency of each letter used in the English language on [Wikipedia](https://en.wikipedia.org/wiki/Letter_frequency), which may not be consistent with the data in the partner database. - - diff --git a/centrallix/Makefile.in b/centrallix/Makefile.in index 7d2b1e238..0d13843de 100644 --- a/centrallix/Makefile.in +++ b/centrallix/Makefile.in @@ -115,6 +115,7 @@ XOBJDRIVERS=objdrv_ux.o \ objdrv_uxprint.o \ objdrv_qytree.o \ objdrv_qypivot.o \ + objdrv_cluster.o \ objdrv_datafile.o \ objdrv_audio.o \ objdrv_link.o \ @@ -133,6 +134,7 @@ XV3OBJDRIVERS= \ objdrv_uxprint_v3.o \ objdrv_qytree.o \ objdrv_qypivot.o \ + objdrv_cluster.o \ objdrv_query.o \ objdrv_datafile.o \ objdrv_audio.o \ @@ -314,6 +316,7 @@ XEXPRMODS=exp_main.o \ exp_compiler.o \ exp_evaluate.o \ exp_functions.o \ + exp_double_metaphone.o \ exp_generator.o EXPRMODS=$(patsubst %,expression/%,$(XEXPRMODS)) diff --git a/centrallix/centrallix.c b/centrallix/centrallix.c index 6467ab2b7..75e19d12d 100644 --- a/centrallix/centrallix.c +++ b/centrallix/centrallix.c @@ -440,6 +440,7 @@ cxDriverInit() stxInitialize(); /* Structure file driver */ qytInitialize(); /* Query Tree driver */ qypInitialize(); /* Query Pivot driver */ + clusterInitialize(); /* Cluster driver */ qyInitialize(); /* stored query (aka view) driver */ rptInitialize(); /* report writer driver */ uxpInitialize(); /* UNIX printer access driver */ @@ -694,4 +695,3 @@ cxLinkSigningSetup(pStructInf my_config) return 0; } - diff --git a/centrallix/etc/types.cfg b/centrallix/etc/types.cfg index 11ebc3e3e..6cbac5ae6 100644 --- a/centrallix/etc/types.cfg +++ b/centrallix/etc/types.cfg @@ -51,6 +51,7 @@ "system/symbolic-link" "Symbolic Link" lnk "" "text/plain" "text/css" "CSS File" css "" "text/plain" "system/querypivot" "Query Pivot Object" qyp "" "system/structure" +"system/cluster" "Clustering Object" cluster "" "system/structure" "application/json" "JSON data" json "" "text/plain" "text/json" "JSON data" "" "" "application/json" "text/x-json" "JSON data" "" "" "application/json" diff --git a/centrallix/expression/exp_compiler.c b/centrallix/expression/exp_compiler.c index 9455d6676..bcda38f71 100644 --- a/centrallix/expression/exp_compiler.c +++ b/centrallix/expression/exp_compiler.c @@ -1022,8 +1022,8 @@ expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflag /*** expBindExpression - do late binding of an expression tree to an *** object list. 'domain' specifies the requested bind domain, whether - *** runstatic (EXP_F_RUNSTATIC), runserver (EXP_F_RUNSERVER), or runclient - *** (EXP_F_RUNCLIENT). 'domain' can also be -0-, in which case we rebind + *** runstatic (EXPR_F_RUNSTATIC), runserver (EXPR_F_RUNSERVER), or runclient + *** (EXPR_F_RUNCLIENT). 'domain' can also be -0-, in which case we rebind *** a domainless expression. ***/ int @@ -1051,16 +1051,10 @@ expBindExpression(pExpression exp, pParamObjects objlist, int flags) break; } } - if (exp->ObjID == -1) - { - cm |= EXPR_MASK_EXTREF; - } - } - else if (exp->ObjID == -2 || exp->ObjID == -3) - { - if (exp->ObjID == -2) cm |= (1<<(objlist->CurrentID)); - if (exp->ObjID == -3) cm |= (1<<(objlist->ParentID)); + cm |= EXPR_MASK_EXTREF; } + else if (exp->ObjID == EXPR_CTL_CURRENT) cm |= (1<<(objlist->CurrentID)); + else if (exp->ObjID == EXPR_CTL_PARENT) cm |= (1<<(objlist->ParentID)); else if (exp->ObjID >= 0) { cm |= (1<<(exp->ObjID)); @@ -1084,4 +1078,3 @@ expBindExpression(pExpression exp, pParamObjects objlist, int flags) return cm; } - diff --git a/centrallix/expression/exp_double_metaphone.c b/centrallix/expression/exp_double_metaphone.c new file mode 100644 index 000000000..f3d76c49b --- /dev/null +++ b/centrallix/expression/exp_double_metaphone.c @@ -0,0 +1,1517 @@ +/************************************************************************/ +/* Text-DoubleMetaphone */ +/* Centrallix Core */ +/* */ +/* Copyright 2000, Maurice Aubrey . */ +/* All rights reserved. */ +/* */ +/* This code is copied for redistribution with modification, from the */ +/* gitpan/Text-DoubleMetaphone implementation on GitHub (1), which is */ +/* under the following license. */ +/* */ +/* This code is based heavily on the C++ implementation by Lawrence */ +/* Philips and incorporates several bug fixes courtesy of Kevin */ +/* Atkinson . */ +/* */ +/* This module is free software; you may redistribute it and/or */ +/* modify it under the same terms as Perl itself. */ +/* */ +/* A summary of the relevant content from https://dev.perl.org/licenses */ +/* has been included below for the convenience of the reader. This */ +/* was collected and saved on September 5th, 2025 and may not reflect */ +/* current information. For the most up to date information, please use */ +/* the link above. */ +/* */ +/* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ +/* */ +/* It is free software; you can redistribute it and/or modify it */ +/* under the terms of either: */ +/* */ +/* a) the GNU General Public License (2) as published by the Free */ +/* Software Foundation (3); either version 1 (2), or (at your */ +/* option) any later version (4), or */ +/* */ +/* b) the "Artistic License" (5). */ +/* */ +/* Citations: */ +/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ +/* 2: https://dev.perl.org/licenses/gpl1.html */ +/* 3: http://www.fsf.org */ +/* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ +/* 5: https://dev.perl.org/licenses/artistic.html */ +/* */ +/* Centrallix is published under the GNU General Public License, */ +/* satisfying the above requirement. A summary of this is included */ +/* below for the convenience of the reader. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: exp_double_metaphone.c */ +/* Author: Maurice Aubrey */ +/* Description: This module implements a "sounds like" algorithm */ +/* developed by Lawrence Philips which he published */ +/* in the June, 2000 issue of C/C++ Users Journal. */ +/* Double Metaphone is an improved version of Philips' */ +/* original Metaphone algorithm. */ +/************************************************************************/ + +/*** Note to future programmers reading this file (by Israel Fuller): + *** + *** This file was copied from a GitHub Repo with proper licensing (in case + *** you didn't read the legal stuff above), so feel free to check it out. + *** + *** As for this code, I've modified it to use styling and memory allocation + *** consistent with the rest of the Centrallix codebase. Also, I have added + *** documentation comments and extensive test cases (at the end of the file), + *** however, these reflect my own (possibly incorrect) understanding, which + *** might not line up with the original author. + *** + *** To be honest, though, trying to make this code as readable as possible + *** was very challanging due to all the messy boolean algebra. If there is + *** ever a professional linguist reading this, please factor out some of the + *** logic into local variables with descriptive names so that the rest of us + *** can read this code without our eyes glazing over. + *** + *** If you have any questions, please feel free to reach out to me or Greg. + *** + *** Original Source: https://github.com/gitpan/Text-meta_double_metaphone + ***/ + +#include +#include +#include +#include +#include +#include + +/*** If running in a testing environment, newmalloc is not + *** available, so we fall back to default C memory allocation. + ***/ +#ifndef TESTING +#include "cxlib/newmalloc.h" +#define META_MALLOC(size) nmSysMalloc(size) +#define META_REALLOC(ptr, size) nmSysRealloc(ptr, size) +#define META_FREE(ptr) nmSysFree(ptr) +#else +#include +#define META_MALLOC(size) malloc(size) +#define META_REALLOC(ptr, size) realloc(ptr, size) +#define META_FREE(ptr) free(ptr) +#endif + +/*** Helper function to handle checking for failed memory allocation + *** Author: Israel Fuller. + *** + *** @param ptr Pointer to the memory that should be allocated. + *** @param fname The name of the function invoked to allocate memory. + *** @param size The amount of memory being allocated. + *** @returns The pointer, for chaining. + ***/ +void* meta_check_allocation(void* ptr, const char* fname, const size_t size) + { + if (ptr == NULL) + { + /** Create the most descriptive error message we can. **/ + char error_buf[BUFSIZ]; + snprintf(error_buf, sizeof(error_buf), "exp_double_metaphone.c: Fail - %s(%lu)", fname, size); + perror(error_buf); + + // Throw error for easier locating in a debugger. + fprintf(stderr, "Program will now crash.\n"); + assert(0); + } + return ptr; + } + +/** Malloc shortcut macros. **/ +#define SAFE_MALLOC(size) \ + ({ \ + const size_t sz = (size); \ + memset(meta_check_allocation(META_MALLOC(sz), "META_MALLOC", sz), 0, sz); \ + }) +#define SAFE_REALLOC(ptr, size) \ + ({ \ + const size_t sz = (size); \ + meta_check_allocation(META_REALLOC(ptr, sz), "META_REALLOC", sz); \ + }) + +typedef struct + { + char* str; + size_t length; + size_t bufsize; + int free_str_on_destroy; + } +MetaString; + +/*** Allocates a new MetaString. + *** + *** @param init_str The initial size of the string. + *** @returns The new MetaString. + ***/ +MetaString* meta_new_string(const char* init_str) + { + MetaString *s; + char empty_string[] = ""; + + s = (MetaString*)SAFE_MALLOC(sizeof(MetaString)); + + if (init_str == NULL) + init_str = empty_string; + + s->length = strlen(init_str); + /** Preallocate a bit more for potential growth. **/ + s->bufsize = s->length + 7u; + + s->str = (char*)SAFE_MALLOC(s->bufsize * sizeof(char)); + + strncpy(s->str, init_str, s->length + 1); + s->free_str_on_destroy = 1; + + return s; + } + +/*** Frees a MetaString. + *** + *** @param s The MetaString. + ***/ +void meta_destroy_string(MetaString* s) + { + if (s == NULL) + return; + + if (s->free_str_on_destroy && s->str != NULL) + META_FREE(s->str); + + META_FREE(s); + } + +/*** Increases a MetaString's buffer size. + *** + *** @param s The MetaString* being modified. + *** @param chars_needed Minimumn number of characters to increase buffer size. + ***/ +void meta_increase_buffer(MetaString* s, const size_t chars_needed) + { + s->bufsize += chars_needed + 8u; + s->str = SAFE_REALLOC(s->str, s->bufsize * sizeof(char)); + } + +/*** Convert all characters of a MetaString to uppercase. + *** + *** @param s The MetaString being modified. + ***/ +void meta_make_upper(MetaString* s) + { + for (char* i = s->str; i[0] != '\0'; i++) + *i = (char)toupper(*i); + } + +/*** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + *** @returns 1 if the location is out of bounds for the MetaString, + *** 0 otherwise. + ***/ +bool meta_is_out_of_bounds(MetaString* s, unsigned int pos) + { + return (s->length <= pos); + } + +/*** Checks if a character in a MetaString is a vowel. + *** + *** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + ***/ +bool meta_is_vowel(MetaString* s, unsigned int pos) + { + if (meta_is_out_of_bounds(s, pos)) return 0; + + const char c = *(s->str + pos); + return ((c == 'A') || (c == 'E') || (c == 'I') || + (c == 'O') || (c == 'U') || (c == 'Y')); + } + +/*** Search a MetaString for "W", "K", "CZ", or "WITZ", which indicate that the + *** string is Slavo Germanic. + *** + *** @param s The MetaString to be searched. + *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. + ***/ +bool meta_is_slavo_germanic(MetaString* s) + { + return (strstr(s->str, "W") != NULL) + || (strstr(s->str, "K") != NULL) + || (strstr(s->str, "CZ") != NULL) + || (strstr(s->str, "WITZ") != NULL); + } + +/*** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + *** @returns The character at the position in the MetaString, or + *** '\0' if the position is not in the MetaString. + ***/ +char meta_get_char_at(MetaString* s, unsigned int pos) + { + return (meta_is_out_of_bounds(s, pos)) ? '\0' : ((char) *(s->str + pos)); + } + +/*** Checks for to see if any of a list of strings appear in a the given + *** MetaString after the given start position. + *** + *** @attention - Note that the START value is 0 based. + *** + *** @param s The MetaString being modified. + *** @param start The zero-based start of at which to begin searching + *** within the MetaString. + *** @param length The length of the character strings being checked. + *** @returns 1 if any of the character sequences appear after the start + *** in the MetaString and 0 otherwise. + ***/ +bool meta_is_str_at(MetaString* s, unsigned int start, ...) + { + va_list ap; + + /** Should never happen. **/ + if (meta_is_out_of_bounds(s, start)) + return 0; + + const char* pos = (s->str + start); + va_start(ap, start); + + char* test; + do + { + test = va_arg(ap, char*); + if (*test && (strncmp(pos, test, strlen(test)) == 0)) + return true; + } + while (test[0] != '\0'); + + va_end(ap); + + return false; + } + +/*** Adds a string to a MetaString, expanding the MetaString if needed. + *** + *** @param s The MetaString being modified. + *** @param new_str The string being added. + ***/ +void meta_add_str(MetaString* s, const char* new_str) + { + if (new_str == NULL) + return; + + const size_t add_length = strlen(new_str); + if ((s->length + add_length) > (s->bufsize - 1)) + meta_increase_buffer(s, add_length); + + strcat(s->str, new_str); + s->length += add_length; + } + +/*** Computes double metaphone. + *** + *** Example Usage: + *** ```c + *** char* primary_code; + *** char* secondary_code; + *** meta_double_metaphone(input, &primary_code, &secondary_code); + *** ``` + *** + *** @param str The string to compute. + *** @param primary_code A pointer to a buffer where the pointer to a string + *** containing the produced primary code will be stored. + *** @param secondary_code A pointer to a buffer where the pointer to a string + *** containing the produced secondary code will be stored. + ***/ +void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) + { + size_t length; + if (str == NULL || (length = strlen(str)) == 0u) { + fprintf(stderr, "Warning: Call to meta_double_metaphone() with invalid string.\n"); + + /** Double Metaphone on an invalid string yeilds two empty strings. **/ + *primary_code = (char*)SAFE_MALLOC(sizeof(char)); + *secondary_code = (char*)SAFE_MALLOC(sizeof(char)); + return; + } + unsigned int current = 0; + unsigned int last = (unsigned int)(length - 1); + + /** Pad original so we can index beyond end. **/ + MetaString* original = meta_new_string(str); + meta_make_upper(original); + meta_add_str(original, " "); + + MetaString* primary = meta_new_string(""); + MetaString* secondary = meta_new_string(""); + primary->free_str_on_destroy = 0; + secondary->free_str_on_destroy = 0; + + /** Skip these if they are at start of a word. **/ + if (meta_is_str_at(original, 0, "GN", "KN", "PN", "WR", "PS", "")) + current += 1; + + /** Initial 'X' is pronounced 'Z' e.g. 'Xavier' **/ + const char first_char = meta_get_char_at(original, 0); + if (first_char == 'X') + { + meta_add_str(primary, "S"); /* 'Z' maps to 'S' */ + meta_add_str(secondary, "S"); + current += 1; + } + + /** Precomputing this is useful. **/ + const bool is_slavo_germanic = meta_is_slavo_germanic(original); + + /** Main loop. **/ + while (current < length) + { + const char cur_char = meta_get_char_at(original, current); + const char next_char = meta_get_char_at(original, current + 1); + switch (cur_char) + { + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + case 'Y': + { + if (current == 0) + { + /** All init vowels now map to 'A'. **/ + meta_add_str(primary, "A"); + meta_add_str(secondary, "A"); + } + current += 1; + break; + } + + case 'B': + { + /** "-mb", e.g", "dumb", already skipped over... **/ + meta_add_str(primary, "P"); + meta_add_str(secondary, "P"); + + current += (next_char == 'B') ? 2 : 1; + break; + } + + case 'C': + { + /** Various germanic. **/ + if ( + (current > 1) + && !meta_is_vowel(original, current - 2) + && meta_is_str_at(original, (current - 1), "ACH", "") + && meta_get_char_at(original, current + 2) != 'I' + && ( + meta_get_char_at(original, current + 2) != 'E' + || meta_is_str_at(original, (current - 2), "BACHER", "MACHER", "") + ) + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + /** Special case 'caesar' **/ + if (current == 0 && meta_is_str_at(original, current, "CAESAR", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "S"); + current += 2; + break; + } + + /** Italian 'chianti' **/ + if (meta_is_str_at(original, current, "CHIA", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + if (meta_is_str_at(original, current, "CH", "")) + { + /** Find 'michael' **/ + if (current > 0 && meta_is_str_at(original, current, "CHAE", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "X"); + current += 2; + break; + } + + /** Greek roots e.g. 'chemistry', 'chorus' **/ + if ( + current == 0 + && meta_is_str_at(original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") + && !meta_is_str_at(original, 0, "CHORE", "") + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + /** Germanic, greek, or otherwise 'ch' for 'kh' sound. */ + if ( + meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + /** 'architect but not 'arch', 'orchestra', 'orchid' **/ + || meta_is_str_at(original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") + || meta_is_str_at(original, (current + 2), "T", "S", "") + || ( + (current == 0 || meta_is_str_at(original, (current - 1), "A", "O", "U", "E", "")) + /** e.g., 'wachtler', 'wechsler', but not 'tichner' **/ + && meta_is_str_at(original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") + ) + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + else + { + if (current > 0) + { + if (meta_is_str_at(original, 0, "MC", "")) + { + /* e.g., "McHugh" */ + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + else + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "K"); + } + } + else + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + } + } + current += 2; + break; + } + + /** e.g, 'czerny' **/ + if (meta_is_str_at(original, current, "CZ", "") + && !meta_is_str_at(original, (current - 2), "WICZ", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + current += 2; + break; + } + + /** e.g., 'focaccia' **/ + if (meta_is_str_at(original, (current + 1), "CIA", "")) + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + current += 3; + break; + } + + /** Double 'C' rule. **/ + if ( + meta_is_str_at(original, current, "CC", "") + && !(current == 1 && first_char == 'M') /* McClellan exception. */ + ) + { + /** 'bellocchio' but not 'bacchus' **/ + if ( + meta_is_str_at(original, (current + 2), "I", "E", "H", "") + && !meta_is_str_at(original, (current + 2), "HU", "") + ) + { + /** 'accident', 'accede' 'succeed' **/ + if ( + (current == 1 && meta_get_char_at(original, current - 1) == 'A') + || meta_is_str_at(original, (current - 1), "UCCEE", "UCCES", "") + ) + { + meta_add_str(primary, "KS"); + meta_add_str(secondary, "KS"); + /** 'bacci', 'bertucci', other italian **/ + } + else + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + } + current += 3; + break; + } + else + { /** Pierce's rule **/ + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + } + + if (meta_is_str_at(original, current, "CK", "CG", "CQ", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + if (meta_is_str_at(original, current, "CI", "CE", "CY", "")) + { + /* Italian vs. English */ + if (meta_is_str_at(original, current, "CIO", "CIE", "CIA", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + } + else + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "S"); + } + current += 2; + break; + } + + /** else **/ + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + + /** Name sent in 'mac caffrey', 'mac gregor **/ + if (meta_is_str_at(original, (current + 1), " C", " Q", " G", "")) + current += 3; + else if (meta_is_str_at(original, (current + 1), "C", "K", "Q", "") + && !meta_is_str_at(original, (current + 1), "CE", "CI", "")) + current += 2; + else + current += 1; + break; + } + + case 'D': + { + if (meta_is_str_at(original, current, "DG", "")) + { + if (meta_is_str_at(original, (current + 2), "I", "E", "Y", "")) + { + /** e.g. 'edge' **/ + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + current += 3; + break; + } + else + { + /** e.g. 'edgar' **/ + meta_add_str(primary, "TK"); + meta_add_str(secondary, "TK"); + current += 2; + break; + } + } + + if (meta_is_str_at(original, current, "DT", "DD", "")) + { + meta_add_str(primary, "T"); + meta_add_str(secondary, "T"); + current += 2; + break; + } + + /** else **/ + meta_add_str(primary, "T"); + meta_add_str(secondary, "T"); + current += 1; + break; + } + + case 'F': + { + current += (next_char == 'F') ? 2 : 1; + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + break; + } + + case 'G': + { + if (next_char == 'H') + { + /** 'Vghee' */ + if (current > 0 && !meta_is_vowel(original, (current - 1))) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + if (current < 3) + { + /** 'ghislane', 'ghiradelli' **/ + if (current == 0) + { + if (meta_get_char_at(original, (current + 2)) == 'I') + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + } + else + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + current += 2; + break; + } + } + + if ( + /** Parker's rule (with some further refinements) - e.g., 'hugh' **/ + (current > 1 && meta_is_str_at(original, (current - 2), "B", "H", "D", "")) + /** e.g., 'bough' **/ + || (current > 2 && meta_is_str_at(original, (current - 3), "B", "H", "D", "")) + /** e.g., 'broughton' **/ + || (current > 3 && meta_is_str_at(original, (current - 4), "B", "H", "")) + ) + { + current += 2; + break; + } + else + { + /** e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough' **/ + if ( + current > 2 + && meta_get_char_at(original, (current - 1)) == 'U' + && meta_is_str_at(original, (current - 3), "C", "G", "L", "R", "T", "") + ) + { + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + } + else if (current > 0 && meta_get_char_at(original, (current - 1)) != 'I') + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + + current += 2; + break; + } + } + + if (next_char == 'N') + { + if (current == 1 && !is_slavo_germanic && meta_is_vowel(original, 0)) + { + meta_add_str(primary, "KN"); + meta_add_str(secondary, "N"); + } + else + /** not e.g. 'cagney' **/ + if ( + next_char != 'Y' + && !is_slavo_germanic + && !meta_is_str_at(original, (current + 2), "EY", "") + ) + { + meta_add_str(primary, "N"); + meta_add_str(secondary, "KN"); + } + else + { + meta_add_str(primary, "KN"); + meta_add_str(secondary, "KN"); + } + current += 2; + break; + } + + /** 'tagliaro' **/ + if ( + !is_slavo_germanic + && meta_is_str_at(original, (current + 1), "LI", "") + ) + { + meta_add_str(primary, "KL"); + meta_add_str(secondary, "L"); + current += 2; + break; + } + + /** -ges-,-gep-,-gel-, -gie- at beginning **/ + if ( + current == 0 + && ( + next_char == 'Y' + || meta_is_str_at( + original, (current + 1), + "ES", "EP", "EB", "EL", "EY", "IB", + "IL", "IN", "IE", "EI", "ER", "" + ) + ) + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "J"); + current += 2; + break; + } + + /** -ger-, -gy- **/ + if ( + (next_char == 'Y' || meta_is_str_at(original, (current + 1), "ER", "")) + /** Exceptions. **/ + && !meta_is_str_at(original, 0, "DANGER", "RANGER", "MANGER", "") + && !meta_is_str_at(original, (current - 1), "E", "I", "RGY", "OGY", "") + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "J"); + current += 2; + break; + } + + /** Italian e.g, 'biaggi' **/ + if ( + meta_is_str_at(original, (current + 1), "E", "I", "Y", "") + || meta_is_str_at(original, (current - 1), "AGGI", "OGGI", "") + ) + { + /** Obvious germanic. **/ + if (meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + || meta_is_str_at(original, (current + 1), "ET", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + else + { + /** Always soft, if french ending. **/ + if (meta_is_str_at(original, (current + 1), "IER ", "")) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + } + else + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "K"); + } + } + current += 2; + break; + } + + current += (next_char == 'G') ? 2 : 1; + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + break; + } + + case 'H': + { + /** Only keep if first & before vowel or between 2 vowels. **/ + if ( + (current == 0 || meta_is_vowel(original, (current - 1))) + && meta_is_vowel(original, current + 1) + ) + { + meta_add_str(primary, "H"); + meta_add_str(secondary, "H"); + current += 2; + } + else /* also takes care of 'HH' */ + current += 1; + break; + } + + case 'J': + { + /** Obvious spanish, 'jose', 'san jacinto' **/ + const bool has_jose_next = meta_is_str_at(original, current, "JOSE", ""); + const bool starts_with_san = meta_is_str_at(original, 0, "SAN ", ""); + if (has_jose_next || starts_with_san) + { + if ( + starts_with_san + /** I don't know what this condition means. **/ + || (current == 0 && meta_get_char_at(original, current + 4) == ' ') + ) + { + meta_add_str(primary, "H"); + meta_add_str(secondary, "H"); + } + else + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "H"); + } + current += 1; + break; + } + + if (current == 0 && !has_jose_next) + { + meta_add_str(primary, "J"); /* Yankelovich/Jankelowicz */ + meta_add_str(secondary, "A"); + } + else + { + /** spanish pron. of e.g. 'bajador' **/ + if ( + !is_slavo_germanic + && (next_char == 'A' || next_char == 'O') + && meta_is_vowel(original, (current - 1)) + ) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "H"); + } + else + { + if (current == last) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, ""); + } + else + { + if ( + !meta_is_str_at(original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") + && !meta_is_str_at(original, (current - 1), "S", "K", "L", "") + ) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + } + } + } + } + + current += (next_char == 'J') ? 2 : 1; + break; + } + + case 'K': + { + current += (next_char == 'K') ? 2 : 1; + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + break; + } + + case 'L': + { + if (next_char == 'L') + { + /** Spanish e.g. 'cabrillo', 'gallegos' **/ + if ( + ( + current == length - 3 + && meta_is_str_at(original, (current - 1), "ILLO", "ILLA", "ALLE", "") + ) + || ( + meta_is_str_at(original, (current - 1), "ALLE", "") + && ( + meta_is_str_at(original, (last - 1), "AS", "OS", "") + || meta_is_str_at(original, last, "A", "O", "") + ) + ) + ) + { + meta_add_str(primary, "L"); + meta_add_str(secondary, ""); + current += 2; + break; + } + current += 2; + } + else + current += 1; + meta_add_str(primary, "L"); + meta_add_str(secondary, "L"); + break; + } + + case 'M': + { + current += ( + ( + meta_is_str_at(original, (current - 1), "UMB", "") + && (current + 1 == last || meta_is_str_at(original, (current + 2), "ER", "")) + ) + /** 'dumb','thumb' **/ + || next_char == 'M' + ) ? 2 : 1; + meta_add_str(primary, "M"); + meta_add_str(secondary, "M"); + break; + } + + case 'N': + { + current += (next_char == 'N') ? 2 : 1; + meta_add_str(primary, "N"); + meta_add_str(secondary, "N"); + break; + } + + case 'P': + { + if (next_char == 'H') + { + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + current += 2; + break; + } + + /** Also account for "campbell", "raspberry" **/ + current += (meta_is_str_at(original, (current + 1), "P", "B", "")) ? 2 : 1; + meta_add_str(primary, "P"); + meta_add_str(secondary, "P"); + break; + } + + case 'Q': + { + current += (next_char == 'Q') ? 2 : 1; + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + break; + } + + case 'R': + { + /** French e.g. 'rogier', but exclude 'hochmeier' **/ + const bool no_primary = ( + !is_slavo_germanic + && current == last + && meta_is_str_at(original, (current - 2), "IE", "") + && !meta_is_str_at(original, (current - 4), "ME", "MA", "") + ); + + meta_add_str(primary, (no_primary) ? "" : "R"); + meta_add_str(secondary, "R"); + current += (next_char == 'R') ? 2 : 1; + break; + } + + case 'S': + { + /** Special cases 'island', 'isle', 'carlisle', 'carlysle' **/ + if (meta_is_str_at(original, (current - 1), "ISL", "YSL", "")) + { + current += 1; + break; + } + + /** Special case 'sugar-' **/ + if (current == 0 && meta_is_str_at(original, current, "SUGAR", "")) + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "S"); + current += 1; + break; + } + + if (meta_is_str_at(original, current, "SH", "")) + { + const bool germanic = meta_is_str_at(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); + const char* sound = (germanic) ? "S" : "X"; + meta_add_str(primary, sound); + meta_add_str(secondary, sound); + current += 2; + break; + } + + /** Italian & Armenian. **/ + if (meta_is_str_at(original, current, "SIO", "SIA", "SIAN", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, (is_slavo_germanic) ? "S" : "X"); + current += 3; + break; + } + + /** german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' **/ + /** also, -sz- in slavic language altho in hungarian it is pronounced 's' **/ + if (current == 0 && meta_is_str_at(original, (current + 1), "M", "N", "L", "W", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + current += 1; + break; + } + if (meta_is_str_at(original, (current + 1), "Z", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + current += 2; + break; + } + + if (meta_is_str_at(original, current, "SC", "")) + { + /** Schlesinger's rule. **/ + if (meta_get_char_at(original, current + 2) == 'H') + { + /** Dutch origin, e.g. 'school', 'schooner' **/ + if (meta_is_str_at(original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) + { + /** 'schermerhorn', 'schenker' **/ + const bool x_sound = meta_is_str_at(original, (current + 3), "ER", "EN", ""); + meta_add_str(primary, (x_sound) ? "X" : "SK"); + meta_add_str(secondary, "SK"); + current += 3; + break; + } + else + { + const bool s_sound = ( + current == 0 + && !meta_is_vowel(original, 3) + && meta_get_char_at(original, 3) != 'W' + ); + meta_add_str(primary, "X"); + meta_add_str(secondary, (s_sound) ? "S" : "X"); + current += 3; + break; + } + } + + /** Default case. **/ + const char* sound = (meta_is_str_at(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; + meta_add_str(primary, sound); + meta_add_str(secondary, sound); + current += 3; + break; + } + + /** French e.g. 'resnais', 'artois' **/ + const bool no_primary = (current == last && meta_is_str_at(original, (current - 2), "AI", "OI", "")); + meta_add_str(primary, (no_primary) ? "" : "S"); + meta_add_str(secondary, "S"); + current += (meta_is_str_at(original, (current + 1), "S", "Z", "")) ? 2 : 1; + break; + } + + case 'T': + { + if (meta_is_str_at(original, current, "TIA", "TCH", "TION", "")) + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + current += 3; + break; + } + + if (meta_is_str_at(original, current, "TH", "TTH", "")) + { + /** Special case 'thomas', 'thames' or germanic. **/ + if ( + meta_is_str_at(original, (current + 2), "OM", "AM", "") + || meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + ) + meta_add_str(primary, "T"); + else + meta_add_str(primary, "0"); /* Yes, zero. */ + meta_add_str(secondary, "T"); + current += 2; + break; + } + + meta_add_str(primary, "T"); + meta_add_str(secondary, "T"); + current += (meta_is_str_at(original, (current + 1), "T", "D", "")) ? 2 : 1; + break; + } + + case 'V': + { + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + current += (next_char == 'V') ? 2 : 1; + break; + } + + case 'W': + { + /** Can also be in middle of word. **/ + if (meta_is_str_at(original, current, "WR", "")) + { + meta_add_str(primary, "R"); + meta_add_str(secondary, "R"); + current += 2; + break; + } + + const bool next_is_vowel = meta_is_vowel(original, current + 1); + if (current == 0 && (next_is_vowel || meta_is_str_at(original, current, "WH", ""))) + { + /** Wasserman should match Vasserman. **/ + meta_add_str(primary, "A"); + meta_add_str(secondary, (next_is_vowel) ? "F" : "A"); + } + + /** Arnow should match Arnoff. **/ + if ((current == last && meta_is_vowel(original, current - 1)) + || meta_is_str_at(original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") + || meta_is_str_at(original, 0, "SCH", "") + ) + { + meta_add_str(primary, ""); + meta_add_str(secondary, "F"); + current += 1; + break; + } + + /** Polish e.g. 'filipowicz' **/ + if (meta_is_str_at(original, current, "WICZ", "WITZ", "")) + { + meta_add_str(primary, "TS"); + meta_add_str(secondary, "FX"); + current += 4; + break; + } + + /** Else skip it. **/ + current += 1; + break; + } + + case 'X': + { + /** French e.g. breaux **/ + const bool silent = ( + current == last + && ( + meta_is_str_at(original, (current - 2), "AU", "OU", "") + || meta_is_str_at(original, (current - 3), "IAU", "EAU", "") + ) + ); + if (!silent) + { + meta_add_str(primary, "KS"); + meta_add_str(secondary, "KS"); + } + + current += (meta_is_str_at(original, (current + 1), "C", "X", "")) ? 2 : 1; + break; + } + + case 'Z': + { + /** Chinese pinyin e.g. 'zhao' **/ + if (next_char == 'H') + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + current += 2; + break; + } + + const bool has_t_sound = ( + meta_is_str_at(original, (current + 1), "ZO", "ZI", "ZA", "") + || (is_slavo_germanic && current > 0 && meta_get_char_at(original, (current - 1)) != 'T') + ); + meta_add_str(primary, "S"); + meta_add_str(secondary, (has_t_sound) ? "TS" : "S"); + current += (next_char == 'Z') ? 2 : 1; + break; + } + + default: + current += 1; + } + } + + *primary_code = primary->str; + *secondary_code = secondary->str; + + meta_destroy_string(original); + meta_destroy_string(primary); + meta_destroy_string(secondary); + } + +#ifdef TESTING +/*** Built in test cases. + *** + *** These tests have been integrated into the Centrallix testing environment, + *** where they can be run using `export TONLY=expfn_double_metaphone_00`, + *** followed by make test, in the Centrallix directory. + *** + *** The can also be run here by executing the following commands in the + *** centrallix/expression directory, which aditionally generates a coverage + *** report. These tests cover all parts of the double metaphone algorithm, + *** although some of the error cases in various helper functions (such as + *** meta_destroy_string(null)) are not covered by testing. + *** + *** Commands: + *** gcc exp_double_metaphone.c -o exp_double_metaphone.o -I .. -DTESTING -fprofile-arcs -ftest-coverage -O0 + *** ./exp_double_metaphone.o + *** gcov exp_double_metaphone.c + ***/ + +unsigned int num_tests_passed = 0u, num_tests_failed = 0u; + +void test(const char* input, const char* expected_primary, const char* expected_secondary) { + char* codes[2]; + + /** Run DoubleMetaphone() and extract results. **/ + char* actual_primary; + char* actual_secondary; + meta_double_metaphone( + input, + memset(&actual_primary, 0, sizeof(actual_primary)), + memset(&actual_secondary, 0, sizeof(actual_secondary)) + ); + + /** Test for correct value. **/ + if (!strcmp(expected_primary, actual_primary) && + !strcmp(expected_secondary, actual_secondary)) + num_tests_passed++; + else + { + printf( + "\nTEST FAILED: \"%s\"\n" + "Expected: %s %s\n" + "Actual: %s %s\n", + input, + expected_primary, expected_secondary, + actual_primary, actual_secondary + ); + num_tests_failed++; + } + } + +// Special thanks to the following websites for double checking the correct results: +// 1: https://words.github.io/double-metaphone +// 2: https://mainegenealogy.net/metaphone_converter.asp +// 3: https://en.toolpage.org/tool/metaphone +void run_tests(void) { + printf("\nRunning tests...\n"); + + /** Test that always fails. **/ + // test("This", "test", "fails."); + + /** Invalid string tests, by Israel. **/ + fprintf(stderr, "There should be two warnings after this line.\n"); + test(NULL, "", ""); + test("", "", ""); + + /** Basic tests, by Israel. **/ + test("Test", "TST", "TST"); + test("Basic", "PSK", "PSK"); + test("Centrallix", "SNTRLKS", "SNTRLKS"); + test("Lawrence", "LRNS", "LRNS"); + test("Philips", "FLPS", "FLPS"); + test("Acceptingness", "AKSPTNNS", "AKSPTNKNS"); + test("Supercalifragilisticexpialidocious", "SPRKLFRJLSTSKSPLTSS", "SPRKLFRKLSTSKSPLTXS"); + test("Suoicodilaipxecitsiligarfilacrepus", "SKTLPKSSTSLKRFLKRPS", "SKTLPKSSTSLKRFLKRPS"); + + /** Match tests from code comments above. **/ + test("Smith", "SM0", "XMT"); + test("Schmidt", "XMT", "SMT"); + test("Snider", "SNTR", "XNTR"); + test("Schneider", "XNTR", "SNTR"); + test("Arnow", "ARN", "ARNF"); + test("Arnoff", "ARNF", "ARNF"); + + /** Tests from examples in code comments above. **/ + test("Accede", "AKST", "AKST"); + test("Accident", "AKSTNT", "AKSTNT"); + test("Actually", "AKTL", "AKTL"); + test("Arch", "ARX", "ARK"); + test("Artois", "ART", "ARTS"); + test("Bacchus", "PKS", "PKS"); + test("Bacci", "PX", "PX"); + test("Bajador", "PJTR", "PHTR"); + test("Bellocchio", "PLX", "PLX"); + test("Bertucci", "PRTX", "PRTX"); + test("Biaggi", "PJ", "PK"); + test("Bough", "P", "P"); + test("Breaux", "PR", "PR"); + test("Broughton", "PRTN", "PRTN"); + test("Cabrillo", "KPRL", "KPR"); + test("Caesar", "SSR", "SSR"); + test("Cagney", "KKN", "KKN"); + test("Campbell", "KMPL", "KMPL"); + test("Carlisle", "KRLL", "KRLL"); + test("Carlysle", "KRLL", "KRLL"); + test("Chemistry", "KMSTR", "KMSTR"); + test("Chianti", "KNT", "KNT"); + test("Chorus", "KRS", "KRS"); + test("Cough", "KF", "KF"); + test("Czerny", "SRN", "XRN"); + test("Dumb", "TM", "TM"); + test("Edgar", "ATKR", "ATKR"); + test("Edge", "AJ", "AJ"); + test("Filipowicz", "FLPTS", "FLPFX"); + test("Focaccia", "FKX", "FKX"); + test("Gallegos", "KLKS", "KKS"); + test("Germanic", "KRMNK", "JRMNK"); + test("Ghiradelli", "JRTL", "JRTL"); + test("Ghislane", "JLN", "JLN"); + test("Gospel", "KSPL", "KSPL"); + test("Gough", "KF", "KF"); + test("Greek", "KRK", "KRK"); + test("Hochmeier", "HKMR", "HKMR"); + test("Hugh", "H", "H"); + test("Island", "ALNT", "ALNT"); + test("Isle", "AL", "AL"); + test("Italian", "ATLN", "ATLN"); + test("Jankelowicz", "JNKLTS", "ANKLFX"); + test("Jose", "HS", "HS"); + test("Laugh", "LF", "LF"); + test("Mac Caffrey", "MKFR", "MKFR"); + test("Mac Gregor", "MKRKR", "MKRKR"); + test("Manager", "MNKR", "MNJR"); + test("McHugh", "MK", "MK"); + test("McLaughlin", "MKLFLN", "MKLFLN"); + test("Michael", "MKL", "MXL"); + test("Middle", "MTL", "MTL"); + test("Orchestra", "ARKSTR", "ARKSTR"); + test("Orchid", "ARKT", "ARKT"); + test("Pinyin", "PNN", "PNN"); + test("Raspberry", "RSPR", "RSPR"); + test("Resnais", "RSN", "RSNS"); + test("Rogier", "RJ", "RJR"); + test("Rough", "RF", "RF"); + test("Salvador", "SLFTR", "SLFTR"); + test("San jacinto", "SNHSNT", "SNHSNT"); + test("Schenker", "XNKR", "SKNKR"); + test("Schermerhorn", "XRMRRN", "SKRMRRN"); + test("Schlesinger", "XLSNKR", "SLSNJR"); + test("School", "SKL", "SKL"); + test("Schooner", "SKNR", "SKNR"); + test("Succeed", "SKST", "SKST"); + test("Sugar", "XKR", "SKR"); + test("Sugary", "XKR", "SKR"); + test("Tagliaro", "TKLR", "TLR"); + test("Thames", "TMS", "TMS"); + test("Thomas", "TMS", "TMS"); + test("Thumb", "0M", "TM"); + test("Tichner", "TXNR", "TKNR"); + test("Tough", "TF", "TF"); + test("Vghee", "FK", "FK"); + test("Wachtler", "AKTLR", "FKTLR"); + test("Wechsler", "AKSLR", "FKSLR"); + test("Word", "ART", "FRT"); + test("Xavier", "SF", "SFR"); + test("Yankelovich", "ANKLFX", "ANKLFK"); + test("Zhao", "J", "J"); + + /** Intereesting Edge Case: "McClellan" **/ + /*** Note: Sources (1) and (3) both include a double K ("MKKLLN"), but the + *** original code on GitHub and mainegenealogy.net do not. I chose "MKLLN" + *** to be correct because I personally do not pronounce the second c. + ***/ + test("McClellan", "MKLLN", "MKLLN"); + + /** Maurice Aubrey's Tests. **/ + /** Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt **/ + test("maurice", "MRS", "MRS"); + test("aubrey", "APR", "APR"); + test("cambrillo", "KMPRL", "KMPR"); + test("heidi", "HT", "HT"); + test("katherine", "K0RN", "KTRN"); + test("catherine", "K0RN", "KTRN"); + test("richard", "RXRT", "RKRT"); + test("bob", "PP", "PP"); + test("eric", "ARK", "ARK"); + test("geoff", "JF", "KF"); + test("dave", "TF", "TF"); + test("ray", "R", "R"); + test("steven", "STFN", "STFN"); + test("bryce", "PRS", "PRS"); + test("randy", "RNT", "RNT"); + test("bryan", "PRN", "PRN"); + test("brian", "PRN", "PRN"); + test("otto", "AT", "AT"); + test("auto", "AT", "AT"); + + /** GPT-5 Coverage Tests. **/ + /*** GPT-5 mini (Preview) running in GitHub Copilot suggested the words + *** after analizing a generated coverage report, and I (Israel) used + *** them to write the tests below. I kept the AI's reasoning for tests, + *** while removing tests that did not contribute any coverage, but after + *** a few reprompts, the AI started just giving words without reasoning. + *** I guess we were both getting pretty tired. + ***/ + test("Abbott", "APT", "APT"); /* double-B ("BB") handling. */ + test("Back", "PK", "PK"); /* "CK"/"CG"/"CQ" branch. */ + test("Bacher", "PKR", "PKR"); /* matches "...BACHER" / ACH special-case. */ + test("Charles", "XRLS", "XRLS"); /* initial "CH" -> the branch that maps to "X"/"X" at start. */ + test("Ghana", "KN", "KN"); /* initial "GH" special-start handling. */ + test("Gnome", "NM", "NM"); /* "GN" sequence handling. */ + test("Raj", "RJ", "R"); /* J at end (exercise J-last behavior). */ + test("Quentin", "KNTN", "KNTN"); /* Q case (Q -> K mapping). */ + test("Who", "A", "A"); /* "WH" at start handling. */ + test("Shoemaker", "XMKR", "XMKR"); /* "SH" general mapping paths. */ + test("Sian", "SN", "XN"); /* "SIO"/"SIA"/"SIAN" branch. */ + test("Scold", "SKLT", "SKLT"); /* "SC" default / "SK" vs other SC subcases. */ + test("Station", "STXN", "STXN"); /* "TION" -> X mapping. */ + test("Match", "MX", "MX"); /* "TCH"/"TIA" -> X mapping. */ + test("Pizza", "PS", "PTS"); /* double-Z ("ZZ") handling. */ + test("Agnes", "AKNS", "ANS"); /* "GN" at index 1 (GN handling that yields KN / N). */ + test("Science", "SNS", "SNS"); /* "SC" followed by I (SC + I/E/Y branch). */ + test("Van Gogh", "FNKK", "FNKK"); + test("Josef", "JSF", "HSF"); + test("Object", "APJKT", "APJKT"); + test("Sholz", "SLS", "SLS"); + test("Scharf", "XRF", "XRF"); + test("Kasia", "KS", "KS"); + test("Van Geller", "FNKLR", "FNKLR"); + + const unsigned int total_tests = num_tests_passed + num_tests_failed; + printf("\nTests completed!\n"); + printf(" > Failed: %u\n", num_tests_failed); + printf(" > Skipped: %u\n", 0u); /* Implementation removed. */ + printf(" > Passed: %u/%u\n", num_tests_passed, total_tests); +} + +int main(void) { + run_tests(); + return 0; +} + +/** Prevent scope leak. **/ +#undef META_FREE +#undef META_MALLOC +#undef META_REALLOC +#undef SAFE_MALLOC +#undef SAFE_REALLOC + +#endif diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 6425114db..df55559be 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1,27 +1,3 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include "obj.h" -#include "cxlib/mtask.h" -#include "cxlib/xarray.h" -#include "cxlib/xhash.h" -#include "cxlib/mtlexer.h" -#include "expression.h" -#include "cxlib/mtsession.h" -#include "cxss/cxss.h" -#include -#include -#include -#include -#include - - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ @@ -65,6 +41,48 @@ /* that issue in exp_evaluate.c */ /************************************************************************/ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cxlib/clusters.h" +#include "cxlib/mtask.h" +#include "cxlib/mtlexer.h" +#include "cxlib/mtsession.h" +#include "cxlib/newmalloc.h" +#include "cxlib/xarray.h" +#include "cxlib/xhash.h" +#include "cxss/cxss.h" +#include "expression.h" +#include "obj.h" + +/** Duplocate detection settings. **/ +// #define SEPARATOR "|" +// #define SEPARATOR_CHAR '|' +// #define DBL_BUF_SIZE 16u +// #define USE_PARALLEL_COMPLETE_SEARCH true +// #define MIN_PARALLEL_COMPLETE_SEARCH 1000 +// #define MAX_COMPLETE_SEARCH 50 * 1000 // Default: 100 * 1000 +// #define KMEANS_IMPROVEMENT_THRESHOLD 0.0002 +#define EXP_NUM_DIMS 251 /* aka. The size of the vector table. */ +const int EXP_VECTOR_TABLE_SIZE = EXP_NUM_DIMS; /* Should probably be removed. */ /****** Evaluator functions follow for expEvalFunction ******/ @@ -1111,7 +1129,7 @@ int exp_fn_reverse(pExpression tree, pParamObjects objlist, pExpression i0, pExp return 0; } - +/** Leading zero trim. */ int exp_fn_lztrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { char* ptr; @@ -1337,6 +1355,7 @@ int exp_fn_ralign(pExpression tree, pParamObjects objlist, pExpression i0, pExpr tree->Alloc = 0; tree->String = tree->Types.StringBuf; } + /** Possible overflow? **/ sprintf(tree->String,"%*.*s",i1->Integer,i1->Integer,i0->String); } return 0; @@ -4119,9 +4138,6 @@ int exp_fn_lev_compare(pExpression tree, pParamObjects objlist, pExpression i0, return 0; } -// This is the size of the vector table. It is also used in calculating the table indices. -const int EXP_VECTOR_TABLE_SIZE = 251; - /* * hash_char_pair * This method creates an vector table index based a given character pair. The characters are represented @@ -4151,6 +4167,8 @@ int exp_fn_i_hash_char_pair(double num1, double num2) * * Returns: * 0 + * + * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_i_frequency_table */ int exp_fn_i_frequency_table(unsigned short *table, char *term) { @@ -4233,7 +4251,9 @@ int exp_fn_i_frequency_table(unsigned short *table, char *term) * r_freq_table2 : the second vector (unsigned short) * * Returns: - * 0 + * 0 + * + * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_i_dot_product */ int exp_fn_i_dot_product(double *dot_product, unsigned short *r_freq_table1, unsigned short *r_freq_table2) { @@ -4252,6 +4272,8 @@ int exp_fn_i_dot_product(double *dot_product, unsigned short *r_freq_table1, uns * Parameters: * magnitude : the place where the result is stored (double) * r_freq_table : the vector (unsigned short) + * + * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_i_magnitude */ int exp_fn_i_magnitude(double *magnitude, unsigned short *r_freq_table) { @@ -4271,13 +4293,15 @@ int exp_fn_i_magnitude(double *magnitude, unsigned short *r_freq_table) * * Parameters: * tree : structure where output is stored - * objlist: + * objlist : unused * i0 : first data entry (pExpression) * i1 : second data entry (pExpression) - * i2 : + * i2 : unused * * Returns: - * 0 + * 0 + * + * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_similarity */ int exp_fn_cos_compare(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { @@ -4343,6 +4367,1722 @@ int exp_fn_cos_compare(pExpression tree, pParamObjects objlist, pExpression i0, return 0; } +// /*** ========================= +// *** DUPE SECTION +// *** By: Israel Fuller +// *** Last Updated: September, 2025 +// *** +// *** This section of the file deals with finding duplocates. +// ***/ + +// /*** @brief Returns the smaller of two values. +// *** +// *** @param a The first value. +// *** @param b The second value. +// *** @return The smaller of the two values. +// *** +// *** @note This macro uses GNU C extensions and is type-safe. +// ***/ +// #define min(a, b) ({ \ +// __typeof__ (a) _a = (a); \ +// __typeof__ (b) _b = (b); \ +// (_a < _b) ? _a : _b; \ +// }) + +// /*** @brief Returns the larger of two values. +// *** +// *** @param a The first value. +// *** @param b The second value. +// *** @return The larger of the two values. +// *** +// *** @note This macro uses GNU C extensions and is type-safe. +// ***/ +// #define max(a, b) ({ \ +// __typeof__ (a) _a = (a); \ +// __typeof__ (b) _b = (b); \ +// (_a > _b) ? _a : _b; \ +// }) + +// /** The character used to create a pair with the first and last characters of a string. **/ +// #define EXP_BOUNDARY_CHAR ('a' - 1) + +// /*** Helpful error handling function. **/ +// void mssErrorf(int clr, char* module, const char* format, ...); + +// /*** Gets the hash, representing a pair of ASCII characters, represented by unsigned ints. +// *** +// *** @param num1 The first character in the pair. +// *** @param num1 The second character in the pair. +// *** @returns The resulting hash. +// ***/ +// unsigned int exp_fn_get_char_pair_hash(const unsigned int num1, const unsigned int num2) +// { +// if (num1 == EXP_BOUNDARY_CHAR && num2 == EXP_BOUNDARY_CHAR) +// { +// mssErrorf(1, "EXP", +// "exp_fn_get_char_pair_hash(%u, %u) - Warning: Pair of boundary characters.", +// num1, num2 +// ); +// } +// const double sum = (num1 * num1 * num1) + (num2 * num2 * num2); +// const double scale = ((double)num1 + 1.0) / ((double)num2 + 1.0); +// const unsigned int hash = (unsigned int)round(sum * scale) - 1u; +// return hash % EXP_NUM_DIMS; +// } + +// /*** Builds a vector using a string. +// *** +// *** Vectors are based on the frequencies of character pairs in the string. +// *** Space characters and punctuation characters (see code for list) are ignored, +// *** and all characters are converted to lowercase. Character 96, which is just +// *** before 'a' in the ASCII table (and maps to '`') is used to make pairs on the +// *** start and end of strings. The only supported characters for the passed char* +// *** are spaces, punctuation, uppercase and lowercase letters, and numbers. +// *** +// *** This results in the following modified ASCII table: +// *** ```csv +// *** #, char, #, char, #, char +// *** 97, a, 109, m, 121, y +// *** 98, b, 110, n, 122, z +// *** 99, c, 111, o, 123, 0 +// *** 100, d, 112, p, 124, 1 +// *** 101, e, 113, q, 125, 2 +// *** 102, f, 114, r, 126, 3 +// *** 103, g, 115, s, 127, 4 +// *** 104, h, 116, t, 128, 5 +// *** 105, i, 117, u, 129, 6 +// *** 106, j, 118, v, 130, 7 +// *** 107, k, 119, w, 131, 8 +// *** 108, l, 120, x, 132, 9 +// *** ``` +// *** Thus, any number from 96 (the start/end character) to 132 ('9') is a valid +// *** input to get_char_pair_hash(). +// *** +// *** After hashing each character pair, we add some number from 1 to 13 to the +// *** coresponding dimention. However, for most names, this results in a lot of +// *** zeros and a FEW positive numbers. Thus, after creating the dense vector, +// *** we convert it to a sparse vector in which a negative number replaces a run +// *** of that many zeros. Consider the following example: +// *** +// *** Dense Vector: `[1,0,0,0,3,0]` +// *** +// *** Sparse Vector: `[1,-3,3,-1]` +// *** +// *** Using these sparse vectors greatly reduces the required memory and gives +// *** aproximately an x5 boost to performance when traversing vectors, at the +// *** cost of more algorithmically complex code. +// *** +// *** @param str The string to be divided into pairs and hashed to make the vector. +// *** @returns The sparse vector built using the hashed character pairs. +// ***/ +// int* build_vector(char* str) { +// /** Allocate space for a dense vector. **/ +// unsigned int dense_vector[EXP_NUM_DIMS] = {0u}; + +// /** j is the former character, i is the latter. **/ +// const unsigned int num_chars = (unsigned int)strlen(str); +// for (unsigned int j = 65535u, i = 0u; i <= num_chars; i++) +// { +// /** isspace: space, \n, \v, \f, \r **/ +// if (isspace(str[i])) continue; + +// /** ispunct: !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ **/ +// if (ispunct(str[i]) && str[i] != EXP_BOUNDARY_CHAR) continue; + +// /*** iscntrl (0-8): SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS +// *** (14-31): SO, SI, DLE, DC1-4, NAK, SYN, ETB, CAN +// *** EM, SUB, ESC, FS, GS, RS, US +// ***/ +// if (iscntrl(str[i]) && i != num_chars) { +// mssErrorf(1, "EXP", +// "build_vector(%s) - Warning: Skipping unknown character #%u.\n", +// str, (unsigned int)str[i] +// ); +// continue; +// } + +// /** First and last character should fall one before 'a' in the ASCII table. **/ +// unsigned int temp1 = (j == 65535u) ? EXP_BOUNDARY_CHAR : (unsigned int)tolower(str[j]); +// unsigned int temp2 = (i == num_chars) ? EXP_BOUNDARY_CHAR : (unsigned int)tolower(str[i]); + +// /** Shift numbers to the end of the lowercase letters. **/ +// if ('0' <= temp1 && temp1 <= '9') temp1 += 75u; +// if ('0' <= temp2 && temp2 <= '9') temp2 += 75u; + +// /** Hash the character pair into an index (dimension). **/ +// /** Note that temp will be between 97 ('a') and 132 ('9'). **/ +// unsigned int dim = exp_fn_get_char_pair_hash(temp1, temp2); + +// /** Increment the dimension of the dense vector by a number from 1 to 13. **/ +// dense_vector[dim] += (temp1 + temp2) % 13u + 1u; + +// j = i; +// } + +// /** Count how much space is needed for a sparse vector. **/ +// bool zero_prev = false; +// size_t size = 0u; +// for (unsigned int dim = 0u; dim < EXP_NUM_DIMS; dim++) +// { +// if (dense_vector[dim] == 0u) +// { +// size += (zero_prev) ? 0u : 1u; +// zero_prev = true; +// } +// else +// { +// size++; +// zero_prev = false; +// } +// } + +// /*** Check compression size. +// *** If this check fails, I doubt anything will break. However, the longest +// *** word I know (supercalifragilisticexpialidocious) has only 35 character +// *** pairs, so it shouldn't reach half this size (and it'd be even shorter +// *** if the hash generates at least one collision). +// *** +// *** Bad vector compression will result in degraded performace and increased +// *** memory usage, and likely also indicates a bug or modified assumption +// *** elsewhere in the code. +// *** +// *** If this warning is ever generated, it's definitely worth investigating. +// ***/ +// const size_t expected_max_size = 64u; +// if (size > expected_max_size) +// { +// mssErrorf(1, "EXP" +// "build_vector(%s) - Warning: Sparse vector larger than expected.\n" +// " > Size: %lu\n" +// " > #Dims: %u\n", +// str, +// size, +// EXP_NUM_DIMS +// ); +// } + +// /** Allocate space for sparse vector. **/ +// const size_t sparse_vector_size = size * sizeof(int); +// int* sparse_vector = (int*)nmSysMalloc(sparse_vector_size); +// if (sparse_vector == NULL) { +// mssErrorf(1, "EXP", +// "build_vector(%s) - nmSysMalloc(%lu) failed.", +// str, sparse_vector_size +// ); +// return NULL; +// } + +// /** Convert the dense vector above to a sparse vector. **/ +// unsigned int j = 0u, sparse_idx = 0u; +// while (j < EXP_NUM_DIMS) +// { +// if (dense_vector[j] == 0u) +// { +// /*** Count and store consecutive zeros, except the first one, +// *** which we already know is zero. +// ***/ +// unsigned int zero_count = 1u; +// j++; +// while (j < EXP_NUM_DIMS && dense_vector[j] == 0u) +// { +// zero_count++; +// j++; +// } +// sparse_vector[sparse_idx++] = (int)-zero_count; +// } +// else +// { +// /** Store the value. **/ +// sparse_vector[sparse_idx++] = (int)dense_vector[j++]; +// } +// } + +// return sparse_vector; +// } + +// /*** Compute the magnitude of a sparsely allocated vector. +// *** +// *** @param vector The vector. +// *** @returns The computed magnitude. +// ***/ +// double exp_fn_magnitude_sparse(const int* vector) +// { +// unsigned int magnitude = 0u; +// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) +// { +// const int val = vector[i++]; + +// /** Negative val represents -val 0s in the array, so skip that many values. **/ +// if (val < 0) dim += (unsigned)(-val); + +// /** We have a param_value, so square it and add it to the magnitude. **/ +// else { magnitude += (unsigned)(val * val); dim++; } +// } +// return sqrt((double)magnitude); +// } + +// /*** Compute the magnitude of a densely allocated centroid. +// *** +// *** @param centroid The centroid. +// *** @returns The computed magnitude. +// ***/ +// double exp_fn_magnitude_dense(const double* centroid) +// { +// double magnitude = 0.0; +// for (int i = 0; i < EXP_NUM_DIMS; i++) +// magnitude += centroid[i] * centroid[i]; +// return sqrt(magnitude); +// } + +// /*** Parse a token from a sparsely allocated vector and write the param_value and +// *** number of remaining values to the passed locations. +// *** +// *** @param token The sparse vector token being parsed. +// *** @param remaining The location to save the remaining number of characters. +// *** @param param_value The location to save the param_value of the token. +// ***/ +// void exp_fn_parse_token(const int token, unsigned int* remaining, unsigned int* param_value) { +// if (token < 0) +// { +// /** This run contains -token zeros. **/ +// *remaining = (unsigned)(-token); +// *param_value = 0u; +// } +// else +// { +// /** This run contains one param_value. **/ +// *remaining = 1u; +// *param_value = (unsigned)(token); +// } +// } + +// /*** Calculate the similarity on sparcely allocated vectors. Comparing +// *** any string to an empty string should always return 0.5 (untested). +// *** +// *** @param v1 Sparse vector #1. +// *** @param v2 Sparse vector #2. +// *** @returns Similarity between 0 and 1 where +// *** 1 indicates identical and +// *** 0 indicates completely different. +// ***/ +// double exp_fn_sparse_similarity(const int* v1, const int* v2) +// { +// /** Calculate dot product. **/ +// unsigned int vec1_remaining = 0u, vec2_remaining = 0u; +// unsigned int dim = 0u, i1 = 0u, i2 = 0u, dot_product = 0u; +// while (dim < EXP_NUM_DIMS) +// { +// unsigned int val1 = 0u, val2 = 0u; +// if (vec1_remaining == 0u) exp_fn_parse_token(v1[i1++], &vec1_remaining, &val1); +// if (vec2_remaining == 0u) exp_fn_parse_token(v2[i2++], &vec2_remaining, &val2); + +// /*** Accumulate the dot_product. If either vector is 0 here, +// *** the total is 0 and this statement does nothing. +// ***/ +// dot_product += val1 * val2; + +// /** Consume overlap from both runs. **/ +// unsigned int overlap = min(vec1_remaining, vec2_remaining); +// vec1_remaining -= overlap; +// vec2_remaining -= overlap; +// dim += overlap; +// } + +// /** Optional optimization to speed up nonsimilar vectors. **/ +// if (dot_product == 0u) return 0.0; + +// /** Return the difference score. **/ +// return (double)dot_product / (exp_fn_magnitude_sparse(v1) * exp_fn_magnitude_sparse(v2)); +// } + +// /*** Calculate the difference on sparcely allocated vectors. Comparing +// *** any string to an empty string should always return 0.5 (untested). +// *** +// *** @param v1 Sparse vector #1. +// *** @param v2 Sparse vector #2. +// *** @returns Similarity between 0 and 1 where +// *** 1 indicates completely different and +// *** 0 indicates identical. +// ***/ +// #define exp_fn_sparse_dif(v1, v2) (1.0 - exp_fn_sparse_similarity(v1, v2)) + +// /*** Calculate the similarity between a sparsely allocated vector +// *** and a densely allocated centroid. Comparing any string to an +// *** empty string should always return 0.5 (untested). +// *** +// *** @param v1 Sparse vector #1. +// *** @param c1 Dense centroid #2. +// *** @returns Similarity between 0 and 1 where +// *** 1 indicates identical and +// *** 0 indicates completely different. +// ***/ +// double exp_fn_sparse_similarity_c(const int* v1, const double* c2) +// { +// /** Calculate dot product. **/ +// double dot_product = 0.0; +// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) +// { +// const int val = v1[i++]; + +// /** Negative val represents -val 0s in the array, so skip that many values. **/ +// if (val < 0) dim += (unsigned)(-val); + +// /** We have a param_value, so square it and add it to the magnitude. **/ +// else dot_product += (double)val * c2[dim++]; +// } + +// /** Return the difference score. **/ +// return dot_product / (exp_fn_magnitude_sparse(v1) * exp_fn_magnitude_dense(c2)); +// } + +// /*** Calculate the difference between a sparsely allocated vector +// *** and a densely allocated centroid. Comparing any string to an +// *** empty string should always return 0.5 (untested). +// *** +// *** @param v1 Sparse vector #1. +// *** @param c1 Dense centroid #2. +// *** @returns Difference between 0 and 1 where +// *** 1 indicates completely different and +// *** 0 indicates identical. +// ***/ +// #define exp_fn_sparse_dif_c(v1, c2) (1.0 - exp_fn_sparse_similarity_c(v1, c2)) + +// /*** Calculate the average size of all clusters in a set of vectors. +// *** +// *** @param vectors The vectors of the dataset (allocated sparsely). +// *** @param num_vectors The number of vectors in the dataset. +// *** @param labels The clusters to which vectors are assigned. +// *** @param centroids The locations of the centroids (allocated densely). +// *** @param num_clusters The number of centroids (k). +// *** @returns The average cluster size. +// ***/ +// double exp_fn_get_cluster_size( +// int** vectors, +// const unsigned int num_vectors, +// unsigned int* labels, +// double centroids[][EXP_NUM_DIMS], +// const unsigned int num_clusters +// ) +// { +// double cluster_sums[num_clusters]; +// unsigned int cluster_counts[num_clusters]; +// for (unsigned int i = 0u; i < num_clusters; i++) +// cluster_sums[i] = 0.0; +// memset(cluster_counts, 0, sizeof(cluster_counts)); + +// /** Sum the difference from each vector to its cluster centroid. **/ +// for (unsigned int i = 0u; i < num_vectors; i++) +// { +// const unsigned int label = labels[i]; +// cluster_sums[label] += exp_fn_sparse_dif_c(vectors[i], centroids[label]); +// cluster_counts[label]++; +// } + +// /** Add up the average cluster size. **/ +// double cluster_total = 0.0; +// unsigned int num_valid_clusters = 0u; +// for (unsigned int label = 0u; label < num_clusters; label++) +// { +// const unsigned int cluster_count = cluster_counts[label]; +// if (cluster_count == 0u) continue; + +// cluster_total += cluster_sums[label] / cluster_count; +// num_valid_clusters++; +// } + +// /** Return average sizes. **/ +// return cluster_total / num_valid_clusters; +// } + +// /*** Compute the param_value for `k` (number of clusters), given a dataset of with +// *** a size of `n`. +// *** +// *** The following table shows data sizes vs.selected cluster size. In testing, +// *** these numbers tended to givea good balance of accuracy and dulocates detected. +// *** +// *** ```csv +// *** Data Size, Actual +// *** 10k, 12 +// *** 100k, 33 +// *** 1M, 67 +// *** 4M, 93 +// *** ``` +// *** +// *** This function is not intended for datasets smaller than (`n < ~2000`). +// *** These should be handled using complete search. +// *** +// *** LaTeX Notation: \log_{36}\left(n\right)^{3.1}-8 +// *** +// *** @param n The size of the dataset. +// *** @returns k, the number of clusters to use. +// *** +// *** Complexity: `O(1)` +// ***/ +// unsigned int exp_fn_compute_k(const unsigned int n) +// { +// return (unsigned)max(2, pow(log(n) / log(36), 3.2) - 8); +// } + +// /*** Executes the k-means clustering algorithm. Selects NUM_CLUSTERS random +// *** vectors as initial centroids. Then points are assigned to the nearest +// *** centroid, after which centroids are moved to the center of their points. +// *** +// *** @param vectors The vectors to cluster. +// *** @param num_vectors The number of vectors to cluster. +// *** @param labels Stores the final cluster identities of the vectors after +// *** clustering is completed. +// *** @param centroids Stores the locations of the centroids used for the clusters +// *** of the data. +// *** @param iterations The number of iterations that actually executed is stored +// *** here. Leave this NULL if you don't care. +// *** @param max_iter The max number of iterations. +// *** @param num_clusters The number of clusters to generate. +// *** +// *** @attention - Assumes: num_vectors is the length of vectors. +// *** @attention - Assumes: num_clusters is the length of labels. +// *** +// *** @attention - Issue: At larger numbers of clustering iterations, some +// *** clusters have a size of negative infinity. In this implementation, +// *** the bug is mitigated by setting a small number of max iterations, +// *** such as 16 instead of 100. +// *** @attention - Issue: Clusters do not apear to improve much after the first +// *** iteration, which puts the efficacy of the algorithm into question. This +// *** may be due to the uneven density of a typical dataset. However, the +// *** clusters still offer useful information. +// *** +// *** Complexity: +// *** +// *** - `O(kd + k + i*(k + n*(k+d) + kd))` +// *** +// *** - `O(kd + k + ik + ink + ind + ikd)` +// *** +// *** - `O(nk + nd)` +// ***/ +// void exp_fn_kmeans( +// int** vectors, +// const unsigned int num_vectors, +// unsigned int* labels, +// const unsigned int num_clusters, +// const unsigned int max_iter +// ) +// { +// // const size_t centroids_size = num_clusters * sizeof(double*); +// // const size_t centroid_size = EXP_NUM_DIMS * sizeof(double); +// // double** centroids = (double**)nmMalloc(centroids_size); +// // if (centroids == NULL) +// // { +// // fprintf(stderr, "exp_fn_kmeans() - nmMalloc(%u) failed.\n", centroids_size); +// // return; +// // } +// // for (int i = 0; i < num_clusters; i++) +// // { +// // double* centroid = centroids[i] = (double*)nmMalloc(centroid_size); +// // if (centroid == NULL) +// // { +// // fprintf(stderr, "exp_fn_kmeans() - nmMalloc(%u) failed.\n", centroid_size); +// // return; +// // } +// // memset(centroids[i], 0, centroid_size); +// // } +// double centroids[num_clusters][EXP_NUM_DIMS]; +// memset(centroids, 0, sizeof(centroids)); + +// /** Select random vectors to use as the initial centroids. **/ +// srand(time(NULL)); +// for (unsigned int i = 0u; i < num_clusters; i++) +// { +// // Pick a random vector. +// const unsigned int random_index = (unsigned int)rand() % num_vectors; + +// // Sparse copy the vector into a densely allocated centroid. +// double* centroid = centroids[i]; +// const int* vector = vectors[random_index]; +// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) +// { +// const int token = vector[i++]; +// if (token > 0) centroid[dim++] = (double)token; +// else for (unsigned int j = 0u; j < -token; j++) centroid[dim++] = 0.0; +// } +// } + +// /** Allocate memory for new centroids. **/ +// double new_centroids[num_clusters][EXP_NUM_DIMS]; + +// /** Main exp_fn_kmeans loop. **/ +// double old_average_cluster_size = 1.0; +// unsigned int cluster_counts[num_clusters]; +// for (unsigned int iter = 0u; iter < max_iter; iter++) +// { +// bool changed = false; + +// /** Reset new centroids. **/ +// for (unsigned int i = 0u; i < num_clusters; i++) +// { +// cluster_counts[i] = 0u; +// for (unsigned int dim = 0; dim < EXP_NUM_DIMS; dim++) +// new_centroids[i][dim] = 0.0; +// } + +// /** Assign each point to the nearest centroid. **/ +// for (unsigned int i = 0u; i < num_vectors; i++) +// { +// const int* vector = vectors[i]; +// double min_dist = DBL_MAX; +// unsigned int best_centroid_label = 0u; + +// // Find nearest centroid. +// for (unsigned int j = 0u; j < num_clusters; j++) +// { +// const double dist = exp_fn_sparse_dif_c(vector, centroids[j]); +// if (dist < min_dist) +// { +// min_dist = dist; +// best_centroid_label = j; +// } +// } + +// /** Update label to new centroid, if necessary. **/ +// if (labels[i] != best_centroid_label) +// { +// labels[i] = best_centroid_label; +// changed = true; +// } + +// /** Accumulate values for new centroid calculation. **/ +// double* best_centroid = new_centroids[best_centroid_label]; +// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) +// { +// const int val = vector[i++]; +// if (val < 0) dim += (unsigned)(-val); +// else best_centroid[dim++] += (double)val; +// } +// cluster_counts[best_centroid_label]++; +// } + +// /** Stop if centroids didn't change. **/ +// if (!changed) break; + +// /** Update centroids. **/ +// for (unsigned int i = 0u; i < num_clusters; i++) +// { +// if (cluster_counts[i] == 0u) continue; +// double* centroid = centroids[i]; +// const double* new_centroid = new_centroids[i]; +// const unsigned int cluster_count = cluster_counts[i]; +// for (unsigned int dim = 0u; dim < EXP_NUM_DIMS; dim++) +// centroid[dim] = new_centroid[dim] / cluster_count; +// } + +// /** Print cluster size for debugging. **/ +// const double average_cluster_size = exp_fn_get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters); + +// /** Is there enough improvement? **/ +// const double improvement = old_average_cluster_size - average_cluster_size; +// if (improvement < KMEANS_IMPROVEMENT_THRESHOLD) break; +// old_average_cluster_size = average_cluster_size; +// } + +// // Free unused memory. +// // for (int i = 0; i < num_clusters; i++) { +// // nmFree(centroids[i], centroid_size); +// // } +// // nmFree(centroids, centroids_size); +// } + +// /** Duplocate information. **/ +// typedef struct +// { +// unsigned int id1; +// unsigned int id2; +// double similarity; +// } +// Dup, *pDup; + +// /*** Runs complete search to find duplocates if `num_vectors < MAX_COMPLETE_SEARCH` +// *** and runs a search using k-means clustering on larger amounts of data. +// *** +// *** @param vectors Array of precomputed frequency vectors for all dataset strings. +// *** @param num_vectors The number of vectors to be scanned. +// *** @param dupe_threshold The similarity threshold, below which dups are ignored. +// *** @returns The duplicates in pDup structs. +// ***/ +// pXArray lightning_search(int** vectors, const unsigned int num_vectors, const double dupe_threshold) +// { +// /** Allocate space for dups. **/ +// const size_t guess_size = num_vectors * 2u; +// pXArray dups = xaNew(guess_size); +// if (dups == NULL) +// { +// mssErrorf(1, "EXP", "lightning_search() - xaNew(%lu) failed.", guess_size); +// return NULL; +// } + +// /** Descide which algorithm to use. **/ +// if (num_vectors <= MAX_COMPLETE_SEARCH) +// { /** Do a complete search. **/ +// for (unsigned int i = 0u; i < num_vectors; i++) +// { +// const int* v1 = vectors[i]; +// for (unsigned int j = i + 1u; j < num_vectors; j++) +// { +// const int* v2 = vectors[j]; +// const double similarity = exp_fn_sparse_similarity(v1, v2); +// if (similarity > dupe_threshold) // Dup found! +// { +// Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); +// if (dup == NULL) +// { +// mssErrorf(1, "EXP", "lightning_search() - nmMalloc(%lu) failed.", sizeof(Dup)); +// goto err_free_dups; +// } + +// dup->id1 = i; +// dup->id2 = j; +// dup->similarity = similarity; +// xaAddItem(dups, (void*)dup); +// } +// } +// } +// } +// else +// { /** Do a k-means search. **/ +// /** Define constants for the algorithm. **/ +// const unsigned int max_iter = 64u; /** Hardcode value because idk. **/ +// const unsigned int num_clusters = exp_fn_compute_k(num_vectors); + +// /** Allocate static memory for finding clusters. **/ +// unsigned int labels[num_vectors]; +// memset(labels, 0u, sizeof(labels)); + +// /** Execute kmeans clustering. **/ +// exp_fn_kmeans(vectors, num_vectors, labels, num_clusters, max_iter); + +// /** Find duplocates in clusters. **/ +// for (unsigned int i = 0u; i < num_vectors; i++) +// { +// const int* v1 = vectors[i]; +// const unsigned int label = labels[i]; +// for (unsigned int j = i + 1u; j < num_vectors; j++) +// { +// if (labels[j] != label) continue; +// const int* v2 = vectors[j]; +// const double similarity = exp_fn_sparse_similarity(v1, v2); +// if (similarity > dupe_threshold) /* Dup found! */ +// { +// Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); +// if (dup == NULL) +// { +// mssErrorf(1, "EXP", +// "lightning_search() - nmMalloc(%lu) failed.", +// sizeof(Dup) +// ); +// goto err_free_dups; +// } + +// dup->id1 = i; +// dup->id2 = j; +// dup->similarity = similarity; +// xaAddItem(dups, (void*)dup); +// } +// } +// } +// } + +// /** Done **/ +// return dups; + +// /** Free dups. **/ +// err_free_dups:; +// const size_t num_dups = dups->nItems; +// for (unsigned int i = 0u; i < num_dups; i++) +// { +// nmFree(dups->Items[i], sizeof(Dup)); +// dups->Items[i] = NULL; +// } +// xaDeInit(dups); +// return NULL; +// } + +// /*** Computes Levenshtein distance between two strings. +// *** +// *** @param str1 The first string. +// *** @param str2 The second string. +// *** @param length1 The length of the first string. +// *** @param length1 The length of the first string. +// *** +// *** @attention - Tip: Pass 0 for the length of either string to infer it +// *** using the null terminating character. Thus, strings with no null +// *** terminator are supported if you pass explicit lengths. +// *** +// *** Complexity: O(length1 * length2). +// *** +// *** @see centrallix-sysdoc/string_comparison.md +// ***/ +// unsigned int exp_fn_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) +// { +// /*** lev_matrix: +// *** For all i and j, d[i][j] will hold the Levenshtein distance between +// *** the first i characters of s and the first j characters of t. +// *** +// *** As they say, no dynamic programming algorithm is complete without a +// *** matrix that you fill out and it has the answer in the final location. +// ***/ +// const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; +// const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; +// unsigned int lev_matrix[str1_len + 1][str2_len + 1]; + +// /*** Base case #0: +// *** Transforming an empty string into an empty string has 0 cost. +// ***/ +// lev_matrix[0][0] = 0u; + +// /*** Base case #1: +// *** Any source prefixe can be transformed into an empty string by +// *** dropping each character. +// ***/ +// for (unsigned int i = 1u; i <= str1_len; i++) +// lev_matrix[i][0] = i; + +// /*** Base case #2: +// *** Any target prefixes can be transformed into an empty string by +// *** inserting each character. +// ***/ +// for (unsigned int j = 1u; j <= str2_len; j++) +// lev_matrix[0][j] = j; + +// /** General Case **/ +// for (unsigned int i = 1u; i <= str1_len; i++) +// { +// for (unsigned int j = 1u; j <= str2_len; j++) +// { +// /** Equal characters need no changes. **/ +// if (str1[i - 1] == str2[j - 1]) +// lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; + +// /*** We need to make a change, so use the opereration with the +// *** lowest cost out of delete, insert, replace, or swap. +// ***/ +// else +// { +// unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; +// unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; +// unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; + +// /** If a swap is possible, calculate the cost. **/ +// bool can_swap = ( +// i > 1 && j > 1 && +// str1[i - 1] == str2[j - 2] && +// str1[i - 2] == str2[j - 1] +// ); +// unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; + +// // Find the best operation. +// lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); +// } +// } +// } + +// return lev_matrix[str1_len][str2_len]; +// } + +// /*** Runs complete search to find duplocates in phone numbers using the +// *** levenshtein min edit distance algorithm. +// *** +// *** @param dataset An array of characters for all dataset strings. +// *** @param dataset_size The number of phone numbers to be scanned. +// *** @param dupe_threshold The similarity threshold, below which dups are ignored. +// *** @returns The duplicates in pDup structs. +// ***/ +// pXArray phone_search(char dataset[][10u], const unsigned int dataset_size, const double dupe_threshold) +// { +// /** Allocate space for dups. **/ +// const size_t guess_size = dataset_size * 2u; +// pXArray dups = xaNew(guess_size); +// if (dups == NULL) +// { +// mssErrorf(1, "EXP", "phone_search() - xaNew(%lu) failed.", guess_size); +// return NULL; +// } + +// /** Search for dups using edit distance. **/ +// for (unsigned int i = 0u; i < dataset_size; i++) +// { +// const char* v1 = dataset[i]; +// for (unsigned int j = i + 1u; j < dataset_size; j++) +// { +// const char* v2 = dataset[j]; +// const unsigned int dist = exp_fn_edit_dist(v1, v2, 10u, 10u); +// const double similarity = (double)dist / 10.0; +// if (similarity > dupe_threshold) /* Dup found! */ +// { +// Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); +// if (dup == NULL) +// { +// mssErrorf(1, "EXP", "phone_search() - nmMalloc(%lu) failed.", sizeof(Dup)); + +// /** Free data before returning. **/ +// const size_t num_dups = dups->nItems; +// for (unsigned int i = 0u; i < num_dups; i++) +// { +// void* dup = dups->Items[i]; +// nmFree(dup, sizeof(Dup)); +// } +// xaDeInit(dups); +// return NULL; +// } + +// dup->id1 = i; +// dup->id2 = j; +// dup->similarity = similarity; +// xaAddItem(dups, (void*)dup); +// } +// } +// } + +// return dups; +// } + +// /*** Usage: get_dups(, , ) +// *** data is assumed to contain only the following characters: +// *** (Data containing ` or control characters is undefined.) +// *** \n\v\f\r 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij +// *** klmnopqrstuvwxyz!"#$%&'()*+,-./:;<=>?@[\]^_{|}~ +// ***/ +// int exp_fn_get_dups_general(pExpression tree, pParamObjects objlist, pExpression maybe_dup_threshold, pExpression maybe_out_file_path, pExpression maybe_data, const char* fn_name, bool is_phone_numbers) +// { +// /** Check number of arguments. **/ +// if (!maybe_dup_threshold || !maybe_out_file_path || !maybe_data) +// { +// mssErrorf(1, "EXP", "%s(?) expects 3 parameters.", fn_name); +// return -1; +// } +// const int num_params = tree->Children.nItems; +// if (num_params != 3) +// { +// mssErrorf(1, "EXP", "%s(?) expects 3 parameter, got %d.", fn_name, num_params); +// return -1; +// } + +// /** Magic checks. **/ +// ASSERTMAGIC(tree, MGK_EXPRESSION); +// ASSERTMAGIC(maybe_dup_threshold, MGK_EXPRESSION); +// ASSERTMAGIC(maybe_out_file_path, MGK_EXPRESSION); +// ASSERTMAGIC(maybe_data, MGK_EXPRESSION); + +// /** Check object list. **/ +// if (!objlist) +// { +// mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); +// return -1; +// } +// ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); + +// /** Extract dup_threshold. **/ +// if (maybe_dup_threshold->Flags & EXPR_F_NULL) +// { +// mssErrorf(1, "EXP", "%s(NULL, ...) dup_threshold cannot be NULL.", fn_name); +// return -1; +// } +// if (maybe_dup_threshold->DataType != DATA_T_DOUBLE) +// { +// mssErrorf(1, "EXP", "%s(?, ...) dup_threshold must be a doube.", fn_name); +// return -1; +// } +// double dup_threshold = maybe_dup_threshold->Types.Double; +// if (isnan(dup_threshold)) +// { +// mssErrorf(1, "EXP", "%s(NAN, ...) dup_threshold cannot be NAN.", fn_name); +// return -1; +// } +// if (dup_threshold <= 0 || 1 <= dup_threshold) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, ...) dup_threshold must be between 0 and 1 (exclusive).", +// fn_name, dup_threshold +// ); +// return -1; +// } + +// /** Extract output file path. **/ +// if (maybe_out_file_path->Flags & EXPR_F_NULL) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, NULL, ...) out_file_path cannot be NULL.", +// fn_name, dup_threshold +// ); +// return -1; +// } +// if (maybe_out_file_path->DataType != DATA_T_STRING) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \?\?\?, ...) out_file_path should be a string.", +// fn_name, dup_threshold +// ); +// return -1; +// } +// char* out_file_path = maybe_out_file_path->String; +// if (out_file_path == NULL) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, nothing?, ...) expected string from out_file_path " +// "(of type DataType = DATA_T_STRING), but the String was NULL " +// "or did not exist!", +// fn_name, dup_threshold +// ); +// return -1; +// } +// size_t out_path_len = strlen(out_file_path); +// if (out_path_len == 0u) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", ...) out_file_path cannot be an empty string.", +// fn_name, dup_threshold, out_file_path +// ); +// return -1; +// } +// const size_t max_len = BUFSIZ - 48u; +// if (out_path_len >= max_len) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", ...) out_file_path length (%lu) > max length (%lu).", +// fn_name, dup_threshold, out_file_path, out_path_len, max_len +// ); +// return -1; +// } +// if (strncmp(out_file_path + (out_path_len - 4u), ".csv", 4u) != 0) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", ...) out_file_path must end in .csv, " +// "because the output file is a csv.", +// fn_name, dup_threshold, out_file_path +// ); +// return -1; +// } + +// /** Extract dataset string. **/ +// if (maybe_data->Flags & EXPR_F_NULL) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", NULL) data cannot be NULL.", +// fn_name, dup_threshold, out_file_path +// ); +// return -1; +// } +// if (maybe_data->DataType != DATA_T_STRING) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \?\?\?) data must be a string.", +// fn_name, dup_threshold, out_file_path +// ); +// return -1; +// } +// char* data = maybe_data->String; +// if (data == NULL) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \?\?\?) expected string from data " +// "(of type DataType = DATA_T_STRING), but the String " +// "was NULL or did not exist!", +// fn_name, dup_threshold, out_file_path +// ); +// return -1; +// } +// if (strlen(data) == 0u) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"%s\") data cannot be an empty string.", +// fn_name, dup_threshold, out_file_path, data +// ); +// return -1; +// } + +// /** Check number of entries in the dataset. **/ +// size_t dataset_size = 1; +// for (char* buf = data; *buf != '\0'; buf++) +// if (*buf == SEPARATOR_CHAR) dataset_size++; + +// /** Verify dataset is reasonable size. **/ +// if (dataset_size == 1) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"\?\?\?\") Expected data to contain multiple " +// "values separated by \""SEPARATOR"\", but data was: \"%s\"", +// fn_name, dup_threshold, out_file_path, data +// ); +// return -1; +// } + +// /** Parse strs out of the data into the dataset. **/ +// size_t count = 0u; +// char* token = strtok(data, SEPARATOR); +// char* dataset[dataset_size]; +// memset(dataset, 0, sizeof(dataset)); +// while (token && count < dataset_size) +// { +// char* new_token = strdup(token); +// if (new_token == NULL) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") Failed to copy token \"%s\" from data.", +// fn_name, dup_threshold, out_file_path, token +// ); +// goto err_free_dataset; +// } +// dataset[count++] = new_token; +// token = strtok(NULL, SEPARATOR); +// } + +// /** Allocate memory to store dups. **/ +// pXArray dups; + +// /** Handle phone numbers. **/ +// if (is_phone_numbers) +// { +// /*** Phone number strings are always 10 characters long. Thus, they +// *** are NOT NULL TERMINATED because we can assume the length. +// ***/ +// unsigned int num_phone_numbers = 0u; +// char phone_numbers[dataset_size][10u]; + +// /** Parse the dataset. **/ +// for (unsigned int i = 0u; i < dataset_size; i++) +// { +// char* maybe_phone_number = dataset[i]; + +// /** Verify length can be a valid phone number. **/ +// const size_t len = strlen(maybe_phone_number); +// if (len < 10u) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") is too short. (skipped)", +// fn_name, dup_threshold, out_file_path, maybe_phone_number +// ); +// continue; +// } +// if (len > 18u) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") is too long. (skipped)", +// fn_name, dup_threshold, out_file_path, maybe_phone_number +// ); +// continue; +// } + +// /** Parse phone number. **/ +// char buf[11u], cur_char = maybe_phone_number[0]; +// unsigned int j = ((cur_char == '+') ? 2u : +// ((cur_char == '1') ? 1u : 0u)); +// unsigned int number_len = 0u; +// while (cur_char != '\0' && number_len <= 10u) +// { +// cur_char = maybe_phone_number[j]; + +// if ( +// cur_char == '-' || +// cur_char == ' ' || +// cur_char == '(' || +// cur_char == ')' +// ) continue; +// else if (!isdigit(cur_char)) +// { +// /** Unknown character. **/ +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") contains unexpected character '%c'. (skipped)", +// fn_name, dup_threshold, out_file_path, maybe_phone_number, cur_char +// ); +// goto next_phone_number; +// } + +// /** Add the character to the phone number. */ +// buf[number_len] = cur_char; +// number_len++; + +// /** Advance to next number. **/ +// j++; +// } + +// /** Check number of digits. **/ +// if (number_len < 10u) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") has less than 10 digits. (skipped)", +// fn_name, dup_threshold, out_file_path, maybe_phone_number +// ); +// continue; +// } +// if (number_len > 10u) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") has more than 10 digits. (skipped)", +// fn_name, dup_threshold, out_file_path, maybe_phone_number +// ); +// continue; +// } + +// /** Copy valid phone number (with no null-terminator). **/ +// memcpy(phone_numbers[num_phone_numbers++], buf, 10u); + +// next_phone_number:; +// } + +// /** Invoke phone number search to find dups in the processed data. **/ +// dups = phone_search(phone_numbers, num_phone_numbers, dup_threshold); +// } + +// /** Handle text. **/ +// else +// { +// /** Build vectors from the strs in the dataset. **/ +// const size_t vectors_size = dataset_size * sizeof(int*); +// int** vectors = (int**)nmMalloc(vectors_size); +// if (vectors == NULL) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") - nmMalloc(%lu) failed.", +// fn_name, dup_threshold, out_file_path, vectors_size +// ); +// goto err_free_dataset; +// } +// for (size_t i = 0; i < dataset_size; i++) +// { +// const int* vector = vectors[i] = build_vector(dataset[i]); +// if (vector == NULL) +// { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") - build_vector(%s) failed.", +// fn_name, dup_threshold, out_file_path, dataset[i] +// ); +// goto err_free_vectors; +// } +// if (vector[0] == -EXP_NUM_DIMS) { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") - build_vector(%s) produced no character pairs.", +// fn_name, dup_threshold, out_file_path, dataset[i] +// ); +// goto err_free_vectors; +// } +// } + +// /** Invoke lightning search to find dups using the vectors. **/ +// dups = lightning_search(vectors, dataset_size, dup_threshold); +// if (dups == NULL) { +// mssErrorf(1, "EXP", +// "%s(%lg, \"%s\", \"...\") - lightning_search() failed.", +// fn_name, dup_threshold, out_file_path +// ); +// goto err_free_vectors; +// } + +// /** Free unused memory. **/ +// for (size_t i = 0; i < dataset_size; i++) +// { +// nmSysFree(vectors[i]); +// vectors[i] = NULL; +// } +// nmFree(vectors, vectors_size); +// vectors = NULL; +// goto search_done; + +// /** Free vectors, if needed. **/ +// err_free_vectors: +// if (vectors != NULL) +// { +// for (size_t i = 0; i < dataset_size; i++) +// { +// if (vectors[i] == NULL) break; +// nmSysFree(vectors[i]); +// vectors[i] = NULL; +// } +// nmFree(vectors, vectors_size); +// vectors = NULL; +// } +// goto err_free_dataset; + +// search_done:; +// } + +// /** Check number of dups found. **/ +// const int num_dups = dups->nItems; + +// // Hack where we hardcode the path to the root directory because trying to +// // track it down is way too hard. +// const char root_path[] = "/usr/local/src/cx-git/centrallix-os"; + +// /** Create output file path. **/ +// char out_path[BUFSIZ]; +// snprintf(memset(out_path, 0, sizeof(out_path)), sizeof(out_path), "%s/%s", root_path, out_file_path); + +// /** Write output file. **/ +// FILE* file = fopen(out_path, "w"); +// if (file == NULL) +// { +// perror("Failed to open file."); +// mssErrorf(1, "EXP", +// "%s(%lg, \"...\", ...) failed to open file: %s", +// fn_name, dup_threshold, out_path +// ); +// goto err_free_dups; +// } +// const int setvbuf_ret = setvbuf(file, NULL, _IOFBF, (1000 * 1000)); +// if (setvbuf_ret != 0) +// { +// perror("Failed to set buffering on file."); +// mssErrorf(1, "EXP", +// "%s(%lg, \"...\", ...) failed to set buffering on file: %d, %s", +// fn_name, dup_threshold, setvbuf_ret, out_path +// ); +// goto err_close_file; +// } + +// /** Write CSV header. **/ +// fprintf(file, "id1,id2,sim\n"); + +// /*** If no data was written, make sure there is at least one row in the +// *** output file since assuming this file has data makes the sql faster. +// ***/ +// if (num_dups == 0u) +// fprintf(file, "error,undefined,0.0\n"); + +// /** Write CSV data rows. **/ +// else +// { +// for (unsigned int i = 0u; i < num_dups; i++) +// { +// Dup* data = (Dup*)dups->Items[i]; +// fprintf(file, "%s,%s,%.8lf\n", dataset[data->id1], dataset[data->id2], data->similarity); +// nmFree(data, sizeof(Dup)); /* Free unused data. */ +// dups->Items[i] = NULL; +// } +// } + +// /** Free unused data. **/ +// for (unsigned int i = 0u; i < dataset_size; i++) +// { +// free(dataset[i]); +// dataset[i] = NULL; +// } +// xaDeInit(dups); +// dups = NULL; + +// /** Close file. **/ +// const int fclose_ret = fclose(file); +// if (fclose_ret != 0) +// { +// perror("Failed to close file."); +// mssErrorf(1, "EXP", +// "%s(%lg, \"...\") failed to close file: %d, %s", +// fn_name, dup_threshold, fclose_ret, out_path +// ); +// goto err_free_dataset; +// } +// file = NULL; + +// /** Success. **/ +// tree->DataType = DATA_T_INTEGER; +// tree->Integer = (int)num_dups; +// return 0; + +// /** Error cases. **/ + +// /** Close file, if needed. **/ +// err_close_file: +// if (file != NULL) +// { +// const int fclose_ret = fclose(file); +// if (fclose_ret != 0) +// { +// char dbl_buf[DBL_BUF_SIZE]; +// snprintf(dbl_buf, sizeof(dbl_buf), "%lg", dup_threshold); +// perror("Failed to close file."); +// mssErrorf(1, "EXP", +// "%s(%s, \"...\") failed to close file: %d, %s", +// fn_name, dbl_buf, fclose_ret, out_path +// ); +// } +// } + +// /** Free dups, if needed. **/ +// err_free_dups: +// if (dups != NULL) +// { +// for (unsigned int i = 0u; i < num_dups; i++) +// { +// nmFree(dups->Items[i], sizeof(Dup)); +// dups->Items[i] = NULL; +// } +// xaDeInit(dups); +// dups = NULL; +// } + +// /** Free dataset, if needed. **/ +// err_free_dataset: +// for (unsigned int i = 0u; i < dataset_size; i++) +// { +// if (dataset[i] == NULL) break; +// free(dataset[i]); +// dataset[i] = NULL; +// } + +// return -1; +// } + +// int exp_fn_get_dups(pExpression tree, pParamObjects objlist, pExpression p1, pExpression p2, pExpression p3) +// { +// return exp_fn_get_dups_general(tree, objlist, p1, p2, p3, "get_dups", false); +// } + +// int exp_fn_get_dups_phone(pExpression tree, pParamObjects objlist, pExpression p1, pExpression p2, pExpression p3) +// { +// return exp_fn_get_dups_general(tree, objlist, p1, p2, p3, "get_dups_phone", true); +// } + +// /** Magic values. **/ +// #define EXP_NUM_FIELDS 7 +// #define EXP_INDEX_FIRST_NAME 0 +// #define EXP_INDEX_FIRST_NAME_METAPHONE 1 +// #define EXP_INDEX_LAST_NAME 2 +// #define EXP_INDEX_LAST_NAME_METAPHONE 3 +// #define EXP_INDEX_EMAIL 4 +// #define EXP_INDEX_PHONE 5 +// #define EXP_INDEX_ADDRESS 6 + +// /** No-op function. **/ +// int exp_fn_do_nothing() { return 0; } + +// /*** Function to add parameters to private storage so that more than 3 parameters can be passed. +// *** Currently, doubles are the only supported param type. +// *** +// *** Usage: param(, , ) : R, +// *** where: V : Double +// *** +// *** @param tree Return param_value. +// *** @param objlist Function scope. +// *** @param maybe_array The 1st param, should be NULL or another call to param(). +// *** @param maybe_param_name The 2nd param, should be a string for the name of the param. +// *** @param maybe_param_value The 3rd param, should be the param_value of the param being set. +// ***/ +// int exp_fn_param(pExpression tree, pParamObjects objlist, pExpression maybe_param_name, pExpression maybe_param_value, pExpression maybe_array) { +// // Verify arg number. +// if (!maybe_param_name || !maybe_param_value) +// { +// mssErrorf(1, "EXP", "param(?) expects two or three parameters."); +// return -1; +// } + +// // Magic checks. +// ASSERTMAGIC(tree, MGK_EXPRESSION); +// ASSERTMAGIC(maybe_param_name, MGK_EXPRESSION); +// ASSERTMAGIC(maybe_param_value, MGK_EXPRESSION); +// ASSERTMAGIC(maybe_array, MGK_EXPRESSION); + +// // Check object list. +// if (!objlist) +// { +// mssErrorf(1, "EXP", "param(\?\?\?) no object list?"); +// return -1; +// } +// ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); + +// // Extract param name. +// if (maybe_param_name->Flags & EXPR_F_NULL) +// { +// mssErrorf(1, "EXP", "param(NULL, ...) param_name cannot be null."); +// return -1; +// } +// if (maybe_param_name->DataType != DATA_T_STRING) +// { +// mssErrorf(1, "EXP", "param(?, ...) param_name must be a string."); +// return -1; +// } +// const char* param_name = maybe_param_name->String; + +// // Extract param value. +// if (maybe_param_value->Flags & EXPR_F_NULL) +// { +// mssErrorf(1, "EXP", "param(\"%s\", NULL, ...) param_value cannot be null.", param_name); +// return -1; +// } +// if (maybe_param_value->DataType != DATA_T_DOUBLE) +// { +// mssErrorf(1, "EXP", "param(\"%s\", ?, ...) param_value must be a doube.", param_name); +// return -1; +// } +// double param_value = maybe_param_value->Types.Double; + +// // Verify the value being set. +// // TODO: Replace with hashmap. +// signed int index = -1; +// if (strcmp(param_name, "first_name") == 0) index = EXP_INDEX_FIRST_NAME; +// else if (strcmp(param_name, "first_name_metaphone") == 0) index = EXP_INDEX_FIRST_NAME_METAPHONE; +// else if (strcmp(param_name, "last_name") == 0) index = EXP_INDEX_LAST_NAME; +// else if (strcmp(param_name, "last_name_metaphone") == 0) index = EXP_INDEX_LAST_NAME_METAPHONE; +// else if (strcmp(param_name, "email") == 0) index = EXP_INDEX_EMAIL; +// else if (strcmp(param_name, "phone") == 0) index = EXP_INDEX_PHONE; +// else if (strcmp(param_name, "address") == 0) index = EXP_INDEX_ADDRESS; +// if (index == -1) +// { +// mssErrorf(1, "EXP", +// "param(\"%s\", %lf, ...) invalid field name %s.", +// param_name, param_value, param_name +// ); +// return -1; +// } + +// // Extract array. +// double* array; +// if (!maybe_array || maybe_array->Flags & EXPR_F_NULL) +// { +// const size_t size = EXP_NUM_FIELDS * sizeof(double); +// void* PrivateData = tree->PrivateData = memset(nmSysMalloc(size), 0, size); +// tree->PrivateDataFinalize = exp_fn_do_nothing; // DON'T FREE MY DATA UNTIL I'M READY. + +// array = (double*)PrivateData; +// for (unsigned int i = 0u; i < EXP_NUM_FIELDS; i++) array[i] = NAN; +// } +// else if ( +// maybe_array->DataType == DATA_T_ARRAY && +// maybe_array->PrivateData != NULL && +// !strcmp(maybe_array->Name, "param") +// ) +// { +// tree->PrivateData = maybe_array->PrivateData; +// tree->PrivateDataFinalize = exp_fn_do_nothing; // DON'T FREE MY DATA UNTIL I'M READY. +// array = (double*)maybe_array->PrivateData; +// } +// else +// { +// mssErrorf(1, "EXP", "param(\"%s\", %lf, ...) if provided, array must be from a call to param().", param_name, param_value); +// return -1; +// } + +// // Warn on previous data. +// double old_value = array[index]; +// if (!isnan(old_value)) +// { +// fprintf(stderr, +// "Warning: Overwriting field '%s'(@ index %d) with %lf (was %lf).\n", +// param_name, index, param_value, old_value +// ); +// } + +// // Set param_value. +// array[index] = param_value; + +// // Done +// tree->DataType = DATA_T_ARRAY; +// tree->Integer = 0; +// tree->Types.Double = 0.0; +// return 0; +// } + +// int exp_fn_get_sim(pExpression tree, pParamObjects objlist, pExpression maybe_fields, pExpression unused1, pExpression unused2) +// { +// if (!maybe_fields || unused1 || unused2) +// { +// mssErrorf(1, "EXP", "get_sim(param(...)) expects one parameter, from param()."); +// return -1; +// } + +// // Magic checks. +// ASSERTMAGIC(tree, MGK_EXPRESSION); +// ASSERTMAGIC(maybe_fields, MGK_EXPRESSION); + +// // Check object list. +// if (!objlist) +// { +// mssErrorf(1, "EXP", "get_sim(\?\?\?) no object list?"); +// return -1; +// } +// ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); + +// // Verify arg. +// if (maybe_fields->Flags & EXPR_F_NULL) +// { +// mssErrorf(1, "EXP", "get_sim(NULL) fields from param() cannot be NULL."); +// return -1; +// } +// if (maybe_fields->DataType != DATA_T_ARRAY || maybe_fields->PrivateData == NULL) +// { +// mssErrorf(1, "EXP", "get_sim(\?\?\?) expects arg 0 to be fields from a call to param()."); +// return -1; +// } + +// // Extract arg(s?). +// double* fields = (double*)maybe_fields->PrivateData; + +// const double first_name = fields[EXP_INDEX_FIRST_NAME]; +// if (isnan(first_name)) +// { +// mssErrorf(1, "EXP", "get_sim(...) first_name similarity not set."); +// return -1; +// } + +// const double first_name_metaphone = fields[EXP_INDEX_FIRST_NAME_METAPHONE]; +// if (isnan(first_name_metaphone)) +// { +// mssErrorf(1, "EXP", "get_sim(...) first_name_metaphone similarity not set."); +// return -1; +// } + +// const double last_name = fields[EXP_INDEX_LAST_NAME]; +// if (isnan(last_name)) +// { +// mssErrorf(1, "EXP", "get_sim(...) last_name similarity not set."); +// return -1; +// } + +// const double last_name_metaphone = fields[EXP_INDEX_LAST_NAME_METAPHONE]; +// if (isnan(last_name_metaphone)) +// { +// mssErrorf(1, "EXP", "get_sim(...) last_name_metaphone similarity not set."); +// return -1; +// } + +// const double email = fields[EXP_INDEX_EMAIL]; +// if (isnan(email)) +// { +// mssErrorf(1, "EXP", "get_sim(...) email similarity not set."); +// return -1; +// } + +// const double phone = fields[EXP_INDEX_PHONE]; +// if (isnan(phone)) +// { +// mssErrorf(1, "EXP", "get_sim(...) phone similarity not set."); +// return -1; +// } + +// const double address = fields[EXP_INDEX_ADDRESS]; +// if (isnan(address)) +// { +// mssErrorf(1, "EXP", "get_sim(...) address similarity not set."); +// return -1; +// } + +// char* primary; +// char* secondary; +// meta_double_metaphone("text", &primary, &secondary); +// printf("Primary: %s, secondary: %s\n", primary, secondary); + +// // Print args. +// printf( +// "Sims:\n" +// "\tfirst_name: %lf\n" +// "\tfirst_name_metaphone: %lf\n" +// "\tlast_name: %lf\n" +// "\tlast_name_metaphone: %lf\n" +// "\temail: %lf\n" +// "\tphone: %lf\n" +// "\taddress: %lf\n", +// first_name, +// first_name_metaphone, +// last_name, +// last_name_metaphone, +// email, +// phone, +// address +// ); + +// // Compute total. +// const double first_name_total = max(first_name * 1.0, first_name_metaphone * 0.9); +// const double last_name_total = max(last_name * 1.0, last_name_metaphone * 0.9); +// double total = (first_name_total * last_name_total) * 0.6 + email * 0.2 + address * 0.2; + +// // Clean up. +// nmSysFree(fields); + +// // Return total. +// tree->DataType = DATA_T_DOUBLE; +// tree->Types.Double = total; +// return 0; +// } + + +int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression maybe_str, pExpression u1, pExpression u2) + { + const char fn_name[] = "double_metaphone"; + + /** Check number of arguments. **/ + if (!maybe_str || u1 || u2) + { + mssErrorf(1, "EXP", "%s(?) expects 1 parameter.", fn_name); + return -1; + } + const int num_params = tree->Children.nItems; + if (num_params != 1) + { + mssErrorf(1, "EXP", "%s(?) expects 1 parameter, got %d.", fn_name, num_params); + return -1; + } + + /** Magic checks. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + ASSERTMAGIC(maybe_str, MGK_EXPRESSION); + + /** Check object list. **/ + if (!objlist) + { + mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); + return -1; + } + ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); + + /** Extract str. **/ + if (maybe_str->Flags & EXPR_F_NULL) + { + mssErrorf(1, "EXP", "%s(NULL) str cannot be NULL.", fn_name); + return -1; + } + if (maybe_str->DataType != DATA_T_STRING) + { + mssErrorf(1, "EXP", "%s(\?\?\?) str should be a string.", fn_name); + return -1; + } + const char* str = maybe_str->String; + if (str == NULL) + { + mssErrorf(1, "EXP", + "%s(nothing?) expected string from str " + "(of type DataType = DATA_T_STRING), but the String " + "was NULL or did not exist!", + fn_name + ); + return -1; + } + const size_t str_len = strlen(str); + if (str_len == 0u) + { + mssErrorf(1, "EXP", "%s(\"\") str cannot be an empty string.", fn_name); + return -1; + } + + /** Compute DoubleMetaphone. **/ + char* primary; + char* secondary; + meta_double_metaphone( + str, + memset(&primary, 0, sizeof(primary)), + memset(&secondary, 0, sizeof(secondary)) + ); + + /** Process result. **/ + const size_t primary_length = strlen(primary); + const size_t secondary_length = strlen(secondary); + char* result = nmSysMalloc(primary_length + 1u + secondary_length + 1u); + sprintf(result, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); + + /** Return the result. **/ + tree->String = result; + tree->DataType = DATA_T_STRING; + return 0; + } + +// // Clean up. +// #undef min +// #undef max + +// // END OF DUPE SECTION +// // =================== /* * exp_fn_argon2id @@ -4521,8 +6261,8 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "log10", (char*)exp_fn_log10); xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); - xhAdd(&EXP.Functions, "levenshtein", (char*)exp_fn_levenshtein); - xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); + xhAdd(&EXP.Functions, "levenshtein", (char*)exp_fn_levenshtein); /* Only used in its own tests. */ + xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); /* Only used in its own tests. */ xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); @@ -4530,7 +6270,16 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "from_hex", (char*)exp_fn_from_hex); xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); - + + /** Duplicate Detection **/ + // xhAdd(&EXP.Functions, "get_dups", (char*)exp_fn_get_dups); + // xhAdd(&EXP.Functions, "get_dups_phone", (char*)exp_fn_get_dups_phone); + // xhAdd(&EXP.Functions, "no_op", (char*)exp_fn_do_nothing); + // xhAdd(&EXP.Functions, "do_nothing", (char*)exp_fn_do_nothing); + // xhAdd(&EXP.Functions, "param", (char*)exp_fn_param); + // xhAdd(&EXP.Functions, "total_sim", (char*)exp_fn_get_sim); + xhAdd(&EXP.Functions, "double_metaphone", (char*)exp_fn_double_metaphone); + /** Windowing **/ xhAdd(&EXP.Functions, "row_number", (char*)exp_fn_row_number); xhAdd(&EXP.Functions, "dense_rank", (char*)exp_fn_dense_rank); diff --git a/centrallix/include/cxss/policy.h b/centrallix/include/cxss/policy.h index aeee11ce8..6f9ca7d83 100644 --- a/centrallix/include/cxss/policy.h +++ b/centrallix/include/cxss/policy.h @@ -2,6 +2,7 @@ #define _CXSS_POLICY_H #include "cxss/cxss.h" +#include "obj.h" /************************************************************************/ /* Centrallix Application Server System */ @@ -89,4 +90,3 @@ typedef struct _CXSSPOL CxssPolicy, *pCxssPolicy; #endif /* defined _CXSS_POLICY_H */ - diff --git a/centrallix/include/expression.h b/centrallix/include/expression.h index 8d506f72e..3b334606b 100644 --- a/centrallix/include/expression.h +++ b/centrallix/include/expression.h @@ -307,6 +307,7 @@ int exp_internal_SetupControl(pExpression exp); pExpControl exp_internal_LinkControl(pExpControl ctl); int exp_internal_UnlinkControl(pExpControl ctl); +void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code); /*** Evaluator functions ***/ int expEvalIsNull(pExpression tree, pParamObjects objlist); diff --git a/centrallix/include/stparse.h b/centrallix/include/stparse.h index 50d9e2c20..fad7f9604 100644 --- a/centrallix/include/stparse.h +++ b/centrallix/include/stparse.h @@ -46,7 +46,7 @@ typedef struct _SI int Magic; int LinkCnt; char* Name; /* name of attrib or group */ - char* UsrType; /* type of group, null if attrib */ + char* UsrType; /* type of group (e.g. "system/object"), null if attrib */ pExpression Value; /* value; EXPR_N_LIST if several listed */ struct _SI* Parent; /* Parent inf, null if toplevel */ struct _SI** SubInf; /* List of attrs/groups included */ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c new file mode 100644 index 000000000..9ffbd1d22 --- /dev/null +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -0,0 +1,3345 @@ + +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: objdrv_cluster.c */ +/* Author: Israel Fuller */ +/* Creation: September 17, 2025 */ +/* Description: Cluster object driver. */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cxlib/clusters.h" +#include "cxlib/mtask.h" +#include "cxlib/mtsession.h" +#include "cxlib/newmalloc.h" +#include "cxlib/util.h" +#include "cxlib/xarray.h" +#include "cxlib/xhash.h" +#include "expression.h" +#include "hints.h" +#include "obj.h" +#include "param.h" +#include "st_node.h" +#include "stparse.h" + +/*** File notes: + *** This file uses comment anchors, provided by the Comment Anchors VSCode + *** extension from Starlane Studios. This allows developers with the extension + *** to control click the "LINK " comments to navigate to the coresponding + *** "ANCHOR[id=]" comment. (Note: Invalid or broken links will default to + *** the first line of the file.) + *** + *** For example, this link should take you to the function signatures: + *** LINK #functions + *** + *** Any developers without this extension can safely ignore these comments, + *** although please try not to break them. :) + *** + *** Comment Anchors VSCode Extension: + *** https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors + ***/ + + +/** Debugging **/ +// void void_func() {} +// #define tprintf void_func +#define tprintf printf + +/** Defaults for unspecified optional attributes. **/ +#define DEFAULT_MIN_IMPROVEMENT 0.0001 +#define DEFAULT_MAX_ITERATIONS 64u + +/** ================ Stuff That Should Be Somewhere Else ================ **/ +/** ANCHOR[id=temp] **/ + +#define INT_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" +#define INT_TO_BINARY(int_val) \ + ((int_val) & 0b10000000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b01000000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00100000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00010000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00001000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000100000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000010000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000001000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000100000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000010000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000001000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000100000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000010000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000001000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000100000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000010000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000001000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000100000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000010000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000001000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000100000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000010000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000001000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000100000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000010000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000001000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000100000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000010000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000001000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000000100 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000000010 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000000001 ? '1' : '0') + + +/** TODO: I think this should be moved to mtsession. **/ +/*** I caused at least 10 bugs so far trying to pass format specifiers to + *** mssError without realizing that it didn't support them. Eventually, I + *** got fed up enough with the whole thing to write the following function. + ***/ +/*** Displays error text to the user. Does not print a stack trace. Does not + *** exit the program, allowing for the calling function to fail, generating + *** an error cascade which may be useful to the user since a stack trace is + *** not readily available. + *** + *** @todo I think this should be moved to somewhere else. + *** + *** @param clr Whether to clear the current error stack. As a rule of thumb, + *** if you are the first one to detec the error, clear the stack so that + *** other unrelated messages are not shown. If you are detecting an error + *** from another function that may also call an mssError() function, do + *** not clear the stack. + *** @param module The name or abbreviation of the module in which this + *** function is being called, to help developers narrow down the location + *** of the error. + *** @param format The format text for the error, which accepts any format + *** specifier that would be accepted by printf(). + *** @param ... Variables matching format specifiers in the format. + *** @returns Nothing, always succeeds. + ***/ +void mssErrorf(int clr, char* module, const char* format, ...) + { + /** Prevent interlacing with stdout flushing at a weird time. **/ + check(fflush(stdout)); + + /** Insert convenient newline before error stack begins. **/ + if (clr == 1) fprintf(stderr, "\n"); + + /** Process the format with all the same rules as printf(). **/ + char buf[BUFSIZ]; + va_list args; + va_start(args, format); + const int num_chars = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + /** Error check vsnprintf, just to be safe. **/ + if (num_chars < 0) + { + perror("vsnprintf() failed"); + fprintf(stderr, "FAIL: mssErrorf(%d, \"%s\", \"%s\", ...)\n", clr, module, format); + return; + } + if (num_chars > BUFSIZ) + fprintf(stderr, "WARNING: Error truncated (length %d > buffer size %d).\n", num_chars, BUFSIZ); + + /** Print the error. **/ + const int ret = mssError(clr, module, "%s", buf); + + /** Not sure why you have to error check the error function... **/ + if (ret != 0) fprintf(stderr, "FAIL %d: mssError(%d, \"%s\", \"%%s\", \"%s\")\n", ret, clr, module, buf); + } + + +/** TODO: I think this should be moved to datatypes. **/ +/** Should maybe replace current type parsing in the presentation hints. **/ +int ci_TypeFromStr(const char* str) + { + if (str == NULL) return -1; + + /** Check string length. **/ + const size_t len = strlen(str); + if (len < 3 || 13 < len) return -1; + + /** Copy str to enable mutability. **/ + char buf[len + 1u]; + strcpy(buf, str); + + /** First character is case insensitive. **/ + buf[0] = toupper(buf[0]); + + /** Check type. **/ + if (strcmp(buf, "Any") == 0) return DATA_T_UNAVAILABLE; + if (strcmp(buf, "Integer") == 0) return DATA_T_INTEGER; + if (strcmp(buf, "String") == 0) return DATA_T_STRING; + if (strcmp(buf, "Double") == 0) return DATA_T_DOUBLE; + if (strcmp(buf, "DateTime") == 0) return DATA_T_DATETIME; + if (strcmp(buf, "IntVecor") == 0) return DATA_T_INTVEC; + if (strcmp(buf, "StringVector") == 0) return DATA_T_STRINGVEC; + if (strcmp(buf, "Money") == 0) return DATA_T_MONEY; + if (strcmp(buf, "Array") == 0) return DATA_T_ARRAY; + if (strcmp(buf, "Code") == 0) return DATA_T_CODE; + if (strcmp(buf, "Binary") == 0) return DATA_T_BINARY; + + /** Invalid type. **/ + return -1; + } + +/** TODO: I think this should be moved to datatypes. **/ +/** Should maybe replace duplocate functionality elsewhere. **/ +char* ci_TypeToStr(const int type) + { + switch (type) + { + case DATA_T_UNAVAILABLE: return "Unknown"; + case DATA_T_INTEGER: return "Integer"; + case DATA_T_STRING: return "String"; + case DATA_T_DOUBLE: return "Double"; + case DATA_T_DATETIME: return "DateTime"; + case DATA_T_INTVEC: return "IntVecor"; + case DATA_T_STRINGVEC: return "StringVector"; + case DATA_T_MONEY: return "Money"; + case DATA_T_ARRAY: return "Array"; + case DATA_T_CODE: return "Code"; + case DATA_T_BINARY: return "Binary"; + } + + /** Invalid type. **/ + mssErrorf(1, "Cluster", "Invalid type %d.\n", type); + return "Invalid"; + } + +/** TODO: I think this should be moved to xarray. **/ +/** Contract: Return value is null iff pXArray has 0 items. **/ +void** ci_xaToTrimmedArray(pXArray arr) + { + if (arr->nItems == 0) { + mssErrorf(1, "Cluster", "Failed to trim XArray of length 0."); + return NULL; + } + + const size_t arr_size = arr->nItems * sizeof(void*); + void** result = check_ptr(nmMalloc(arr_size)); + memcpy(result, arr->Items, arr_size); + return result; + } + +/** ================ Enum Declairations ================ **/ +/** ANCHOR[id=enums] **/ + +/** Enum representing a clustering algorithm. **/ +typedef unsigned char ClusterAlgorithm; +#define ALGORITHM_NULL (ClusterAlgorithm)0u +#define ALGORITHM_NONE (ClusterAlgorithm)1u +#define ALGORITHM_SLIDING_WINDOW (ClusterAlgorithm)2u +#define ALGORITHM_KMEANS (ClusterAlgorithm)3u +#define ALGORITHM_KMEANS_PLUS_PLUS (ClusterAlgorithm)4u +#define ALGORITHM_KMEDOIDS (ClusterAlgorithm)5u +#define ALGORITHM_DB_SCAN (ClusterAlgorithm)6u + +/** Converts a clustering algorithm to its string name. **/ +char* ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) + { + switch (clustering_algorithm) + { + case ALGORITHM_NULL: return "NULL algorithm"; + case ALGORITHM_NONE: return "none"; + case ALGORITHM_SLIDING_WINDOW: return "sliding-window"; + case ALGORITHM_KMEANS: return "k-means"; + case ALGORITHM_KMEANS_PLUS_PLUS: return "k-means++"; + case ALGORITHM_KMEDOIDS: return "k-medoids"; + case ALGORITHM_DB_SCAN: return "db-scan"; + default: return "Unknown algorithm"; + } + } + +/** Enum representing a similarity measurement algorithm. **/ +typedef unsigned char SimilarityMeasure; +#define SIMILARITY_NULL (SimilarityMeasure)0u +#define SIMILARITY_COSINE (SimilarityMeasure)1u +#define SIMILARITY_LEVENSHTEIN (SimilarityMeasure)2u + +/** Converts a similarity measure to its string name. **/ +char* ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) + { + switch (similarity_measure) + { + case SIMILARITY_NULL: return "NULL similarity measure"; + case SIMILARITY_COSINE: return "cosine"; + case SIMILARITY_LEVENSHTEIN: return "levenshtein"; + default: return "Unknown similarity measure"; + } + } + +/*** Enum representing the type of data targetted by the driver, + *** set based on the path given when the driver is used to open + *** a cluster file. + *** + *** `0u` is reserved for a possible `NULL` value in the future. + *** However, there is currently no allowed `NULL` TargetType. + ***/ +typedef unsigned char TargetType; +#define TARGET_ROOT (TargetType)1u +#define TARGET_CLUSTER (TargetType)2u +#define TARGET_SEARCH (TargetType)3u +#define TARGET_CLUSTER_ENTRY (TargetType)4u +#define TARGET_SEARCH_ENTRY (TargetType)5u + +/** Attribute name lists by TargetType. **/ +#define nATTR_ROOT 2u +char* const ATTR_ROOT[nATTR_ROOT] = { + "source", + "attr_name", +}; +#define nATTR_CLUSTER 5u +char* const ATTR_CLUSTER[nATTR_CLUSTER] = { + "algorithm", + "similarity_measure", + "num_clusters", + "min_improvement", + "max_iterations", +}; +#define nATTR_SEARCH 4u +char* const ATTR_SEARCH[nATTR_SEARCH] = { + "source", + "threshold", + "similarity_measure", +}; +#define nATTR_CLUSTER_ENTRY 2u +char* const ATTR_CLUSTER_ENTRY[nATTR_CLUSTER_ENTRY] = { + "val", + "sim", +}; +#define nATTR_SEARCH_ENTRY 3u +char* const ATTR_SEARCH_ENTRY[nATTR_SEARCH_ENTRY] = { + "val1", + "val2", + "sim", +}; +#define END_OF_ATTRIBUTES NULL + + +/** Method name list. **/ +#define nMETHOD_NAME 2u +char* const METHOD_NAME[nMETHOD_NAME] = { + "cache", +}; +#define END_OF_METHODS END_OF_ATTRIBUTES + + +/** ================ Struct Declarations ================ **/ +/** ANCHOR[id=structs] **/ + +/** Represents the data source which may have data already fetched. **/ +typedef struct _SOURCE + { + /** Top level attributes (specified in the .cluster file). **/ + char* Name; /* The node name, specified in the .cluster file. + * Warning: Some code makes the assumption that this + * is the first field in the struct. + */ + char* Key; /* The key associated with this object in the global SourceCache. */ + char* SourcePath; /* The path to the data source from which to retrieve data. */ + char* AttrName; /* The name of the attribute to get from the data source. */ + + /** Computed data. **/ + char** Data; /* The data strings to be clustered and searched, or NULL if they + * have not been fetched from the source. + */ + pVector* Vectors; /* The cosine comparison vectors from the fetched data, or NULL if + * they haven't been computed. Note that vectors are no longer + * needed once all clusters and searches have been computed, so + * they are automatically freed in that case to save memory. + */ + unsigned int nVectors; /* The number of vectors and data strings. Note: This is not + * set to 0 if the vector array is freed, this case should be + * checked separately. + */ + } SourceData, *pSourceData; + +/** Data for each cluster. **/ +typedef struct _CLUSTER + { + /** Attribute Data. **/ + char* Name; /* The cluster name, specified in the .cluster file. + * Warning: Some code makes the assumption that this + * is the first field in the struct. + */ + char* Key; /* The key associated with this object in the global ClusterCache. */ + ClusterAlgorithm ClusterAlgorithm; /* The clustering algorithm to be used. */ + SimilarityMeasure SimilarityMeasure; /* The similarity measurse to be used when clustering. */ + unsigned int NumClusters; /* The number of clusters. 1 if algorithm = none. */ + double MinImprovement; /* The minimum amount of improvement that must be met each + * clustering iteration. If there is less improvement, the + * algorithm will stop. Specifying "max" in the .cluster + * file should be represented by a value of -inf. + */ + unsigned int MaxIterations; /* The maximum number of iterations to run clustering. */ + + /** Other data (ignored by caching). **/ + unsigned int nSubClusters; /* The number of subclusters of this cluster. */ + struct _CLUSTER** SubClusters; /* A pClusterData array, NULL if nSubClusters == 0. */ + struct _CLUSTER* Parent; /* This cluster's parent. NULL if it is not a subcluster. */ + pSourceData SourceData; /* Pointer to the source data that this cluster uses. */ + + /** Computed data. **/ + unsigned int* Labels; /* An array with one element for each vector in the data + * (aka. DriverData->nVectors). For vector i, Labels[i] is + * the ID of the cluster to which that data is assigned. + * NULL if the cluster has not been computed. */ + } + ClusterData, *pClusterData; + +/** Data for each search. **/ +typedef struct _SEARCH + { + char* Name; /* The search name, specified in the .cluster file. + * Warning: Some code makes the assumption that this + * is the first field in the struct. + */ + char* Key; /* The key associated with this object in the global SearchCache. */ + pClusterData Source; /* The cluster from which this search is to be derived. */ + double Threshold; /* The minimum similarity threshold for elements to be + * included in the results of the search. + */ + SimilarityMeasure SimilarityMeasure; /* The similarity measure used to compare items. */ + + /** Computed data. **/ + pDup* Dups; /* An array holding the dups found by the search, or NULL + * if the search has not been computed. + */ + unsigned int nDups; /* The number of dups found. */ + } + SearchData, *pSearchData; + +/*** Node instance data. + *** When a .cluster file is openned, there will be only one node for that + *** file. However, in the course of the query, many driver instance structs + *** may be created by functions like clusterQueryFetch(), and closed by the + *** object system using clusterClose(). + ***/ +typedef struct _NODE + { + /** Substructures. **/ + pSourceData SourceData; /* Data from the provided source. */ + pParam* Params; /* A pParam array storing the params in the .cluster file. */ + unsigned int nParams; /* The number of specified params. */ + pParamObjects ParamList; /* Functions as a "scope" for resolving values during parsing. */ + pClusterData* Clusters; /* A pCluster array storing the clusters in the .cluster file. + * Will be NULL if nClusters = 0. + */ + unsigned int nClusters; /* The number of specified clusters. */ + pSearchData* Searches; /* A SearchData array storing the searches in the .cluster file. */ + unsigned int nSearches; /* The number of specified searches. */ + + /** Other stuff, idk why it's here. **/ + pSnNode Node; + pObject Obj; + char* CreateDateField; + char* ModifyDateField; + } + NodeData, *pNodeData; + +/** Driver instance data. **/ +/*** Similar to a pointer to specific, computed data in the pNodeData struct. + *** If target type is the root, a cluster, or a search, no data is guarnteed + *** to be computed yet. These three types can be returned from clusterOpen(). + *** To target a cluster entry or search entry, fetch a driver targetting a + *** cluster or search (respectively). These target types ensure that the data + *** has been computed, so the GetAttr functions do not need to ensure this. + ***/ +typedef struct _DRIVER + { + pNodeData NodeData; /* The associated node data. */ + TargetType TargetType; /* The type of data targetted by this driver instance. */ + void* TargetData; /* A pointer to the specific targetted cluster or search. */ + unsigned int TargetIndex; /* An index into the cluster or search (entries only). */ + unsigned char TargetAttrIndex; /* An index into an attribute list (for GetNextAttr()). */ + unsigned char TargetMethodIndex; /* An index into an method list (for GetNextMethod()). */ + } + DriverData, *pDriverData; + +/** Query instance data. **/ +typedef struct + { + pDriverData DriverData; /* The associated driver instance being queried. */ + unsigned int RowIndex; /* The selected row of the data targetted by the driver. */ + } + ClusterQuery, *pClusterQuery; + +/** Global storage for caches. **/ +struct + { + XHashTable SourceCache; + XHashTable ClusterCache; + XHashTable SearchCache; + } + ClusterCaches; + + +/** ================ Function Declarations ================ **/ +/** ANCHOR[id=functions] **/ + +/** Note: ci stands for "cluster_internal". **/ + +/** Parsing Functions. **/ +// LINK #parsing +int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); +ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); +SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); +pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path); +pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data); +pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data); +pNodeData ci_ParseNodeData(pStructInf inf, pObject obj); + +/** Freeing Functions. **/ +// LINK #freeing +void ci_FreeSourceData(pSourceData source_data); +void ci_FreeClusterData(pClusterData cluster_data, bool recursive); +void ci_FreeSearchData(pSearchData search_data); +void ci_FreeNodeData(pNodeData node_data); + +/** Deep Size Computation Functions. **/ +// LINK #sizing +unsigned int ci_SizeOfSourceData(pSourceData source_data); +unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive); +unsigned int ci_SizeOfSearchData(pSearchData search_data); +unsigned int ci_SizeOfNodeData(pNodeData node_data); + +/** Cache Invalidation Functions. **/ +// LINK #invalidation +void ci_CacheFreeSourceData(pXHashEntry entry, void* _); +void ci_CacheFreeCluster(pXHashEntry entry, void* _); +void ci_CacheFreeSearch(pXHashEntry entry, void* _); + +/** Computation Functions. (Ensure data is computed.) **/ +// LINK #computation +int ci_ComputeSourceData(pSourceData source_data, pObjSession session); +int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data); +int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data); + +/** Parameter Functions. **/ +// LINK #params +int ci_GetParamType(void* inf_v, const char* attr_name); +int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); +int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); + +/** Driver Functions. **/ +// LINK #driver +void* clusterOpen(pObject obj, int mask, pContentType systype, char* usr_type, pObjTrxTree* oxt); +int clusterClose(void* inf_v, pObjTrxTree* oxt); +void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); +void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt); +int clusterQueryClose(void* qy_v, pObjTrxTree* oxt); +int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); +int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt); +char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt); +int clusterInfo(void* inf_v, pObjectInfo info); + +/** Method Execution Functions. **/ +// LINK #method +char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt); +char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt); +int clusterExecuteMethod(void* inf_v, char* methodname, pObjData param, pObjTrxTree oxt); + +/** Unimplemented DriverFunctions. **/ +// LINK #unimplemented +int clusterCreate(pObject obj, int mask, pContentType systype, char* usrtype, pObjTrxTree* oxt); +int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt); +int clusterDelete(pObject obj, pObjTrxTree* oxt); +int clusterRead(void* inf_v, char* buffer, int maxcnt, int offset, int flags, pObjTrxTree* oxt); +int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt); +int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree oxt); +int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree oxt); +void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree oxt); +int clusterCommit(void* inf_v, pObjTrxTree *oxt); +pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt); + +/** ================ Parsing Functions ================ **/ +/** ANCHOR[id=parsing] **/ +// LINK #functions + +/*** Returns 0 for success and -1 on failure. Promises that mssError() will be + *** invoked on failure, so the caller need not specify their own error message. + *** Returns 1 if attribute is available, printing an error if the attribute was + *** marked as required. + *** + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** TODO: Greg + *** This function took several hours of debugging before it worked at all, and I + *** still don't know if it works correctly... or really how it works. Please + *** review this code carefully! + ***/ +int ci_ParseAttribute( + pStructInf inf, + char* attr_name, + int datatype, + pObjData data, + pParamObjects param_list, + bool required, + bool print_type_error) + { + int ret; + + /** Get attribute name. **/ + pStructInf attr_info = stLookup(inf, attr_name); + if (attr_info == NULL) + { + if (required) mssErrorf(1, "Cluster", "'%s' must be specified for clustering.", attr_name); + return 1; + } + ASSERTMAGIC(attr_info, MGK_STRUCTINF); + + /** Get the attribute. **/ + tprintf("Invoking ci_ParseAttribute('%s')...\n", attr_name); + pExpression exp = check_ptr(stGetExpression(attr_info, 0)); + expBindExpression(exp, param_list, EXPR_F_RUNSERVER); + ret = expEvalTree(exp, param_list); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Expression evaluation failed."); + goto err; + } + + /** Check for data type mismatch. **/ + if (datatype != exp->DataType) + { + mssErrorf(1, "Cluster", + "Expected \"%s\" : %s, but got type %s.", + attr_name, ci_TypeToStr(datatype), ci_TypeToStr(exp->DataType) + ); + goto err; + } + + /** Get the data out of the expression. **/ + ret = expExpressionToPod(exp, datatype, data); + if (ret != 0) + { + mssErrorf(1, "Cluster", + "Failed to get data of type \"%s\" from exp \"%s\" (error code %d).", + ci_TypeToStr(datatype), exp->Name, ret + ); + goto err; + } + +// const int ret = stGetAttrValueOSML( +// attr_info, +// datatype, +// data, +// 0, +// param_list->Session, +// param_list +// ); +// if (ret == 1) +// { +// mssErrorf(1, "Cluster", +// "stGetAttrValueOSML('%s') because %s cannot be null.\n" +// " > Hint: You might have used an undefined variable or forgot to add runserver().", +// attr_name, attr_name +// ); +// return 1; +// } +// if (ret != 0) +// { +// if (print_type_error) +// { +// mssErrorf(1, "Cluster", +// "stGetAttrValueOSML('%s') failed (error code %d).\n" +// " > Hint: It might be a type mismatch, or you used an undefined variable.", +// attr_name, ret +// ); +// } +// return ret; +// } + + return 0; + + err: + mssErrorf(0, "Cluster", + "Failed to parse attribute \"%s\" from group \"%s\"", + attr_name, inf->Name + ); + return -1; + } + + +/*** Parses a ClusteringAlgorithm from the algorithm field in the pStructInf + *** representing some structure with that attribute in a parsed structure file. + *** + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @returns The data algorithm, or ALGORITHM_NULL on failure. + ***/ +ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) + { + /** Get the algorithm attribute. **/ + char* algorithm; + if (ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0) + { + mssErrorf(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); + return ALGORITHM_NULL; + } + + /** Parse known clustering algorithms. **/ + if (!strcasecmp(algorithm, "none")) return ALGORITHM_NONE; + if (!strcasecmp(algorithm, "sliding-window")) return ALGORITHM_SLIDING_WINDOW; + if (!strcasecmp(algorithm, "k-means")) return ALGORITHM_KMEANS; + if (!strcasecmp(algorithm, "k-means++")) return ALGORITHM_KMEANS_PLUS_PLUS; + if (!strcasecmp(algorithm, "k-medoids")) return ALGORITHM_KMEDOIDS; + if (!strcasecmp(algorithm, "db-scan")) return ALGORITHM_DB_SCAN; + + /** Unknown value for clustering algorithm. **/ + mssErrorf(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); + return ALGORITHM_NULL; + } + + +/*** Parses a SimilarityMeasure from the similarity_measure field in the given + *** pStructInf parameter, which represents some structure with that attribute + *** in a parsed structure file. + *** + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @returns The similarity measure, or SIMILARITY_NULL on failure. + ***/ +SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) + { + /** Get the similarity_measure attribute. **/ + char* measure; + if (ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0) + { + mssErrorf(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); + return SIMILARITY_NULL; + } + + /** Parse known clustering algorithms. **/ + if (!strcasecmp(measure, "cosine")) return SIMILARITY_COSINE; + if (!strcasecmp(measure, "levenshtein")) return SIMILARITY_LEVENSHTEIN; + + mssErrorf(1, "Cluster", "Unknown \"similarity measure\": %s", measure); + return SIMILARITY_NULL; + } + + +/*** Allocates a new pSourceData struct from a parsed pStructInf representing + *** a .cluster structure file. + *** + *** @attention - Warning: Caching in use. + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf for a .cluster structure file. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @param path The file path to the parsed structure file, used to generate + *** cache entry keys. + *** @returns A new pSourceData struct on success, or NULL on failure. + ***/ +pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) + { + char* buf; + + /** Get source. **/ + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; + char* source_path = check_ptr(strdup(buf)); + + /** Get attribute name. **/ + if (ci_ParseAttribute(inf, "attr_name", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; + char* attr_name = check_ptr(strdup(buf)); + + /** Create cache entry key. **/ + const size_t len = strlen(path) + strlen(source_path) + strlen(attr_name) + 3lu; + char* key = check_ptr(nmSysMalloc(len * sizeof(char))); + snprintf(key, len, "%s?%s:%s", path, source_path, attr_name); + pXHashTable source_cache = &ClusterCaches.SourceCache; + + /** Check for a cached version. **/ + pSourceData source_maybe = (pSourceData)xhLookup(source_cache, key); + if (source_maybe != NULL) + { + /** Cache hit. **/ + tprintf("# source: \"%s\"\n", key); + tprintf("--> Name: %s\n", source_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ + + /** Free data we don't need. */ + free(source_path); + free(attr_name); + nmSysFree(key); + + /** Return the cached source data. **/ + return source_maybe; + } + + /** Cache miss: Create a new source data object. **/ + pSourceData source_data = check_ptr(nmMalloc(sizeof(SourceData))); + memset(source_data, 0, sizeof(SourceData)); + source_data->Name = check_ptr(strdup(inf->Name)); + source_data->Key = key; + source_data->SourcePath = source_path; + source_data->AttrName = attr_name; + + /** Add the new object to the cache for next time. **/ + tprintf("+ source: \"%s\"\n", key); + check(xhAdd(source_cache, key, (void*)source_data)); + + return source_data; + + err: + mssErrorf(0, "Cluster", "Failed to parse source data from group \"%s\" in file: %s", inf->Name, path); + return NULL; + } + + +/*** Allocates a new pClusterData struct from a parsed pStructInf. + *** + *** @attention - Warning: Caching in use. + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf for a cluster group in a structure file. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @param source_data The pSourceData that clusters are to be built from, also + *** used to generate cache entry keys. + *** @returns A new pClusterData struct on success, or NULL on failure. + ***/ +pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) + { + int result; + + tprintf("Parsing cluster: %s\n", inf->Name); + + pParamObjects param_list = node_data->ParamList; + pSourceData source_data = node_data->SourceData; + + /** Allocate space for data struct. **/ + pClusterData cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); + memset(cluster_data, 0, sizeof(ClusterData)); + + /** Basic Properties. **/ + cluster_data->Name = check_ptr(strdup(inf->Name)); + cluster_data->SourceData = source_data; + + /** Get algorithm. **/ + cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); + if (cluster_data->ClusterAlgorithm == ALGORITHM_NULL) goto err; + + /** Handle no clustering case. **/ + if (cluster_data->ClusterAlgorithm == ALGORITHM_NONE) + { + cluster_data->NumClusters = 1u; + goto parsing_done; + } + + /** Get similarity_measure. **/ + cluster_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); + if (cluster_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free_cluster; + + /** Handle sliding window case. **/ + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) + goto parsing_done; + + /** Get num_clusters. **/ + int num_clusters; + if (ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0) goto err_free_cluster; + if (num_clusters < 2) + { + mssErrorf(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); + if (num_clusters == 1) fprintf(stderr, "HINT: Use algorithm=\"none\" to disable clustering.\n"); + goto err_free_cluster; + } + cluster_data->NumClusters = (unsigned int)num_clusters; + tprintf("Got value for num_clusters: %d\n", num_clusters); + + /** Get min_improvement. **/ + double improvement; + result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); + if (result == 1) cluster_data->MinImprovement = DEFAULT_MIN_IMPROVEMENT; + else if (result == 0) + { + if (improvement <= 0.0 || 1.0 <= improvement) + { + mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %g", improvement); + goto err_free_cluster; + } + cluster_data->MinImprovement = improvement; + } + else if (result == -1) + { + char* str; + result = ci_ParseAttribute(inf, "min_improvement", DATA_T_STRING, POD(&str), param_list, false, true); + if (result == 0 && !strcasecmp(str, "none")) + { + /** Specify no min improvement. **/ + cluster_data->MinImprovement = -INFINITY; + } + } + if (result == -1) goto err_free_cluster; + + /** Get max_iterations. **/ + int max_iterations; + result = ci_ParseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); + if (result == -1) goto err_free_cluster; + if (result == 0) + { + if (max_iterations < 0) + { + mssErrorf(1, "Cluster", "Invalid value for [max_iterations : uint]: %d", max_iterations); + goto err_free_cluster; + } + cluster_data->MaxIterations = (unsigned int)max_iterations; + } + else cluster_data->MaxIterations = DEFAULT_MAX_ITERATIONS; + + /** Search for sub-clusters. **/ + XArray sub_clusters; + const int ret = xaInit(&sub_clusters, 4u); + if (ret != 0) + { + mssErrorf(1, "Cluster", "FAIL - xaInit(&sub_clusters, %u): %d", 4u, ret); + goto err_free_cluster; + } + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + /** Check that this is a group (not an attribute). **/ + pStructInf group_inf = inf->SubInf[i]; + ASSERTMAGIC(group_inf, MGK_STRUCTINF); + if (stStructType(group_inf) != ST_T_SUBGROUP) continue; + + /** Select array by group type. **/ + assert(group_inf->UsrType != NULL); + if (strcmp(group_inf->UsrType, "cluster/cluster")) continue; + + /** Subcluster found. **/ + pClusterData sub_cluster = ci_ParseClusterData(group_inf, node_data); + if (sub_cluster == NULL) goto err_free_sub_clusters; + sub_cluster->Parent = cluster_data; + xaAddItem(&sub_clusters, sub_cluster); + } + cluster_data->nSubClusters = sub_clusters.nItems; + cluster_data->SubClusters = (cluster_data->nSubClusters > 0u) ? + (pClusterData*)ci_xaToTrimmedArray(&sub_clusters) + : NULL; /* No sub-clusters. */ + xaDeInit(&sub_clusters); + + /** Create the cache key. **/ + parsing_done:; + char* key; + switch (cluster_data->ClusterAlgorithm) + { + case ALGORITHM_NONE: + { + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 5lu; + key = nmSysMalloc(len * sizeof(char)); + snprintf(key, len, "%s/%s?%u", + source_data->Key, + cluster_data->Name, + ALGORITHM_NONE + ); + break; + } + + case ALGORITHM_SLIDING_WINDOW: + { + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 8lu; + key = nmSysMalloc(len * sizeof(char)); + snprintf(key, len, "%s/%s?%u&%u", + source_data->Key, + cluster_data->Name, + ALGORITHM_SLIDING_WINDOW, + cluster_data->SimilarityMeasure + ); + break; + } + + default: + { + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 32lu; + key = nmSysMalloc(len * sizeof(char)); + snprintf(key, len, "%s/%s?%u&%u&%u&%g&%u", + source_data->Key, + cluster_data->Name, + cluster_data->ClusterAlgorithm, + cluster_data->SimilarityMeasure, + cluster_data->NumClusters, + cluster_data->MinImprovement, + cluster_data->MaxIterations + ); + break; + } + } + pXHashTable cluster_cache = &ClusterCaches.ClusterCache; + cluster_data->Key = key; + + /** Check for a cached version. **/ + pClusterData cluster_maybe = (pClusterData)xhLookup(cluster_cache, key); + if (cluster_maybe != NULL) + { + /** Cache hit. **/ + tprintf("# cluster: \"%s\"\n", key); + tprintf("--> Name: %s\n", cluster_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ + + /** Free the parsed cluster that we no longer need. */ + ci_FreeClusterData(cluster_data, false); + nmSysFree(key); + + /** Return the cached cluster. **/ + return cluster_maybe; + } + + /** Cache miss. **/ + tprintf("+ cluster: \"%s\"\n", key); + check(xhAdd(cluster_cache, key, (void*)cluster_data)); + return cluster_data; + + /** Error cleanup. **/ + err_free_sub_clusters: + for (unsigned int i = 0u; i < sub_clusters.nItems; i++) + ci_FreeClusterData(sub_clusters.Items[i], true); + xaDeInit(&sub_clusters); + + err_free_cluster: + nmFree(cluster_data, sizeof(ClusterData)); + + err: + mssErrorf(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); + return NULL; + } + + +/*** Allocates a new pSearchData struct from a parsed pStructInf. + *** + *** @attention - Warning: Caching in use. + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf for a search group in a structure file. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @param node_data The pNodeData, used to get the param list and to look up + *** the cluster pointed to by the source attribute. + *** @returns A new pSearchData struct on success, or NULL on failure. + ***/ +pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) + { + tprintf("Parsing search: %s\n", inf->Name); + + /** Allocate space for search struct. **/ + pSearchData search_data = nmMalloc(sizeof(SearchData)); + assert(search_data != NULL); + memset(search_data, 0, sizeof(SearchData)); + + /** Get search name. **/ + search_data->Name = check_ptr(strdup(inf->Name)); + + /** Get source. **/ + char* source_name; + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_name), node_data->ParamList, true, true) != 0) return NULL; + for (unsigned int i = 0; i < node_data->nClusters; i++) + { + pClusterData cluster_data = node_data->Clusters[i]; + if (strcmp(source_name, cluster_data->Name) == 0) + { + /** Source found. **/ + search_data->Source = cluster_data; + break; + } + + /** Note: Subclusters not implemented here. **/ + } + if (search_data->Source == NULL) + { + mssErrorf(1, "Cluster", "Could not find cluster %s for search %s.", source_name, search_data->Name); + goto err_free_search; + } + + /** Get threshold attribute. **/ + if (ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), node_data->ParamList, true, true) != 0) goto err_free_search; + if (search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold) + { + mssErrorf(1, "Cluster", + "Invalid value for [threshold : 0.0 < x < 1.0 | \"none\"]: %g", + search_data->Threshold + ); + goto err_free_search; + } + + /** Get similarity measure. **/ + search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, node_data->ParamList); + if (search_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free_search; + + /** Create cache entry key. **/ + char* source_key = search_data->Source->Key; + const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; + char* key = nmSysMalloc(len * sizeof(char)); + snprintf(key, len, "%s/%s?%g&%u", + source_key, + search_data->Name, + search_data->Threshold, + search_data->SimilarityMeasure + ); + pXHashTable search_cache = &ClusterCaches.SearchCache; + + /** Check for a cached version. **/ + pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); + if (search_maybe != NULL) + { + /** Cache hit. **/ + tprintf("# search: \"%s\"\n", key); + tprintf("--> Name: %s\n", search_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ + + /** Free the parsed search that we no longer need. */ + ci_FreeSearchData(search_data); + nmSysFree(key); + + /** Return the cached search. **/ + return search_maybe; + } + + /** Cache miss. **/ + tprintf("+ search: \"%s\"\n", key); + check(xhAdd(search_cache, key, (void*)search_data)); + return search_data; + + err_free_search: + ci_FreeSearchData(search_data); + mssErrorf(0, "Cluster", "Failed to parse search from group \"%s\".", inf->Name); + return NULL; + } + + +/*** Allocates a new pNodeData struct from a parsed pStructInf. + *** + *** @attention - Does not use caching directly, but uses subfunctions to + *** handle caching of substructures. + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf for the top level group in a .cluster + *** structure file. + *** @param obj The parent object struct. + *** @returns A new pNodeData struct on success, or NULL on failure. + ***/ +pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) + { + int ret; + + /** Retrieve path so we'll know we have it later. **/ + char* path = obj_internal_PathPart(obj->Pathname, 0, obj->SubPtr); + + /** Allocate node struct data. **/ + // pNodeData node_data = NodeData |> sizeof() |> nmMalloc() |> check_ptr(); + pNodeData node_data = check_ptr(nmMalloc(sizeof(NodeData))); + memset(node_data, 0, sizeof(NodeData)); + node_data->Obj = obj; + + /** Set up param list. **/ + node_data->ParamList = check_ptr(expCreateParamList()); + node_data->ParamList->Session = obj->Session; + ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to add parameters to the param list scope (error code %d).", ret); + goto err_free_node; + } + + /** Set the param functions, defined later in the file. **/ + ret = expSetParamFunctions( + node_data->ParamList, + "parameters", + ci_GetParamType, + ci_GetParamValue, + ci_SetParamValue + ); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to set param functions (error code %d).", ret); + goto err_free_node; + } + + /** Detect relevant groups. **/ + XArray param_infs, cluster_infs, search_infs; + check(xaInit(¶m_infs, 8)); + check(xaInit(&cluster_infs, 8)); + check(xaInit(&search_infs, 8)); + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + /** Check that this is a group (not an attribute). **/ + pStructInf group_inf = inf->SubInf[i]; + ASSERTMAGIC(group_inf, MGK_STRUCTINF); + if (stStructType(group_inf) != ST_T_SUBGROUP) continue; + + /** Select array by group type. **/ + const char* group_type = group_inf->UsrType; + if (strcmp(group_type, "cluster/parameter") == 0) check_strict(xaAddItem(¶m_infs, group_inf)); + else if (strcmp(group_type, "cluster/cluster") == 0) check_strict(xaAddItem(&cluster_infs, group_inf)); + else if (strcmp(group_type, "cluster/search") == 0) check_strict(xaAddItem(&search_infs, group_inf)); + else + { + mssErrorf(1, "Cluster", + "Unkown group type \"%s\" on group \"%s\".", + group_type, group_inf->Name + ); + goto err_free_arrs; + } + } + + /** Extract OpenCtl for use below. **/ + bool has_provided_params = obj != NULL + && obj->Pathname != NULL + && obj->Pathname->OpenCtl != NULL + && obj->Pathname->OpenCtl[obj->SubPtr - 1] != NULL + && obj->Pathname->OpenCtl[obj->SubPtr - 1]->nSubInf > 0 + && obj->Pathname->OpenCtl[obj->SubPtr - 1]->SubInf != NULL; + int num_provided_params = (has_provided_params) ? obj->Pathname->OpenCtl[obj->SubPtr - 1]->nSubInf : 0; + pStruct* provided_params = (has_provided_params) ? obj->Pathname->OpenCtl[obj->SubPtr - 1]->SubInf : NULL; + + /** Itterate over each param in the structure file. **/ + node_data->nParams = param_infs.nItems; + const size_t params_size = node_data->nParams * sizeof(pParam); + node_data->Params = check_ptr(nmMalloc(params_size)); + memset(node_data->Params, 0, params_size); + for (unsigned int i = 0u; i < node_data->nParams; i++) + { + pParam param = paramCreateFromInf(param_infs.Items[i]); + if (param == NULL) + { + mssErrorf(0, "Cluster", + "Failed to create param from inf for param #%u: %s", + i, ((pStructInf)param_infs.Items[i])->Name + ); + goto err_free_arrs; + } + node_data->Params[i] = param; + + /** Check each provided param to see if the user provided value. **/ + for (unsigned int j = 0u; j < num_provided_params; j++) + { + pStruct provided_param = provided_params[j]; + if (provided_param == NULL) + { + mssErrorf(1, "Cluster", "Provided param struct cannot be NULL."); + fprintf(stderr, + "Debug info: obj->Pathname->OpenCtl[%d]->SubInf[%u] is NULL", + obj->SubPtr - 1, j + ); + goto err_free_arrs; + } + + /** If this provided param value isn't for the param, ignore it. **/ + if (strcmp(provided_param->Name, param->Name) != 0) continue; + + /** Matched! The user is providing a value for this param. **/ + ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, obj->Session); + if (ret != 0) + { + mssErrorf(0, "Cluster", + "Failed to set param value from struct info.\n" + " > Param #%u: %s\n" + " > Provided Param #%u: %n\n" + " > Error code: %d", + i, param->Name, + j, provided_param->Name, + ret + ); + goto err_free_arrs; + } + tprintf("Found provided value for %s, which is now %d\n", param->Name, param->Value->Data.Integer); + + /** Provided value successfully handled, we're done. **/ + break; + } + + /** Invoke param hints parsing. **/ + ret = paramEvalHints(param, node_data->ParamList, obj->Session); + if (ret != 0) + { + mssErrorf(0, "Cluster", + "Failed to evaluate parameter hints for parameter \"%s\" (error code %d).", + param->Name, ret + ); + goto err_free_arrs; + } + if (strcmp("k", param->Name) == 0) tprintf("Param k is now %d\n", param->Value->Data.Integer); + } + check(xaDeInit(¶m_infs)); + param_infs.nAlloc = 0; + + /** Parse source data. **/ + node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); + if (node_data->SourceData == NULL) goto err_free_node; + + /** Parse each cluster. **/ + node_data->nClusters = cluster_infs.nItems; + if (node_data->nClusters > 0) + { + const size_t clusters_size = node_data->nClusters * sizeof(pClusterData); + node_data->Clusters = check_ptr(nmMalloc(clusters_size)); + memset(node_data->Clusters, 0, clusters_size); + for (unsigned int i = 0u; i < node_data->nClusters; i++) + { + node_data->Clusters[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data); + if (node_data->Clusters[i] == NULL) goto err_free_arrs; + } + } + else node_data->Clusters = NULL; + check(xaDeInit(&cluster_infs)); + cluster_infs.nAlloc = 0; + + /** Parse each search. **/ + node_data->nSearches = search_infs.nItems; + if (node_data->nSearches > 0) + { + const size_t searches_size = node_data->nSearches * sizeof(pSearchData); + node_data->Searches = check_ptr(nmMalloc(searches_size)); + memset(node_data->Searches, 0, searches_size); + for (unsigned int i = 0u; i < node_data->nSearches; i++) + { + node_data->Searches[i] = ci_ParseSearchData(search_infs.Items[i], node_data); + if (node_data->Searches[i] == NULL) goto err_free_node; /* The XArrays are already freed. */ + } + } + else node_data->Searches = NULL; + check(xaDeInit(&search_infs)); + search_infs.nAlloc = 0; + + /** Success. **/ + return node_data; + + err_free_arrs: + if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); + if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); + if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); + + err_free_node: + ci_FreeNodeData(node_data); + mssErrorf(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); + return NULL; + } + + +/** ================ Freeing Functions ================ **/ +/** ANCHOR[id=freeing] **/ +// LINK #functions + +/** @param source_data A pSourceData struct, freed by this function. **/ +void ci_FreeSourceData(pSourceData source_data) + { + /** Free top level attributes, if they exist. **/ + if (source_data->Name != NULL) + { + free(source_data->Name); + source_data->Name = NULL; + } + if (source_data->SourcePath != NULL) + { + free(source_data->SourcePath); + source_data->SourcePath = NULL; + } + if (source_data->AttrName != NULL) + { + free(source_data->AttrName); + source_data->AttrName = NULL; + } + + /** Free fetched data, if it exists. **/ + if (source_data->Data != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + free(source_data->Data[i]); + nmFree(source_data->Data, source_data->nVectors * sizeof(char*)); + source_data->Data = NULL; + } + + /** Free computed vectors, if they exist. **/ + if (source_data->Vectors != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + ca_free_vector(source_data->Vectors[i]); + nmFree(source_data->Vectors, source_data->nVectors * sizeof(pVector)); + source_data->Vectors = NULL; + } + + /** Free the source_data struct. **/ + nmFree(source_data, sizeof(SourceData)); + } + + +/*** Free pClusterData struct with an option to recursively free subclusters. + *** + *** @param cluster_data The cluster data struct to free. + *** @param recrusive Whether to recursively free subclusters. + ***/ +void ci_FreeClusterData(pClusterData cluster_data, bool recursive) + { + /** Free top level cluster data. **/ + if (cluster_data->Name != NULL) free(cluster_data->Name); + + /** Free computed data, if it exists. **/ + if (cluster_data->Labels != NULL) + { + const unsigned int nVectors = cluster_data->SourceData->nVectors; + nmFree(cluster_data->Labels, nVectors * sizeof(unsigned int)); + } + + /** Free subclusters recursively. **/ + if (cluster_data->SubClusters != NULL) + { + if (recursive) + { + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + ci_FreeClusterData(cluster_data->SubClusters[i], recursive); + } + nmFree(cluster_data->SubClusters, cluster_data->nSubClusters * sizeof(void*)); + } + + /** Free the cluster struct. **/ + nmFree(cluster_data, sizeof(ClusterData)); + } + + +/** @param search_data A pSearchData struct, freed by this function. **/ +void ci_FreeSearchData(pSearchData search_data) + { + if (search_data->Name != NULL) free(search_data->Name); + if (search_data->Dups != NULL) + { + for (unsigned int i = 0; i < search_data->nDups; i++) + nmFree(search_data->Dups[i], sizeof(Dup)); + nmFree(search_data->Dups, search_data->nDups * sizeof(void*)); + } + nmFree(search_data, sizeof(SearchData)); + } + + +/** @param node_data A pNodeData struct, freed by this function. **/ +void ci_FreeNodeData(pNodeData node_data) + { + /** Free parsed params, if they exist. **/ + if (node_data->Params != NULL) + { + for (unsigned int i = 0u; i < node_data->nParams; i++) + { + if (node_data->Params[i] == NULL) break; + paramFree(node_data->Params[i]); + } + nmFree(node_data->Params, node_data->nParams * sizeof(pParam)); + } + if (node_data->ParamList != NULL) expFreeParamList(node_data->ParamList); + + /** Free parsed clusters, if they exist. **/ + if (node_data->Clusters != NULL) + { + /*** This data is cached, so we should NOT free it! + *** The caching system is responsible for the memory. + ***/ + nmFree(node_data->Clusters, node_data->nClusters * sizeof(pClusterData)); + node_data->Clusters = NULL; + } + + /** Free parsed searches, if they exist. **/ + if (node_data->Searches != NULL) + { + /*** This data is cached, so we should NOT free it! + *** The caching system is responsible for the memory. + ***/ + nmFree(node_data->Searches, node_data->nSearches * sizeof(pSearchData)); + node_data->Searches = NULL; + } + + /** Free data source, if one exists. **/ + /*** Note: SourceData is freed last since other free functions may need to + *** access information from this structure when freeing data. + *** (For example, nVector which is used to determine the size of the + *** label struct in each cluster.) + ***/ + if (node_data->SourceData != NULL) + { + /*** This data is cached, so we should NOT free it! + *** The caching system is responsible for the memory. + ***/ + node_data->SourceData = NULL; + } + + /** Free the node data. **/ + nmFree(node_data, sizeof(NodeData)); + } + +/** ================ Deep Size Computation Functions ================ **/ +/** ANCHOR[id=sizing] **/ +// LINK #functions + +/*** Returns the deep size of a SourceData struct, including the size of all + *** allocated substructures. As far as I can tell, this is probably only + *** useful for cache management and debugging. + *** + *** Note that Key is ignored because it is a pointer to data managed by the + *** caching systems, so it is not technically part of the struct. + *** + *** @param source_data The source data struct to be queried. + *** @returns The size in bytes of the struct and all internal allocated data. + ***/ +unsigned int ci_SizeOfSourceData(pSourceData source_data) + { + unsigned int size = 0u; + if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); + if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); + if (source_data->AttrName != NULL) size += strlen(source_data->AttrName) * sizeof(char); + if (source_data->Data != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + size += strlen(source_data->Data[i]) * sizeof(char); + size += source_data->nVectors * sizeof(char*); + } + if (source_data->Vectors != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + size += ca_sparse_len(source_data->Vectors[i]) * sizeof(int); + size += source_data->nVectors * sizeof(pVector); + } + size += sizeof(SourceData); + return size; + } + + +/*** Returns the deep size of a ClusterData struct, including the size of all + *** allocated substructures. As far as I can tell, this is probably only + *** useful for cache management and debugging. + *** + *** Note that Key is ignored because it is a pointer to data managed by the + *** caching systems, so it is not technically part of the struct. + *** + *** @param cluster_data The cluster data struct to be queried. + *** @param recrusive Whether to recursively free subclusters. + *** @returns The size in bytes of the struct and all internal allocated data. + ***/ +unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) + { + unsigned int size = 0u; + if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); + if (cluster_data->Labels != NULL) size += cluster_data->SourceData->nVectors * sizeof(unsigned int); + if (cluster_data->SubClusters != NULL) + { + if (recursive) + { + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + size += ci_SizeOfClusterData(cluster_data->SubClusters[i], recursive); + } + size += cluster_data->nSubClusters * sizeof(void*); + } + size += sizeof(ClusterData); + return size; + } + + +/*** Returns the deep size of a SearchData struct, including the size of all + *** allocated substructures. As far as I can tell, this is probably only + *** useful for cache management and debugging. + *** + *** Note that Key is ignored because it is a pointer to data managed by the + *** caching systems, so it is not technically part of the struct. + *** + *** @param search_data The search data struct to be queried. + *** @returns The size in bytes of the struct and all internal allocated data. + ***/ +unsigned int ci_SizeOfSearchData(pSearchData search_data) + { + unsigned int size = 0u; + if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); + if (search_data->Dups != NULL) size += search_data->nDups * (sizeof(void*) + sizeof(Dup)); + size += sizeof(SearchData); + return size; + } + + +/*** Returns the deep size of a NodeData struct, including the size of all + *** allocated substructures. As far as I can tell, this is probably only + *** useful for cache management and debugging. + *** + *** Note that Key is ignored because it is a pointer to data managed by the + *** caching systems, so it is not technically part of the struct. + *** + *** @param node_data The cluster data struct to be queried. + *** @returns The size in bytes of the struct and all internal allocated data. + ***/ +unsigned int ci_SizeOfNodeData(pNodeData node_data) + { + unsigned int size = 0u; + if (node_data->Params != NULL) + { + /** Approximate. **/ + size += node_data->nParams * (sizeof(Param) + sizeof(pParam)); + } + if (node_data->ParamList == NULL) + { + /** Approximate. **/ + size += node_data->nParams * 30u * sizeof(char); + size += sizeof(pParamObjects); + } + if (node_data->Clusters != NULL) + { + /** Note: This data is also stored in a cache. **/ + for (unsigned int i = 0u; i < node_data->nClusters; i++) + size += ci_SizeOfClusterData(node_data->Clusters[i], true); + size += node_data->nClusters * sizeof(pClusterData); + } + if (node_data->Searches != NULL) + { + /** Note: This data is also stored in a cache. **/ + for (unsigned int i = 0u; i < node_data->nSearches; i++) + size += ci_SizeOfSearchData(node_data->Searches[i]); + size += node_data->nSearches * sizeof(pSearchData); + } + if (node_data->SourceData != NULL) + { + /** Note: This data is also stored in a cache. **/ + size += ci_SizeOfSourceData(node_data->SourceData); + } + size += sizeof(NodeData); + return size; + } + + +/** ================ Cache Invalidation Functions ================ **/ +/** ANCHOR[id=invalidation] **/ +// LINK #functions + +/** Intended for use in xhClearKeySafe(). **/ +void ci_CacheFreeSourceData(pXHashEntry entry, void* _) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pSourceData source_data = (pSourceData)entry->Data; + + /** Free data. **/ + tprintf("- source: \"%s\"\n", key); + ci_FreeSourceData(source_data); + nmSysFree(key); + } + +/** Intended for use in xhClearKeySafe(). **/ +void ci_CacheFreeCluster(pXHashEntry entry, void* _) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pClusterData cluster_data = (pClusterData)entry->Data; + + /** Free data. **/ + tprintf("- cluster: \"%s\"\n", key); + ci_FreeClusterData(cluster_data, false); + nmSysFree(key); + } + +/** Intended for use in xhClearKeySafe(). **/ +void ci_CacheFreeSearch(pXHashEntry entry, void* _) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pSearchData search_data = (pSearchData)entry->Data; + + /** Free data. **/ + tprintf("- search: \"%s\"\n", key); + ci_FreeSearchData(search_data); + nmSysFree(key); + } + +/** ================ Computation Functions ================ **/ +/** ANCHOR[id=computation] **/ +// LINK #functions + +/*** Ensures that the source_data->Data has been fetched from the data source + *** and that source_data->nVectors has been computed from the fetched data. + *** + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param source_data The pSourceData affected by the computation. + *** @param session The current session, used to open the data source. + *** @returns 0 if successful, or + *** -1 other value on failure. + ***/ +int ci_ComputeSourceData(pSourceData source_data, pObjSession session) + { + /** If the vectors are already computed, we're done. **/ + if (source_data->Vectors != NULL) return 0; + + /** Handle error case that happens if memory optimizations break. **/ + if (source_data->Data != NULL) + { + /*** We have data, but not vectors, which means that this function ran + *** before, but the vectors were cleared by ci_GCSourceData(). This + *** should only happen if the vectors will not be needed again. Thus, + *** clearly something has gone wrong. + ***/ + fprintf(stderr, "ERROR:" + "\tci_computeSourceData() invoked on source data \"%s\" where\n" + "\tvectors were previously freed. There is likely a bug in\n" + "\tci_GCSourceData() which caused it to free vectors when we\n" + "\tstill needed them.\n", + source_data->Name + ); + fprintf(stderr, "Resolution:\n" + "\tThe original data will be dropped and refetched, and the\n" + "\tthe vectors will be recomputed, avoiding possible issues\n" + "\tfrom stale data.\n" + ); + + /** Drop source_data->Data. **/ + for (unsigned int i = 0u; i < source_data->nVectors; i++) + free(source_data->Data[i]); + nmFree(source_data->Data, source_data->nVectors * sizeof(char*)); + source_data->Data = NULL; + source_data->nVectors = 0; + } + + /** Time to play shoots-and-ladders in an error-handling jungle of gotos. **/ + bool successful = false; + int ret; + + /** Open the source path specified by the .cluster file. **/ + tprintf("Openning...\n"); + pObject obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); + if (obj == NULL) + { + mssErrorf(0, "Cluster", + "Failed to open object driver:" + " > Attribute: \"%s\" : String\n" + " > Source Path: %s", + source_data->AttrName, + source_data->SourcePath + ); + successful = false; + goto end; + } + + /** Generate a "query" for retrieving data. **/ + tprintf("Openning query...\n"); + pObjQuery query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); + if (query == NULL) + { + mssErrorf(0, "Cluster", + "Failed to open query:\n" + " > Attribute: \"%s\" : String\n" + " > Driver Used: %s\n" + " > Source Path: %s", + source_data->AttrName, + obj->Driver->Name, + source_data->SourcePath + ); + successful = false; + goto end_close; + } + + /** Initialize an xarray to store the retrieved data. **/ + XArray data_xarray, vector_xarray; + check(xaInit(&data_xarray, 64)); + check(xaInit(&vector_xarray, 64)); + + /** Fetch data and build vectors. **/ + tprintf("Skips: "); + unsigned int i = 0u; + while (true) + { + pObject entry = objQueryFetch(query, O_RDONLY); + if (entry == NULL) break; /* Done. */ + + /** Type checking. **/ + const int datatype = objGetAttrType(entry, source_data->AttrName); + if (datatype == -1) + { + mssErrorf(0, "Cluster", + "Failed to get type for %uth entry:\n" + " > Attribute: \"%s\" : String\n" + " > Driver Used: %s\n" + " > Source Path: %s", + i, + source_data->AttrName, + obj->Driver->Name, + source_data->SourcePath + ); + goto end_free_data; + } + if (datatype != DATA_T_STRING) + { + mssErrorf(1, "Cluster", + "Type for %uth entry was not a string:\n" + " > Attribute: \"%s\" : %s!!\n" + " > Driver Used: %s\n" + " > Source Path: %s", + i, + source_data->AttrName, ci_TypeToStr(datatype), + obj->Driver->Name, + source_data->SourcePath + ); + goto end_free_data; + } + + /** Get value from database. **/ + char* val; + ret = objGetAttrValue(entry, source_data->AttrName, DATA_T_STRING, POD(&val)); + if (ret != 0) + { + tprintf("\n"); + mssErrorf(0, "Cluster", + "Failed to value for %uth entry:\n" + " > Attribute: \"%s\" : String\n" + " > Driver Used: %s\n" + " > Source Path: %s\n" + " > Error code: %d", + i, + source_data->AttrName, + obj->Driver->Name, + source_data->SourcePath, + ret + ); + successful = false; + goto end_free_data; + } + + /** Skip empty strings. **/ + if (strlen(val) == 0) + { + tprintf("_"); + check(fflush(stdout)); + continue; + } + + /** Convert the string to a vector. **/ + pVector vector = ca_build_vector(val); + if (vector == NULL) + { + mssErrorf(1, "Cluster", "Failed to build vectors for string \"%s\".", val); + successful = false; + goto end_free_data; + } + if (vector[0] == -CA_NUM_DIMS) + { + mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", val); + successful = false; + goto end_free_data; + } + if (vector[0] == -172 && vector[1] == 11 && vector[2] == -78) + { + /** Skip pVector with no pairs. **/ + tprintf("."); + check(fflush(stdout)); + ca_free_vector(vector); + continue; + } + + /** Store value. **/ + char* dup_val = check_ptr(strdup(val)); + check_strict(xaAddItem(&data_xarray, (void*)dup_val)); + check_strict(xaAddItem(&vector_xarray, (void*)vector)); + + /** Clean up. **/ + check(objClose(entry)); + } + tprintf("\nData aquired.\n"); + source_data->nVectors = vector_xarray.nItems; + + /** Trim data and store data. **/ + const size_t data_size = source_data->nVectors * sizeof(char*); + source_data->Data = check_ptr(nmMalloc(data_size)); + memcpy(source_data->Data, data_xarray.Items, data_size); + check(xaDeInit(&data_xarray)); + data_xarray.nAlloc = 0; + + /** Trim data and store vectors. **/ + const size_t vectors_size = source_data->nVectors * sizeof(pVector); + source_data->Vectors = check_ptr(nmMalloc(vectors_size)); + memcpy(source_data->Vectors, vector_xarray.Items, vectors_size); + check(xaDeInit(&vector_xarray)); + vector_xarray.nAlloc = 0; + + /** Success. **/ + successful = true; + + end_free_data: + if (data_xarray.nAlloc != 0) + { + for (unsigned int i = 0u; i < data_xarray.nItems; i++) + free(data_xarray.Items[i]); + check(xaDeInit(&data_xarray)); + } + if (vector_xarray.nAlloc != 0) + { + for (unsigned int i = 0u; i < vector_xarray.nItems; i++) + ca_free_vector(vector_xarray.Items[i]); + check(xaDeInit(&vector_xarray)); + } + + // end_close_query: + ret = objQueryClose(query); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to close query (error code %d).", ret); + // ret = ret; // Fall-through: Continue through failure. + } + + end_close: + ret = objClose(obj); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to close object driver (error code %d).", ret); + // ret = ret; // Fall-through: Continue through failure. + } + + end: + if (!successful) mssErrorf(0, "Cluster", "Vector computation failed."); + return (successful) ? 0 : -1; + } + +/*** Ensures that the cluster_data->Labels has been computed, running the + *** specified clustering algorithm if necessary. + *** + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param cluster_data The pClusterData affected by the computation. + *** @param node_data The current pNodeData, used to get vectors to cluster. + *** @returns 0 if successful, or + *** -1 other value on failure. + ***/ +int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) + { + /** If the clusters are alreadyd computed, we're done. **/ + if (cluster_data->Labels != NULL) return 0; + + /** Make source data available. **/ + pSourceData source_data = node_data->SourceData; + + /** We need the vectors to compute clusters. **/ + if (ci_ComputeSourceData(source_data, node_data->ParamList->Session) != 0) + { + mssErrorf(0, "Cluster", "Vectors not found."); + goto err; + } + + /** Allocate static memory for finding clusters. **/ + const size_t labels_size = source_data->nVectors * sizeof(unsigned int); + cluster_data->Labels = check_ptr(nmMalloc(labels_size)); + + /** Execute clustering. **/ + switch (cluster_data->ClusterAlgorithm) + { + case ALGORITHM_NONE: + case ALGORITHM_SLIDING_WINDOW: /* Clusters are not computed separately for performance reasons. */ + tprintf("Applying no clustering...\n"); + memset(cluster_data->Labels, 0u, labels_size); + break; + + case ALGORITHM_KMEANS: + /** Check for unimplemented similarity measures. **/ + if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) + { + mssErrorf(1, "Cluster", + "The similarity meausre \"%s\" is not implemented.", + ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) + ); + goto err; + } + + /** kmeans expects clusters to be initialized. **/ + memset(cluster_data->Labels, 0u, labels_size); + + tprintf("Running kmeans\n"); + Timer timer_i, *timer = timer_start(timer_init(&timer_i)); + ca_kmeans( + source_data->Vectors, + source_data->nVectors, + cluster_data->Labels, + cluster_data->NumClusters, + cluster_data->MaxIterations, + cluster_data->MinImprovement + ); + timer_stop(timer); + tprintf("Done after %.4lf.\n", timer_get(timer)); + break; + + default: + mssErrorf(1, "Cluster", + "Clustering algorithm \"%s\" is not implemented.", + ci_ClusteringAlgorithmToString(cluster_data->ClusterAlgorithm) + ); + goto err; + } + + tprintf("Clustering done.\n"); + return 0; + + err: + mssErrorf(0, "Cluster", "Cluster computation failed for \"%s\".", cluster_data->Name); + return -1; + } + +/*** Ensures that the search_data->Dups has been computed, running the a + *** search with the specified similarity measure if necessary. + *** + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param cluster_data The pClusterData affected by the computation. + *** @param node_data The current pNodeData, used to get vectors to cluster. + *** @returns 0 if successful, or + *** -1 other value on failure. + ***/ +int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) + { + int ret; + + /** If the clusters are already computed, we're done. **/ + if (search_data->Dups != NULL) return 0; + + /** Extract structs. **/ + pClusterData cluster_data = search_data->Source; + pSourceData source_data = node_data->SourceData; + + /** We need the clusters to be able to search them. **/ + ret = ci_ComputeClusterData(cluster_data, node_data); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Search computation failed due to missing clusters."); + goto err; + } + + /** Check for unimplemented similarity measures. **/ + if (search_data->SimilarityMeasure != SIMILARITY_COSINE) + { + mssErrorf(1, "Cluster", + "The similarity meausre \"%s\" is not implemented.", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err; + } + + /** Execute the search. **/ + tprintf("Invoking ca_search.\n"); + Timer timer_i, *timer = timer_start(timer_init(&timer_i)); + pXArray dups_temp = ca_search( + source_data->Vectors, + source_data->nVectors, + cluster_data->Labels, + search_data->Threshold + ); + timer_stop(timer); + if (dups_temp == NULL) goto err; + tprintf("ca_search done after %.4lf.\n", timer_get(timer)); + + /** Store dups. **/ + search_data->nDups = dups_temp->nItems; + search_data->Dups = (dups_temp->nItems == 0) + ? check_ptr(nmMalloc(0)) + : ci_xaToTrimmedArray(dups_temp); + + /** Free unused data. **/ + tprintf("Cleanup.\n"); + check(xaFree(dups_temp)); + + return 0; + + err: + mssErrorf(0, "Cluster", "Search computation failed for \"%s\".", search_data->Name); + return -1; + } + + +/** ================ Parameter Functions ================ **/ +/** ANCHOR[id=params] **/ +// LINK #functions + +/*** Get the type of a parameter. Intended for expSetParamFunctions(). + *** + *** @param inf_v Node data containing the list of paramenters. + *** @param attr_name The name of the requested paramenter. + *** @returns The datatype, see datatypes.h for a list of valid datatypes. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int ci_GetParamType(void* inf_v, const char* attr_name) + { + tprintf("Call to ci_GetParamType(\"%s\")\n", attr_name); + pNodeData node_data = (pNodeData)inf_v; + + /** Find the parameter. **/ + for (unsigned int i = 0; i < node_data->nParams; i++) + { + pParam param = node_data->Params[i]; + if (strcmp(param->Name, attr_name) != 0) continue; + + /** Parameter found. **/ + return (param->Value == NULL) ? DATA_T_UNAVAILABLE : param->Value->DataType; + } + + /** Parameter not found. **/ + return DATA_T_UNAVAILABLE; + } + + +/*** Get the value of a parameter. Intended for `expSetParamFunctions()`. + *** + *** @attention - Warning: If the retrieved value is `NULL`, the pObjectData + *** val is not updated, and the function returns 1, indicating `NULL`. + *** This is intended behavior, for consistancy with other Centrallix + *** functions, so keep it in mind so you're not surpised. + *** + *** @param inf_v Node data containing the list of paramenters. + *** @param attr_name The name of the requested paramenter. + *** @param datatype The expected datatype of the parameter value. + *** See datatypes.h for a list of valid datatypes. + *** @param val A pointer to a location where a pointer to the requested + *** data should be stored. Typically, the caller creates a local variable + *** to store this pointer, then passes a pointer to that local variable + *** so that they will have a pointer to the data. + *** This buffer will not be modified unless the data is successfully + *** found. If a value other than 0 is returned, the buffer is not updated. + *** @returns 0 if successsful, + *** 1 if the variable is null, + *** -1 if an error occures. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) + { + tprintf("Call to ci_GetParamValue(\"%s\", %s)\n", attr_name, ci_TypeToStr(datatype)); + pNodeData node_data = (pNodeData)inf_v; + + /** Find the parameter. **/ + for (unsigned int i = 0; i < node_data->nParams; i++) + { + pParam param = (pParam)node_data->Params[i]; + if (strcmp(param->Name, attr_name) != 0) continue; + + tprintf("Param found: Parsing...\n"); + + /** Parameter found. **/ + if (param->Value == NULL) return 1; + if (param->Value->Flags & DATA_TF_NULL) return 1; + if (param->Value->DataType != datatype) + { + mssErrorf(1, "Cluster", "Type mismatch accessing parameter '%s'.", param->Name); + return -1; + } + + tprintf("Param found: Copying...\n"); + /** Return param value. **/ + objCopyData(&(param->Value->Data), val, datatype); + return 0; + } + + /** Param not found. **/ + tprintf("Param not found.\n"); + return -1; + } + + +/** Not implemented. **/ +int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) + { + tprintf("Call to ci_SetParamValue(%s, %s)\n", attr_name, ci_TypeToStr(datatype)); + mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are imutable."); + return -1; + } + + +/** ================ Driver functions ================ **/ +/** ANCHOR[id=driver] **/ +// LINK #functions + +/*** Opens a new cluster driver instance by parsing a `.cluster` file found + *** at the path provided in obj. + *** + *** @param obj The object being opened, including the path, session, and + *** other necessary information. + *** @param mask Driver permission mask (unused). + *** @param systype ? (unused) + *** @param usr_type The object system file type being openned. Should always + *** be "system/cluster" because this driver is only registered for that + *** type of file. + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** + *** @returns A pDriverData struct representing a driver instance, or + *** NULL if an error occures. + ***/ +void* clusterOpen(pObject obj, int mask, pContentType systype, char* usr_type, pObjTrxTree* oxt) + { + tprintf( + "Warning: clusterOpen(\"%s\") is under active development.\n", + obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt, 1) + ); + + /** If CREAT and EXCL are specified, create it and fail if it already exists. **/ + pSnNode node_struct = NULL; + bool can_create = (obj->Mode & O_CREAT) && (obj->SubPtr == obj->Pathname->nElements); + if (can_create && (obj->Mode & O_EXCL)) + { + node_struct = snNewNode(obj->Prev, usr_type); + if (node_struct == NULL) + { + mssErrorf(0, "Cluster", "Failed to EXCL create new node struct."); + goto err; + } + } + + /** Read the node if it exists. **/ + if (node_struct == NULL) + node_struct = snReadNode(obj->Prev); + + /** If we can't read, create it (if allowed). **/ + if (node_struct == NULL && can_create) + node_struct = snNewNode(obj->Prev, usr_type); + + /** If there still isn't a node, fail early. **/ + if (node_struct == NULL) + { + mssErrorf(0, "Cluster", "Failed to create node struct."); + goto err; + } + + /** Parse node data. **/ + pNodeData node_data = ci_ParseNodeData(node_struct->Data, obj); + if (node_data == NULL) + { + mssErrorf(0, "Cluster", + "Failed to parse structure file of name %s.", + obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt, 1) + ); + goto err; + } + node_data->Node = node_struct; + node_data->Node->OpenCnt++; + + /** Allocate driver instance data. **/ + pDriverData driver_data = check_ptr(nmMalloc(sizeof(DriverData))); + memset(driver_data, 0, sizeof(DriverData)); + driver_data->NodeData = node_data; + + /** Detect target from path. **/ + tprintf("Parsing node path: %d %d\n", obj->SubPtr, obj->SubCnt); obj->SubCnt = 0; + char* target_name = obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt++, 1); + if (target_name == NULL) + { + /** Target found: Root **/ + tprintf("Found target: Root.\n"); + driver_data->TargetType = TARGET_ROOT; + driver_data->TargetData = (void*)driver_data->NodeData->SourceData; + return (void*)driver_data; /* Sucess. */ + } + + /** Search clusters. **/ + for (unsigned int i = 0u; i < node_data->nClusters; i++) + { + pClusterData cluster = node_data->Clusters[i]; + if (strcmp(cluster->Name, target_name) != 0) continue; + + /** Target found: Cluster **/ + driver_data->TargetType = TARGET_CLUSTER; + tprintf("Found target cluster: %s\n", cluster->Name); + + /** Check for sub-clusters in the path. **/ + while (true) + { + /** Decend one path part deeper into the path. **/ + const char* path_part = obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt++, 1); + + /** If the path does not go any deeper, we're done. **/ + if (path_part == NULL) + { + driver_data->TargetData = (void*)cluster; + break; + } + + /** Need to go deeper: Search for the requested sub-cluster. **/ + for (unsigned int i = 0u; i < cluster->nSubClusters; i++) + { + pClusterData sub_cluster = cluster->SubClusters[i]; + if (strcmp(sub_cluster->Name, path_part) != 0) continue; + + /** Target found: Sub-cluster **/ + tprintf("Found target sub-cluster: %s\n", sub_cluster->Name); + cluster = sub_cluster; + goto continue_descent; + } + + /** Path names sub-cluster that does not exist. **/ + mssErrorf(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); + goto err_free_node; + + continue_descent:; + } + return (void*)driver_data; /* Sucess. */ + } + + /** Search searches. **/ + for (unsigned int i = 0u; i < node_data->nSearches; i++) + { + pSearchData search = node_data->Searches[i]; + if (strcmp(search->Name, target_name) != 0) continue; + + /** Target found: Search **/ + driver_data->TargetType = TARGET_SEARCH; + driver_data->TargetData = (void*)search; + + /** Check for extra, invalid path parts. **/ + char* extra_data = obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt++, 1); + if (extra_data != NULL) + { + mssErrorf(1, "Cluster", "Unknown path part %s.", extra_data); + goto err_free_node; + } + tprintf("Found target search: %s %d %d\n", search->Name, obj->SubPtr, obj->SubCnt); + return (void*)driver_data; /* Sucess. */ + } + + /** We were unable to find the requested cluster or search. **/ + mssErrorf(1, "Cluster", "\"%s\" is not the name of a declaired cluster or search.", target_name); + + /** Error cleanup. **/ + err_free_node: + ci_FreeNodeData(node_data); + nmFree(driver_data, sizeof(DriverData)); + + err: + return NULL; + } + + +/*** Close a cluster driver instance object, releasing any necessary memory + *** and closing any necessary underlying resources. However, most of that + *** data will be cached and won't be freed unless the cache is dropped. + *** + *** @param inf_v The affected driver instance. + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @returns 0, success. + ***/ +int clusterClose(void* inf_v, pObjTrxTree* oxt) + { + tprintf("Warning: clusterClose() is under active development.\n"); + pDriverData driver_data = (pDriverData)inf_v; + + /** Entries are shallow copies so we shouldn't do a deep free. **/ + if (driver_data->TargetType == TARGET_CLUSTER_ENTRY + || driver_data->TargetType == TARGET_SEARCH_ENTRY) + { + nmFree(driver_data, sizeof(DriverData)); + return 0; + } + + /** Free the node data (which is held in cache). **/ + ci_FreeNodeData(driver_data->NodeData); + + /** Free driver data. **/ + nmFree(driver_data, sizeof(DriverData)); + + return 0; + } + + +/*** Opens a new query pointing to the first row of the data targetted by + *** the driver instance struct. The query has an internal index counter + *** that starts at the first row and increments as data is fetched. + *** + *** @param inf_v The driver instance to be queried. + *** @param query The query to use on this struct. This is assumed to be + *** handled elsewhere, so we don't read it here (unused). + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @returns The cluster query. + ***/ +void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) + { + tprintf("Warning: clusterOpenQuery() is under active development.\n"); + pClusterQuery cluster_query = check_ptr(nmMalloc(sizeof(ClusterQuery))); + cluster_query->DriverData = (pDriverData)inf_v; + cluster_query->RowIndex = 0u; + return cluster_query; + } + + +/*** Get the next entry as an open driver instance object. + *** + *** @param qy_v A query instance, storing an internal index which is + *** incremented once that data has been fetched. + *** @param obj Unused. + *** @param mode Unused. + *** @param oxt Unused. + *** @returns pDriverData that is either a cluster entry or search entry, + *** pointing to a specific target index into the relevant data. + *** OR NULL, indicating that all data has been fetched. + ***/ +void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) + { + int ret; + tprintf("Warning: clusterQueryFetch() is under active development.\n"); + pClusterQuery cluster_query = (pClusterQuery)qy_v; + + /** Ensure that the data being fetched exists and is computed. **/ + TargetType target_type = cluster_query->DriverData->TargetType, new_target_type; + unsigned int data_amount = 0u; + switch (target_type) + { + case TARGET_ROOT: + mssErrorf(1, "Cluster", "Querying the root node of a cluster file is not allowed."); + fprintf(stderr, " > Hint: Try / or /\n"); + return NULL; + + case TARGET_CLUSTER: + { + new_target_type = TARGET_CLUSTER_ENTRY; + pClusterData target = (pClusterData)cluster_query->DriverData->TargetData; + ret = ci_ComputeClusterData(target, cluster_query->DriverData->NodeData); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Internal cluster computation failed."); + return NULL; + } + data_amount = cluster_query->DriverData->NodeData->SourceData->nVectors; + break; + } + + case TARGET_SEARCH: + { + new_target_type = TARGET_SEARCH_ENTRY; + pSearchData target = (pSearchData)cluster_query->DriverData->TargetData; + ret = ci_ComputeSearchData(target, cluster_query->DriverData->NodeData); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Internal search computation failed."); + return NULL; + } + data_amount = target->nDups; + break; + } + + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + mssErrorf(1, "Cluster", "Querying a query result is not allowed."); + return NULL; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + return NULL; + } + tprintf("Fetch Index: %u/16 (total: %u)\n", cluster_query->RowIndex, data_amount); + + /** Cap results to 16 for faster debugging. TODO: Remove. **/ + data_amount = min(data_amount, 16); + + /** Check that the requested data exists, returning null if we've reached the end of the data. **/ + if (cluster_query->RowIndex >= data_amount) return NULL; + + /** Create the result struct. **/ + pDriverData driver_data = nmMalloc(sizeof(DriverData)); + assert(driver_data != NULL); + memcpy(driver_data, cluster_query->DriverData, sizeof(DriverData)); + driver_data->TargetType = new_target_type; + driver_data->TargetIndex = cluster_query->RowIndex++; + + return driver_data; + } + + +/*** Close a cluster query instance, releasing any necessary memory and + *** closing any necessary underlying resources. This does not close the + *** underlying driver instance, which must be closed with clusterClose(). + *** + *** @param qy_v The affected query instance. + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @returns 0, success. + ***/ +int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) + { + tprintf("Warning: clusterQueryClose() is under active development.\n"); + + nmFree(qy_v, sizeof(ClusterQuery)); + return 0; + } + + +/*** Get the type of a cluster driver instance attribute. + *** + *** @param inf_v The driver instance. + *** @param attr_name The name of the requested attribute. + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @returns The datatype, see datatypes.h for a list of valid datatypes. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) + { + pDriverData driver_data = (pDriverData)inf_v; + + /** Performance shortcut for frequently requested attributes: val1, val2, and sim. **/ + if (attr_name[0] == 'v' || attr_name[0] == 's') goto handle_targets; + + /** Debug info. **/ + if (oxt == NULL) tprintf(" > "); + tprintf("Call to clusterGetAttrType(%s)\n", attr_name); + + /** Guard possible segfault. **/ + if (attr_name == NULL) + { + fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); + return DATA_T_UNAVAILABLE; + } + + /** Types for general attributes. **/ + if (strcmp(attr_name, "name") == 0 + || strcmp(attr_name, "annotation") == 0 + || strcmp(attr_name,"content_type") == 0 + || strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name,"outer_type") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "last_modification") == 0) + return DATA_T_DATETIME; + + /** Types for specific data targets. **/ + handle_targets: + switch (driver_data->TargetType) + { + case TARGET_ROOT: + if (strcmp(attr_name, "source") == 0 + || strcmp(attr_name, "attr_name") == 0) + return DATA_T_STRING; + break; + + case TARGET_CLUSTER: + if (strcmp(attr_name, "algorithm") == 0 + || strcmp(attr_name, "similarity_measure") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "num_clusters") == 0 + || strcmp(attr_name, "max_iterations") == 0) + return DATA_T_INTEGER; + if (strcmp(attr_name, "min_improvement") == 0 + || strcmp(attr_name, "average_similarity") == 0 + || strcmp(attr_name, "size") == 0) + return DATA_T_DOUBLE; + break; + + case TARGET_SEARCH: + if (strcmp(attr_name, "source") == 0 + || strcmp(attr_name, "similarity_measure") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "threshold") == 0) + return DATA_T_DOUBLE; + break; + + case TARGET_CLUSTER_ENTRY: + if (strcmp(attr_name, "id") == 0) + return DATA_T_INTEGER; + if (strcmp(attr_name, "val") == 0) + { + /** TODO: Replace with type calculation. **/ + return DATA_T_STRING; + } + if (strcmp(attr_name, "sim") == 0) + return DATA_T_DOUBLE; + break; + + case TARGET_SEARCH_ENTRY: + if (strcmp(attr_name, "id1") == 0 + || strcmp(attr_name, "id2") == 0) + return DATA_T_INTEGER; + if (strcmp(attr_name, "val1") == 0 + || strcmp(attr_name, "val2") == 0) + { + /** TODO: Replace with type calculation. **/ + return DATA_T_STRING; + } + if (strcmp(attr_name, "sim") == 0) + return DATA_T_DOUBLE; + break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return DATA_T_UNAVAILABLE; + } + + return DATA_T_UNAVAILABLE; + } + + +/*** Get the value of a cluster driver instance attribute. + *** + *** @param inf_v Node data containing the list of paramenters. + *** @param attr_name The name of the requested paramenter. + *** @param datatype The expected datatype of the parameter value. + *** See datatypes.h for a list of valid datatypes. + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param val A pointer to a location where a pointer to the requested + *** data should be stored. Typically, the caller creates a local variable + *** to store this pointer, then passes a pointer to that local variable + *** so that they will have a pointer to the data. + *** This buffer will not be modified unless the data is successfully + *** found. If a value other than 0 is returned, the buffer is not updated. + *** @returns 0 if successsful, + *** -1 if an error occures. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) + { + pDriverData driver_data = (pDriverData)inf_v; + + /** Performance shortcut for frequently requested attributes: val1, val2, and sim. **/ + if ( + (attr_name[0] == 'v' && datatype == DATA_T_STRING) /* val, val1, val2 : String */ + || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ + ) goto handle_targets; + + /** Debug info. **/ + tprintf("Call to clusterGetAttrValue(%s)\n", attr_name); + + /** Type check. **/ + const int expected_datatype = clusterGetAttrType(inf_v, attr_name, NULL); + if (datatype != expected_datatype) + { + mssErrorf(1, "Cluster", + "Type mismatch: Accessing attribute '%s' : %s as type %s.", + attr_name, ci_TypeToStr(expected_datatype), ci_TypeToStr(datatype) + ); + return -1; + } + + /** Handle name and annotation. **/ + if (strcmp(attr_name, "name") == 0) + { + switch (driver_data->TargetType) + { + case TARGET_ROOT: + val->String = ((pSourceData)driver_data->TargetData)->Name; + break; + + case TARGET_CLUSTER: + case TARGET_CLUSTER_ENTRY: + val->String = ((pClusterData)driver_data->TargetData)->Name; + break; + + case TARGET_SEARCH: + case TARGET_SEARCH_ENTRY: + val->String = ((pSearchData)driver_data->TargetData)->Name; + break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; + } + + return 0; + } + if (strcmp(attr_name, "annotation") == 0) + { + switch (driver_data->TargetType) + { + case TARGET_ROOT: val->String = "Clustering driver."; break; + case TARGET_CLUSTER: val->String = "Clustering driver: Cluster."; break; + case TARGET_CLUSTER_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + case TARGET_SEARCH: val->String = "Clustering driver: Search."; break; + case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + } + return 0; + } + + /** Return the appropriate types. **/ + if (strcmp(attr_name, "outer_type") == 0) + { + val->String = "system/row"; + return 0; + } + if (strcmp(attr_name, "content_type") == 0 + || strcmp(attr_name, "inner_type") == 0) + { + val->String = "system/void"; + return 0; + } + + /** Last modification is not implemented yet. **/ + if (strcmp(attr_name, "last_modification") == 0) return 1; /* null */ + + /** Handle attributes for specific data targets. **/ + handle_targets: + switch (driver_data->TargetType) + { + case TARGET_ROOT: + if (strcmp(attr_name, "source") == 0) + { + val->String = ((pSourceData)driver_data->TargetData)->SourcePath; + return 0; + } + if (strcmp(attr_name, "attr_name") == 0) + { + val->String = ((pSourceData)driver_data->TargetData)->AttrName; + return 0; + } + break; + + case TARGET_CLUSTER: + { + pClusterData target = (pClusterData)driver_data->TargetData; + + if (strcmp(attr_name, "algorithm") == 0) + { + val->String = ci_ClusteringAlgorithmToString(target->ClusterAlgorithm); + return 0; + } + if (strcmp(attr_name, "similarity_measure") == 0) + { + val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + return 0; + } + if (strcmp(attr_name, "num_clusters") == 0) + { + if (target->NumClusters > INT_MAX) + fprintf(stderr, "Warning: num_clusters value of %u exceeds INT_MAX.\n", target->NumClusters); + val->Integer = (int)target->NumClusters; + return 0; + } + if (strcmp(attr_name, "max_iterations") == 0) + { + if (target->MaxIterations > INT_MAX) + fprintf(stderr, "Warning: max_iterations value of %u exceeds INT_MAX.\n", target->MaxIterations); + val->Integer = (int)target->MaxIterations; + return 0; + } + if (strcmp(attr_name, "min_improvement") == 0) + { + val->Double = target->MinImprovement; + return 0; + } + if (strcmp(attr_name, "average_similarity") == 0 + || strcmp(attr_name, "size") == 0) + { + mssErrorf(1, "Cluster", "average_similarity is not implemented."); + return -1; + } + break; + } + + case TARGET_SEARCH: + { + pSearchData target = (pSearchData)driver_data->TargetData; + + if (strcmp(attr_name, "source") == 0) + { + val->String = target->Source->Name; + return 0; + } + if (strcmp(attr_name, "similarity_measure") == 0) + { + val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + return 0; + } + if (strcmp(attr_name, "threshold") == 0) + { + val->Double = target->Threshold; + return 0; + } + } + + case TARGET_CLUSTER_ENTRY: + { + pClusterData target = (pClusterData)driver_data->TargetData; + + if (strcmp(attr_name, "id") == 0) + { + val->Integer = (int)target->Labels[driver_data->TargetIndex]; + return 0; + } + if (strcmp(attr_name, "val") == 0) + { + val->String = driver_data->NodeData->SourceData->Data[driver_data->TargetIndex]; + return 0; + } + if (strcmp(attr_name, "sim") == 0) + { + mssErrorf(1, "Cluster", "Cluster entry similarity is not supported."); + return -1; + } + break; + } + + case TARGET_SEARCH_ENTRY: + { + pSearchData target = (pSearchData)driver_data->TargetData; + pDup target_dup = target->Dups[driver_data->TargetIndex]; + + if (strcmp(attr_name, "id1") == 0) + { + val->Integer = (int)target_dup->id1; + return 0; + } + if (strcmp(attr_name, "id2") == 0) + { + val->Integer = (int)target_dup->id2; + return 0; + } + if (strcmp(attr_name, "val1") == 0) + { + val->String = driver_data->NodeData->SourceData->Data[target_dup->id1]; + // val->Integer = (int)target_dup->id1; + return 0; + } + if (strcmp(attr_name, "val2") == 0) + { + val->String = driver_data->NodeData->SourceData->Data[target_dup->id2]; + // val->Integer = (int)target_dup->id2; + return 0; + } + if (strcmp(attr_name, "sim") == 0) + { + val->Double = target_dup->similarity; + return 0; + } + break; + } + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; + } + + /** Unknown attribute. **/ + char* name; + clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); + mssErrorf(1, "Cluster", + "Unknown attribute '%s' for cluster object %s (target type: %u, \"%s\").", + attr_name, driver_data->NodeData->SourceData->Name, driver_data->TargetType, name + ); + + return -1; + } + + +/*** Returns the name of the first attribute that one can get from + *** this driver instance (using GetAttrType() and GetAttrValue()). + *** Resets the internal variable (TargetAttrIndex) used to maintain + *** itteration state for clusterGetNextAttr(). + *** + *** @param inf_v The driver instance to be read. + *** @param oxt Unused. + *** @returns The name of the first attribute. + ***/ +char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt) + { + tprintf("Warning: clusterGetFirstAttr() is under active development.\n"); + pDriverData driver_data = (pDriverData)inf_v; + driver_data->TargetAttrIndex = 0u; + return clusterGetNextAttr(inf_v, oxt); + } + + +/*** Returns the name of the next attribute that one can get from + *** this driver instance (using GetAttrType() and GetAttrValue()). + *** Uses an internal variable (TargetAttrIndex) used to maintain + *** the state of this itteration over repeated calls. + *** + *** @param inf_v The driver instance to be read. + *** @param oxt Unused. + *** @returns The name of the next attribute. + ***/ +char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt) + { + tprintf("Warning: clusterGetNextAttr("); + pDriverData driver_data = (pDriverData)inf_v; + const unsigned int i = driver_data->TargetAttrIndex++; + tprintf("%u) is under active development.\n", i); + switch (driver_data->TargetType) + { + case TARGET_ROOT: return (i < nATTR_ROOT) ? ATTR_ROOT[i] : END_OF_ATTRIBUTES; + case TARGET_CLUSTER: return (i < nATTR_CLUSTER) ? ATTR_CLUSTER[i] : END_OF_ATTRIBUTES; + case TARGET_SEARCH: return (i < nATTR_SEARCH) ? ATTR_SEARCH[i] : END_OF_ATTRIBUTES; + case TARGET_CLUSTER_ENTRY: return (i < nATTR_CLUSTER_ENTRY) ? ATTR_CLUSTER_ENTRY[i] : END_OF_ATTRIBUTES; + case TARGET_SEARCH_ENTRY: return (i < nATTR_SEARCH_ENTRY) ? ATTR_SEARCH_ENTRY[i] : END_OF_ATTRIBUTES; + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return NULL; + } + } + + +/*** Get the capabilities of the driver instance object. + *** + *** @param inf_v The driver instance to be checked. + *** @param info The struct to be populated with driver flags. + *** @returns 0 if succesful, + *** -1 if the driver is an unimplemented type (should never happen). + ***/ +int clusterInfo(void* inf_v, pObjectInfo info) + { + tprintf("Warning: clusterInfo() is under active development.\n"); + pDriverData driver_data = (pDriverData)inf_v; + pNodeData node_data = (pNodeData)driver_data->NodeData; + + /** Reset flags buffer. **/ + info->Flags = 0; + + /** Disallow unsupported functionality. **/ + info->Flags |= OBJ_INFO_F_CANT_ADD_ATTR; + info->Flags |= OBJ_INFO_F_CANT_HAVE_CONTENT; + info->Flags |= OBJ_INFO_F_NO_CONTENT; + + switch (driver_data->TargetType) + { + case TARGET_ROOT: + info->nSubobjects = node_data->nClusters + node_data->nSearches; + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; + break; + + case TARGET_CLUSTER: + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_HAS_SUBOBJ; /* Data must not be empty. */ + + /*** Clusters always have one label per vector. + *** If we know how many vectors are in the dataset, + *** we know how many labels this cluster will have, + *** even if it hasn't been computed yet. + ***/ + if (node_data->SourceData->Vectors != NULL) + { + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->nSubobjects = node_data->SourceData->nVectors; + } + break; + + case TARGET_SEARCH: + { + pSearchData search_data = (pSearchData)driver_data->TargetData; + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + if (search_data->Dups != NULL) + { + info->nSubobjects = search_data->nDups; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; + } + break; + } + + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + /** No Subobjects. **/ + info->Flags |= OBJ_INFO_F_CANT_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_NO_SUBOBJ; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->nSubobjects = 0; + break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; + } + + tprintf("Info result: "INT_TO_BINARY_PATTERN"\n", INT_TO_BINARY(info->Flags)); + return 0; + } + + +/** ================ Method Execution Functions ================ **/ +/** ANCHOR[id=method] **/ +// LINK #functions + +/*** Returns the name of the first method that one can execute from + *** this driver instance (using clusterExecuteMethod()). Resets the + *** internal variable (TargetMethodIndex) used to maintain itteration + *** state for clusterGetNextMethod(). + *** + *** @param inf_v The driver instance to be read. + *** @param oxt Unused. + *** @returns The name of the first methd. + ***/ +char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt) + { + tprintf("Warning: clusterGetFirstMethod() is under active development.\n"); + pDriverData driver_data = (pDriverData)inf_v; + driver_data->TargetMethodIndex = 0u; + return clusterGetNextMethod(inf_v, oxt); + } + + +/*** Returns the name of the next method that one can get from + *** this driver instance (using GetAttrType() and GetAttrValue()). + *** Uses an internal variable (TargetMethodIndex) used to maintain + *** the state of this itteration over repeated calls. + *** + *** @param inf_v The driver instance to be read. + *** @param oxt Unused. + *** @returns The name of the next method. + ***/ +char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt) + { + tprintf("Warning: clusterGetNextMethod("); + pDriverData driver_data = (pDriverData)inf_v; + const unsigned int i = driver_data->TargetMethodIndex++; + tprintf("%u) is under active development.\n", i); + return (i < nMETHOD_NAME) ? METHOD_NAME[i] : END_OF_METHODS; + } + +/** Intended for use in xhForEach(). **/ +static int ci_PrintEntry(pXHashEntry entry, void* arg) + { + /** Extract entry. **/ + char* key = entry->Key; + void* data = entry->Data; + + /** Extract args. **/ + void** args = (void**)arg; + unsigned int* type_id_ptr = (unsigned int*)args[0]; + unsigned int* total_bytes_ptr = (unsigned int*)args[1]; + char* path = (char*)args[2]; + + /** If a path is provided, check that it matches the start of the key. **/ +// if (path != NULL) printf("Comparing \"%s\" to \"%s\"[0,%lu].\n", path, key, strlen((char*)path)); + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return 0; + + /** Handle type. **/ + char* type; + char* name; + unsigned int bytes; + switch (*type_id_ptr) + { + case 1u: + { + pSourceData source_data = (pSourceData)data; + type = "Source"; + name = source_data->Name; + bytes = ci_SizeOfSourceData(source_data); + break; + } + case 2u: + { + pClusterData cluster_data = (pClusterData)data; + type = "Cluster"; + name = cluster_data->Name; + bytes = ci_SizeOfClusterData(cluster_data, false); + break; + } + case 3u: + { + pSearchData search_data = (pSearchData)data; + type = "Search"; + name = search_data->Name; + bytes = ci_SizeOfSearchData(search_data); + break; + } + default: assert(false); + } + + /** Increment total bytes. **/ + *total_bytes_ptr += bytes; + + char buf[12]; + snprint_bytes(buf, sizeof(buf), bytes); + printf("%-8s %-16s %-12s \"%s\"\n", type, name, buf, key); + + return 0; + } + + +/*** Executes a method with the given name. + *** + *** @param inf_v The affected driver instance. + *** @param method_name The name of the method. + *** @param param A possibly optional param passed to the method. + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + ***/ +int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree oxt) + { + tprintf("Warning: clusterExecuteMethod(\"%s\") is under active development.\n", method_name); + + /** Cache management method. **/ + if (strcmp(method_name, "cache") == 0) + { + /** Second parameter is required. **/ + if (param->String == NULL) + { + mssErrorf(1, "Cluster", + "param : \"show\" | \"show_all\" | \"drop_all\" is required for the cache method." + ); + return -1; + } + + /** Show cache. **/ + if (strcmp(param->String, "show") == 0) + { + const pObject obj = ((pDriverData)inf_v)->NodeData->Obj; + char* path = obj_internal_PathPart(obj->Pathname, 0, obj->SubPtr); + + /** Print cache info table. **/ + unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; + printf("\nShowing cache for \"%s\":\n", path); + printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); + xhForEach(&ClusterCaches.SourceCache, ci_PrintEntry, (void*[]){&i, &source_bytes, path}); i++; + xhForEach(&ClusterCaches.ClusterCache, ci_PrintEntry, (void*[]){&i, &cluster_bytes, path}); i++; + xhForEach(&ClusterCaches.SearchCache, ci_PrintEntry, (void*[]){&i, &search_bytes, path}); i++; + + /** Print stats. **/ + char buf[16]; + printf("\nCache Stats:\n"); + printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); + const int n_sources = ClusterCaches.SourceCache.nItems; + snprint_bytes(buf, sizeof(buf), source_bytes); + printf("%-8s %-4d %-12s\n", "Source", n_sources, buf); + const int n_clusters = ClusterCaches.ClusterCache.nItems; + snprint_bytes(buf, sizeof(buf), cluster_bytes); + printf("%-8s %-4d %-12s\n", "Cluster", n_clusters, buf); + const int n_searches = ClusterCaches.SearchCache.nItems; + snprint_bytes(buf, sizeof(buf), search_bytes); + printf("%-8s %-4d %-12s\n", "Search", n_searches, buf); + snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes); + printf("%-8s %-4d %-12s\n\n", "Total", n_sources + n_clusters + n_searches, buf); + return 0; + } + + + /** Show all cache. **/ + if (strcmp(param->String, "show_all") == 0) + { + /** Print cache info table. **/ + unsigned int i = 1u, total_bytes = 0u; + tprintf("Showing cluster driver cache for all files...\n"); + printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); + xhForEach(&ClusterCaches.SourceCache, ci_PrintEntry, (void*[]){&i, &total_bytes, NULL}); i++; + xhForEach(&ClusterCaches.ClusterCache, ci_PrintEntry, (void*[]){&i, &total_bytes, NULL}); i++; + xhForEach(&ClusterCaches.SearchCache, ci_PrintEntry, (void*[]){&i, &total_bytes, NULL}); i++; + + /** Print total size. **/ + char buf[16]; + snprint_bytes(buf, sizeof(buf), total_bytes); + printf("Total cache size: %s\n", buf); + return 0; + } + + /** Drop allcache. **/ + if (strcmp(param->String, "drop_all") == 0) + { + tprintf("Dropping cluster driver cache for all files...\n"); + /*** Free caches in reverse of the order they are created in case + *** cached data relies on its source during the freeing process. + ***/ + xhClearKeySafe(&ClusterCaches.SearchCache, ci_CacheFreeSearch, NULL); + xhClearKeySafe(&ClusterCaches.ClusterCache, ci_CacheFreeCluster, NULL); + xhClearKeySafe(&ClusterCaches.SourceCache, ci_CacheFreeSourceData, NULL); + printf("Cache dropped.\n"); + return 0; + } + + /** Unknown parameter. **/ + mssErrorf(1, "Cluster", + "Expected param : \"show\" | \"show_all\" | \"drop_all\" the cache method, but got: \"%s\"", + param->String + ); + return -1; + } + + return -1; + } + +/** ================ Unimplemented Functions ================ **/ +/** ANCHOR[id=unimplemented] **/ +// LINK #functions + +/** Not implemented. **/ +int clusterCreate(pObject obj, int mask, pContentType systype, char* usrtype, pObjTrxTree* oxt) + { + mssErrorf(1, "Cluster", "clusterCreate() is not implemented."); + return -ENOSYS; + } +/** Not implemented. **/ +int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) + { + mssErrorf(1, "Cluster", "clusterDeleteObj() is not implemented."); + return -1; + } +/** Not implemented. **/ +int clusterDelete(pObject obj, pObjTrxTree* oxt) + { + mssErrorf(1, "Cluster", "clusterDelete() is not implemented."); + return -1; + } +/** Not implemented. **/ +int clusterRead(void* inf_v, char* buffer, int maxcnt, int offset, int flags, pObjTrxTree* oxt) + { + mssErrorf(1, "Cluster", "clusterRead() not implemented."); + fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); + return -1; + } +/** Not implemented. **/ +int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt) + { + mssErrorf(1, "Cluster", "clusterWrite() not implemented because clusters are imutable."); + return -1; + } +/** Not implemented. **/ +int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree oxt) + { + mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are imutable."); + return -1; + } +/** Not implemented. **/ +int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree oxt) + { + mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are imutable."); + return -1; + } +/** Not implemented. **/ +void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree oxt) + { + mssErrorf(1, "Cluster", "clusterOpenAttr() not implemented."); + return NULL; + } +/** Not implemented. **/ +int clusterCommit(void* inf_v, pObjTrxTree *oxt) + { + mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are imutable."); + return 0; + } +/** Not implemented. **/ +pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) + { + mssErrorf(1, "Cluster", "clusterPresentationHints() not implemented."); + return NULL; + } + + +/*** Initialize the driver. This includes: + *** - Registering the driver with the objectsystem. + *** - Registering structs with newmalloc for debugging. + *** - Initializing global data needed for the driver. + *** + *** @returns 0 if successful, or + *** a negative value if an error occured. + ***/ +int clusterInitialize(void) + { + int ret; + /** Initialize library. **/ + ca_init(); + + /** Allocate the driver. **/ + pObjDriver drv = (pObjDriver)nmMalloc(sizeof(ObjDriver)); + if (drv == NULL) return -1; + memset(drv, 0, sizeof(ObjDriver)); + + /** Initialize globals. **/ + memset(&ClusterCaches, 0, sizeof(ClusterCaches)); + ret = xhInit(&ClusterCaches.SourceCache, 251, 0); + if (ret < 0) return ret; + ret = xhInit(&ClusterCaches.ClusterCache, 251, 0); + if (ret < 0) return ret; + ret = xhInit(&ClusterCaches.SearchCache, 251, 0); + if (ret < 0) return ret; + + /** Setup the structure. **/ + strcpy(drv->Name, "clu - Clustering Driver"); + drv->Capabilities = OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; // OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; + ret = xaInit(&(drv->RootContentTypes), 1); + if (ret < 0) return ret; + ret = xaAddItem(&(drv->RootContentTypes), "system/cluster"); + if (ret < 0) return ret; + + /** Setup the function references. **/ + drv->Open = clusterOpen; + drv->Close = clusterClose; + drv->Create = clusterCreate; + drv->Delete = clusterDelete; + drv->DeleteObj = clusterDeleteObj; + drv->OpenQuery = clusterOpenQuery; + drv->QueryDelete = NULL; + drv->QueryFetch = clusterQueryFetch; + drv->QueryClose = clusterQueryClose; + drv->Read = clusterRead; + drv->Write = clusterWrite; + drv->GetAttrType = clusterGetAttrType; + drv->GetAttrValue = clusterGetAttrValue; + drv->GetFirstAttr = clusterGetFirstAttr; + drv->GetNextAttr = clusterGetNextAttr; + drv->SetAttrValue = clusterSetAttrValue; + drv->AddAttr = clusterAddAttr; + drv->OpenAttr = clusterOpenAttr; + drv->GetFirstMethod = clusterGetFirstMethod; + drv->GetNextMethod = clusterGetNextMethod; + drv->ExecuteMethod = clusterExecuteMethod; + drv->Commit = clusterCommit; + drv->Info = clusterInfo; + drv->PresentationHints = clusterPresentationHints; + + /** Register some structures. **/ + nmRegister(sizeof(ClusterData), "ClusterData"); + nmRegister(sizeof(SearchData), "ClusterSearch"); + nmRegister(sizeof(SourceData), "ClusterSourceData"); + nmRegister(sizeof(NodeData), "ClusterNodeData"); + nmRegister(sizeof(DriverData), "ClusterDriverData"); + nmRegister(sizeof(ClusterQuery), "ClusterQuery"); + nmRegister(sizeof(ClusterCaches), "ClusterCaches"); + + /** Print debug size info. **/ + char cluster_size_buf[16]; + char search_size_buf[16]; + char source_size_buf[16]; + char node_size_buf[16]; + char driver_size_buf[16]; + char query_size_buf[16]; + char caches_size_buf[16]; + tprintf( + "Cluster driver struct sizes:\n" + " > sizeof(ClusterData): %s\n" + " > sizeof(SearchData): %s\n" + " > sizeof(SourceData): %s\n" + " > sizeof(NodeData): %s\n" + " > sizeof(DriverData): %s\n" + " > sizeof(ClusterQuery): %s\n" + " > sizeof(ClusterCaches): %s\n", + snprint_bytes(cluster_size_buf, sizeof(cluster_size_buf), sizeof(ClusterData)), + snprint_bytes(search_size_buf, sizeof(search_size_buf), sizeof(SearchData)), + snprint_bytes(source_size_buf, sizeof(source_size_buf), sizeof(SourceData)), + snprint_bytes(node_size_buf, sizeof(node_size_buf), sizeof(NodeData)), + snprint_bytes(driver_size_buf, sizeof(driver_size_buf), sizeof(DriverData)), + snprint_bytes(query_size_buf, sizeof(query_size_buf), sizeof(ClusterQuery)), + snprint_bytes(caches_size_buf, sizeof(caches_size_buf), sizeof(ClusterCaches)) + ); + + /** Register the driver. **/ + ret = objRegisterDriver(drv); + if (ret < 0) return ret; + + return 0; + } diff --git a/centrallix/tests/test_expfn_double_metaphone_00.cmp b/centrallix/tests/test_expfn_double_metaphone_00.cmp new file mode 100644 index 000000000..d13cf05ca --- /dev/null +++ b/centrallix/tests/test_expfn_double_metaphone_00.cmp @@ -0,0 +1,140 @@ +Attribute [result]: string "TST`TST" +Attribute [result]: string "PSK`PSK" +Attribute [result]: string "SNTRLKS`SNTRLKS" +Attribute [result]: string "LRNS`LRNS" +Attribute [result]: string "FLPS`FLPS" +Attribute [result]: string "AKSPTNNS`AKSPTNKNS" +Attribute [result]: string "SPRKLFRJLSTSKSPLTSS`SPRKLFRKLSTSKSPLTXS" +Attribute [result]: string "SKTLPKSSTSLKRFLKRPS`SKTLPKSSTSLKRFLKRPS" +Attribute [result]: string "SM0`XMT" +Attribute [result]: string "XMT`SMT" +Attribute [result]: string "SNTR`XNTR" +Attribute [result]: string "XNTR`SNTR" +Attribute [result]: string "ARN`ARNF" +Attribute [result]: string "ARNF`ARNF" +Attribute [result]: string "AKST`AKST" +Attribute [result]: string "AKSTNT`AKSTNT" +Attribute [result]: string "AKTL`AKTL" +Attribute [result]: string "ARX`ARK" +Attribute [result]: string "ART`ARTS" +Attribute [result]: string "PKS`PKS" +Attribute [result]: string "PX`PX" +Attribute [result]: string "PJTR`PHTR" +Attribute [result]: string "PLX`PLX" +Attribute [result]: string "PRTX`PRTX" +Attribute [result]: string "PJ`PK" +Attribute [result]: string "P`P" +Attribute [result]: string "PR`PR" +Attribute [result]: string "PRTN`PRTN" +Attribute [result]: string "KPRL`KPR" +Attribute [result]: string "SSR`SSR" +Attribute [result]: string "KKN`KKN" +Attribute [result]: string "KMPL`KMPL" +Attribute [result]: string "KRLL`KRLL" +Attribute [result]: string "KRLL`KRLL" +Attribute [result]: string "KMSTR`KMSTR" +Attribute [result]: string "KNT`KNT" +Attribute [result]: string "KRS`KRS" +Attribute [result]: string "KF`KF" +Attribute [result]: string "SRN`XRN" +Attribute [result]: string "TM`TM" +Attribute [result]: string "ATKR`ATKR" +Attribute [result]: string "AJ`AJ" +Attribute [result]: string "FLPTS`FLPFX" +Attribute [result]: string "FKX`FKX" +Attribute [result]: string "KLKS`KKS" +Attribute [result]: string "KRMNK`JRMNK" +Attribute [result]: string "JRTL`JRTL" +Attribute [result]: string "JLN`JLN" +Attribute [result]: string "KSPL`KSPL" +Attribute [result]: string "KF`KF" +Attribute [result]: string "KRK`KRK" +Attribute [result]: string "HKMR`HKMR" +Attribute [result]: string "H`H" +Attribute [result]: string "ALNT`ALNT" +Attribute [result]: string "AL`AL" +Attribute [result]: string "ATLN`ATLN" +Attribute [result]: string "JNKLTS`ANKLFX" +Attribute [result]: string "HS`HS" +Attribute [result]: string "LF`LF" +Attribute [result]: string "MKFR`MKFR" +Attribute [result]: string "MKRKR`MKRKR" +Attribute [result]: string "MNKR`MNJR" +Attribute [result]: string "MK`MK" +Attribute [result]: string "MKLFLN`MKLFLN" +Attribute [result]: string "MKL`MXL" +Attribute [result]: string "MTL`MTL" +Attribute [result]: string "ARKSTR`ARKSTR" +Attribute [result]: string "ARKT`ARKT" +Attribute [result]: string "PNN`PNN" +Attribute [result]: string "RSPR`RSPR" +Attribute [result]: string "RSN`RSNS" +Attribute [result]: string "RJ`RJR" +Attribute [result]: string "RF`RF" +Attribute [result]: string "SLFTR`SLFTR" +Attribute [result]: string "SNHSNT`SNHSNT" +Attribute [result]: string "XNKR`SKNKR" +Attribute [result]: string "XRMRRN`SKRMRRN" +Attribute [result]: string "XLSNKR`SLSNJR" +Attribute [result]: string "SKL`SKL" +Attribute [result]: string "SKNR`SKNR" +Attribute [result]: string "SKST`SKST" +Attribute [result]: string "XKR`SKR" +Attribute [result]: string "XKR`SKR" +Attribute [result]: string "TKLR`TLR" +Attribute [result]: string "TMS`TMS" +Attribute [result]: string "TMS`TMS" +Attribute [result]: string "0M`TM" +Attribute [result]: string "TXNR`TKNR" +Attribute [result]: string "TF`TF" +Attribute [result]: string "FK`FK" +Attribute [result]: string "AKTLR`FKTLR" +Attribute [result]: string "AKSLR`FKSLR" +Attribute [result]: string "ART`FRT" +Attribute [result]: string "SF`SFR" +Attribute [result]: string "ANKLFX`ANKLFK" +Attribute [result]: string "J`J" +Attribute [result]: string "MKLLN`MKLLN" +Attribute [result]: string "MRS`MRS" +Attribute [result]: string "APR`APR" +Attribute [result]: string "KMPRL`KMPR" +Attribute [result]: string "HT`HT" +Attribute [result]: string "K0RN`KTRN" +Attribute [result]: string "K0RN`KTRN" +Attribute [result]: string "RXRT`RKRT" +Attribute [result]: string "PP`PP" +Attribute [result]: string "ARK`ARK" +Attribute [result]: string "JF`KF" +Attribute [result]: string "TF`TF" +Attribute [result]: string "R`R" +Attribute [result]: string "STFN`STFN" +Attribute [result]: string "PRS`PRS" +Attribute [result]: string "RNT`RNT" +Attribute [result]: string "PRN`PRN" +Attribute [result]: string "PRN`PRN" +Attribute [result]: string "AT`AT" +Attribute [result]: string "AT`AT" +Attribute [result]: string "APT`APT" +Attribute [result]: string "PK`PK" +Attribute [result]: string "PKR`PKR" +Attribute [result]: string "XRLS`XRLS" +Attribute [result]: string "KN`KN" +Attribute [result]: string "NM`NM" +Attribute [result]: string "RJ`R" +Attribute [result]: string "KNTN`KNTN" +Attribute [result]: string "A`A" +Attribute [result]: string "XMKR`XMKR" +Attribute [result]: string "SN`XN" +Attribute [result]: string "SKLT`SKLT" +Attribute [result]: string "STXN`STXN" +Attribute [result]: string "MX`MX" +Attribute [result]: string "PS`PTS" +Attribute [result]: string "AKNS`ANS" +Attribute [result]: string "SNS`SNS" +Attribute [result]: string "FNKK`FNKK" +Attribute [result]: string "JSF`HSF" +Attribute [result]: string "APJKT`APJKT" +Attribute [result]: string "SLS`SLS" +Attribute [result]: string "XRF`XRF" +Attribute [result]: string "KS`KS" +Attribute [result]: string "FNKLR`FNKLR" diff --git a/centrallix/tests/test_expfn_double_metaphone_00.to b/centrallix/tests/test_expfn_double_metaphone_00.to new file mode 100644 index 000000000..efd7548cc --- /dev/null +++ b/centrallix/tests/test_expfn_double_metaphone_00.to @@ -0,0 +1,161 @@ +##NAME double_metaphone() function + +# Special thanks to the following websites for double checking the correct results: +# 1: https://words.github.io/double-metaphone +# 2: https://mainegenealogy.net/metaphone_converter.asp +# 3: https://en.toolpage.org/tool/metaphone + +# These tests were collected from the following sources: +# - Example comments in the source code of exp_double_metaphone.c +# - Maurice Aubrey's Tests* +# - Tests manually written by Israel Fuller +# - Tests written by prompting ChatGPT-5 (preview)** +# +# *Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt +# **GPT-5 mini (Preview) was run in GitHub Copilot to suggest the words +# for some tests after analizing a generated coverage report. I (Israel) +# used the suggestions to write some "AI generated" test cases. +# +# For more information, see the manual test suite implementation at the +# end of the exp_double_metaphone.c file. + +query select result = double_metaphone("Test") +query select result = double_metaphone("Basic") +query select result = double_metaphone("Centrallix") +query select result = double_metaphone("Lawrence") +query select result = double_metaphone("Philips") +query select result = double_metaphone("Acceptingness") +query select result = double_metaphone("Supercalifragilisticexpialidocious") +query select result = double_metaphone("Suoicodilaipxecitsiligarfilacrepus") +query select result = double_metaphone("Smith") +query select result = double_metaphone("Schmidt") +query select result = double_metaphone("Snider") +query select result = double_metaphone("Schneider") +query select result = double_metaphone("Arnow") +query select result = double_metaphone("Arnoff") +query select result = double_metaphone("Accede") +query select result = double_metaphone("Accident") +query select result = double_metaphone("Actually") +query select result = double_metaphone("Arch") +query select result = double_metaphone("Artois") +query select result = double_metaphone("Bacchus") +query select result = double_metaphone("Bacci") +query select result = double_metaphone("Bajador") +query select result = double_metaphone("Bellocchio") +query select result = double_metaphone("Bertucci") +query select result = double_metaphone("Biaggi") +query select result = double_metaphone("Bough") +query select result = double_metaphone("Breaux") +query select result = double_metaphone("Broughton") +query select result = double_metaphone("Cabrillo") +query select result = double_metaphone("Caesar") +query select result = double_metaphone("Cagney") +query select result = double_metaphone("Campbell") +query select result = double_metaphone("Carlisle") +query select result = double_metaphone("Carlysle") +query select result = double_metaphone("Chemistry") +query select result = double_metaphone("Chianti") +query select result = double_metaphone("Chorus") +query select result = double_metaphone("Cough") +query select result = double_metaphone("Czerny") +query select result = double_metaphone("Dumb") +query select result = double_metaphone("Edgar") +query select result = double_metaphone("Edge") +query select result = double_metaphone("Filipowicz") +query select result = double_metaphone("Focaccia") +query select result = double_metaphone("Gallegos") +query select result = double_metaphone("Germanic") +query select result = double_metaphone("Ghiradelli") +query select result = double_metaphone("Ghislane") +query select result = double_metaphone("Gospel") +query select result = double_metaphone("Gough") +query select result = double_metaphone("Greek") +query select result = double_metaphone("Hochmeier") +query select result = double_metaphone("Hugh") +query select result = double_metaphone("Island") +query select result = double_metaphone("Isle") +query select result = double_metaphone("Italian") +query select result = double_metaphone("Jankelowicz") +query select result = double_metaphone("Jose") +query select result = double_metaphone("Laugh") +query select result = double_metaphone("Mac Caffrey") +query select result = double_metaphone("Mac Gregor") +query select result = double_metaphone("Manager") +query select result = double_metaphone("McHugh") +query select result = double_metaphone("McLaughlin") +query select result = double_metaphone("Michael") +query select result = double_metaphone("Middle") +query select result = double_metaphone("Orchestra") +query select result = double_metaphone("Orchid") +query select result = double_metaphone("Pinyin") +query select result = double_metaphone("Raspberry") +query select result = double_metaphone("Resnais") +query select result = double_metaphone("Rogier") +query select result = double_metaphone("Rough") +query select result = double_metaphone("Salvador") +query select result = double_metaphone("San jacinto") +query select result = double_metaphone("Schenker") +query select result = double_metaphone("Schermerhorn") +query select result = double_metaphone("Schlesinger") +query select result = double_metaphone("School") +query select result = double_metaphone("Schooner") +query select result = double_metaphone("Succeed") +query select result = double_metaphone("Sugar") +query select result = double_metaphone("Sugary") +query select result = double_metaphone("Tagliaro") +query select result = double_metaphone("Thames") +query select result = double_metaphone("Thomas") +query select result = double_metaphone("Thumb") +query select result = double_metaphone("Tichner") +query select result = double_metaphone("Tough") +query select result = double_metaphone("Vghee") +query select result = double_metaphone("Wachtler") +query select result = double_metaphone("Wechsler") +query select result = double_metaphone("Word") +query select result = double_metaphone("Xavier") +query select result = double_metaphone("Yankelovich") +query select result = double_metaphone("Zhao") +query select result = double_metaphone("McClellan") +query select result = double_metaphone("maurice") +query select result = double_metaphone("aubrey") +query select result = double_metaphone("cambrillo") +query select result = double_metaphone("heidi") +query select result = double_metaphone("katherine") +query select result = double_metaphone("catherine") +query select result = double_metaphone("richard") +query select result = double_metaphone("bob") +query select result = double_metaphone("eric") +query select result = double_metaphone("geoff") +query select result = double_metaphone("dave") +query select result = double_metaphone("ray") +query select result = double_metaphone("steven") +query select result = double_metaphone("bryce") +query select result = double_metaphone("randy") +query select result = double_metaphone("bryan") +query select result = double_metaphone("brian") +query select result = double_metaphone("otto") +query select result = double_metaphone("auto") +query select result = double_metaphone("Abbott") +query select result = double_metaphone("Back") +query select result = double_metaphone("Bacher") +query select result = double_metaphone("Charles") +query select result = double_metaphone("Ghana") +query select result = double_metaphone("Gnome") +query select result = double_metaphone("Raj") +query select result = double_metaphone("Quentin") +query select result = double_metaphone("Who") +query select result = double_metaphone("Shoemaker") +query select result = double_metaphone("Sian") +query select result = double_metaphone("Scold") +query select result = double_metaphone("Station") +query select result = double_metaphone("Match") +query select result = double_metaphone("Pizza") +query select result = double_metaphone("Agnes") +query select result = double_metaphone("Science") +query select result = double_metaphone("Van Gogh") +query select result = double_metaphone("Josef") +query select result = double_metaphone("Object") +query select result = double_metaphone("Sholz") +query select result = double_metaphone("Scharf") +query select result = double_metaphone("Kasia") +query select result = double_metaphone("Van Geller") From 994e99fa9c6f494b9709f4dac99d9924b5b1b95e Mon Sep 17 00:00:00 2001 From: Israel Date: Tue, 14 Oct 2025 11:41:41 -0600 Subject: [PATCH 002/361] Checkpoint: Switching to DM project. --- centrallix-lib/Makefile.in | 2 +- centrallix-os/cluster-schema.cluster | 10 +- centrallix/osdrivers/objdrv_cluster.c | 699 ++++++++++++++++++++++---- 3 files changed, 602 insertions(+), 109 deletions(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 20c57c11f..0daf7e568 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -66,7 +66,7 @@ TCFLAGS=$(patsubst -DNDEBUG,,$(CFLAGS)) XSTATICFILES=mtask.o mtlexer.o memstr.o xarray.o xhash.o xstring.o mtsession.o newmalloc.o xhashqueue.o bdqs_transport.o xhandle.o xringqueue.o cxsec.o smmalloc.o clusters.o qprintf.o strtcpy.o util.o STATICFILES=$(patsubst %,src/%,$(XSTATICFILES)) -XDYNAMICFILES=mtask.lo mtlexer.lo memstr.lo xarray.lo xhash.lo xstring.lo mtsession.lo newmalloc.lo xhashqueue.lo bdqs_transport.lo xhandle.lo xringqueue.lo cxsec.lo smmalloc.lo clusters.o qprintf.lo strtcpy.lo util.lo +XDYNAMICFILES=mtask.lo mtlexer.lo memstr.lo xarray.lo xhash.lo xstring.lo mtsession.lo newmalloc.lo xhashqueue.lo bdqs_transport.lo xhandle.lo xringqueue.lo cxsec.lo smmalloc.lo clusters.lo qprintf.lo strtcpy.lo util.lo DYNAMICFILES=$(patsubst %,src/%,$(XDYNAMICFILES)) INCLUDEFILES:=$(wildcard include/*.h) diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index 201c41255..a97d7f9ba 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -51,16 +51,16 @@ file_name "system/cluster" - /average_similarity : double && 0.0 < x < 1.0 - /size = average_similarity - /{arbitrary uint} - - /val : typeof(attr_name) // The value of the data point. + - /val : string // The value of the data point. - /label : uint < num_clusters // id of the cluster to which this data point belongs. - /sim : double && 0.0 < x <= threshold // Similarity to cluster centroid. ... /search_name - /{arbitrary uint} - - /id1 : uint // The id of the first data point. - - /id2 : uint // The id of the second data point. - - /val1 : typeof(attr_name) // The value of the first data point. - - /val2 : typeof(attr_name) // The value of the second data point. + - /id1 : uint < sizeof(source/attr_name) // The id of the first data point. + - /id2 : uint < sizeof(source/attr_name) // The id of the second data point. + - /val1 : string // The value of the first data point. + - /val2 : string // The value of the second data point. - /sim : double && 0.0 < x <= threshold // The similarity of the two data points. ... diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 9ffbd1d22..2369bc1fb 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -74,9 +74,9 @@ /** Debugging **/ -// void void_func() {} -// #define tprintf void_func -#define tprintf printf +void void_func() {} +#define tprintf void_func +// #define tprintf printf /** Defaults for unspecified optional attributes. **/ #define DEFAULT_MIN_IMPROVEMENT 0.0001 @@ -181,33 +181,62 @@ void mssErrorf(int clr, char* module, const char* format, ...) /** TODO: I think this should be moved to datatypes. **/ /** Should maybe replace current type parsing in the presentation hints. **/ +/*** Parse the given string into a datatype. The case of the first character + *** is ignored, but all other characters must be capitalized correctly. + *** + *** @attention - This function is optimized to prevent performance hits + *** situations where it may need to be called many thousands of times. + *** + *** @param str The string to be parsed to a datatype. + *** @returns The datatype. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ int ci_TypeFromStr(const char* str) { - if (str == NULL) return -1; - - /** Check string length. **/ - const size_t len = strlen(str); - if (len < 3 || 13 < len) return -1; - - /** Copy str to enable mutability. **/ - char buf[len + 1u]; - strcpy(buf, str); - - /** First character is case insensitive. **/ - buf[0] = toupper(buf[0]); + /** All valid types are non-null strings, at least 2 characters long. **/ + if (str == NULL || str[0] == '\0' || str[1] == '\0') return -1; /** Check type. **/ - if (strcmp(buf, "Any") == 0) return DATA_T_UNAVAILABLE; - if (strcmp(buf, "Integer") == 0) return DATA_T_INTEGER; - if (strcmp(buf, "String") == 0) return DATA_T_STRING; - if (strcmp(buf, "Double") == 0) return DATA_T_DOUBLE; - if (strcmp(buf, "DateTime") == 0) return DATA_T_DATETIME; - if (strcmp(buf, "IntVecor") == 0) return DATA_T_INTVEC; - if (strcmp(buf, "StringVector") == 0) return DATA_T_STRINGVEC; - if (strcmp(buf, "Money") == 0) return DATA_T_MONEY; - if (strcmp(buf, "Array") == 0) return DATA_T_ARRAY; - if (strcmp(buf, "Code") == 0) return DATA_T_CODE; - if (strcmp(buf, "Binary") == 0) return DATA_T_BINARY; + switch (str[0]) + { + case 'A': case 'a': + if (strcmp(str+1, "Array"+1) == 0) return DATA_T_ARRAY; + if (strcmp(str+1, "Any"+1) == 0) return DATA_T_ANY; + break; + + case 'B': case 'b': + if (strcmp(str+1, "Binary"+1) == 0) return DATA_T_BINARY; + break; + + case 'C': case 'c': + if (strcmp(str+1, "Code"+1) == 0) return DATA_T_CODE; + break; + + case 'D': case 'd': + if (strcmp(str+1, "Double"+1) == 0) return DATA_T_DOUBLE; + if (strcmp(str+1, "DateTime"+1) == 0) return DATA_T_DATETIME; + break; + + case 'I': case 'i': + if (strcmp(str+1, "Integer"+1) == 0) return DATA_T_INTEGER; + if (strcmp(str+1, "IntVecor"+1) == 0) return DATA_T_INTVEC; + break; + + case 'M': case 'm': + if (strcmp(str+1, "Money"+1) == 0) return DATA_T_MONEY; + break; + + case 'S': case 's': + if (strcmp(str+1, "String"+1) == 0) return DATA_T_STRING; + if (strcmp(str+1, "StringVector"+1) == 0) return DATA_T_STRINGVEC; + break; + + case 'U': case 'u': + if (strcmp(str+1, "Unknown"+1) == 0) return DATA_T_UNAVAILABLE; + if (strcmp(str+1, "Unavailable"+1) == 0) return DATA_T_UNAVAILABLE; + break; + } /** Invalid type. **/ return -1; @@ -220,21 +249,21 @@ char* ci_TypeToStr(const int type) switch (type) { case DATA_T_UNAVAILABLE: return "Unknown"; - case DATA_T_INTEGER: return "Integer"; - case DATA_T_STRING: return "String"; - case DATA_T_DOUBLE: return "Double"; - case DATA_T_DATETIME: return "DateTime"; - case DATA_T_INTVEC: return "IntVecor"; - case DATA_T_STRINGVEC: return "StringVector"; - case DATA_T_MONEY: return "Money"; - case DATA_T_ARRAY: return "Array"; - case DATA_T_CODE: return "Code"; - case DATA_T_BINARY: return "Binary"; + case DATA_T_INTEGER: return "Integer"; + case DATA_T_STRING: return "String"; + case DATA_T_DOUBLE: return "Double"; + case DATA_T_DATETIME: return "DateTime"; + case DATA_T_INTVEC: return "IntVecor"; + case DATA_T_STRINGVEC: return "StringVector"; + case DATA_T_MONEY: return "Money"; + case DATA_T_ARRAY: return "Array"; + case DATA_T_CODE: return "Code"; + case DATA_T_BINARY: return "Binary"; } /** Invalid type. **/ mssErrorf(1, "Cluster", "Invalid type %d.\n", type); - return "Invalid"; + return "Invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ } /** TODO: I think this should be moved to xarray. **/ @@ -252,6 +281,19 @@ void** ci_xaToTrimmedArray(pXArray arr) return result; } +/** I got tired of forgetting how to do these. **/ +#define ci_file_name(obj) \ + ({ \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, _obj->SubPtr - 1, 1); \ + }) +#define ci_file_path(obj) \ + ({ \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ + }) + + /** ================ Enum Declairations ================ **/ /** ANCHOR[id=enums] **/ @@ -265,6 +307,18 @@ typedef unsigned char ClusterAlgorithm; #define ALGORITHM_KMEDOIDS (ClusterAlgorithm)5u #define ALGORITHM_DB_SCAN (ClusterAlgorithm)6u +#define nClusteringAlgorithms 7u +ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[nClusteringAlgorithms] = + { + ALGORITHM_NULL, + ALGORITHM_NONE, + ALGORITHM_SLIDING_WINDOW, + ALGORITHM_KMEANS, + ALGORITHM_KMEANS_PLUS_PLUS, + ALGORITHM_KMEDOIDS, + ALGORITHM_DB_SCAN, + }; + /** Converts a clustering algorithm to its string name. **/ char* ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) { @@ -287,6 +341,14 @@ typedef unsigned char SimilarityMeasure; #define SIMILARITY_COSINE (SimilarityMeasure)1u #define SIMILARITY_LEVENSHTEIN (SimilarityMeasure)2u +#define nSimilarityMeasures 3u +SimilarityMeasure ALL_SIMILARITY_MEASURES[nSimilarityMeasures] = + { + SIMILARITY_NULL, + SIMILARITY_COSINE, + SIMILARITY_LEVENSHTEIN, + }; + /** Converts a similarity measure to its string name. **/ char* ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) { @@ -319,39 +381,48 @@ char* const ATTR_ROOT[nATTR_ROOT] = { "source", "attr_name", }; -#define nATTR_CLUSTER 5u -char* const ATTR_CLUSTER[nATTR_CLUSTER] = { +#define nATTR_CLUSTER 7u +char* const ATTR_CLUSTER[nATTR_CLUSTER] = + { "algorithm", "similarity_measure", "num_clusters", "min_improvement", "max_iterations", -}; -#define nATTR_SEARCH 4u -char* const ATTR_SEARCH[nATTR_SEARCH] = { + "date_created", + "date_computed", + }; +#define nATTR_SEARCH 5u +char* const ATTR_SEARCH[nATTR_SEARCH] = + { "source", "threshold", "similarity_measure", -}; + "date_created", + "date_computed", + }; #define nATTR_CLUSTER_ENTRY 2u -char* const ATTR_CLUSTER_ENTRY[nATTR_CLUSTER_ENTRY] = { +char* const ATTR_CLUSTER_ENTRY[nATTR_CLUSTER_ENTRY] = + { "val", "sim", -}; + }; #define nATTR_SEARCH_ENTRY 3u -char* const ATTR_SEARCH_ENTRY[nATTR_SEARCH_ENTRY] = { +char* const ATTR_SEARCH_ENTRY[nATTR_SEARCH_ENTRY] = + { "val1", "val2", "sim", -}; + }; #define END_OF_ATTRIBUTES NULL /** Method name list. **/ #define nMETHOD_NAME 2u -char* const METHOD_NAME[nMETHOD_NAME] = { +char* const METHOD_NAME[nMETHOD_NAME] = + { "cache", -}; + }; #define END_OF_METHODS END_OF_ATTRIBUTES @@ -362,27 +433,31 @@ char* const METHOD_NAME[nMETHOD_NAME] = { typedef struct _SOURCE { /** Top level attributes (specified in the .cluster file). **/ - char* Name; /* The node name, specified in the .cluster file. - * Warning: Some code makes the assumption that this - * is the first field in the struct. - */ - char* Key; /* The key associated with this object in the global SourceCache. */ - char* SourcePath; /* The path to the data source from which to retrieve data. */ - char* AttrName; /* The name of the attribute to get from the data source. */ + char* Name; /* The node name, specified in the .cluster file. + * Warning: Some code makes the assumption that this + * is the first field in the struct. + */ + char* Key; /* The key associated with this object in the global SourceCache. */ + char* SourcePath; /* The path to the data source from which to retrieve data. */ + char* AttrName; /* The name of the attribute to get from the data source. */ /** Computed data. **/ - char** Data; /* The data strings to be clustered and searched, or NULL if they - * have not been fetched from the source. - */ - pVector* Vectors; /* The cosine comparison vectors from the fetched data, or NULL if - * they haven't been computed. Note that vectors are no longer - * needed once all clusters and searches have been computed, so - * they are automatically freed in that case to save memory. - */ - unsigned int nVectors; /* The number of vectors and data strings. Note: This is not - * set to 0 if the vector array is freed, this case should be - * checked separately. - */ + char** Data; /* The data strings to be clustered and searched, or NULL if they + * have not been fetched from the source. + */ + pVector* Vectors; /* The cosine comparison vectors from the fetched data, or NULL if + * they haven't been computed. Note that vectors are no longer + * needed once all clusters and searches have been computed, so + * they are automatically freed in that case to save memory. + */ + unsigned int nVectors; /* The number of vectors and data strings. Note: This is not + * set to 0 if the vector array is freed, this case should be + * checked separately. + */ + + /** Time. **/ + DateTime DateCreated; /* The date and time that this object was created and initialized. */ + DateTime DateComputed; /* The date and time that the Data and Vectors fields were computed. */ } SourceData, *pSourceData; /** Data for each cluster. **/ @@ -415,6 +490,10 @@ typedef struct _CLUSTER * (aka. DriverData->nVectors). For vector i, Labels[i] is * the ID of the cluster to which that data is assigned. * NULL if the cluster has not been computed. */ + + /** Time. **/ + DateTime DateCreated; /* The date and time that this object was created and initialized. */ + DateTime DateComputed; /* The date and time that the Labels field was computed. */ } ClusterData, *pClusterData; @@ -437,6 +516,10 @@ typedef struct _SEARCH * if the search has not been computed. */ unsigned int nDups; /* The number of dups found. */ + + /** Time. **/ + DateTime DateCreated; /* The date and time that this object was created and initialized. */ + DateTime DateComputed; /* The date and time that the Dups field was computed. */ } SearchData, *pSearchData; @@ -463,8 +546,6 @@ typedef struct _NODE /** Other stuff, idk why it's here. **/ pSnNode Node; pObject Obj; - char* CreateDateField; - char* ModifyDateField; } NodeData, *pNodeData; @@ -694,6 +775,7 @@ int ci_ParseAttribute( } +// LINK #functions /*** Parses a ClusteringAlgorithm from the algorithm field in the pStructInf *** representing some structure with that attribute in a parsed structure file. *** @@ -729,6 +811,7 @@ ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param } +// LINK #functions /*** Parses a SimilarityMeasure from the similarity_measure field in the given *** pStructInf parameter, which represents some structure with that attribute *** in a parsed structure file. @@ -760,6 +843,7 @@ SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_ } +// LINK #functions /*** Allocates a new pSourceData struct from a parsed pStructInf representing *** a .cluster structure file. *** @@ -816,6 +900,7 @@ pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* p source_data->Key = key; source_data->SourcePath = source_path; source_data->AttrName = attr_name; + check(objCurrentDate(&source_data->DateCreated)); /** Add the new object to the cache for next time. **/ tprintf("+ source: \"%s\"\n", key); @@ -829,6 +914,7 @@ pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* p } +// LINK #functions /*** Allocates a new pClusterData struct from a parsed pStructInf. *** *** @attention - Warning: Caching in use. @@ -858,6 +944,7 @@ pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) /** Basic Properties. **/ cluster_data->Name = check_ptr(strdup(inf->Name)); cluster_data->SourceData = source_data; + check(objCurrentDate(&cluster_data->DateCreated)); /** Get algorithm. **/ cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); @@ -1046,6 +1133,7 @@ pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) } +// LINK #functions /*** Allocates a new pSearchData struct from a parsed pStructInf. *** *** @attention - Warning: Caching in use. @@ -1068,8 +1156,9 @@ pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) assert(search_data != NULL); memset(search_data, 0, sizeof(SearchData)); - /** Get search name. **/ + /** Get basic information. **/ search_data->Name = check_ptr(strdup(inf->Name)); + check(objCurrentDate(&search_data->DateCreated)); /** Get source. **/ char* source_name; @@ -1147,6 +1236,7 @@ pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) } +// LINK #functions /*** Allocates a new pNodeData struct from a parsed pStructInf. *** *** @attention - Does not use caching directly, but uses subfunctions to @@ -1164,7 +1254,7 @@ pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) int ret; /** Retrieve path so we'll know we have it later. **/ - char* path = obj_internal_PathPart(obj->Pathname, 0, obj->SubPtr); + char* path = ci_file_path(obj); /** Allocate node struct data. **/ // pNodeData node_data = NodeData |> sizeof() |> nmMalloc() |> check_ptr(); @@ -1404,6 +1494,7 @@ void ci_FreeSourceData(pSourceData source_data) } +// LINK #functions /*** Free pClusterData struct with an option to recursively free subclusters. *** *** @param cluster_data The cluster data struct to free. @@ -1437,6 +1528,7 @@ void ci_FreeClusterData(pClusterData cluster_data, bool recursive) } +// LINK #functions /** @param search_data A pSearchData struct, freed by this function. **/ void ci_FreeSearchData(pSearchData search_data) { @@ -1451,6 +1543,7 @@ void ci_FreeSearchData(pSearchData search_data) } +// LINK #functions /** @param node_data A pNodeData struct, freed by this function. **/ void ci_FreeNodeData(pNodeData node_data) { @@ -1541,6 +1634,7 @@ unsigned int ci_SizeOfSourceData(pSourceData source_data) } +// LINK #functions /*** Returns the deep size of a ClusterData struct, including the size of all *** allocated substructures. As far as I can tell, this is probably only *** useful for cache management and debugging. @@ -1571,6 +1665,7 @@ unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) } +// LINK #functions /*** Returns the deep size of a SearchData struct, including the size of all *** allocated substructures. As far as I can tell, this is probably only *** useful for cache management and debugging. @@ -1591,6 +1686,7 @@ unsigned int ci_SizeOfSearchData(pSearchData search_data) } +// LINK #functions /*** Returns the deep size of a NodeData struct, including the size of all *** allocated substructures. As far as I can tell, this is probably only *** useful for cache management and debugging. @@ -1656,6 +1752,7 @@ void ci_CacheFreeSourceData(pXHashEntry entry, void* _) nmSysFree(key); } +// LINK #functions /** Intended for use in xhClearKeySafe(). **/ void ci_CacheFreeCluster(pXHashEntry entry, void* _) { @@ -1669,6 +1766,7 @@ void ci_CacheFreeCluster(pXHashEntry entry, void* _) nmSysFree(key); } +// LINK #functions /** Intended for use in xhClearKeySafe(). **/ void ci_CacheFreeSearch(pXHashEntry entry, void* _) { @@ -1731,6 +1829,10 @@ int ci_ComputeSourceData(pSourceData source_data, pObjSession session) source_data->nVectors = 0; } + /** Record the date and time. **/ + /** Even if this computation fails, we may want this information. **/ + check(objCurrentDate(&source_data->DateComputed)); + /** Time to play shoots-and-ladders in an error-handling jungle of gotos. **/ bool successful = false; int ret; @@ -1929,6 +2031,8 @@ int ci_ComputeSourceData(pSourceData source_data, pObjSession session) return (successful) ? 0 : -1; } + +// LINK #functions /*** Ensures that the cluster_data->Labels has been computed, running the *** specified clustering algorithm if necessary. *** @@ -1955,6 +2059,10 @@ int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) goto err; } + /** Record the date and time. **/ + /** Even if this computation fails, we may want this information. **/ + check(objCurrentDate(&cluster_data->DateComputed)); + /** Allocate static memory for finding clusters. **/ const size_t labels_size = source_data->nVectors * sizeof(unsigned int); cluster_data->Labels = check_ptr(nmMalloc(labels_size)); @@ -2012,6 +2120,8 @@ int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) return -1; } + +// LINK #functions /*** Ensures that the search_data->Dups has been computed, running the a *** search with the specified similarity measure if necessary. *** @@ -2052,6 +2162,10 @@ int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) goto err; } + /** Record the date and time. **/ + /** Even if this computation fails, we may want this information. **/ + check(objCurrentDate(&search_data->DateComputed)); + /** Execute the search. **/ tprintf("Invoking ca_search.\n"); Timer timer_i, *timer = timer_start(timer_init(&timer_i)); @@ -2115,6 +2229,7 @@ int ci_GetParamType(void* inf_v, const char* attr_name) } +// LINK #functions /*** Get the value of a parameter. Intended for `expSetParamFunctions()`. *** *** @attention - Warning: If the retrieved value is `NULL`, the pObjectData @@ -2191,7 +2306,7 @@ int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) *** @param obj The object being opened, including the path, session, and *** other necessary information. *** @param mask Driver permission mask (unused). - *** @param systype ? (unused) + *** @param sys_type ? (unused) *** @param usr_type The object system file type being openned. Should always *** be "system/cluster" because this driver is only registered for that *** type of file. @@ -2200,12 +2315,9 @@ int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) *** @returns A pDriverData struct representing a driver instance, or *** NULL if an error occures. ***/ -void* clusterOpen(pObject obj, int mask, pContentType systype, char* usr_type, pObjTrxTree* oxt) +void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { - tprintf( - "Warning: clusterOpen(\"%s\") is under active development.\n", - obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt, 1) - ); + tprintf("Warning: clusterOpen(\"%s\") is under active development.\n", ci_file_name(obj)); /** If CREAT and EXCL are specified, create it and fail if it already exists. **/ pSnNode node_struct = NULL; @@ -2239,10 +2351,7 @@ void* clusterOpen(pObject obj, int mask, pContentType systype, char* usr_type, p pNodeData node_data = ci_ParseNodeData(node_struct->Data, obj); if (node_data == NULL) { - mssErrorf(0, "Cluster", - "Failed to parse structure file of name %s.", - obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt, 1) - ); + mssErrorf(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(obj)); goto err; } node_data->Node = node_struct; @@ -2343,6 +2452,7 @@ void* clusterOpen(pObject obj, int mask, pContentType systype, char* usr_type, p } +// LINK #functions /*** Close a cluster driver instance object, releasing any necessary memory *** and closing any necessary underlying resources. However, most of that *** data will be cached and won't be freed unless the cache is dropped. @@ -2374,6 +2484,7 @@ int clusterClose(void* inf_v, pObjTrxTree* oxt) } +// LINK #functions /*** Opens a new query pointing to the first row of the data targetted by *** the driver instance struct. The query has an internal index counter *** that starts at the first row and increments as data is fetched. @@ -2394,6 +2505,7 @@ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) } +// LINK #functions /*** Get the next entry as an open driver instance object. *** *** @param qy_v A query instance, storing an internal index which is @@ -2477,6 +2589,7 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) } +// LINK #functions /*** Close a cluster query instance, releasing any necessary memory and *** closing any necessary underlying resources. This does not close the *** underlying driver instance, which must be closed with clusterClose(). @@ -2494,6 +2607,7 @@ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) } +// LINK #functions /*** Get the type of a cluster driver instance attribute. *** *** @param inf_v The driver instance. @@ -2507,13 +2621,6 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - /** Performance shortcut for frequently requested attributes: val1, val2, and sim. **/ - if (attr_name[0] == 'v' || attr_name[0] == 's') goto handle_targets; - - /** Debug info. **/ - if (oxt == NULL) tprintf(" > "); - tprintf("Call to clusterGetAttrType(%s)\n", attr_name); - /** Guard possible segfault. **/ if (attr_name == NULL) { @@ -2521,6 +2628,13 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) return DATA_T_UNAVAILABLE; } + /** Performance shortcut for frequently requested attributes: val, val1, val2, and sim. **/ + if (attr_name[0] == 'v' || attr_name[0] == 's') goto handle_targets; + + /** Debug info. **/ + if (oxt == NULL) tprintf(" > "); + tprintf("Call to clusterGetAttrType(%s)\n", attr_name); + /** Types for general attributes. **/ if (strcmp(attr_name, "name") == 0 || strcmp(attr_name, "annotation") == 0 @@ -2530,6 +2644,12 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) return DATA_T_STRING; if (strcmp(attr_name, "last_modification") == 0) return DATA_T_DATETIME; + if ((strcmp(attr_name, "date_created") == 0 + || strcmp(attr_name, "date_computed") == 0) + && + (driver_data->TargetType == TARGET_CLUSTER + || driver_data->TargetType == TARGET_SEARCH)) + return DATA_T_DATETIME; /** Types for specific data targets. **/ handle_targets: @@ -2566,10 +2686,7 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "id") == 0) return DATA_T_INTEGER; if (strcmp(attr_name, "val") == 0) - { - /** TODO: Replace with type calculation. **/ return DATA_T_STRING; - } if (strcmp(attr_name, "sim") == 0) return DATA_T_DOUBLE; break; @@ -2580,10 +2697,7 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) return DATA_T_INTEGER; if (strcmp(attr_name, "val1") == 0 || strcmp(attr_name, "val2") == 0) - { - /** TODO: Replace with type calculation. **/ return DATA_T_STRING; - } if (strcmp(attr_name, "sim") == 0) return DATA_T_DOUBLE; break; @@ -2597,6 +2711,7 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) } +// LINK #functions /*** Get the value of a cluster driver instance attribute. *** *** @param inf_v Node data containing the list of paramenters. @@ -2619,7 +2734,14 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val { pDriverData driver_data = (pDriverData)inf_v; - /** Performance shortcut for frequently requested attributes: val1, val2, and sim. **/ + /** Guard possible segfault. **/ + if (attr_name == NULL) + { + fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); + return DATA_T_UNAVAILABLE; + } + + /** Performance shortcut for frequently requested attributes: val, val1, val2, and sim. **/ if ( (attr_name[0] == 'v' && datatype == DATA_T_STRING) /* val, val1, val2 : String */ || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ @@ -2674,6 +2796,10 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val case TARGET_CLUSTER_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; case TARGET_SEARCH: val->String = "Clustering driver: Search."; break; case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; } return 0; } @@ -2694,6 +2820,61 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val /** Last modification is not implemented yet. **/ if (strcmp(attr_name, "last_modification") == 0) return 1; /* null */ + /** Handle creation and computation dates. **/ + if (strcmp(attr_name, "date_created") == 0) + { + switch (driver_data->TargetType) + { + case TARGET_ROOT: + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + /** Field is not defined for this target type. **/ + return -1; + + case TARGET_CLUSTER: + val->DateTime = &((pClusterData)driver_data->TargetData)->DateCreated; + return 0; + + case TARGET_SEARCH: + val->DateTime = &((pSearchData)driver_data->TargetData)->DateCreated; + return 0; + } + return -1; + } + if (strcmp(attr_name, "date_computed") == 0) + { + switch (driver_data->TargetType) + { + case TARGET_ROOT: + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + /** Field is not defined for this target type. **/ + return -1; + + case TARGET_CLUSTER: + { + pClusterData target = (pClusterData)driver_data->TargetData; + pDateTime date_time = &target->DateComputed; + if (date_time->Value == 0) return 1; /* null */ + else val->DateTime = date_time; + return 0; + } + + case TARGET_SEARCH: + { + pSearchData target = (pSearchData)driver_data->TargetData; + pDateTime date_time = &target->DateComputed; + if (date_time->Value == 0) return 1; /* null */ + else val->DateTime = date_time; + return 0; + } + } + + /** Default: Unknown type. **/ + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; + } + /** Handle attributes for specific data targets. **/ handle_targets: switch (driver_data->TargetType) @@ -2848,6 +3029,318 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val } +// LINK #functions +/** Not implemented. **/ +pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) + { + tprintf("Warning: clusterPresentationHints(\"%s\") is under active development.", attr_name); + pDriverData driver_data = (pDriverData)inf_v; + + /** Malloc presentation hints struct. **/ + pObjPresentationHints hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); + memset(hints, 0, sizeof(ObjPresentationHints)); + + /** Hints that are the same for all fields */ + hints->GroupID = -1; + hints->VisualLength2 = 1; + hints->Style |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; + hints->StyleMask |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; + + /** Temporary param list for compiling expressions. **/ + pParamObjects tmp_list = check_ptr(expCreateParamList()); + + if (strcmp(attr_name, "name") == 0) + { + hints->Length = 32; + hints->VisualLength = 16; + goto end; + } + if (strcmp(attr_name, "annotation") == 0) + { + hints->Length = 36; + hints->VisualLength = 36; + goto end; + } + if (strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name, "outer_type") == 0 + || strcmp(attr_name, "content_type") == 0 + || strcmp(attr_name, "last_modification") == 0) + { + hints->VisualLength = 30; + goto end; + } + + if (strcmp(attr_name, "date_created") == 0 + || strcmp(attr_name, "date_computed") == 0) + { + hints->Length = 24; + hints->VisualLength = 20; + hints->Format = nmSysStrdup("datetime"); + goto end; + } + + switch (driver_data->TargetType) + { + case TARGET_ROOT: + if (strcmp(attr_name, "source") == 0) + { + hints->Length = _PC_PATH_MAX; + hints->VisualLength = 64; + hints->FriendlyName = "Source Path"; + goto end; + } + if (strcmp(attr_name, "attr_name") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = "Attribute Name"; + goto end; + } + break; + + case TARGET_CLUSTER: + if (strcmp(attr_name, "num_clusters") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("2", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + hints->FriendlyName = nmSysStrdup("Number of Clusters"); + goto end; + } + if (strcmp(attr_name, "min_improvement") == 0) + { + /** Min and max values. **/ + hints->DefaultExpr = expCompileExpression("0.0001", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = nmSysStrdup("Minimum Improvement Threshold"); + goto end; + } + if (strcmp(attr_name, "max_iterations") == 0) + { + /** Min and max values. **/ + hints->DefaultExpr = expCompileExpression("64", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + hints->FriendlyName = nmSysStrdup("Maximum Number of Clustering Iterations"); + goto end; + } + if (strcmp(attr_name, "average_similarity") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = nmSysStrdup("Average Similarity"); + goto end; + } + if (strcmp(attr_name, "size") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = nmSysStrdup("Average Cluster Size"); + goto end; + } + if (strcmp(attr_name, "algorithm") == 0) + { + /** Enum values. **/ + check(xaInit(&(hints->EnumList), nClusteringAlgorithms)); + for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) + check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + char buf[4u]; + snprintf(buf, sizeof(buf), "%d", nClusteringAlgorithms); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Display flags. **/ + hints->Style |= OBJ_PH_STYLE_BUTTONS; + hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; + + /** Other hints. **/ + hints->Length = 24; + hints->VisualLength = 20; + hints->FriendlyName = nmSysStrdup("Clustering Algorithm"); + goto end; + } + /** Fall-through: Start of overlapping region. **/ + + case TARGET_SEARCH: + if (strcmp(attr_name, "similarity_measure") == 0) + { + /** Enum values. **/ + check(xaInit(&(hints->EnumList), nSimilarityMeasures)); + for (unsigned int i = 0u; i < nSimilarityMeasures; i++) + check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); + + /** Display flags. **/ + hints->Style |= OBJ_PH_STYLE_BUTTONS; + hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + char buf[4u]; + snprintf(buf, sizeof(buf), "%d", nSimilarityMeasures); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 32; + hints->VisualLength = 20; + hints->FriendlyName = nmSysStrdup("Similarity Measure"); + goto end; + } + + /** End of overlapping region. **/ + if (driver_data->TargetType == TARGET_CLUSTER) break; + + if (strcmp(attr_name, "source") == 0) + { + hints->Length = 64; + hints->VisualLength = 32; + hints->FriendlyName = nmSysStrdup("Source Cluster Name"); + goto end; + } + if (strcmp(attr_name, "threshold") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = nmSysStrdup("Similarity Threshold"); + goto end; + } + break; + + case TARGET_CLUSTER_ENTRY: + { + pClusterData target = (pClusterData)driver_data->TargetData; + + if (strcmp(attr_name, "id") == 0) + { + pSourceData source_data = (pSourceData)target->SourceData; + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + if (source_data->Vectors != NULL) + { + char buf[16u]; + snprintf(buf, sizeof(buf), "%u", source_data->nVectors); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + return 0; + } + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + goto end; + } + if (strcmp(attr_name, "val") == 0) + { + /** Other hints. **/ + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = nmSysStrdup("Value"); + goto end; + } + if (strcmp(attr_name, "sim") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = nmSysStrdup("Similarity"); + goto end; + } + break; + } + + case TARGET_SEARCH_ENTRY: + { + pSearchData target = (pSearchData)driver_data->TargetData; + + if (strcmp(attr_name, "id1") == 0 || strcmp(attr_name, "id2") == 0) + { + pSourceData source_data = (pSourceData)target->Source->SourceData; + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + if (source_data->Vectors != NULL) + { + char buf[16u]; + snprintf(buf, sizeof(buf), "%u", source_data->nVectors); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + return 0; + } + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + goto end; + } + if (strcmp(attr_name, "val1") == 0 || strcmp(attr_name, "val2") == 0) + { + /** Other hints. **/ + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = nmSysStrdup("Value"); + goto end; + } + if (strcmp(attr_name, "sim") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = nmSysStrdup("Similarity"); + goto end; + } + break; + } + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return NULL; + } + + + end: + check(expFreeParamList(tmp_list)); + return hints; + } + + +// LINK #functions /*** Returns the name of the first attribute that one can get from *** this driver instance (using GetAttrType() and GetAttrValue()). *** Resets the internal variable (TargetAttrIndex) used to maintain @@ -2866,6 +3359,7 @@ char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt) } +// LINK #functions /*** Returns the name of the next attribute that one can get from *** this driver instance (using GetAttrType() and GetAttrValue()). *** Uses an internal variable (TargetAttrIndex) used to maintain @@ -2895,6 +3389,7 @@ char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt) } +// LINK #functions /*** Get the capabilities of the driver instance object. *** *** @param inf_v The driver instance to be checked. @@ -2995,6 +3490,7 @@ char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt) } +// LINK #functions /*** Returns the name of the next method that one can get from *** this driver instance (using GetAttrType() and GetAttrValue()). *** Uses an internal variable (TargetMethodIndex) used to maintain @@ -3013,6 +3509,7 @@ char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt) return (i < nMETHOD_NAME) ? METHOD_NAME[i] : END_OF_METHODS; } +// LINK #functions /** Intended for use in xhForEach(). **/ static int ci_PrintEntry(pXHashEntry entry, void* arg) { @@ -3074,6 +3571,7 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) } +// LINK #functions /*** Executes a method with the given name. *** *** @param inf_v The affected driver instance. @@ -3101,7 +3599,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx if (strcmp(param->String, "show") == 0) { const pObject obj = ((pDriverData)inf_v)->NodeData->Obj; - char* path = obj_internal_PathPart(obj->Pathname, 0, obj->SubPtr); + char* path = ci_file_path(obj); /** Print cache info table. **/ unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; @@ -3232,14 +3730,9 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt) mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are imutable."); return 0; } -/** Not implemented. **/ -pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) - { - mssErrorf(1, "Cluster", "clusterPresentationHints() not implemented."); - return NULL; - } +// LINK #functions /*** Initialize the driver. This includes: *** - Registering the driver with the objectsystem. *** - Registering structs with newmalloc for debugging. From ea6430fa8e0965aaac782b49cc73e5ceff457ddc Mon Sep 17 00:00:00 2001 From: Israel Date: Thu, 16 Oct 2025 08:55:52 -0600 Subject: [PATCH 003/361] Checkpoing: Switching to DM project. --- centrallix-lib/include/util.h | 47 +++ centrallix-os/testdir/file.cluster | 64 ++++ centrallix/expression/exp_functions.c | 1 - centrallix/multiquery/multiquery.c | 8 +- centrallix/osdrivers/objdrv_cluster.c | 451 +++++++++++--------------- centrallix/test_obj.c | 1 + 6 files changed, 314 insertions(+), 258 deletions(-) create mode 100644 centrallix-os/testdir/file.cluster diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 2b9d7b26f..12019abfb 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -134,6 +134,53 @@ void fail(const char* function_name, int code); _r; \ }) +/** Pattern for printing a binary int using printf(). **/ +#define INT_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" + +/*** Converts an int to the values that should be passed to printf() for the + *** INT_TO_BINARY_PATTERN pattern. + *** + *** @attention - Double evaluation is NOT HANDLED so int_val will be evaluted + *** 32 times when this macro is used. Ensure that evaluation of the value + *** passed for int_val does not have important side effects! + *** + *** @param int_val The int to be printed. + *** @returns Values for printf(). + ***/ +#define INT_TO_BINARY(int_val) \ + ((int_val) & 0b10000000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b01000000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00100000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00010000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00001000000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000100000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000010000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000001000000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000100000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000010000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000001000000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000100000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000010000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000001000000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000100000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000010000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000001000000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000100000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000010000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000001000000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000100000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000010000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000001000000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000100000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000010000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000001000000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000100000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000010000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000001000 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000000100 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000000010 ? '1' : '0'), \ + ((int_val) & 0b00000000000000000000000000000001 ? '1' : '0') + #endif /* __cplusplus */ #endif /* UTILITY_H */ diff --git a/centrallix-os/testdir/file.cluster b/centrallix-os/testdir/file.cluster new file mode 100644 index 000000000..929efdd03 --- /dev/null +++ b/centrallix-os/testdir/file.cluster @@ -0,0 +1,64 @@ +$Version=2$ +file_name "system/cluster" + { + // Developer can specify parameters to improve file reuseability. + // TIP: Improve performance by declairing frequently used parameters first. + k "cluster/parameter" { type = integer; style=notnull; } + str "cluster/parameter" { type = string; } + int "cluster/parameter" { type = integer; default = runserver(:parameters:k); } + dbl "cluster/parameter" { type = double; default=4.2; } + // conversion "cluster/parameter" { type=double; default=4; } + + null_str "cluster/parameter" { type = string; default = null; } + null_int "cluster/parameter" { type = integer; default = null; } + null_dbl "cluster/parameter" { type = double; default = null; } + + // We calculate k in a centrallix script using: + // k = max(2, pow(log(n) / log(36), 3.2) - 8) + // where n is the number of records passed. + + // Specify the data source at the top of the file. + // How do we pass distinct data? Should the driver + // handle that for us? + source = "/apps/kardia/data/Kardia_DB/p_partner/rows"; + attr_name = p_given_name; // runserver(:parameters:str) + + // Clustering object specifies properties for clustering. + kmeans_cluster "cluster/cluster" + { + algorithm = "k-means"; + similarity_measure = "cosine"; + num_clusters = runserver(:parameters:k); + min_improvement = 0.0001; + max_iterations = 48; + + // Create subclusters. (Not implemented) + sub_cluster "cluster/cluster" + { + algorithm = "none"; + similarity_measure = "cosine"; + num_clusters = 7; + min_improvement = "max"; + } + } + + // Complete search. + no_clustering "cluster/cluster" + { + algorithm = "none"; + } + + dups "cluster/search" + { + source = kmeans_cluster; + threshold = 0.75; + similarity_measure = "cosine"; + } + + dups2 "cluster/search" + { + source = no_clustering; + threshold = 0.75; + similarity_measure = "cosine"; + } + } diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index df55559be..a8e16ecc7 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1355,7 +1355,6 @@ int exp_fn_ralign(pExpression tree, pParamObjects objlist, pExpression i0, pExpr tree->Alloc = 0; tree->String = tree->Types.StringBuf; } - /** Possible overflow? **/ sprintf(tree->String,"%*.*s",i1->Integer,i1->Integer,i0->String); } return 0; diff --git a/centrallix/multiquery/multiquery.c b/centrallix/multiquery/multiquery.c index 897362751..069186e80 100644 --- a/centrallix/multiquery/multiquery.c +++ b/centrallix/multiquery/multiquery.c @@ -2086,6 +2086,7 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p mssError(1,"MQ","Expected equals after EXEC parameter"); mlxNoteError(lxs); xsFree(xs); + xs = NULL; break; } @@ -2098,6 +2099,7 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p mssError(1,"MQ","Error in EXEC parameter"); mlxNoteError(lxs); xsFree(xs); + xs = NULL; xsFree(param); break; } @@ -2108,6 +2110,7 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p mssError(1,"MQ","Could not evaluate EXEC parameter"); mlxNoteError(lxs); xsFree(xs); + xs = NULL; xsFree(param); break; } @@ -2120,7 +2123,8 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p } } - strtcpy(new_qs->Source, xs->String, sizeof(new_qs->Source)); + if (xs != NULL) + strtcpy(new_qs->Source, xs->String, sizeof(new_qs->Source)); next_state = LookForClause; } else @@ -4774,5 +4778,3 @@ mqInitialize() return 0; } - - diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 2369bc1fb..f56cca5de 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -72,11 +72,17 @@ *** https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors ***/ +/** Pure Laziness **/ +#define ENABLE_TPRINTF /** Debugging **/ +#ifndef ENABLE_TPRINTF void void_func() {} #define tprintf void_func -// #define tprintf printf +#endif +#ifdef ENABLE_TPRINTF +#define tprintf printf +#endif /** Defaults for unspecified optional attributes. **/ #define DEFAULT_MIN_IMPROVEMENT 0.0001 @@ -85,42 +91,6 @@ void void_func() {} /** ================ Stuff That Should Be Somewhere Else ================ **/ /** ANCHOR[id=temp] **/ -#define INT_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" -#define INT_TO_BINARY(int_val) \ - ((int_val) & 0b10000000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b01000000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00100000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00010000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00001000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000100000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000010000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000001000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000100000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000010000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000001000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000100000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000010000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000001000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000100000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000010000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000001000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000100000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000010000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000001000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000100000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000010000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000001000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000100000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000010000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000001000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000100000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000010000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000001000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000000100 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000000010 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000000001 ? '1' : '0') - - /** TODO: I think this should be moved to mtsession. **/ /*** I caused at least 10 bugs so far trying to pass format specifiers to *** mssError without realizing that it didn't support them. Eventually, I @@ -593,45 +563,38 @@ struct /** Parsing Functions. **/ // LINK #parsing -int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); -ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); -SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); -pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path); -pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data); -pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data); -pNodeData ci_ParseNodeData(pStructInf inf, pObject obj); +static int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); +static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); +static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); +static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path); +static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data); +static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data); +static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj); /** Freeing Functions. **/ // LINK #freeing -void ci_FreeSourceData(pSourceData source_data); -void ci_FreeClusterData(pClusterData cluster_data, bool recursive); -void ci_FreeSearchData(pSearchData search_data); -void ci_FreeNodeData(pNodeData node_data); +static void ci_FreeSourceData(pSourceData source_data); +static void ci_FreeClusterData(pClusterData cluster_data, bool recursive); +static void ci_FreeSearchData(pSearchData search_data); +static void ci_FreeNodeData(pNodeData node_data); /** Deep Size Computation Functions. **/ // LINK #sizing -unsigned int ci_SizeOfSourceData(pSourceData source_data); -unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive); -unsigned int ci_SizeOfSearchData(pSearchData search_data); -unsigned int ci_SizeOfNodeData(pNodeData node_data); - -/** Cache Invalidation Functions. **/ -// LINK #invalidation -void ci_CacheFreeSourceData(pXHashEntry entry, void* _); -void ci_CacheFreeCluster(pXHashEntry entry, void* _); -void ci_CacheFreeSearch(pXHashEntry entry, void* _); +static unsigned int ci_SizeOfSourceData(pSourceData source_data); +static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive); +static unsigned int ci_SizeOfSearchData(pSearchData search_data); /** Computation Functions. (Ensure data is computed.) **/ // LINK #computation -int ci_ComputeSourceData(pSourceData source_data, pObjSession session); -int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data); -int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data); +static int ci_ComputeSourceData(pSourceData source_data, pObjSession session); +static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data); +static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data); /** Parameter Functions. **/ // LINK #params -int ci_GetParamType(void* inf_v, const char* attr_name); -int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); -int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); +static int ci_GetParamType(void* inf_v, const char* attr_name); +static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); +static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); /** Driver Functions. **/ // LINK #driver @@ -642,6 +605,7 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt); int clusterQueryClose(void* qy_v, pObjTrxTree* oxt); int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt); char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt); char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt); int clusterInfo(void* inf_v, pObjectInfo info); @@ -650,6 +614,10 @@ int clusterInfo(void* inf_v, pObjectInfo info); // LINK #method char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt); char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt); +static int ci_PrintEntry(pXHashEntry entry, void* arg); +static void ci_CacheFreeSourceData(pXHashEntry entry, void* path); +static void ci_CacheFreeCluster(pXHashEntry entry, void* path); +static void ci_CacheFreeSearch(pXHashEntry entry, void* path); int clusterExecuteMethod(void* inf_v, char* methodname, pObjData param, pObjTrxTree oxt); /** Unimplemented DriverFunctions. **/ @@ -663,7 +631,6 @@ int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree oxt); void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree oxt); int clusterCommit(void* inf_v, pObjTrxTree *oxt); -pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt); /** ================ Parsing Functions ================ **/ /** ANCHOR[id=parsing] **/ @@ -682,7 +649,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb *** still don't know if it works correctly... or really how it works. Please *** review this code carefully! ***/ -int ci_ParseAttribute( +static int ci_ParseAttribute( pStructInf inf, char* attr_name, int datatype, @@ -787,7 +754,7 @@ int ci_ParseAttribute( *** evaluating parameter variables in the structure file. *** @returns The data algorithm, or ALGORITHM_NULL on failure. ***/ -ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) +static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) { /** Get the algorithm attribute. **/ char* algorithm; @@ -824,7 +791,7 @@ ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param *** evaluating parameter variables in the structure file. *** @returns The similarity measure, or SIMILARITY_NULL on failure. ***/ -SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) +static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) { /** Get the similarity_measure attribute. **/ char* measure; @@ -858,17 +825,17 @@ SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_ *** cache entry keys. *** @returns A new pSourceData struct on success, or NULL on failure. ***/ -pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) +static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) { char* buf; /** Get source. **/ if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; - char* source_path = check_ptr(strdup(buf)); + char* source_path = check_ptr(nmSysStrdup(buf)); /** Get attribute name. **/ if (ci_ParseAttribute(inf, "attr_name", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; - char* attr_name = check_ptr(strdup(buf)); + char* attr_name = check_ptr(nmSysStrdup(buf)); /** Create cache entry key. **/ const size_t len = strlen(path) + strlen(source_path) + strlen(attr_name) + 3lu; @@ -885,8 +852,8 @@ pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* p tprintf("--> Name: %s\n", source_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ /** Free data we don't need. */ - free(source_path); - free(attr_name); + nmSysFree(source_path); + nmSysFree(attr_name); nmSysFree(key); /** Return the cached source data. **/ @@ -896,7 +863,7 @@ pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* p /** Cache miss: Create a new source data object. **/ pSourceData source_data = check_ptr(nmMalloc(sizeof(SourceData))); memset(source_data, 0, sizeof(SourceData)); - source_data->Name = check_ptr(strdup(inf->Name)); + source_data->Name = check_ptr(nmSysStrdup(inf->Name)); source_data->Key = key; source_data->SourcePath = source_path; source_data->AttrName = attr_name; @@ -928,7 +895,7 @@ pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* p *** used to generate cache entry keys. *** @returns A new pClusterData struct on success, or NULL on failure. ***/ -pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) +static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) { int result; @@ -942,13 +909,13 @@ pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) memset(cluster_data, 0, sizeof(ClusterData)); /** Basic Properties. **/ - cluster_data->Name = check_ptr(strdup(inf->Name)); + cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); cluster_data->SourceData = source_data; check(objCurrentDate(&cluster_data->DateCreated)); /** Get algorithm. **/ cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); - if (cluster_data->ClusterAlgorithm == ALGORITHM_NULL) goto err; + if (cluster_data->ClusterAlgorithm == ALGORITHM_NULL) goto err_free_cluster; /** Handle no clustering case. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_NONE) @@ -1008,7 +975,7 @@ pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) if (result == -1) goto err_free_cluster; if (result == 0) { - if (max_iterations < 0) + if (max_iterations < 1) { mssErrorf(1, "Cluster", "Invalid value for [max_iterations : uint]: %d", max_iterations); goto err_free_cluster; @@ -1033,8 +1000,7 @@ pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) if (stStructType(group_inf) != ST_T_SUBGROUP) continue; /** Select array by group type. **/ - assert(group_inf->UsrType != NULL); - if (strcmp(group_inf->UsrType, "cluster/cluster")) continue; + if (strcmp(check_ptr(group_inf->UsrType), "cluster/cluster") != 0) continue; /** Subcluster found. **/ pClusterData sub_cluster = ci_ParseClusterData(group_inf, node_data); @@ -1125,9 +1091,9 @@ pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) xaDeInit(&sub_clusters); err_free_cluster: - nmFree(cluster_data, sizeof(ClusterData)); + ci_FreeClusterData(cluster_data, false); - err: + // err: mssErrorf(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); return NULL; } @@ -1147,7 +1113,7 @@ pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) *** the cluster pointed to by the source attribute. *** @returns A new pSearchData struct on success, or NULL on failure. ***/ -pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) +static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) { tprintf("Parsing search: %s\n", inf->Name); @@ -1157,7 +1123,7 @@ pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) memset(search_data, 0, sizeof(SearchData)); /** Get basic information. **/ - search_data->Name = check_ptr(strdup(inf->Name)); + search_data->Name = check_ptr(nmSysStrdup(inf->Name)); check(objCurrentDate(&search_data->DateCreated)); /** Get source. **/ @@ -1249,7 +1215,7 @@ pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) *** @param obj The parent object struct. *** @returns A new pNodeData struct on success, or NULL on failure. ***/ -pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) +static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) { int ret; @@ -1452,30 +1418,18 @@ pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) // LINK #functions /** @param source_data A pSourceData struct, freed by this function. **/ -void ci_FreeSourceData(pSourceData source_data) +static void ci_FreeSourceData(pSourceData source_data) { /** Free top level attributes, if they exist. **/ - if (source_data->Name != NULL) - { - free(source_data->Name); - source_data->Name = NULL; - } - if (source_data->SourcePath != NULL) - { - free(source_data->SourcePath); - source_data->SourcePath = NULL; - } - if (source_data->AttrName != NULL) - { - free(source_data->AttrName); - source_data->AttrName = NULL; - } + if (source_data->Name != NULL) nmSysFree(source_data->Name); + if (source_data->SourcePath != NULL) nmSysFree(source_data->SourcePath); + if (source_data->AttrName != NULL) nmSysFree(source_data->AttrName); /** Free fetched data, if it exists. **/ if (source_data->Data != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) - free(source_data->Data[i]); + nmSysFree(source_data->Data[i]); nmFree(source_data->Data, source_data->nVectors * sizeof(char*)); source_data->Data = NULL; } @@ -1500,16 +1454,17 @@ void ci_FreeSourceData(pSourceData source_data) *** @param cluster_data The cluster data struct to free. *** @param recrusive Whether to recursively free subclusters. ***/ -void ci_FreeClusterData(pClusterData cluster_data, bool recursive) +static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) { /** Free top level cluster data. **/ - if (cluster_data->Name != NULL) free(cluster_data->Name); + if (cluster_data->Name != NULL) nmSysFree(cluster_data->Name); /** Free computed data, if it exists. **/ if (cluster_data->Labels != NULL) { const unsigned int nVectors = cluster_data->SourceData->nVectors; nmFree(cluster_data->Labels, nVectors * sizeof(unsigned int)); + cluster_data->Labels = NULL; } /** Free subclusters recursively. **/ @@ -1521,6 +1476,7 @@ void ci_FreeClusterData(pClusterData cluster_data, bool recursive) ci_FreeClusterData(cluster_data->SubClusters[i], recursive); } nmFree(cluster_data->SubClusters, cluster_data->nSubClusters * sizeof(void*)); + cluster_data->SubClusters = NULL; } /** Free the cluster struct. **/ @@ -1530,14 +1486,15 @@ void ci_FreeClusterData(pClusterData cluster_data, bool recursive) // LINK #functions /** @param search_data A pSearchData struct, freed by this function. **/ -void ci_FreeSearchData(pSearchData search_data) +static void ci_FreeSearchData(pSearchData search_data) { - if (search_data->Name != NULL) free(search_data->Name); + if (search_data->Name != NULL) nmSysFree(search_data->Name); if (search_data->Dups != NULL) { for (unsigned int i = 0; i < search_data->nDups; i++) nmFree(search_data->Dups[i], sizeof(Dup)); nmFree(search_data->Dups, search_data->nDups * sizeof(void*)); + search_data->Dups = NULL; } nmFree(search_data, sizeof(SearchData)); } @@ -1545,7 +1502,7 @@ void ci_FreeSearchData(pSearchData search_data) // LINK #functions /** @param node_data A pNodeData struct, freed by this function. **/ -void ci_FreeNodeData(pNodeData node_data) +static void ci_FreeNodeData(pNodeData node_data) { /** Free parsed params, if they exist. **/ if (node_data->Params != NULL) @@ -1611,7 +1568,7 @@ void ci_FreeNodeData(pNodeData node_data) *** @param source_data The source data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -unsigned int ci_SizeOfSourceData(pSourceData source_data) +static unsigned int ci_SizeOfSourceData(pSourceData source_data) { unsigned int size = 0u; if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); @@ -1646,7 +1603,7 @@ unsigned int ci_SizeOfSourceData(pSourceData source_data) *** @param recrusive Whether to recursively free subclusters. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) +static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) { unsigned int size = 0u; if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); @@ -1676,7 +1633,7 @@ unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) *** @param search_data The search data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -unsigned int ci_SizeOfSearchData(pSearchData search_data) +static unsigned int ci_SizeOfSearchData(pSearchData search_data) { unsigned int size = 0u; if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); @@ -1686,100 +1643,6 @@ unsigned int ci_SizeOfSearchData(pSearchData search_data) } -// LINK #functions -/*** Returns the deep size of a NodeData struct, including the size of all - *** allocated substructures. As far as I can tell, this is probably only - *** useful for cache management and debugging. - *** - *** Note that Key is ignored because it is a pointer to data managed by the - *** caching systems, so it is not technically part of the struct. - *** - *** @param node_data The cluster data struct to be queried. - *** @returns The size in bytes of the struct and all internal allocated data. - ***/ -unsigned int ci_SizeOfNodeData(pNodeData node_data) - { - unsigned int size = 0u; - if (node_data->Params != NULL) - { - /** Approximate. **/ - size += node_data->nParams * (sizeof(Param) + sizeof(pParam)); - } - if (node_data->ParamList == NULL) - { - /** Approximate. **/ - size += node_data->nParams * 30u * sizeof(char); - size += sizeof(pParamObjects); - } - if (node_data->Clusters != NULL) - { - /** Note: This data is also stored in a cache. **/ - for (unsigned int i = 0u; i < node_data->nClusters; i++) - size += ci_SizeOfClusterData(node_data->Clusters[i], true); - size += node_data->nClusters * sizeof(pClusterData); - } - if (node_data->Searches != NULL) - { - /** Note: This data is also stored in a cache. **/ - for (unsigned int i = 0u; i < node_data->nSearches; i++) - size += ci_SizeOfSearchData(node_data->Searches[i]); - size += node_data->nSearches * sizeof(pSearchData); - } - if (node_data->SourceData != NULL) - { - /** Note: This data is also stored in a cache. **/ - size += ci_SizeOfSourceData(node_data->SourceData); - } - size += sizeof(NodeData); - return size; - } - - -/** ================ Cache Invalidation Functions ================ **/ -/** ANCHOR[id=invalidation] **/ -// LINK #functions - -/** Intended for use in xhClearKeySafe(). **/ -void ci_CacheFreeSourceData(pXHashEntry entry, void* _) - { - /** Extract hash entry. **/ - char* key = entry->Key; - pSourceData source_data = (pSourceData)entry->Data; - - /** Free data. **/ - tprintf("- source: \"%s\"\n", key); - ci_FreeSourceData(source_data); - nmSysFree(key); - } - -// LINK #functions -/** Intended for use in xhClearKeySafe(). **/ -void ci_CacheFreeCluster(pXHashEntry entry, void* _) - { - /** Extract hash entry. **/ - char* key = entry->Key; - pClusterData cluster_data = (pClusterData)entry->Data; - - /** Free data. **/ - tprintf("- cluster: \"%s\"\n", key); - ci_FreeClusterData(cluster_data, false); - nmSysFree(key); - } - -// LINK #functions -/** Intended for use in xhClearKeySafe(). **/ -void ci_CacheFreeSearch(pXHashEntry entry, void* _) - { - /** Extract hash entry. **/ - char* key = entry->Key; - pSearchData search_data = (pSearchData)entry->Data; - - /** Free data. **/ - tprintf("- search: \"%s\"\n", key); - ci_FreeSearchData(search_data); - nmSysFree(key); - } - /** ================ Computation Functions ================ **/ /** ANCHOR[id=computation] **/ // LINK #functions @@ -1795,7 +1658,7 @@ void ci_CacheFreeSearch(pXHashEntry entry, void* _) *** @returns 0 if successful, or *** -1 other value on failure. ***/ -int ci_ComputeSourceData(pSourceData source_data, pObjSession session) +static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { /** If the vectors are already computed, we're done. **/ if (source_data->Vectors != NULL) return 0; @@ -1823,7 +1686,7 @@ int ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Drop source_data->Data. **/ for (unsigned int i = 0u; i < source_data->nVectors; i++) - free(source_data->Data[i]); + nmSysFree(source_data->Data[i]); nmFree(source_data->Data, source_data->nVectors * sizeof(char*)); source_data->Data = NULL; source_data->nVectors = 0; @@ -1969,7 +1832,7 @@ int ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Store value. **/ - char* dup_val = check_ptr(strdup(val)); + char* dup_val = check_ptr(nmSysStrdup(val)); check_strict(xaAddItem(&data_xarray, (void*)dup_val)); check_strict(xaAddItem(&vector_xarray, (void*)vector)); @@ -2000,7 +1863,7 @@ int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (data_xarray.nAlloc != 0) { for (unsigned int i = 0u; i < data_xarray.nItems; i++) - free(data_xarray.Items[i]); + nmSysFree(data_xarray.Items[i]); check(xaDeInit(&data_xarray)); } if (vector_xarray.nAlloc != 0) @@ -2044,7 +1907,7 @@ int ci_ComputeSourceData(pSourceData source_data, pObjSession session) *** @returns 0 if successful, or *** -1 other value on failure. ***/ -int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) +static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { /** If the clusters are alreadyd computed, we're done. **/ if (cluster_data->Labels != NULL) return 0; @@ -2133,7 +1996,7 @@ int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) *** @returns 0 if successful, or *** -1 other value on failure. ***/ -int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) +static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { int ret; @@ -2209,7 +2072,7 @@ int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -int ci_GetParamType(void* inf_v, const char* attr_name) +static int ci_GetParamType(void* inf_v, const char* attr_name) { tprintf("Call to ci_GetParamType(\"%s\")\n", attr_name); pNodeData node_data = (pNodeData)inf_v; @@ -2253,7 +2116,7 @@ int ci_GetParamType(void* inf_v, const char* attr_name) *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) +static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { tprintf("Call to ci_GetParamValue(\"%s\", %s)\n", attr_name, ci_TypeToStr(datatype)); pNodeData node_data = (pNodeData)inf_v; @@ -2286,9 +2149,9 @@ int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) return -1; } - +// LINK #functions /** Not implemented. **/ -int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) +static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { tprintf("Call to ci_SetParamValue(%s, %s)\n", attr_name, ci_TypeToStr(datatype)); mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are imutable."); @@ -2714,9 +2577,9 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) // LINK #functions /*** Get the value of a cluster driver instance attribute. *** - *** @param inf_v Node data containing the list of paramenters. - *** @param attr_name The name of the requested paramenter. - *** @param datatype The expected datatype of the parameter value. + *** @param inf_v The driver instance to be read. + *** @param attr_name The name of the requested attribute. + *** @param datatype The expected datatype of the attribute value. *** See datatypes.h for a list of valid datatypes. *** @param oxt The object system tree, similar to a kind of "scope" (unused). *** @param val A pointer to a location where a pointer to the requested @@ -2909,14 +2772,14 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val if (strcmp(attr_name, "num_clusters") == 0) { if (target->NumClusters > INT_MAX) - fprintf(stderr, "Warning: num_clusters value of %u exceeds INT_MAX.\n", target->NumClusters); + fprintf(stderr, "Warning: num_clusters value of %u exceeds INT_MAX (%d).\n", target->NumClusters, INT_MAX); val->Integer = (int)target->NumClusters; return 0; } if (strcmp(attr_name, "max_iterations") == 0) { if (target->MaxIterations > INT_MAX) - fprintf(stderr, "Warning: max_iterations value of %u exceeds INT_MAX.\n", target->MaxIterations); + fprintf(stderr, "Warning: max_iterations value of %u exceeds INT_MAX (%d).\n", target->MaxIterations, INT_MAX); val->Integer = (int)target->MaxIterations; return 0; } @@ -3030,7 +2893,20 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val // LINK #functions -/** Not implemented. **/ +/*** Create a new presentation hints object, describing this attribute on the + *** provided cluster driver instance. + *** + *** Note: expCompileExpression() and nmSysStrdup() are run unchecked because + *** the worst case senario is that the fields are set to null and ignored, + *** which I consider to be better than ending the script because one of + *** them failed. + *** + *** @param inf_v The driver instance to be read. + *** @param attr_name The name of the requested attribute. + *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @returns A presentation hints object, if successsful, + *** NULL if an error occures. + ***/ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { tprintf("Warning: clusterPresentationHints(\"%s\") is under active development.", attr_name); @@ -3251,7 +3127,6 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb char buf[16u]; snprintf(buf, sizeof(buf), "%u", source_data->nVectors); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - return 0; } /** Other hints. **/ @@ -3297,7 +3172,6 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb char buf[16u]; snprintf(buf, sizeof(buf), "%u", source_data->nVectors); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - return 0; } /** Other hints. **/ @@ -3330,13 +3204,17 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb default: mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - return NULL; + goto err; } end: check(expFreeParamList(tmp_list)); return hints; + + err: + mssErrorf(0, "Cluster", "Failed execute generate presentation hints."); + return NULL; } @@ -3460,11 +3338,15 @@ int clusterInfo(void* inf_v, pObjectInfo info) default: mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - return -1; + goto err; } tprintf("Info result: "INT_TO_BINARY_PATTERN"\n", INT_TO_BINARY(info->Flags)); return 0; + + err: + mssErrorf(0, "Cluster", "Failed execute get info."); + return -1; } @@ -3509,6 +3391,7 @@ char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt) return (i < nMETHOD_NAME) ? METHOD_NAME[i] : END_OF_METHODS; } + // LINK #functions /** Intended for use in xhForEach(). **/ static int ci_PrintEntry(pXHashEntry entry, void* arg) @@ -3524,7 +3407,6 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) char* path = (char*)args[2]; /** If a path is provided, check that it matches the start of the key. **/ -// if (path != NULL) printf("Comparing \"%s\" to \"%s\"[0,%lu].\n", path, key, strlen((char*)path)); if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return 0; /** Handle type. **/ @@ -3571,6 +3453,60 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) } +// LINK #functions +/** Intended for use in xhClearKeySafe(). **/ +static void ci_CacheFreeSourceData(pXHashEntry entry, void* path) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pSourceData source_data = (pSourceData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + tprintf("- source: \"%s\"\n", key); + ci_FreeSourceData(source_data); + nmSysFree(key); + } + + +// LINK #functions +/** Intended for use in xhClearKeySafe(). **/ +static void ci_CacheFreeCluster(pXHashEntry entry, void* path) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pClusterData cluster_data = (pClusterData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + tprintf("- cluster: \"%s\"\n", key); + ci_FreeClusterData(cluster_data, false); + nmSysFree(key); + } + + +// LINK #functions +/** Intended for use in xhClearKeySafe(). **/ +static void ci_CacheFreeSearch(pXHashEntry entry, void* path) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pSearchData search_data = (pSearchData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + tprintf("- search: \"%s\"\n", key); + ci_FreeSearchData(search_data); + nmSysFree(key); + } + + // LINK #functions /*** Executes a method with the given name. *** @@ -3582,28 +3518,38 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree oxt) { tprintf("Warning: clusterExecuteMethod(\"%s\") is under active development.\n", method_name); + pDriverData driver_data = (pDriverData)inf_v; /** Cache management method. **/ if (strcmp(method_name, "cache") == 0) { + char* path = NULL; + /** Second parameter is required. **/ if (param->String == NULL) { mssErrorf(1, "Cluster", "param : \"show\" | \"show_all\" | \"drop_all\" is required for the cache method." ); - return -1; + goto err; } - /** Show cache. **/ + /** show and show_all. **/ + bool show = false; if (strcmp(param->String, "show") == 0) { - const pObject obj = ((pDriverData)inf_v)->NodeData->Obj; - char* path = ci_file_path(obj); - + show = true; + path = ci_file_path(driver_data->NodeData->Obj); + } + if (strcmp(param->String, "show_all") == 0) show = true; + + if (show) + { /** Print cache info table. **/ unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; - printf("\nShowing cache for \"%s\":\n", path); + printf("\nShowing cache for "); + if (path != NULL) printf("\"%s\":\n", path); + else printf("all files:\n"); printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); xhForEach(&ClusterCaches.SourceCache, ci_PrintEntry, (void*[]){&i, &source_bytes, path}); i++; xhForEach(&ClusterCaches.ClusterCache, ci_PrintEntry, (void*[]){&i, &cluster_bytes, path}); i++; @@ -3627,35 +3573,27 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx return 0; } - - /** Show all cache. **/ - if (strcmp(param->String, "show_all") == 0) + /** drop and drop_all. **/ + bool drop = false; + if (strcmp(param->String, "drop") == 0) { - /** Print cache info table. **/ - unsigned int i = 1u, total_bytes = 0u; - tprintf("Showing cluster driver cache for all files...\n"); - printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); - xhForEach(&ClusterCaches.SourceCache, ci_PrintEntry, (void*[]){&i, &total_bytes, NULL}); i++; - xhForEach(&ClusterCaches.ClusterCache, ci_PrintEntry, (void*[]){&i, &total_bytes, NULL}); i++; - xhForEach(&ClusterCaches.SearchCache, ci_PrintEntry, (void*[]){&i, &total_bytes, NULL}); i++; - - /** Print total size. **/ - char buf[16]; - snprint_bytes(buf, sizeof(buf), total_bytes); - printf("Total cache size: %s\n", buf); - return 0; + show = true; + path = ci_file_path(driver_data->NodeData->Obj); } + if (strcmp(param->String, "drop_all") == 0) drop = true; - /** Drop allcache. **/ - if (strcmp(param->String, "drop_all") == 0) + if (drop) { - tprintf("Dropping cluster driver cache for all files...\n"); + printf("\nDropping cache for "); + if (path != NULL) printf("\"%s\":\n", path); + else printf("all files:\n"); + /*** Free caches in reverse of the order they are created in case *** cached data relies on its source during the freeing process. ***/ - xhClearKeySafe(&ClusterCaches.SearchCache, ci_CacheFreeSearch, NULL); - xhClearKeySafe(&ClusterCaches.ClusterCache, ci_CacheFreeCluster, NULL); - xhClearKeySafe(&ClusterCaches.SourceCache, ci_CacheFreeSourceData, NULL); + xhClearKeySafe(&ClusterCaches.SearchCache, ci_CacheFreeSearch, path); + xhClearKeySafe(&ClusterCaches.ClusterCache, ci_CacheFreeCluster, path); + xhClearKeySafe(&ClusterCaches.SourceCache, ci_CacheFreeSourceData, path); printf("Cache dropped.\n"); return 0; } @@ -3665,9 +3603,14 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx "Expected param : \"show\" | \"show_all\" | \"drop_all\" the cache method, but got: \"%s\"", param->String ); - return -1; + goto err; } + + /** Unknown parameter. **/ + mssErrorf(1, "Cluster", "Unknown command: \"%s\"", method_name); + err: + mssErrorf(0, "Cluster", "Failed execute command."); return -1; } diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index c4c64e25b..5ef492de3 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -1443,6 +1443,7 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) else { printf("Unknown command '%s'\n",cmdname); + mlxCloseSession(ls); return -1; } From cf0dbb5fb1f061c617e65e6fd91924f4d389ec9f Mon Sep 17 00:00:00 2001 From: Israel Date: Mon, 27 Oct 2025 17:14:15 -0600 Subject: [PATCH 004/361] Finish implementing major features for the cluster driver. --- centrallix-lib/include/clusters.h | 69 +- centrallix-lib/include/glyph.h | 78 + centrallix-lib/include/util.h | 59 +- centrallix-lib/src/clusters.c | 716 ++++----- centrallix-lib/src/util.c | 25 +- centrallix-os/cluster-schema.cluster | 27 +- centrallix-os/file.cluster | 3 + centrallix-sysdoc/string_similarity.md | 167 +++ centrallix/include/obj.h | 1 + centrallix/osdrivers/objdrv_cluster.c | 1854 ++++++++++++++++-------- centrallix/test_obj.c | 7 + 11 files changed, 1936 insertions(+), 1070 deletions(-) create mode 100644 centrallix-lib/include/glyph.h create mode 100644 centrallix-sysdoc/string_similarity.md diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 2605b4314..d8b7f97c6 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -1,3 +1,5 @@ +#ifndef CLUSTERS_H +#define CLUSTERS_H /************************************************************************/ /* Centrallix Application Server System */ @@ -23,7 +25,7 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: lib_cluster.c */ +/* Module: lib_cluster.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description: Internal algorithms for the cluster object driver. */ @@ -40,6 +42,7 @@ #define CA_NUM_DIMS 251 /* aka. The vector table size. */ +/// LINK ../../centrallix-sysdoc/string_comparison.md#cosine_charsets /** The character used to create a pair with the first and last characters of a string. **/ #define CA_BOUNDARY_CHAR ('a' - 1) @@ -57,37 +60,47 @@ typedef struct } Dup, *pDup; +/** Registering all defined types for debugging. **/ +#define ca_init() \ + nmRegister(sizeof(pVector), "pVector"); \ + nmRegister(sizeof(pCentroid), "pCentroid"); \ + nmRegister(pCentroidSize, "Centroid"); \ + nmRegister(sizeof(Dup), "Dup") + pVector ca_build_vector(const char* str); unsigned int ca_sparse_len(const pVector vector); void ca_free_vector(pVector sparse_vector); -void ca_kmeans( +int ca_kmeans( pVector* vectors, const unsigned int num_vectors, - unsigned int* labels, const unsigned int num_clusters, const unsigned int max_iter, - const double improvement_threshold -); -pXArray ca_search( - pVector* vectors, - const unsigned int num_vectors, - const unsigned int* labels, - const double dupe_threshold -); -pXArray ca_lightning_search( - pVector* vectors, - const unsigned int num_vectors, - const double dupe_threshold -); -unsigned int ca_edit_dist( - const char* str1, - const char* str2, - const size_t str1_length, - const size_t str2_length -); -pXArray ca_phone_search( - char dataset[][10u], - const unsigned int dataset_size, - const double dupe_threshold -); -void ca_init(); + const double min_improvement, + unsigned int* labels, + double* vector_sims); + +/** Comparison functions, for ca_search(). **/ +double ca_cos_compare(void* v1, void* v2); +double ca_lev_compare(void* str1, void* str2); + +void* ca_most_similar( + void* target, + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double threshold); +pXArray ca_sliding_search( + void** data, + const unsigned int num_data, + const unsigned int window_size, + const double (*similarity)(void*, void*), + const double dupe_threshold, + pXArray dups); +pXArray ca_complete_search( + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double dupe_threshold, + pXArray dups); + +#endif /* End of .h file. */ diff --git a/centrallix-lib/include/glyph.h b/centrallix-lib/include/glyph.h new file mode 100644 index 000000000..5f78eab5d --- /dev/null +++ b/centrallix-lib/include/glyph.h @@ -0,0 +1,78 @@ +#ifndef GLYPH_H +#define GLYPH_H + +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: glyph.h */ +/* Author: Israel Fuller */ +/* Creation: October 27, 2025 */ +/* Description: A simple debug visualizer to make pretty patterns in */ +/* developer's terminal which can be surprisingly useful */ +/* for debugging algorithms. */ +/************************************************************************/ + +#include + +/** Uncomment to use glyphs. **/ +/** TODO: Israel - Comment this out. **/ +// #define ENABLE_GLYPHS + +#ifdef ENABLE_GLYPHS +#define glyph_print(s) printf("%s", s); +/*** Initialize a simple debug visualizer to make pretty patterns in the + *** developer's terminal. Great for when you need to run a long task and + *** want a super simple way to make sure it's still working. + *** + *** @attention - Relies on storing data in variables in scope, so calling + *** glyph() requires a call to glyph_init() previously in the same scope. + *** + *** @param name The symbol name of the visualizer. + *** @param str The string printed for the visualization. + *** @param interval The number of invokations of glyph() required to print. + *** @param flush Whether to flush on output. + ***/ +#define glyph_init(name, str, interval, flush) \ + const char* vis_##name##_str = str; \ + const unsigned int vis_##name##_interval = interval; \ + const bool vis_##name##_flush = flush; \ + unsigned int vis_##name##_i = 0u; + +/*** Invoke a visualizer. + *** + *** @param name The name of the visualizer to invoke. + ***/ +#define glyph(name) \ + if (++vis_##name##_i % vis_##name##_interval == 0) \ + { \ + glyph_print(vis_##name##_str); \ + if (vis_##name##_flush) fflush(stdout); \ + } +#else +#define glyph_print(str) +#define glyph_init(name, str, interval, flush) +#define glyph(name) +#endif + +#endif /* End of .h file. */ diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 12019abfb..dd821767f 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -46,6 +46,7 @@ extern "C" { #endif #ifndef __cplusplus +#include /** TODO: Greg, is the __typeof__ syntax from GCC a portability concern? **/ @@ -79,58 +80,72 @@ extern "C" { (_a > _b) ? _a : _b; \ }) +/** File name macro, expanding functionality like __FILE__ and __LINE__. **/ +#define __FILENAME__ \ + ({ \ + const char* last_directory = strrchr(__FILE__, '/'); \ + ((last_directory != NULL) ? last_directory + 1 : __FILE__); \ + }) + /** Error Handling. **/ -void fail(const char* function_name, int code); +void print_diagnostics(int code, const char* function_name, const char* file_name, const int line_number); -/*** Helper function for compact error handling on library & system function calls. - *** Any non-zero value is treated as an error, exiting the program. +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is not zero. Not intended for user errors. *** *** @param result The result of the function we're checking. - *** @returns result + *** @returns Whether the passed function succeeded. ***/ #define check(result) \ ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ - if (_r != 0) fail(#result, _r); \ - _r; \ + const bool success = (_r == 0); \ + if (!success) print_diagnostics(_r, #result, __FILE__, __LINE__); \ + success; \ }) - -/*** Helper function for compact error handling on library & system function calls. - *** Any negative is treated as an error, exiting the program. + +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is negative. Not intended for user errors. *** *** @param result The result of the function we're checking. - *** @returns result + *** @returns Whether the passed function succeeded. ***/ #define check_neg(result) \ ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ - if (_r < 0) fail(#result, _r); \ - _r; \ + const bool success = (_r >= 0); \ + if (!success) print_diagnostics(_r, #result, __FILE__, __LINE__); \ + success; \ }) -/*** Helper function for compact error handling on library & system function calls. - *** Any value of -1 is treated as an error, exiting the program. +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is -1. Not intended for user errors. *** *** @param result The result of the function we're checking. - *** @returns result + *** @returns Whether the passed function succeeded. ***/ -#define check_strict(result) \ +#define check_weak(result) \ ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ - if (_r == -1) fail(#result, _r); \ - _r; \ + const bool success = (_r != -1); \ + if (!success) print_diagnostics(_r, #result, __FILE__, __LINE__); \ + success; \ }) -/*** Helper function for compact error handling on library & system function calls. - *** Any null value is treated as an error, exiting the program. +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is a NULL pointer. Not intended for user errors. *** - *** @param result The result of the function we're checking + *** @param result The result of the function we're checking. *** @returns result ***/ #define check_ptr(result) \ ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ - if (_r == NULL) fail(#result, 0); \ + if (_r == NULL) print_diagnostics(0, #result, __FILE__, __LINE__); \ _r; \ }) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 4e41d449d..90599269c 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -27,10 +27,10 @@ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description: Internal algorithms for the cluster object driver. */ -/* See centrallix-sysdoc/EAV_Pivot.md for more information. */ /************************************************************************/ -#include +/** This file has additional documentation in string_similarity.md. **/ + #include #include #include @@ -42,6 +42,7 @@ #include #include "clusters.h" +#include "glyph.h" #include "newmalloc.h" #include "util.h" #include "xarray.h" @@ -55,13 +56,6 @@ ***/ static unsigned int hash_char_pair(const unsigned int num1, const unsigned int num2) { - if (num1 == CA_BOUNDARY_CHAR && num2 == CA_BOUNDARY_CHAR) - { - // fprintf(stderr, - // "hash_char_pair(%u, %u) - Warning: Pair of boundary characters.\n", - // num1, num2 - // ); - } const double sum = (num1 * num1 * num1) + (num2 * num2 * num2); const double scale = ((double)num1 + 1.0) / ((double)num2 + 1.0); const unsigned int hash = (unsigned int)round(sum * scale) - 1u; @@ -201,15 +195,8 @@ pVector ca_build_vector(const char* str) /** Allocate space for sparse vector. **/ const size_t sparse_vector_size = size * sizeof(int); - pVector sparse_vector = (pVector)nmSysMalloc(sparse_vector_size); - if (sparse_vector == NULL) - { - fprintf(stderr, - "cli_build_vector(%s) - nmSysMalloc(%lu) failed.\n", - str, sparse_vector_size - ); - return NULL; - } + pVector sparse_vector = (pVector)check_ptr(nmSysMalloc(sparse_vector_size)); + if (sparse_vector == NULL) return NULL; /** Convert the dense vector above to a sparse vector. **/ unsigned int j = 0u, sparse_idx = 0u; @@ -248,46 +235,46 @@ void ca_free_vector(pVector sparse_vector) nmSysFree(sparse_vector); } -/*** Compute the magnitude of a sparsely allocated vector. +/*** Compute the length of a sparsely allocated vector. *** *** @param vector The vector. - *** @returns The computed magnitude. + *** @returns The computed length. ***/ -static double magnitude_sparse(const pVector vector) +unsigned int ca_sparse_len(const pVector vector) { - unsigned int magnitude = 0u; - for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + unsigned int i = 0u; + for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) { const int val = vector[i++]; /** Negative val represents -val 0s in the array, so skip that many values. **/ if (val < 0) dim += (unsigned)(-val); - /** We have a param_value, so square it and add it to the magnitude. **/ - else { magnitude += (unsigned)(val * val); dim++; } + /** We have a param_value, but we don't need to do anything with it. **/ + else dim++; } - return sqrt((double)magnitude); + return i; } -/*** Compute the length of a sparsely allocated vector. +/*** Compute the magnitude of a sparsely allocated vector. *** *** @param vector The vector. - *** @returns The computed length. + *** @returns The computed magnitude. ***/ -unsigned int ca_sparse_len(const pVector vector) +static double magnitude_sparse(const pVector vector) { - unsigned int i = 0u; - for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) + unsigned int magnitude = 0u; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) { const int val = vector[i++]; /** Negative val represents -val 0s in the array, so skip that many values. **/ if (val < 0) dim += (unsigned)(-val); - /** We have a param_value, but we don't need to do anything with it. **/ - else dim++; + /** We have a param_value, so square it and add it to the magnitude. **/ + else { magnitude += (unsigned)(val * val); dim++; } } - return i; + return sqrt((double)magnitude); } /*** Compute the magnitude of a densely allocated centroid. @@ -417,6 +404,163 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 ***/ #define sparse_dif_to_centroid(v1, c2) (1.0 - sparse_similarity_to_centroid(v1, c2)) +/*** Computes Levenshtein distance between two strings. + *** + *** @param str1 The first string. + *** @param str2 The second string. + *** @param length1 The length of the first string. + *** @param length1 The length of the first string. + *** + *** @attention - `Tip`: Pass 0 for the length of either string to infer it + *** using the null terminating character. Conversely, character arrays + *** with no null terminator are allowed if an explicit length is specified. + *** + *** @attention - `Complexity`: O(nm), where n and m are the lengths of str1 + *** and str2 (respectively). + *** + *** @skip + *** LINK ../../centrallix-sysdoc/string_comparison.md#levenshtein + ***/ +static unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) + { + /*** lev_matrix: + *** For all i and j, d[i][j] will hold the Levenshtein distance between + *** the first i characters of s and the first j characters of t. + *** + *** As they say, no dynamic programming algorithm is complete without a + *** matrix that you fill out and it has the answer in the final location. + ***/ + const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; + const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; + unsigned int* lev_matrix[str1_len + 1]; + for (unsigned int i = 0u; i < str1_len + 1u; i++) + lev_matrix[i] = nmMalloc((str2_len + 1) * sizeof(unsigned int)); + + /*** Base case #0: + *** Transforming an empty string into an empty string has 0 cost. + ***/ + lev_matrix[0][0] = 0u; + + /*** Base case #1: + *** Any source prefixe can be transformed into an empty string by + *** dropping each character. + ***/ + for (unsigned int i = 1u; i <= str1_len; i++) + lev_matrix[i][0] = i; + + /*** Base case #2: + *** Any target prefixes can be transformed into an empty string by + *** inserting each character. + ***/ + for (unsigned int j = 1u; j <= str2_len; j++) + lev_matrix[0][j] = j; + + /** General Case **/ + for (unsigned int i = 1u; i <= str1_len; i++) + { + for (unsigned int j = 1u; j <= str2_len; j++) + { + /** Equal characters need no changes. **/ + if (str1[i - 1] == str2[j - 1]) + lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; + + /*** We need to make a change, so use the opereration with the + *** lowest cost out of delete, insert, replace, or swap. + ***/ + else + { + unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; + unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; + unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; + + /** If a swap is possible, calculate the cost. **/ + bool can_swap = ( + i > 1 && j > 1 && + str1[i - 1] == str2[j - 2] && + str1[i - 2] == str2[j - 1] + ); + unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; + + // Find the best operation. + lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); + } + } + } + + /** Store result. **/ + unsigned int result = lev_matrix[str1_len][str2_len]; + + /** Cleanup. **/ + for (unsigned int i = 0u; i < str1_len + 1u; i++) + nmFree(lev_matrix[i], (str2_len + 1) * sizeof(unsigned int)); + + return result; + } + +/*** Compares two strings using their cosie simiarity, returning a value + *** between `0.0` (completely different) and `1.0` (identical). If either + *** OR BOTH strings are NULL, this function returns `0.0`. + *** + *** @attention - This function takes `void*` instead of `pVector` so that it + *** can be used as the similarity function in the ca_search() function + *** family without needing a messy typecast to avoid the compiler warning. + *** + *** @param v1 A `pVector` to the first string to compare. + *** @param v2 A `pVector` to the second string to compare. + *** @returns The cosine similarity between the two strings. + *** + *** @skip + *** LINK ../../centrallix-sysdoc/string_comparison.md#cosine + ***/ +double ca_cos_compare(void* v1, void* v2) + { + /** Input validation checks. **/ + if (v1 == NULL || v2 == NULL) return 0.0; + if (v1 == v2) return 1.0; + + /** Return the sparse similarity. **/ + return sparse_similarity((const pVector)v1, (const pVector)v2); + } + +/*** Compares two strings using their levenstien edit distance to compute a + *** similarity between `0.0` (completely different) and `1.0` (identical). + *** If both strings are empty, this function returns `1.0` (identical). If + *** either OR BOTH strings are NULL, this function returns `0.0`. + *** + *** @attention - This function takes `void*` instead of `char*` so that it + *** can be used as the similarity function in the ca_search() function + *** family without needing a messy typecast to avoid the compiler warning. + *** + *** @param str1 A `char*` to the first string to compare. + *** @param str2 A `char*` to the second string to compare. + *** @returns The levenshtein similarity between the two strings. + *** + *** @skip + *** LINK ../../centrallix-sysdoc/string_comparison.md#levenshtein + ***/ +double ca_lev_compare(void* str1, void* str2) + { + /** Input validation checks. **/ + if (str1 == NULL || str2 == NULL) return 0.0; + if (str1 == str2) return 1.0; + + /** Compute string length. **/ + const size_t len1 = strlen(str1); + const size_t len2 = strlen(str2); + + /** Empty strings are identical, avoiding a divide by zero. */ + if (len1 == 0lu && len2 == 0lu) return 1.0; + + /** Compute levenshtein edit distance. **/ + const unsigned int dist = edit_dist((const char*)str1, (const char*)str2, len1, len2); + + /** Normalize edit distance into a similarity measure. **/ + const double normalized_similarity = 1.0 - (double)dist / (double)max(len1, len2); + + /** Done. **/ + return normalized_similarity; + } + /*** Calculate the average size of all clusters in a set of vectors. *** *** @param vectors The vectors of the dataset (allocated sparsely). @@ -436,8 +580,7 @@ static double get_cluster_size( /** Could be up to around 1KB on the stack, but I think that's fine. **/ double cluster_sums[num_clusters]; unsigned int cluster_counts[num_clusters]; - for (unsigned int i = 0u; i < num_clusters; i++) - cluster_sums[i] = 0.0; + memset(cluster_sums, 0, sizeof(cluster_sums)); memset(cluster_counts, 0, sizeof(cluster_counts)); /** Sum the difference from each vector to its cluster centroid. **/ @@ -499,14 +642,16 @@ unsigned int compute_k(const unsigned int n) *** *** @param vectors The vectors to cluster. *** @param num_vectors The number of vectors to cluster. - *** @param labels Stores the final cluster identities of the vectors after - *** clustering is completed. - *** @param centroids Stores the locations of the centroids used for the clusters - *** of the data. - *** @param iterations The number of iterations that actually executed is stored - *** here. Leave this NULL if you don't care. - *** @param max_iter The max number of iterations. *** @param num_clusters The number of clusters to generate. + *** @param max_iter The max number of iterations. + *** @param min_improvement The minimum amount of improvement that must be met + *** each clustering iteration. If there is less improvement, the algorithm + *** will stop. Pass any value less than -1 to fully disable this feature. + *** @param labels Stores the final cluster identities of the vectors after + *** clustering is completed. Each value will be `0 <= n < num_clusters`. + *** @param vector_sims An array of num_vectors elements, allocated by the + *** caller, where index i stores the similarity of vector i to its assigned + *** cluster. Passing NULL skips evaluation of these values. *** *** @attention - Assumes: num_vectors is the length of vectors. *** @attention - Assumes: num_clusters is the length of labels. @@ -528,49 +673,39 @@ unsigned int compute_k(const unsigned int n) *** *** - `O(nk + nd)` ***/ -void ca_kmeans( +int ca_kmeans( pVector* vectors, const unsigned int num_vectors, - unsigned int* labels, const unsigned int num_clusters, const unsigned int max_iter, - const double improvement_threshold) + const double min_improvement, + unsigned int* labels, + double* vector_sims) { - /** Ensure labels is clean. **/ - memset(labels, 0, num_clusters * sizeof(unsigned int)); + /** Setup stuff. **/ + bool successful = false; + unsigned int cluster_counts[num_clusters]; + memset(labels, 0u, num_vectors * sizeof(unsigned int)); /** Allocate space to store centroids and new_centroids. **/ /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ - pCentroid* centroids = (pCentroid*)nmMalloc(num_clusters * sizeof(pCentroid)); - if (centroids == NULL) - { - fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", num_clusters * sizeof(pCentroid)); - assert(false); - } - pCentroid* new_centroids = (pCentroid*)nmMalloc(num_clusters * sizeof(pCentroid)); - if (new_centroids == NULL) - { - fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", num_clusters * sizeof(pCentroid)); - assert(false); - } + const size_t centroids_size = num_clusters * sizeof(pCentroid); + pCentroid* centroids = (pCentroid*)check_ptr(nmMalloc(centroids_size)); + if (centroids == NULL) goto end; + memset(centroids, 0, centroids_size); + pCentroid* new_centroids = (pCentroid*)check_ptr(nmMalloc(centroids_size)); + if (new_centroids == NULL) goto end_free_centroids; + memset(new_centroids, 0, centroids_size); for (unsigned int i = 0u; i < num_clusters; i++) { /** Malloc each centroid. **/ - centroids[i] = (pCentroid)nmMalloc(pCentroidSize); - if (centroids[i] == NULL) - { - fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", pCentroidSize); - assert(false); - } + centroids[i] = (pCentroid)check_ptr(nmMalloc(pCentroidSize)); + if (centroids[i] == NULL) goto end_deep_free_centroids; memset(centroids[i], 0, pCentroidSize); /** Malloc each new centroid. **/ - new_centroids[i] = (pCentroid)nmMalloc(pCentroidSize); - if (new_centroids[i] == NULL) - { - fprintf(stderr, "ca_kmeans() - nmMalloc(%lu) failed.\n", pCentroidSize); - assert(false); - } + new_centroids[i] = (pCentroid)check_ptr(nmMalloc(pCentroidSize)); + if (new_centroids[i] == NULL) goto end_deep_free_centroids; memset(new_centroids[i], 0, pCentroidSize); } @@ -578,10 +713,10 @@ void ca_kmeans( srand(time(NULL)); for (unsigned int i = 0u; i < num_clusters; i++) { - // Pick a random vector. + /** Pick a random vector. **/ const pVector vector = vectors[rand() % num_vectors]; - // Sparse copy the vector to expand it into a densely allocated centroid. + /** Sparse copy the vector to expand it into a densely allocated centroid. **/ pCentroid centroid = centroids[i]; for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) { @@ -591,11 +726,17 @@ void ca_kmeans( } } + /** Setup debug visualizations. **/ + glyph_init(iter, "\n", 1, false); + glyph_init(find, ".", 64, false); + glyph_init(update_label, "!", 16, false); + glyph_init(update_centroid, ":", 8, false); + /** Main kmeans loop. **/ double old_average_cluster_size = 1.0; - unsigned int cluster_counts[num_clusters]; for (unsigned int iter = 0u; iter < max_iter; iter++) { + glyph(iter); bool changed = false; /** Reset new centroids. **/ @@ -609,6 +750,7 @@ void ca_kmeans( /** Assign each point to the nearest centroid. **/ for (unsigned int i = 0u; i < num_vectors; i++) { + glyph(find); const pVector vector = vectors[i]; double min_dist = DBL_MAX; unsigned int best_centroid_label = 0u; @@ -627,6 +769,7 @@ void ca_kmeans( /** Update label to new centroid, if necessary. **/ if (labels[i] != best_centroid_label) { + glyph(update_label); labels[i] = best_centroid_label; changed = true; } @@ -648,6 +791,7 @@ void ca_kmeans( /** Update centroids. **/ for (unsigned int i = 0u; i < num_clusters; i++) { + glyph(update_centroid); if (cluster_counts[i] == 0u) continue; pCentroid centroid = centroids[i]; const pCentroid new_centroid = new_centroids[i]; @@ -657,331 +801,187 @@ void ca_kmeans( } /** Is there enough improvement? **/ + if (min_improvement < -1) continue; /** Skip check if it will always fail. **/ const double average_cluster_size = get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters); const double improvement = old_average_cluster_size - average_cluster_size; - if (improvement < improvement_threshold) break; + if (improvement < min_improvement) break; old_average_cluster_size = average_cluster_size; } - /** Clean up. **/ - for (unsigned int i = 0u; i < num_clusters; i++) + /** Compute vector similarities, if requested. **/ + if (vector_sims != NULL) { - nmFree(centroids[i], pCentroidSize); - nmFree(new_centroids[i], pCentroidSize); - } - nmFree(centroids, num_clusters * sizeof(pCentroid)); - nmFree(new_centroids, num_clusters * sizeof(pCentroid)); - } - -pXArray ca_search( - pVector* vectors, - const unsigned int num_vectors, - const unsigned int* labels, - const double dupe_threshold) - { - /** Allocate space for dups. **/ - pXArray dups = xaNew(num_vectors); - if (dups == NULL) - { - fprintf(stderr, "ca_search() - xaNew(%u) failed.\n", num_vectors); - return NULL; + for (unsigned int i = 0u; i < num_vectors; i++) + vector_sims[i] = sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); } - unsigned int a = 0, b = 0, c = 0, d = 0; - for (unsigned int i = 0u; i < num_vectors; i++) - { - const pVector v1 = vectors[i]; - const unsigned int label = labels[i]; - for (unsigned int j = i + 1u; j < num_vectors; j++) - { - if (b++ % 100 == 0) printf("."); - if (labels[j] != label) continue; - if (c++ % 100 == 0) printf(":"); - const pVector v2 = vectors[j]; - const double similarity = sparse_similarity(v1, v2); - if (similarity > dupe_threshold) /* Dup found! */ - { - Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); - if (dup == NULL) - { - fprintf(stderr, - "ca_search() - nmMalloc(%lu) failed.\n", - sizeof(Dup) - ); - goto err_free_dups; - } - - dup->id1 = i; - dup->id2 = j; - dup->similarity = similarity; - xaAddItem(dups, (void*)dup); - if (d++ % 4 == 0) printf("!"); - } - } - if (a++ % 4 == 0) printf("\n"); - } + glyph_print("\n"); - return dups; + /** Success. **/ + successful = true; - /** Free dups. **/ - err_free_dups:; - const size_t num_dups = dups->nItems; - for (unsigned int i = 0u; i < num_dups; i++) - { - nmFree(dups->Items[i], sizeof(Dup)); - dups->Items[i] = NULL; - } - xaDeInit(dups); - return NULL; - } - -/*** Runs complete search to find duplocates if `num_vectors < MAX_COMPLETE_SEARCH` - *** and runs a search using k-means clustering on larger amounts of data. - *** - *** @param vectors Array of precomputed frequency vectors for all dataset strings. - *** @param num_vectors The number of vectors to be scanned. - *** @param dupe_threshold The similarity threshold, below which dups are ignored. - *** @returns The duplicates in pDup structs. - ***/ -pXArray ca_lightning_search(pVector* vectors, const unsigned int num_vectors, const double dupe_threshold) - { - /** Allocate space for dups. **/ - const size_t guess_size = num_vectors * 2u; - pXArray dups = xaNew(guess_size); - if (dups == NULL) + /** Clean up. **/ + end_deep_free_centroids: + for (unsigned int i = 0u; i < num_clusters; i++) { - fprintf(stderr, "ca_lightning_search() - xaNew(%lu) failed.\n", guess_size); - return NULL; + if (centroids[i] != NULL) nmFree(centroids[i], pCentroidSize); + else break; + if (new_centroids[i] != NULL) nmFree(new_centroids[i], pCentroidSize); + else break; } - /** Descide which algorithm to use. **/ - if (num_vectors <= 50 * 1000) - { /** Do a complete search. **/ - for (unsigned int i = 0u; i < num_vectors; i++) - { - const pVector v1 = vectors[i]; - for (unsigned int j = i + 1u; j < num_vectors; j++) - { - const pVector v2 = vectors[j]; - const double similarity = sparse_similarity(v1, v2); - if (similarity > dupe_threshold) // Dup found! - { - Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); - if (dup == NULL) - { - fprintf(stderr, "ca_lightning_search() - nmMalloc(%lu) failed.\n", sizeof(Dup)); - goto err_free_dups; - } - - dup->id1 = i; - dup->id2 = j; - dup->similarity = similarity; - xaAddItem(dups, (void*)dup); - } - } - } - } - else - { /** Do a k-means search. **/ - /** Define constants for the algorithm. **/ - const unsigned int max_iter = 64u; /** Hardcode value because idk. **/ - const unsigned int num_clusters = compute_k(num_vectors); - - /** Allocate static memory for finding clusters. **/ - unsigned int labels[num_vectors]; - memset(labels, 0u, sizeof(labels)); - - /** Execute kmeans clustering. **/ - ca_kmeans(vectors, num_vectors, labels, num_clusters, max_iter, 0.0002); - - /** Find duplocates in clusters. **/ - for (unsigned int i = 0u; i < num_vectors; i++) - { - const pVector v1 = vectors[i]; - const unsigned int label = labels[i]; - for (unsigned int j = i + 1u; j < num_vectors; j++) - { - if (labels[j] != label) continue; - const pVector v2 = vectors[j]; - const double similarity = sparse_similarity(v1, v2); - if (similarity > dupe_threshold) /* Dup found! */ - { - Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); - if (dup == NULL) - { - fprintf(stderr, - "ca_lightning_search() - nmMalloc(%lu) failed.\n", - sizeof(Dup) - ); - goto err_free_dups; - } - - dup->id1 = i; - dup->id2 = j; - dup->similarity = similarity; - xaAddItem(dups, (void*)dup); - } - } - } - } + // end_free_new_centroids: + nmFree(new_centroids, num_clusters * sizeof(pCentroid)); - /** Done **/ - return dups; + end_free_centroids: + nmFree(centroids, num_clusters * sizeof(pCentroid)); - /** Free dups. **/ - err_free_dups:; - const size_t num_dups = dups->nItems; - for (unsigned int i = 0u; i < num_dups; i++) - { - nmFree(dups->Items[i], sizeof(Dup)); - dups->Items[i] = NULL; - } - xaDeInit(dups); - return NULL; + end: + return (successful) ? 0 : -1; } -/*** Computes Levenshtein distance between two strings. - *** - *** @param str1 The first string. - *** @param str2 The second string. - *** @param length1 The length of the first string. - *** @param length1 The length of the first string. - *** - *** @attention - Tip: Pass 0 for the length of either string to infer it - *** using the null terminating character. Thus, strings with no null - *** terminator are supported if you pass explicit lengths. - *** - *** Complexity: O(length1 * length2). - *** - *** @see centrallix-sysdoc/string_comparison.md +/*** Finds the data that is the most similar to the target and returns + *** it if the similarity meets the threshold. + *** + *** @param target The target data to compare to the rest of the data. + *** @param data The rest of the data, compared against the target to + *** find the data that is the most similar. + *** @param num_data The number of elements in data. Specify 0 to detect + *** length on a null terminated array of data. + *** @param similarity A function which takes two data items of the type + *** of the data param and returns their similarity. + *** @param threshold The minimum similarity threshold. If the most similar + *** data does not meet this threshold, the funciton returns NULL. + *** @returns A pointer to the most similar piece of data found in the data + *** array, or NULL if the most similar data did not meet the threshold. ***/ -unsigned int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) +void* ca_most_similar( + void* target, + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double threshold) { - /*** lev_matrix: - *** For all i and j, d[i][j] will hold the Levenshtein distance between - *** the first i characters of s and the first j characters of t. - *** - *** As they say, no dynamic programming algorithm is complete without a - *** matrix that you fill out and it has the answer in the final location. - ***/ - const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; - const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; - unsigned int lev_matrix[str1_len + 1][str2_len + 1]; - - /*** Base case #0: - *** Transforming an empty string into an empty string has 0 cost. - ***/ - lev_matrix[0][0] = 0u; - - /*** Base case #1: - *** Any source prefixe can be transformed into an empty string by - *** dropping each character. - ***/ - for (unsigned int i = 1u; i <= str1_len; i++) - lev_matrix[i][0] = i; - - /*** Base case #2: - *** Any target prefixes can be transformed into an empty string by - *** inserting each character. - ***/ - for (unsigned int j = 1u; j <= str2_len; j++) - lev_matrix[0][j] = j; - - /** General Case **/ - for (unsigned int i = 1u; i <= str1_len; i++) + void* most_similar = NULL; + double best_sim = -INFINITY; + for (unsigned int i = 0u; (num_data == 0u) ? (data[i] != NULL) : (i < num_data); i++) { - for (unsigned int j = 1u; j <= str2_len; j++) + const double sim = similarity(target, data[i]); + if (sim > best_sim && sim > threshold) { - /** Equal characters need no changes. **/ - if (str1[i - 1] == str2[j - 1]) - lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; - - /*** We need to make a change, so use the opereration with the - *** lowest cost out of delete, insert, replace, or swap. - ***/ - else - { - unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; - unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; - unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; - - /** If a swap is possible, calculate the cost. **/ - bool can_swap = ( - i > 1 && j > 1 && - str1[i - 1] == str2[j - 2] && - str1[i - 2] == str2[j - 1] - ); - unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; - - // Find the best operation. - lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); - } + most_similar = data[i]; + best_sim = sim; } } - - return lev_matrix[str1_len][str2_len]; + return most_similar; } -/*** Runs complete search to find duplocates in phone numbers using the - *** levenshtein min edit distance algorithm. - *** - *** @param dataset An array of characters for all dataset strings. - *** @param dataset_size The number of phone numbers to be scanned. - *** @param dupe_threshold The similarity threshold, below which dups are ignored. - *** @returns The duplicates in pDup structs. + +/*** Runs a sliding search over the povided data, comparing each element to + *** the following `window_size` elements, invoking the passed comparison + *** function just under `window_size * num_data` times. If any comparison + *** yeilds a similarity greater than the threshold, it is stored in the + *** xArray returned by this function. + *** + *** @param data The data to be searched. + *** @param num_data The number of data items in data. + *** @param window_size The size of the sliding window used for the search. + *** @param similarity A function which takes two data items of the type of + *** the data param and returns their similarity. + *** @param threshold The minimum threshold required for a duplocate to be + *** included in the returned xArray. + *** @param maybe_dups A pointer to an xArray in which dups should be found. + *** Pass NULL to allocate a new one. + *** @returns An xArray holding all of the duplocates found. If maybe_dups is + *** not NULL, this will be that xArray, to allow for chaining. ***/ -pXArray ca_phone_search(char dataset[][10u], const unsigned int dataset_size, const double dupe_threshold) +pXArray ca_sliding_search( + void** data, + const unsigned int num_data, + const unsigned int window_size, + const double (*similarity)(void*, void*), + const double threshold, + pXArray dups) { - /** Allocate space for dups. **/ - const size_t guess_size = dataset_size * 2u; - pXArray dups = xaNew(guess_size); - if (dups == NULL) + /** Allocate space for dups (if necessary). **/ + const bool allocate_dups = (dups == NULL); + if (allocate_dups) { - fprintf(stderr, "ca_phone_search() - xaNew(%lu) failed.\n", guess_size); - return NULL; + /** Guess that we will need space for num_data * 2 dups. **/ + const int guess_size = num_data * 2; + dups = check_ptr(xaNew(guess_size)); + if (dups == NULL) goto err; } + const int num_starting_dups = dups->nItems; - /** Search for dups using edit distance. **/ - for (unsigned int i = 0u; i < dataset_size; i++) - { - const char* v1 = dataset[i]; - for (unsigned int j = i + 1u; j < dataset_size; j++) + /** Setup debug visualizations. **/ + glyph_init(outer, " ", 4, true); + glyph_init(inner, ".", 128, false); + glyph_init(find, "!", 32, false); + + /** Search for dups. **/ + for (unsigned int i = 0u; i < num_data; i++) + { + glyph(outer); + const unsigned int window_start = i + 1u; + const unsigned int window_end = min(i + window_size, num_data); + for (unsigned int j = window_start; j < window_end; j++) { - const char* v2 = dataset[j]; - const unsigned int dist = ca_edit_dist(v1, v2, 10u, 10u); - const double similarity = (double)dist / 10.0; - if (similarity > dupe_threshold) /* Dup found! */ + glyph(inner); + const double sim = similarity(data[i], data[j]); + if (sim > threshold) /* Dup found! */ { - Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); - if (dup == NULL) - { - fprintf(stderr, "ca_phone_search() - nmMalloc(%lu) failed.\n", sizeof(Dup)); - - /** Free data before returning. **/ - const size_t num_dups = dups->nItems; - for (unsigned int i = 0u; i < num_dups; i++) - { - void* dup = dups->Items[i]; - nmFree(dup, sizeof(Dup)); - } - xaDeInit(dups); - return NULL; - } - + glyph(find); + Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); + if (dup == NULL) goto err_free_dups; dup->id1 = i; dup->id2 = j; - dup->similarity = similarity; - xaAddItem(dups, (void*)dup); + dup->similarity = sim; + if (!check_neg(xaAddItem(dups, (void*)dup))) goto err_free_dups; } } } + glyph_print("\n"); + /** Success. **/ return dups; + + /** Error cleanup. **/ + + err_free_dups: + /** Free the dups we added to the XArray. */ + while (dups->nItems > num_starting_dups) + nmFree(dups->Items[dups->nItems--], sizeof(Dup)); + if (allocate_dups) check(xaDeInit(dups)); /* Failure ignored. */ + + err: + return NULL; } -void ca_init() +/*** Runs a complete search over the povided data, comparing each element to + *** each other element, invoking the passed comparison function `num_data^2` + *** times. If any comparison yeilds a similarity greater than the threshold, + *** it is stored in the xArray returned by this function. + *** + *** @param data The data to be searched. + *** @param num_data The number of data items in data. + *** @param similarity A function which takes two data items of the type of + *** the data param and returns their similarity. + *** @param threshold The minimum threshold required for a duplocate to be + *** included in the returned xArray. + *** @param maybe_dups A pointer to an xArray in which dups should be found. + *** Pass NULL to allocate a new one. + *** @returns An xArray holding all of the duplocates found. If maybe_dups is + *** not NULL, this will be that xArray, to allow for chaining. + ***/ +pXArray ca_complete_search( + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double threshold, + pXArray dups) { - nmRegister(sizeof(Dup), "Dup"); + return ca_sliding_search(data, num_data, num_data, similarity, threshold, dups); } /** Scope cleanup. **/ diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index ec1d87bcf..450c16593 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -110,10 +110,13 @@ int util_detect_num_threads(void) ***/ #define USE_METRIC false #define nUnits 6u -static char* units_cs[nUnits] = {"bytes", "KiB", "MiB", "GiB", "TiB", "PiB"}; -static char* units_metric[nUnits] = {"bytes", "KB", "MB", "GB", "TB", "PB"}; +static char* units_cs[nUnits] = {"bytes", "KiB", "MiB", "GiB"}; +static char* units_metric[nUnits] = {"bytes", "KB", "MB", "GB"}; + /*** Displays a size in bytes using the largest unit where the result would be - *** at least 1.0. + *** at least 1.0. Note that units larger than GB and GiB are not supported + *** because the largest possible unsigned int is 4,294,967,295, which is + *** exactly 4 GiB (or approximately 4.29 GB). *** *** @param buf The buffer to which new text will be written, using snprintf(). *** @param buf_size The amount of space in the buffer, passed to snprintf(). @@ -228,16 +231,14 @@ void timer_free(pTimer timer) /*** Function for failing on error, assuming the error came from a library or *** system function call, so that the error buffer is set to a valid value. ***/ -void fail(const char* function_name, int code) +void print_diagnostics(int code, const char* function_name, const char* file_name, const int line_number) { - /** Create the most descriptive error message we can. **/ + /** Create a descriptive error message. **/ char error_buf[BUFSIZ]; - snprintf(error_buf, sizeof(error_buf), "kmeans.c: Fail - %s", function_name); - if (errno != 0) perror(error_buf); - else if (code != 0) fprintf(stderr, "%s (error code %d)\n", error_buf, code); - else fprintf(stderr, "%s", error_buf); + snprintf(error_buf, sizeof(error_buf), "%s:%d: %s failed", file_name, line_number, function_name); - /** Throw error for easier locating in a debugger. **/ - fprintf(stderr, "Program will now crash.\n"); - raise(SIGSEGV); + /** Print it with as much info as we can reasonably find. **/ + if (errno != 0) perror(error_buf); + else if (code != 0) fprintf(stderr, "%s (error code %d).\n", error_buf, code); + else fprintf(stderr, "%s.\n", error_buf); } diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index a97d7f9ba..9f11c1636 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -10,7 +10,7 @@ file_name "system/cluster" ?style : StyleObj // idk where to find docs for this. } // Access with :parameters:name. Accessing dynamic data (e.g. parameters) - // should be managed within a runserver() call. + // should be done within a runserver() call. ... source : DataSourcePath @@ -18,12 +18,14 @@ file_name "system/cluster" cluster_name "cluster/cluster" { - algorithm : "none" | "sliding-window" | "k-means" - | "k-means++" | "k-medoids" |"db-scan" // dbscan not implemented + algorithm : "none" | "sliding-window" | "k-means" // Implemented + | "k-means++" | "k-medoids" | "db-scan" // Not implemented similarity_measure : "cosine" | "levenshtein" // levenshtein not implemented. num_clusters : uint > 1 // (probably a parameter) ?min_improvement : double && 0.0 < x < 1.0 | "none" // default: 0.0001 ?max_iterations : uint // default: 64 + ?window_size : uint > 0 // required for algorithm = sliding_window. + ?overlap_size : double && 0.0 <= x <= 1.0 // default: 0.0, only allowed for algorithm = k-means | k-means++ | k-medoids, not implemented // Not implemented sub_cluster_name "cluster/cluster" @@ -37,26 +39,21 @@ file_name "system/cluster" { source : string ⊂ [cluster_name, ...] threshold : double && 0.0 < x < 1.0 // optimization. - similarity_measure : "cosine" | "levenshtein" // levenshtein not implemented. + similarity_measure : "cosine" | "levenshtein" } ... } // Output schema -- /{arbitrary uint} +- /cluster_name ? /sub_cluster_name - ? /{arbitrary uint} + ? ... + - /{query} + - /items : StringVec // The data points in the cluster. ... - - /average_similarity : double && 0.0 < x < 1.0 - - /size = average_similarity - - /{arbitrary uint} - - /val : string // The value of the data point. - - /label : uint < num_clusters // id of the cluster to which this data point belongs. - - /sim : double && 0.0 < x <= threshold // Similarity to cluster centroid. -... /search_name -- /{arbitrary uint} +- /{query} - /id1 : uint < sizeof(source/attr_name) // The id of the first data point. - /id2 : uint < sizeof(source/attr_name) // The id of the second data point. - /val1 : string // The value of the first data point. @@ -71,7 +68,7 @@ file_name "system/cluster" // thing, because that feels like a higher-level responsibility. // Invoke file: -// select * from /file.cl +// select * from /file.cluster // Driver-authoring.md // Comprehend stparse.c (lib vs. centrallix?) diff --git a/centrallix-os/file.cluster b/centrallix-os/file.cluster index 929efdd03..078a39fcc 100644 --- a/centrallix-os/file.cluster +++ b/centrallix-os/file.cluster @@ -23,11 +23,14 @@ file_name "system/cluster" source = "/apps/kardia/data/Kardia_DB/p_partner/rows"; attr_name = p_given_name; // runserver(:parameters:str) + // Multiple data sources when? + // Clustering object specifies properties for clustering. kmeans_cluster "cluster/cluster" { algorithm = "k-means"; similarity_measure = "cosine"; + // window_size = 16; num_clusters = runserver(:parameters:k); min_improvement = 0.0001; max_iterations = 48; diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md new file mode 100644 index 000000000..f466a057c --- /dev/null +++ b/centrallix-sysdoc/string_similarity.md @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# String Similarity +The following sections discuss the approaches to calculating similarity between two strings which are implemented in the `clusters.c` library. This library can be incuded using `#include "clusters.h"` in centrallix-lib and `#include "cxlib/clusters.h"` in centrallix. + + +## Table of Contents +- [String Comparison](#string-comparison) + - [Table of Contents](#table-of-contents) + - [Cosine Similarity](#cosine-similarity) + - [Character Sets](#character-sets) + - [Character Pair Hashing](#character-pair-hashing) + - [String Vectors](#string-vectors) + - [Sparse Vectors](#sparse-vectors) + - [Computing Similarity](#computing-similarity) + - [Levenshtein Similarity](#levenshtein-similarity) + - [Clustering](#clustering) + - [K-means Clustering](#k-means-clustering) + - [K-means++ Clustering](#k-means-clustering-1) + - [K-medoids Clustering](#k-medoids-clustering) + - [DBScan Clustering](#db-scan) + - [Sliding Clusters](#sliding-clusters) + - [Future Implementation](#future-implementation) + - [K-means Fuzzy Clustering](#k-means-fuzzy-clusterings) + - [Implement Missing Algorithms](#implement-missing-algorithms) + + +## Cosine Similarity +The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. Conceptually, it's like finding the _angle_ between two vectors. To get these vectors, we use the relative frequency of character pairs within each string. To reduce memory cost and speed up computation, we store them in a special sparcely allocated form, described below. + +### Character Sets +Cosine compare currnetly uses the following character sets. These can be extended or modified later, if necessary. +```c +const char ALLOW_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +const char CHAR_SET[] = "`abcdefghijklmnopqrstuvwxyz0123456789"; +const char SIGNIFICANT_SET[] = "`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +const char IGNORE_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}"; +const char BOUNDARY_CHAR = ('a' - 1); // aka. '`' +``` +- `ALLOW_SET` represents all characters which can be passed to a similarity detection algorithm. Passing other characters may cause warnings and errors, undefined or unintended behavior, and even security concerns. +- `CHAR_SET` represents all of the characters that will be uniquely considered during the calculation of similarity. Currently, this is all lowercase letters and numbers. +- `SIGNIFICANT_SET` represents all of the characters that are significant for the purposes of similarity. For example, the upercase letters are significant because they are considered identical to lowercase letters. Thus, they are included in the `SIGNIFICANT_SET`, but not in the `CHAR_SET`. +- `IGNORE_SET` represents characters which, while allowed to be passed to a similarity algorithm, will be ignored. For example, the strings "Ya!!" and "Ya..." will be considered identical. +- The `BOUNDARY_CHAR` is a special character which is conceptually added to the start and end of any string to be checked. + - This allows for pairs that functionally include only the first and last character. + - This character appears to have been selected to be one before the first character in `CHAR_SET` (thus convention dictates that it be written `'a' - 1` to indicate this), although it's unknown if that's the main or only reason. + - If `clusters.h` is included, it can be accessed using the `CA_BOUNDARY_CHAR` macro. + +### Character Pair Hashing +Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296` possible character pairs. If the number of characters in the `CHAR_SET` ever needed to be expanded - for example, to include all UTF-8 characters - this number would quickly explode exponentially to utterly infeasible proportions. Thus, a hashing algorithm is employed to hash each character pair down to a more reasonable number of dimensions (which can be accessed with the `CA_NUM_DIMS` macro). + +### String Vectors +Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicty, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies to based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). + +Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. + +### Sparse Vectors +As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. + +**Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. + +**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. + +### Computing Similarity +Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. + + +## Levenshtein Similarity +The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using levenshtein edit distance. + +The levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. + + +## Clustering +When searching for similar strings in a large amount of data (for example, `1,000,000` strings), comparing every string to every other string can be very computationally expensive. To speed up this process, it is helpful to _cluster_ similar strings together, then only compare strings within similar clusters. This sacrifices some accuracy to allow large amounts of data to be searched and compared in a feasable amount of time. + +### K-means Clustering +When clustering data using the [k-means](https://en.wikipedia.org/wiki/K-means_clustering) algorithm, data is divided into a predefined number of clusters with the goal of maximizing the average similarity of datapoints within any given cluster. To quickly summarize the algorithm: +1. Randomly select `k` datapoints to be the initial centroids of each cluster. +2. For each datapoint, find the centroid it is most similar to, and assign it to that clustser. +3. For each cluster, find the new centroid by averaging all datapoints in the cluster. +4. Repeat steps 2 and 3 until the clusters stabilize (i.e. no datapoint changes clusters). + +The implementation used in `clusters.c` also allows the programmer to specify a maximum number of iterations (called `max_iter` in the code) to prevent this process from running forever. Additionally, successive iterations can give diminishing results or even produce clusters that are slightly worse. To improve performance, the programmer can also specify a minimum improvement threshold (called `min_improvement`). Clusters must become more similar by at least this amount each iteration, otherwise the algorithm ends, even if the maximum number of iterations has not yet been reached. + +The `ca_kmeans()` function can be invoked using [the cosine comparison string vectors](#string-vectors) (see above) to cluster them into similar clusters. + +### K-means++ Clustering +**Not yet implemented** +This method is largely identical to k-means, except that [k-means++](https://en.wikipedia.org/wiki/K-means%2B%2B) assignes the initial centroids using an aproximate algorithm designed to avoid some of the poor clusterings possible with random assignment. + +### K-medoids Clustering +**Not yet implemented** +This method is also very similar to k-means, except that [k-medoids](https://en.wikipedia.org/wiki/K-medoids) places an aditional requirement that all centroids be points in the data. This would theoretically allow for other similarity measures (such as levenshtein edit distance) to be used for clustering instead of only cosine compare. + +### DB-Scan +**Proposed, not yet implemented or documented** + +### Sliding Clusters +A far more basic method of "clustering" is to simply sort all data alphabetically, then, instead of comparing each string to all other strings, it can be compared to only the next `n` strings. Of course, differences near the start of a string (for example, "fox" vs. "box") will cause those strings to sort far away from each other, leading them to be completely missed. + +Sorting using a similarity measure, such as `ca_cos_compare()` or `ca_lev_compare()` would resolve this issue. However, these comparison functions do not meet the transitivity requirement for sorting, which is that `(A < B) & (B < C) -> (A < C)`. For example, "car" is similar to "boxcar", which is also similar to "box". However, "car" and "box" are not similar at all. + +Additionally, sorting by the cosine vectors (similarly to how we cluster by them when using k-means) was proposed, but further investigation showed that this was also not possible. + +For problems where a sorting algorithm exists which can mitigate the above issues, this solution may prove very promissing. However, so far we have not found such a problem, so the other clustering algorithms tend to out perform Sliding Clusters. + + +## Future Implementation + +### K-means Fuzzy Clustering +One of the biggest downsides with k-means is that it creates very arbitrary boundaries between clusters. Elements on either side of these boundaries may be highly similar, but if comparisons only occur within a cluster, these similar entries will be missed. The problem becomes more extreme as a higher k value (more clusters) is used, creating more arbitrary boundaries. This drawback is probably the main reason that clustering sacrifices some accuracy over searching every element. + +Running the entire search multiple types may allow some of these to be found because the initial cluster locations are random. This approach is partially implemented for duplocate searching because the algorithm runs nightly anyway, so a simple up-sert (**UP**date existing entries; in**SERT** new entries) slightly reduces this problem. However, this solution is obviously far from ideal. + +If the clustering could be expanded with an additional step that makes clusters larger, adding elements from other clusters to them, this might effectively mitigate the issue. It may also allow developers to use larger numbers of clusters, improving performance as well as accuracy. Further research is needed to verify the effectiveness of this approach before an implementation is written. + +### Implement Missing Algorithms +Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-medoids-clustering), and [DBScan](#db-scan)) above are proposed but lack an implementation. They may be effective and useful, however, to reduce development time, they have not yet been implemented. diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index 045d57f85..54d4c988a 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -192,6 +192,7 @@ typedef struct _OSD int (*Commit)(); int (*GetQueryCoverageMask)(); int (*GetQueryIdentityPath)(); + int (*Unregister)(); } ObjDriver, *pObjDriver; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index f56cca5de..4acfc8579 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -29,7 +29,6 @@ /* Description: Cluster object driver. */ /************************************************************************/ -#include #include #include #include @@ -73,7 +72,7 @@ ***/ /** Pure Laziness **/ -#define ENABLE_TPRINTF +// #define ENABLE_TPRINTF /** Debugging **/ #ifndef ENABLE_TPRINTF @@ -101,8 +100,6 @@ void void_func() {} *** an error cascade which may be useful to the user since a stack trace is *** not readily available. *** - *** @todo I think this should be moved to somewhere else. - *** *** @param clr Whether to clear the current error stack. As a rule of thumb, *** if you are the first one to detec the error, clear the stack so that *** other unrelated messages are not shown. If you are detecting an error @@ -119,7 +116,7 @@ void void_func() {} void mssErrorf(int clr, char* module, const char* format, ...) { /** Prevent interlacing with stdout flushing at a weird time. **/ - check(fflush(stdout)); + check(fflush(stdout)); /* Failure ignored. */ /** Insert convenient newline before error stack begins. **/ if (clr == 1) fprintf(stderr, "\n"); @@ -139,7 +136,7 @@ void mssErrorf(int clr, char* module, const char* format, ...) return; } if (num_chars > BUFSIZ) - fprintf(stderr, "WARNING: Error truncated (length %d > buffer size %d).\n", num_chars, BUFSIZ); + fprintf(stderr, "Warning: Error truncated (length %d > buffer size %d).\n", num_chars, BUFSIZ); /** Print the error. **/ const int ret = mssError(clr, module, "%s", buf); @@ -346,13 +343,13 @@ typedef unsigned char TargetType; #define TARGET_SEARCH_ENTRY (TargetType)5u /** Attribute name lists by TargetType. **/ -#define nATTR_ROOT 2u -char* const ATTR_ROOT[nATTR_ROOT] = { +#define END_OF_ARRAY NULL +char* const ATTR_ROOT[] = { "source", "attr_name", + END_OF_ARRAY, }; -#define nATTR_CLUSTER 7u -char* const ATTR_CLUSTER[nATTR_CLUSTER] = +char* const ATTR_CLUSTER[] = { "algorithm", "similarity_measure", @@ -361,199 +358,300 @@ char* const ATTR_CLUSTER[nATTR_CLUSTER] = "max_iterations", "date_created", "date_computed", + END_OF_ARRAY, }; -#define nATTR_SEARCH 5u -char* const ATTR_SEARCH[nATTR_SEARCH] = +char* const ATTR_SEARCH[] = { "source", "threshold", "similarity_measure", "date_created", "date_computed", + END_OF_ARRAY, }; -#define nATTR_CLUSTER_ENTRY 2u -char* const ATTR_CLUSTER_ENTRY[nATTR_CLUSTER_ENTRY] = +char* const ATTR_CLUSTER_ENTRY[] = { - "val", - "sim", + "items", + END_OF_ARRAY, }; -#define nATTR_SEARCH_ENTRY 3u -char* const ATTR_SEARCH_ENTRY[nATTR_SEARCH_ENTRY] = +char* const ATTR_SEARCH_ENTRY[] = { "val1", "val2", "sim", + END_OF_ARRAY, }; -#define END_OF_ATTRIBUTES NULL - /** Method name list. **/ -#define nMETHOD_NAME 2u -char* const METHOD_NAME[nMETHOD_NAME] = +char* const METHOD_NAME[] = { "cache", + END_OF_ARRAY, }; -#define END_OF_METHODS END_OF_ATTRIBUTES /** ================ Struct Declarations ================ **/ /** ANCHOR[id=structs] **/ -/** Represents the data source which may have data already fetched. **/ +/*** Represents the data source which may have data already fetched. + *** + *** Memory Stats: + *** - Padding: 4 bytes + *** - Total size: 72 bytes + *** + *** @skip --> Attribute Data. + *** @param Name The source name, specified in the .cluster file. + *** @param Key The key associated with this object in the SourceDataCache. + *** @param SourcePath The path to the data source from which to retrieve data. + *** @param AttrName The name of the attribute to get from the data source. + *** + *** @skip --> Computed data. + *** @param Strings The data strings to be clustered and searched, or NULL if + *** they have not been fetched from the source. + *** @param Vectors The cosine comparison vectors from the fetched data, or + *** NULL if they haven't been computed. Note that vectors are no longer + *** needed once all clusters and searches have been computed, so they are + *** automatically freed in that case to save memory. + *** @param nVectors The number of vectors and data strings. + *** + *** @skip --> Time. + *** @param DateCreated The date and time that this object was created and initialized. + *** @param DateComputed The date and time that the Labels field was computed. + ***/ typedef struct _SOURCE { - /** Top level attributes (specified in the .cluster file). **/ - char* Name; /* The node name, specified in the .cluster file. - * Warning: Some code makes the assumption that this - * is the first field in the struct. - */ - char* Key; /* The key associated with this object in the global SourceCache. */ - char* SourcePath; /* The path to the data source from which to retrieve data. */ - char* AttrName; /* The name of the attribute to get from the data source. */ - - /** Computed data. **/ - char** Data; /* The data strings to be clustered and searched, or NULL if they - * have not been fetched from the source. - */ - pVector* Vectors; /* The cosine comparison vectors from the fetched data, or NULL if - * they haven't been computed. Note that vectors are no longer - * needed once all clusters and searches have been computed, so - * they are automatically freed in that case to save memory. - */ - unsigned int nVectors; /* The number of vectors and data strings. Note: This is not - * set to 0 if the vector array is freed, this case should be - * checked separately. - */ - - /** Time. **/ - DateTime DateCreated; /* The date and time that this object was created and initialized. */ - DateTime DateComputed; /* The date and time that the Data and Vectors fields were computed. */ - } SourceData, *pSourceData; - -/** Data for each cluster. **/ + char* Name; + char* Key; + char* SourcePath; + char* AttrName; + char** Strings; + pVector* Vectors; + unsigned int nVectors; + DateTime DateCreated; + DateTime DateComputed; + } + SourceData, *pSourceData; + + +/*** Computed data for a single cluster. + *** + *** Memory Stats: + *** - Padding: 4 bytes + *** - Total size: 24 bytes + *** + *** @param Size The number of items in the cluster. + *** @param Strings The string values of each item. + *** @param Vectors The cosine vectors for each item. + ***/ +typedef struct + { + unsigned int Size; + char** Strings; + pVector* Vectors; + } + Cluster, *pCluster; + + +/*** Data for each cluster. Only attribute data is checked for caching. + *** + *** Memory Stats: + *** - Padding: 2 bytes + *** - Total size: 96 bytes + *** + *** @skip --> Attribute Data. + *** @param Name The cluster name, specified in the .cluster file. + *** @param Key The key associated with this object in the ClusterDataCache. + *** @param ClusterAlgorithm The clustering algorithm to be used. + *** @param SimilarityMeasure The similarity measure used to compare items. + *** @param nClusters The number of clusters. 1 if algorithm = none. + *** @param MinImprovement The minimum amount of improvement that must be met + *** each clustering iteration. If there is less improvement, the algorithm + *** will stop. The "max" in a .cluster file is represented by -inf. + *** @param MaxIterations The maximum number of iterations that a clustering + *** algorithm can run for. Note: Sliding window uses this field to store + *** the window_size. + *** + *** @skip --> Relationship Data. + *** @param nSubClusters The number of subclusters of this cluster. + *** @param SubClusters A pClusterData array, NULL if nSubClusters == 0. + *** @param Parent This cluster's parent. NULL if it is not a subcluster. + *** @param SourceData Pointer to the source data that this cluster uses. + *** + *** @skip --> Computed data. + *** @param Clusters An array of length num_clusters, NULL if the clusters + *** have not yet been computed. + *** @param Sims An array of num_vectors elements, where index i stores the + *** similarity of vector i to its assigned cluster. This field is NULL + *** if the clusters have not yet been computed. + *** + *** @skip --> Time. + *** @param DateCreated The date and time that this object was created and initialized. + *** @param DateComputed The date and time that the Labels field was computed. + ***/ typedef struct _CLUSTER { - /** Attribute Data. **/ - char* Name; /* The cluster name, specified in the .cluster file. - * Warning: Some code makes the assumption that this - * is the first field in the struct. - */ - char* Key; /* The key associated with this object in the global ClusterCache. */ - ClusterAlgorithm ClusterAlgorithm; /* The clustering algorithm to be used. */ - SimilarityMeasure SimilarityMeasure; /* The similarity measurse to be used when clustering. */ - unsigned int NumClusters; /* The number of clusters. 1 if algorithm = none. */ - double MinImprovement; /* The minimum amount of improvement that must be met each - * clustering iteration. If there is less improvement, the - * algorithm will stop. Specifying "max" in the .cluster - * file should be represented by a value of -inf. - */ - unsigned int MaxIterations; /* The maximum number of iterations to run clustering. */ - - /** Other data (ignored by caching). **/ - unsigned int nSubClusters; /* The number of subclusters of this cluster. */ - struct _CLUSTER** SubClusters; /* A pClusterData array, NULL if nSubClusters == 0. */ - struct _CLUSTER* Parent; /* This cluster's parent. NULL if it is not a subcluster. */ - pSourceData SourceData; /* Pointer to the source data that this cluster uses. */ - - /** Computed data. **/ - unsigned int* Labels; /* An array with one element for each vector in the data - * (aka. DriverData->nVectors). For vector i, Labels[i] is - * the ID of the cluster to which that data is assigned. - * NULL if the cluster has not been computed. */ - - /** Time. **/ - DateTime DateCreated; /* The date and time that this object was created and initialized. */ - DateTime DateComputed; /* The date and time that the Labels field was computed. */ + char* Name; + char* Key; + ClusterAlgorithm ClusterAlgorithm; + SimilarityMeasure SimilarityMeasure; + unsigned int nClusters; + double MinImprovement; + unsigned int MaxIterations; + unsigned int nSubClusters; + struct _CLUSTER** SubClusters; + struct _CLUSTER* Parent; + pSourceData SourceData; + Cluster* Clusters; + double* Sims; + DateTime DateCreated; + DateTime DateComputed; } ClusterData, *pClusterData; -/** Data for each search. **/ + +/*** Data for each search. + *** + *** Memory Stats: + *** - Padding: 3 bytes + *** - Total size: 64 bytes + *** + *** @skip --> Attribute Data. + *** @param Name The search name, specified in the .cluster file. + *** @param Key The key associated with this object in the SearchDataCache. + *** @param Source The cluster from which this search is to be derived. + *** @param SimilarityMeasure The similarity measure used to compare items. + *** @param Threshold The minimum similarity threshold for elements to be + *** included in the results of the search. + *** + *** @skip --> Computed data. + *** @param Dups An array holding the dups found by the search, or NULL if the + *** search has not been computed. + *** @param nDups The number of dups found. + *** + *** @skip --> Time. + *** @param DateCreated The date and time that this object was created and initialized. + *** @param DateComputed The date and time that the Dups field was computed. + ***/ typedef struct _SEARCH { - char* Name; /* The search name, specified in the .cluster file. - * Warning: Some code makes the assumption that this - * is the first field in the struct. - */ - char* Key; /* The key associated with this object in the global SearchCache. */ - pClusterData Source; /* The cluster from which this search is to be derived. */ - double Threshold; /* The minimum similarity threshold for elements to be - * included in the results of the search. - */ - SimilarityMeasure SimilarityMeasure; /* The similarity measure used to compare items. */ - - /** Computed data. **/ - pDup* Dups; /* An array holding the dups found by the search, or NULL - * if the search has not been computed. - */ - unsigned int nDups; /* The number of dups found. */ - - /** Time. **/ - DateTime DateCreated; /* The date and time that this object was created and initialized. */ - DateTime DateComputed; /* The date and time that the Dups field was computed. */ + char* Name; + char* Key; + pClusterData Source; + double Threshold; + pDup* Dups; + unsigned int nDups; + SimilarityMeasure SimilarityMeasure; + DateTime DateCreated; + DateTime DateComputed; } SearchData, *pSearchData; + /*** Node instance data. - *** When a .cluster file is openned, there will be only one node for that + *** + *** Memory Stats: + *** - Padding: 4 bytes + *** - Total size: 64 bytes + *** + *** @note When a .cluster file is openned, there will be only one node for that *** file. However, in the course of the query, many driver instance structs *** may be created by functions like clusterQueryFetch(), and closed by the *** object system using clusterClose(). + *** + *** @param SourceData Data from the provided source. + *** @param Params A pParam array storing the params in the .cluster file. + *** @param nParams The number of specified params. + *** @param ParamList Functions as a "scope" for resolving values during parsing. + *** @param ClusterDatas A pCluster array storing the clusters in the .cluster file. + *** Will be NULL if nClusters = 0. + *** @param nClusterDatas The number of specified clusters. + *** @param SearchDatas A SearchData array storing the searches in the .cluster file. + *** @param nSearches The number of specified searches. + *** @param nSearchDatas The parent object used to open this NodeData instance. ***/ typedef struct _NODE { - /** Substructures. **/ - pSourceData SourceData; /* Data from the provided source. */ - pParam* Params; /* A pParam array storing the params in the .cluster file. */ - unsigned int nParams; /* The number of specified params. */ - pParamObjects ParamList; /* Functions as a "scope" for resolving values during parsing. */ - pClusterData* Clusters; /* A pCluster array storing the clusters in the .cluster file. - * Will be NULL if nClusters = 0. - */ - unsigned int nClusters; /* The number of specified clusters. */ - pSearchData* Searches; /* A SearchData array storing the searches in the .cluster file. */ - unsigned int nSearches; /* The number of specified searches. */ - - /** Other stuff, idk why it's here. **/ - pSnNode Node; - pObject Obj; + pObject Parent; + pParam* Params; + pParamObjects ParamList; + pSourceData SourceData; + pClusterData* ClusterDatas; + pSearchData* SearchDatas; + unsigned int nParams; + unsigned int nClusterDatas; + unsigned int nSearchDatas; } NodeData, *pNodeData; -/** Driver instance data. **/ -/*** Similar to a pointer to specific, computed data in the pNodeData struct. - *** If target type is the root, a cluster, or a search, no data is guarnteed - *** to be computed yet. These three types can be returned from clusterOpen(). - *** To target a cluster entry or search entry, fetch a driver targetting a - *** cluster or search (respectively). These target types ensure that the data - *** has been computed, so the GetAttr functions do not need to ensure this. +/*** Driver instance data. + *** + *** Memory Stats: + *** - Padding: 1 bytes + *** - Total size: 24 bytes + *** + *** This struct can be thought of like a "pointer" to specific data accessible + *** through the stored pNodeData struct. This struct also communicates whether + *** that data is guaranteed to have been computed. + *** + *** For example, if target type is the root, a cluster, or a search, no data + *** is guaranteed to be computed. These three types can be returned from + *** clusterOpen(), based on the provided path. + *** + *** Alternatively, a cluster entry or search entry can be targetted by calling + *** fetch on a query pointing to a driver instance that targets a cluster or + *** search (respectively). These two entry target types ensure that the data + *** they indicate has been computed, so the GetAttrType() and GetAttrValue() + *** functions do not need to check this repeatedly each time they are called. + *** + *** @param NodeData The associated node data struct. There can be many driver + *** instances pointing to one NodeData at a time, but each driver instance + *** always points to singular NodeData struct. + *** @param TargetType The type of data targetted (see above). + *** @param TargetData If target type is: + *** ```csv + *** Root: A pointer to the SourceData struct. + *** Cluster or ClusterEntry: A pointer to the targetted cluster. + *** Search or SearchEntry: A pointer to the targetted search. + *** ``` + *** @param TargetAttrIndex An index into an attribute list (for GetNextAttr()). + *** @param TargetMethodIndex An index into an method list (for GetNextMethod()). ***/ typedef struct _DRIVER { - pNodeData NodeData; /* The associated node data. */ - TargetType TargetType; /* The type of data targetted by this driver instance. */ - void* TargetData; /* A pointer to the specific targetted cluster or search. */ - unsigned int TargetIndex; /* An index into the cluster or search (entries only). */ - unsigned char TargetAttrIndex; /* An index into an attribute list (for GetNextAttr()). */ - unsigned char TargetMethodIndex; /* An index into an method list (for GetNextMethod()). */ + pNodeData NodeData; + void* TargetData; + unsigned int TargetIndex; + unsigned char TargetAttrIndex; + unsigned char TargetMethodIndex; + TargetType TargetType; } DriverData, *pDriverData; -/** Query instance data. **/ +/*** Query instance data. + *** + *** Memory Stats: + *** - Padding: 4 bytes + *** - Total size: 16 bytes + *** + *** @param DriverData The associated driver instance being queried. + *** @param RowIndex The selected row of the data targetted by the driver. + ***/ typedef struct { - pDriverData DriverData; /* The associated driver instance being queried. */ - unsigned int RowIndex; /* The selected row of the data targetted by the driver. */ + pDriverData DriverData; + unsigned int RowIndex; } ClusterQuery, *pClusterQuery; + /** Global storage for caches. **/ struct { - XHashTable SourceCache; - XHashTable ClusterCache; - XHashTable SearchCache; + XHashTable SourceDataCache; + XHashTable ClusterDataCache; + XHashTable SearchDataCache; } - ClusterCaches; + ClusterDriverCaches; /** ================ Function Declarations ================ **/ @@ -577,6 +675,7 @@ static void ci_FreeSourceData(pSourceData source_data); static void ci_FreeClusterData(pClusterData cluster_data, bool recursive); static void ci_FreeSearchData(pSearchData search_data); static void ci_FreeNodeData(pNodeData node_data); +static void ci_FreeCaches(void); /** Deep Size Computation Functions. **/ // LINK #sizing @@ -619,6 +718,7 @@ static void ci_CacheFreeSourceData(pXHashEntry entry, void* path); static void ci_CacheFreeCluster(pXHashEntry entry, void* path); static void ci_CacheFreeSearch(pXHashEntry entry, void* path); int clusterExecuteMethod(void* inf_v, char* methodname, pObjData param, pObjTrxTree oxt); +int clusterUnregister(pObjDriver object_driver, pObjSession session); /** Unimplemented DriverFunctions. **/ // LINK #unimplemented @@ -636,18 +736,44 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt); /** ANCHOR[id=parsing] **/ // LINK #functions +/** Format a hint to give to the user. **/ +static void ci_GiveHint(const char* hint) + { + fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); + } + +/*** Given the user a hint when they specify an invalid string for a field + *** where we know the list of valid strings. The hint is only displayed if + *** their string is close enough to a valid string. + *** + *** @param value The value the user gave. + *** @param valid_values The valid values that could be what they meant. + *** @param n_valid_values The number of valid values. Specify 0 to detect + *** length on a null terminated array of values. + *** @returns Whether a hint was given. + ***/ +static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) + { + char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.5); + if (guess == NULL) return false; /* No hint. */ + + /** Issue hint. **/ + ci_GiveHint(guess); + return true; + } + + +// LINK #functions /*** Returns 0 for success and -1 on failure. Promises that mssError() will be *** invoked on failure, so the caller need not specify their own error message. *** Returns 1 if attribute is available, printing an error if the attribute was *** marked as required. *** - *** @attention - Promises that mssError() will be invoked on failure, so the - *** caller is not required to specify their own error message. + *** @attention - Promises that a failure invokes mssError() at least once. *** - *** TODO: Greg - *** This function took several hours of debugging before it worked at all, and I - *** still don't know if it works correctly... or really how it works. Please - *** review this code carefully! + *** TODO: Greg - Review Carefully. + *** This function took a lot of debugging to get it to work. Please make sure + *** it works correctly and properly requires runserver() for dynamic attributes. ***/ static int ci_ParseAttribute( pStructInf inf, @@ -659,8 +785,9 @@ static int ci_ParseAttribute( bool print_type_error) { int ret; + tprintf("Invoking ci_ParseAttribute('%s').\n", attr_name); - /** Get attribute name. **/ + /** Get attribute inf. **/ pStructInf attr_info = stLookup(inf, attr_name); if (attr_info == NULL) { @@ -669,14 +796,19 @@ static int ci_ParseAttribute( } ASSERTMAGIC(attr_info, MGK_STRUCTINF); - /** Get the attribute. **/ - tprintf("Invoking ci_ParseAttribute('%s')...\n", attr_name); + /** Allocate expression. **/ pExpression exp = check_ptr(stGetExpression(attr_info, 0)); + if (exp == NULL) goto err; + + /** Bind parameters. **/ + /** TODO: Greg - What does this return? How do I know if it fails? **/ expBindExpression(exp, param_list, EXPR_F_RUNSERVER); + + /** Evaluate expression. **/ ret = expEvalTree(exp, param_list); if (ret != 0) { - mssErrorf(0, "Cluster", "Expression evaluation failed."); + mssErrorf(0, "Cluster", "Expression evaluation failed (error code %d).", ret); goto err; } @@ -695,42 +827,13 @@ static int ci_ParseAttribute( if (ret != 0) { mssErrorf(1, "Cluster", - "Failed to get data of type \"%s\" from exp \"%s\" (error code %d).", - ci_TypeToStr(datatype), exp->Name, ret + "Failed to get \"%s\" : %s using expression \"%s\" (error code %d).", + attr_name, ci_TypeToStr(datatype), exp->Name, ret ); goto err; } -// const int ret = stGetAttrValueOSML( -// attr_info, -// datatype, -// data, -// 0, -// param_list->Session, -// param_list -// ); -// if (ret == 1) -// { -// mssErrorf(1, "Cluster", -// "stGetAttrValueOSML('%s') because %s cannot be null.\n" -// " > Hint: You might have used an undefined variable or forgot to add runserver().", -// attr_name, attr_name -// ); -// return 1; -// } -// if (ret != 0) -// { -// if (print_type_error) -// { -// mssErrorf(1, "Cluster", -// "stGetAttrValueOSML('%s') failed (error code %d).\n" -// " > Hint: It might be a type mismatch, or you used an undefined variable.", -// attr_name, ret -// ); -// } -// return ret; -// } - + /** Success. **/ return 0; err: @@ -746,8 +849,7 @@ static int ci_ParseAttribute( /*** Parses a ClusteringAlgorithm from the algorithm field in the pStructInf *** representing some structure with that attribute in a parsed structure file. *** - *** @attention - Promises that mssError() will be invoked on failure, so the - *** caller is not required to specify their own error message. + *** @attention - Promises that a failure invokes mssError() at least once. *** *** @param inf A parsed pStructInf. *** @param param_list The param objects that function as a kind of "scope" for @@ -758,7 +860,7 @@ static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObject { /** Get the algorithm attribute. **/ char* algorithm; - if (ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0) + if (ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0) { mssErrorf(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); return ALGORITHM_NULL; @@ -771,9 +873,21 @@ static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObject if (!strcasecmp(algorithm, "k-means++")) return ALGORITHM_KMEANS_PLUS_PLUS; if (!strcasecmp(algorithm, "k-medoids")) return ALGORITHM_KMEDOIDS; if (!strcasecmp(algorithm, "db-scan")) return ALGORITHM_DB_SCAN; - + /** Unknown value for clustering algorithm. **/ mssErrorf(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); + + /** Attempt to give a hint. **/ + char* all_names[nClusteringAlgorithms] = {NULL}; + for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) + all_names[i] = ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); + if (ci_TryHint(algorithm, all_names, nClusteringAlgorithms)); + else if (strcasecmp(algorithm, "sliding") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + else if (strcasecmp(algorithm, "nothing") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + + /** Fail. **/ return ALGORITHM_NULL; } @@ -783,8 +897,7 @@ static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObject *** pStructInf parameter, which represents some structure with that attribute *** in a parsed structure file. *** - *** @attention - Promises that mssError() will be invoked on failure, so the - *** caller is not required to specify their own error message. + *** @attention - Promises that a failure invokes mssError() at least once. *** *** @param inf A parsed pStructInf. *** @param param_list The param objects that function as a kind of "scope" for @@ -805,7 +918,20 @@ static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects if (!strcasecmp(measure, "cosine")) return SIMILARITY_COSINE; if (!strcasecmp(measure, "levenshtein")) return SIMILARITY_LEVENSHTEIN; + /** Unknown similarity measure. **/ mssErrorf(1, "Cluster", "Unknown \"similarity measure\": %s", measure); + + /** Attempt to give a hint. **/ + char* all_names[nSimilarityMeasures] = {NULL}; + for (unsigned int i = 0u; i < nSimilarityMeasures; i++) + all_names[i] = ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); + if (ci_TryHint(measure, all_names, nSimilarityMeasures)); + else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); + else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-distance") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + + /** Fail. **/ return SIMILARITY_NULL; } @@ -815,8 +941,7 @@ static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects *** a .cluster structure file. *** *** @attention - Warning: Caching in use. - *** @attention - Promises that mssError() will be invoked on failure, so the - *** caller is not required to specify their own error message. + *** @attention - Promises that a failure invokes mssError() at least once. *** *** @param inf A parsed pStructInf for a .cluster structure file. *** @param param_list The param objects that function as a kind of "scope" for @@ -832,26 +957,30 @@ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, /** Get source. **/ if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; char* source_path = check_ptr(nmSysStrdup(buf)); + if (source_path == NULL) goto err; /** Get attribute name. **/ if (ci_ParseAttribute(inf, "attr_name", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; char* attr_name = check_ptr(nmSysStrdup(buf)); + if (attr_name == NULL) goto err_free_path; /** Create cache entry key. **/ const size_t len = strlen(path) + strlen(source_path) + strlen(attr_name) + 3lu; char* key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (key == NULL) goto err_free_attr; snprintf(key, len, "%s?%s:%s", path, source_path, attr_name); - pXHashTable source_cache = &ClusterCaches.SourceCache; /** Check for a cached version. **/ - pSourceData source_maybe = (pSourceData)xhLookup(source_cache, key); + pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, key); if (source_maybe != NULL) { /** Cache hit. **/ tprintf("# source: \"%s\"\n", key); - tprintf("--> Name: %s\n", source_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ - /** Free data we don't need. */ + /** Cause an imediate invalid read if cache was incorrectly freed. **/ + tprintf("--> Name: %s\n", source_maybe->Name); + + /** Free data we don't need. **/ nmSysFree(source_path); nmSysFree(attr_name); nmSysFree(key); @@ -862,21 +991,43 @@ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, /** Cache miss: Create a new source data object. **/ pSourceData source_data = check_ptr(nmMalloc(sizeof(SourceData))); + if (source_data == NULL) goto err_free_key; memset(source_data, 0, sizeof(SourceData)); - source_data->Name = check_ptr(nmSysStrdup(inf->Name)); source_data->Key = key; source_data->SourcePath = source_path; source_data->AttrName = attr_name; - check(objCurrentDate(&source_data->DateCreated)); + source_data->Name = check_ptr(nmSysStrdup(inf->Name)); + if (source_data->Name == NULL) goto err_free_source; + if (!check(objCurrentDate(&source_data->DateCreated))) goto err_free_source; /** Add the new object to the cache for next time. **/ tprintf("+ source: \"%s\"\n", key); - check(xhAdd(source_cache, key, (void*)source_data)); + if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, key, (void*)source_data))) + goto err_free_source; + /** Success. **/ return source_data; + /** Error handling. **/ + err_free_source: + ci_FreeSourceData(source_data); + nmSysFree(key); + goto err; + + err_free_key: + nmSysFree(key); + + err_free_attr: + nmSysFree(attr_name); + + err_free_path: + nmSysFree(source_path); + err: - mssErrorf(0, "Cluster", "Failed to parse source data from group \"%s\" in file: %s", inf->Name, path); + mssErrorf(0, "Cluster", + "Failed to parse source data from group \"%s\" in file: %s", + inf->Name, path + ); return NULL; } @@ -901,17 +1052,21 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) tprintf("Parsing cluster: %s\n", inf->Name); + /** Extract values. **/ pParamObjects param_list = node_data->ParamList; pSourceData source_data = node_data->SourceData; /** Allocate space for data struct. **/ pClusterData cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); + if (cluster_data == NULL) goto err; memset(cluster_data, 0, sizeof(ClusterData)); /** Basic Properties. **/ cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); - cluster_data->SourceData = source_data; - check(objCurrentDate(&cluster_data->DateCreated)); + if (cluster_data->Name == NULL) goto err_free_cluster; + cluster_data->SourceData = check_ptr(source_data); + if (cluster_data->SourceData == NULL) goto err_free_cluster; + if (!check(objCurrentDate(&cluster_data->DateCreated))) goto err_free_cluster; /** Get algorithm. **/ cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); @@ -920,7 +1075,7 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) /** Handle no clustering case. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_NONE) { - cluster_data->NumClusters = 1u; + cluster_data->nClusters = 1u; goto parsing_done; } @@ -930,19 +1085,36 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) /** Handle sliding window case. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) + { + /** Sliding window doesn't allocate any clusters. **/ + cluster_data->nClusters = 0u; + + /** Get window_size. **/ + int window_size; + if (ci_ParseAttribute(inf, "window_size", DATA_T_INTEGER, POD(&window_size), param_list, true, true) != 0) + goto err_free_cluster; + if (window_size < 1) + { + mssErrorf(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); + goto err_free_cluster; + } + + /** Store value. **/ + cluster_data->MaxIterations = (unsigned int)window_size; goto parsing_done; + } /** Get num_clusters. **/ int num_clusters; - if (ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0) goto err_free_cluster; + if (ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0) + goto err_free_cluster; if (num_clusters < 2) { mssErrorf(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); if (num_clusters == 1) fprintf(stderr, "HINT: Use algorithm=\"none\" to disable clustering.\n"); goto err_free_cluster; } - cluster_data->NumClusters = (unsigned int)num_clusters; - tprintf("Got value for num_clusters: %d\n", num_clusters); + cluster_data->nClusters = (unsigned int)num_clusters; /** Get min_improvement. **/ double improvement; @@ -955,19 +1127,24 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %g", improvement); goto err_free_cluster; } + + /** Successfully got value. **/ cluster_data->MinImprovement = improvement; } else if (result == -1) { char* str; result = ci_ParseAttribute(inf, "min_improvement", DATA_T_STRING, POD(&str), param_list, false, true); - if (result == 0 && !strcasecmp(str, "none")) + if (result != 0) goto err_free_cluster; + if (strcasecmp(str, "none") != 0) { - /** Specify no min improvement. **/ - cluster_data->MinImprovement = -INFINITY; + mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %s", str); + goto err_free_cluster; } + + /** Successfully got none. **/ + cluster_data->MinImprovement = -INFINITY; } - if (result == -1) goto err_free_cluster; /** Get max_iterations. **/ int max_iterations; @@ -986,33 +1163,89 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) /** Search for sub-clusters. **/ XArray sub_clusters; - const int ret = xaInit(&sub_clusters, 4u); - if (ret != 0) - { - mssErrorf(1, "Cluster", "FAIL - xaInit(&sub_clusters, %u): %d", 4u, ret); - goto err_free_cluster; - } + if (!check(xaInit(&sub_clusters, 4u))) goto err_free_cluster; for (unsigned int i = 0u; i < inf->nSubInf; i++) { - /** Check that this is a group (not an attribute). **/ - pStructInf group_inf = inf->SubInf[i]; - ASSERTMAGIC(group_inf, MGK_STRUCTINF); - if (stStructType(group_inf) != ST_T_SUBGROUP) continue; - - /** Select array by group type. **/ - if (strcmp(check_ptr(group_inf->UsrType), "cluster/cluster") != 0) continue; + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; - /** Subcluster found. **/ - pClusterData sub_cluster = ci_ParseClusterData(group_inf, node_data); - if (sub_cluster == NULL) goto err_free_sub_clusters; - sub_cluster->Parent = cluster_data; - xaAddItem(&sub_clusters, sub_cluster); + /** Handle various struct types. **/ + const int struct_type = stStructType(sub_inf); + switch (struct_type) + { + case ST_T_ATTRIB: + { + /** Valid attribute names. **/ + char* attrs[] = { + "algorithm", + "similarity_measure", + "num_clusters", + "min_improvement", + "max_iterations", + "window_size", + }; + const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < nattrs; i++) + { + if (strcmp(name, attrs[i]) == 0) + { + is_valid = true; + break; + } + } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); + if (ci_TryHint(name, attrs, nattrs)); + else if (strcasecmp(name, "k") == 0) ci_GiveHint("num_clusters"); + else if (strcasecmp(name, "threshold") == 0) ci_GiveHint("min_improvement"); + + break; + } + + case ST_T_SUBGROUP: + { + /** Select array by group type. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (group_type == NULL) goto err_free_subclusters; + if (strcmp(group_type, "cluster/cluster") != 0) + { + fprintf(stderr, + "Warning: Unknown group \"%s\" : \"%s\" in cluster \"%s\".\n", + name, group_type, inf->Name + ); + continue; + } + + /** Subcluster found. **/ + pClusterData sub_cluster = ci_ParseClusterData(sub_inf, node_data); + if (sub_cluster == NULL) goto err_free_subclusters; + sub_cluster->Parent = cluster_data; + if (!check_neg(xaAddItem(&sub_clusters, sub_cluster))) goto err_free_subclusters; + + break; + } + + default: + { + mssErrorf(1, "Cluster", + "Warning: Unknown struct type %d in cluster \"%s\".", + struct_type, inf->Name + ); + goto err_free_subclusters; + } + } } cluster_data->nSubClusters = sub_clusters.nItems; cluster_data->SubClusters = (cluster_data->nSubClusters > 0u) ? (pClusterData*)ci_xaToTrimmedArray(&sub_clusters) : NULL; /* No sub-clusters. */ - xaDeInit(&sub_clusters); + check(xaDeInit(&sub_clusters)); /* Failure ignored. */ /** Create the cache key. **/ parsing_done:; @@ -1021,7 +1254,7 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) { case ALGORITHM_NONE: { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 5lu; + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 8lu; key = nmSysMalloc(len * sizeof(char)); snprintf(key, len, "%s/%s?%u", source_data->Key, @@ -1033,13 +1266,14 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) case ALGORITHM_SLIDING_WINDOW: { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 8lu; + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 16lu; key = nmSysMalloc(len * sizeof(char)); - snprintf(key, len, "%s/%s?%u&%u", + snprintf(key, len, "%s/%s?%u&%u&%u", source_data->Key, cluster_data->Name, ALGORITHM_SLIDING_WINDOW, - cluster_data->SimilarityMeasure + cluster_data->SimilarityMeasure, + cluster_data->MaxIterations ); break; } @@ -1053,23 +1287,24 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) cluster_data->Name, cluster_data->ClusterAlgorithm, cluster_data->SimilarityMeasure, - cluster_data->NumClusters, + cluster_data->nClusters, cluster_data->MinImprovement, cluster_data->MaxIterations ); break; } } - pXHashTable cluster_cache = &ClusterCaches.ClusterCache; cluster_data->Key = key; /** Check for a cached version. **/ - pClusterData cluster_maybe = (pClusterData)xhLookup(cluster_cache, key); + pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, key); if (cluster_maybe != NULL) { /** Cache hit. **/ tprintf("# cluster: \"%s\"\n", key); - tprintf("--> Name: %s\n", cluster_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ + + /** Cause invalid read if cache was incorrectly freed. **/ + tprintf("--> Name: %s\n", cluster_maybe->Name); /** Free the parsed cluster that we no longer need. */ ci_FreeClusterData(cluster_data, false); @@ -1081,19 +1316,22 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) /** Cache miss. **/ tprintf("+ cluster: \"%s\"\n", key); - check(xhAdd(cluster_cache, key, (void*)cluster_data)); + if (!check(xhAdd(&ClusterDriverCaches.ClusterDataCache, key, (void*)cluster_data))) goto err_free_key; return cluster_data; /** Error cleanup. **/ - err_free_sub_clusters: + err_free_key: + nmSysFree(key); + + err_free_subclusters: for (unsigned int i = 0u; i < sub_clusters.nItems; i++) ci_FreeClusterData(sub_clusters.Items[i], true); - xaDeInit(&sub_clusters); + check(xaDeInit(&sub_clusters)); /* Failure ignored. */ err_free_cluster: ci_FreeClusterData(cluster_data, false); - // err: + err: mssErrorf(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); return NULL; } @@ -1118,20 +1356,21 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) tprintf("Parsing search: %s\n", inf->Name); /** Allocate space for search struct. **/ - pSearchData search_data = nmMalloc(sizeof(SearchData)); - assert(search_data != NULL); + pSearchData search_data = check_ptr(nmMalloc(sizeof(SearchData))); + if (search_data == NULL) goto err; memset(search_data, 0, sizeof(SearchData)); - + /** Get basic information. **/ search_data->Name = check_ptr(nmSysStrdup(inf->Name)); - check(objCurrentDate(&search_data->DateCreated)); + if (search_data->Name == NULL) goto err_free_search; + if (!check(objCurrentDate(&search_data->DateCreated))) goto err_free_search; /** Get source. **/ char* source_name; if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_name), node_data->ParamList, true, true) != 0) return NULL; - for (unsigned int i = 0; i < node_data->nClusters; i++) + for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { - pClusterData cluster_data = node_data->Clusters[i]; + pClusterData cluster_data = node_data->ClusterDatas[i]; if (strcmp(source_name, cluster_data->Name) == 0) { /** Source found. **/ @@ -1139,11 +1378,22 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) break; } - /** Note: Subclusters not implemented here. **/ + /** Note: Subclusters should probably be parsed here, if they were implemented. **/ } + + /** Did we find the requested source? **/ if (search_data->Source == NULL) { - mssErrorf(1, "Cluster", "Could not find cluster %s for search %s.", source_name, search_data->Name); + /** Print error. **/ + mssErrorf(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_name, search_data->Name); + + /** Attempt to give a hint. **/ + char* cluster_names[node_data->nClusterDatas]; + for (unsigned int i = 0; i < node_data->nClusterDatas; i++) + cluster_names[i] = node_data->ClusterDatas[i]->Name; + ci_TryHint(source_name, cluster_names, node_data->nClusterDatas); + + /** Fail. **/ goto err_free_search; } @@ -1162,17 +1412,81 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, node_data->ParamList); if (search_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free_search; + /** Check for additional data to warn the user about. **/ + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; + + /** Handle various struct types. **/ + const int struct_type = stStructType(sub_inf); + switch (struct_type) + { + case ST_T_ATTRIB: + { + /** Valid attribute names. **/ + char* attrs[] = { + "source", + "threshold", + "similarity_measure", + }; + const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < nattrs; i++) + { + if (strcmp(name, attrs[i]) == 0) + { + is_valid = true; + break; + } + } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); + ci_TryHint(name, attrs, nattrs); + + break; + } + + case ST_T_SUBGROUP: + { + /** The spec does not specify any valid sub-groups for searches. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (group_type == NULL) goto err_free_search; + fprintf(stderr, + "Warning: Unknown group \"%s\" : \"%s\" in search \"%s\".\n", + name, group_type, inf->Name + ); + break; + } + + default: + { + mssErrorf(1, "Cluster", + "Warning: Unknown struct type %d in search \"%s\".", + struct_type, inf->Name + ); + goto err_free_search; + } + } + } + /** Create cache entry key. **/ char* source_key = search_data->Source->Key; const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; - char* key = nmSysMalloc(len * sizeof(char)); + char* key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (key == NULL) goto err_free_search; snprintf(key, len, "%s/%s?%g&%u", source_key, search_data->Name, search_data->Threshold, search_data->SimilarityMeasure ); - pXHashTable search_cache = &ClusterCaches.SearchCache; + pXHashTable search_cache = &ClusterDriverCaches.SearchDataCache; /** Check for a cached version. **/ pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); @@ -1182,7 +1496,7 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) tprintf("# search: \"%s\"\n", key); tprintf("--> Name: %s\n", search_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ - /** Free the parsed search that we no longer need. */ + /** Free the parsed search that we no longer need. **/ ci_FreeSearchData(search_data); nmSysFree(key); @@ -1195,8 +1509,11 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) check(xhAdd(search_cache, key, (void*)search_data)); return search_data; + /** Error cleanup. **/ err_free_search: ci_FreeSearchData(search_data); + + err: mssErrorf(0, "Cluster", "Failed to parse search from group \"%s\".", inf->Name); return NULL; } @@ -1212,25 +1529,27 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) *** *** @param inf A parsed pStructInf for the top level group in a .cluster *** structure file. - *** @param obj The parent object struct. + *** @param parent The parent object struct. *** @returns A new pNodeData struct on success, or NULL on failure. ***/ -static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) +static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) { int ret; - - /** Retrieve path so we'll know we have it later. **/ - char* path = ci_file_path(obj); + char* path = check_ptr(ci_file_path(parent)); + if (path == NULL) goto err; /** Allocate node struct data. **/ // pNodeData node_data = NodeData |> sizeof() |> nmMalloc() |> check_ptr(); pNodeData node_data = check_ptr(nmMalloc(sizeof(NodeData))); + if (node_data == NULL) goto err; memset(node_data, 0, sizeof(NodeData)); - node_data->Obj = obj; + node_data->Parent = parent; /** Set up param list. **/ node_data->ParamList = check_ptr(expCreateParamList()); - node_data->ParamList->Session = obj->Session; + if (node_data->ParamList == NULL) goto err; + node_data->ParamList->Session = check_ptr(parent->Session); + if (node_data->ParamList->Session == NULL) goto err; ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); if (ret != 0) { @@ -1254,45 +1573,113 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) /** Detect relevant groups. **/ XArray param_infs, cluster_infs, search_infs; - check(xaInit(¶m_infs, 8)); - check(xaInit(&cluster_infs, 8)); - check(xaInit(&search_infs, 8)); + memset(¶m_infs, 0, sizeof(XArray)); + memset(&cluster_infs, 0, sizeof(XArray)); + memset(&search_infs, 0, sizeof(XArray)); + if (!check(xaInit(¶m_infs, 8))) goto err_free_arrs; + if (!check(xaInit(&cluster_infs, 8))) goto err_free_arrs; + if (!check(xaInit(&search_infs, 8))) goto err_free_arrs; for (unsigned int i = 0u; i < inf->nSubInf; i++) { - /** Check that this is a group (not an attribute). **/ - pStructInf group_inf = inf->SubInf[i]; - ASSERTMAGIC(group_inf, MGK_STRUCTINF); - if (stStructType(group_inf) != ST_T_SUBGROUP) continue; - - /** Select array by group type. **/ - const char* group_type = group_inf->UsrType; - if (strcmp(group_type, "cluster/parameter") == 0) check_strict(xaAddItem(¶m_infs, group_inf)); - else if (strcmp(group_type, "cluster/cluster") == 0) check_strict(xaAddItem(&cluster_infs, group_inf)); - else if (strcmp(group_type, "cluster/search") == 0) check_strict(xaAddItem(&search_infs, group_inf)); - else + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; + + /** Handle various struct types. **/ + const int struct_type = stStructType(sub_inf); + switch (struct_type) { - mssErrorf(1, "Cluster", - "Unkown group type \"%s\" on group \"%s\".", - group_type, group_inf->Name - ); - goto err_free_arrs; + case ST_T_ATTRIB: + { + /** Valid attribute names. **/ + char* attrs[] = { + "source", + "attr_name", + }; + const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < nattrs; i++) + { + if (strcmp(name, attrs[i]) == 0) + { + is_valid = true; + break; + } + } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in cluster node \"%s\".\n", name, inf->Name); + ci_TryHint(name, attrs, nattrs); + + break; + } + + case ST_T_SUBGROUP: + { + /** The spec does not specify any valid sub-groups for searches. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (group_type == NULL) goto err_free_arrs; + if (strcmp(group_type, "cluster/parameter") == 0) + { + if (!check_neg(xaAddItem(¶m_infs, sub_inf))) + goto err_free_arrs; + } + else if (strcmp(group_type, "cluster/cluster") == 0) + { + if (!check_neg(xaAddItem(&cluster_infs, sub_inf))) + goto err_free_arrs; + } + else if (strcmp(group_type, "cluster/search") == 0) + { + if (!check_neg(xaAddItem(&search_infs, sub_inf))) + goto err_free_arrs; + } + else + { + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, + "Warning: Unknown group type \"%s\" on group \"%s\".\n", + group_type, sub_inf->Name + ); + ci_TryHint(group_type, (char*[]){ + "cluster/parameter", + "cluster/cluster", + "cluster/search", + NULL, + }, 0u); + } + break; + } + + default: + { + mssErrorf(1, "Cluster", + "Warning: Unknown struct type %d in search \"%s\".", + struct_type, inf->Name + ); + goto err_free_arrs; + } } } /** Extract OpenCtl for use below. **/ - bool has_provided_params = obj != NULL - && obj->Pathname != NULL - && obj->Pathname->OpenCtl != NULL - && obj->Pathname->OpenCtl[obj->SubPtr - 1] != NULL - && obj->Pathname->OpenCtl[obj->SubPtr - 1]->nSubInf > 0 - && obj->Pathname->OpenCtl[obj->SubPtr - 1]->SubInf != NULL; - int num_provided_params = (has_provided_params) ? obj->Pathname->OpenCtl[obj->SubPtr - 1]->nSubInf : 0; - pStruct* provided_params = (has_provided_params) ? obj->Pathname->OpenCtl[obj->SubPtr - 1]->SubInf : NULL; + bool has_provided_params = parent != NULL + && parent->Pathname != NULL + && parent->Pathname->OpenCtl != NULL + && parent->Pathname->OpenCtl[parent->SubPtr - 1] != NULL + && parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf > 0 + && parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf != NULL; + int num_provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf : 0; + pStruct* provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf : NULL; /** Itterate over each param in the structure file. **/ node_data->nParams = param_infs.nItems; const size_t params_size = node_data->nParams * sizeof(pParam); node_data->Params = check_ptr(nmMalloc(params_size)); + if (node_data->Params == NULL) goto err_free_arrs; memset(node_data->Params, 0, params_size); for (unsigned int i = 0u; i < node_data->nParams; i++) { @@ -1316,7 +1703,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) mssErrorf(1, "Cluster", "Provided param struct cannot be NULL."); fprintf(stderr, "Debug info: obj->Pathname->OpenCtl[%d]->SubInf[%u] is NULL", - obj->SubPtr - 1, j + parent->SubPtr - 1, j ); goto err_free_arrs; } @@ -1325,7 +1712,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) if (strcmp(provided_param->Name, param->Name) != 0) continue; /** Matched! The user is providing a value for this param. **/ - ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, obj->Session); + ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, node_data->ParamList->Session); if (ret != 0) { mssErrorf(0, "Cluster", @@ -1346,7 +1733,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) } /** Invoke param hints parsing. **/ - ret = paramEvalHints(param, node_data->ParamList, obj->Session); + ret = paramEvalHints(param, node_data->ParamList, node_data->ParamList->Session); if (ret != 0) { mssErrorf(0, "Cluster", @@ -1355,59 +1742,62 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj) ); goto err_free_arrs; } - if (strcmp("k", param->Name) == 0) tprintf("Param k is now %d\n", param->Value->Data.Integer); } - check(xaDeInit(¶m_infs)); + check(xaDeInit(¶m_infs)); /* Failure ignored. */ param_infs.nAlloc = 0; /** Parse source data. **/ node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); - if (node_data->SourceData == NULL) goto err_free_node; + if (node_data->SourceData == NULL) goto err_free_arrs; /** Parse each cluster. **/ - node_data->nClusters = cluster_infs.nItems; - if (node_data->nClusters > 0) + node_data->nClusterDatas = cluster_infs.nItems; + if (node_data->nClusterDatas > 0) { - const size_t clusters_size = node_data->nClusters * sizeof(pClusterData); - node_data->Clusters = check_ptr(nmMalloc(clusters_size)); - memset(node_data->Clusters, 0, clusters_size); - for (unsigned int i = 0u; i < node_data->nClusters; i++) + const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); + node_data->ClusterDatas = check_ptr(nmMalloc(clusters_size)); + if (node_data->ClusterDatas == NULL) goto err_free_arrs; + memset(node_data->ClusterDatas, 0, clusters_size); + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) { - node_data->Clusters[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data); - if (node_data->Clusters[i] == NULL) goto err_free_arrs; + node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data); + if (node_data->ClusterDatas[i] == NULL) goto err_free_arrs; } } - else node_data->Clusters = NULL; - check(xaDeInit(&cluster_infs)); + else node_data->ClusterDatas = NULL; + check(xaDeInit(&cluster_infs)); /* Failure ignored. */ cluster_infs.nAlloc = 0; /** Parse each search. **/ - node_data->nSearches = search_infs.nItems; - if (node_data->nSearches > 0) + node_data->nSearchDatas = search_infs.nItems; + if (node_data->nSearchDatas > 0) { - const size_t searches_size = node_data->nSearches * sizeof(pSearchData); - node_data->Searches = check_ptr(nmMalloc(searches_size)); - memset(node_data->Searches, 0, searches_size); - for (unsigned int i = 0u; i < node_data->nSearches; i++) + const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); + node_data->SearchDatas = check_ptr(nmMalloc(searches_size)); + if (node_data->SearchDatas == NULL) goto err_free_arrs; + memset(node_data->SearchDatas, 0, searches_size); + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) { - node_data->Searches[i] = ci_ParseSearchData(search_infs.Items[i], node_data); - if (node_data->Searches[i] == NULL) goto err_free_node; /* The XArrays are already freed. */ + node_data->SearchDatas[i] = ci_ParseSearchData(search_infs.Items[i], node_data); + if (node_data->SearchDatas[i] == NULL) goto err_free_arrs; } } - else node_data->Searches = NULL; - check(xaDeInit(&search_infs)); + else node_data->SearchDatas = NULL; + check(xaDeInit(&search_infs)); /* Failure ignored. */ search_infs.nAlloc = 0; /** Success. **/ return node_data; err_free_arrs: - if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); - if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); - if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); + if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); /* Failure ignored. */ + if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); /* Failure ignored. */ + if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); /* Failure ignored. */ err_free_node: ci_FreeNodeData(node_data); + + err: mssErrorf(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); return NULL; } @@ -1426,12 +1816,12 @@ static void ci_FreeSourceData(pSourceData source_data) if (source_data->AttrName != NULL) nmSysFree(source_data->AttrName); /** Free fetched data, if it exists. **/ - if (source_data->Data != NULL) + if (source_data->Strings != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) - nmSysFree(source_data->Data[i]); - nmFree(source_data->Data, source_data->nVectors * sizeof(char*)); - source_data->Data = NULL; + nmSysFree(source_data->Strings[i]); + nmFree(source_data->Strings, source_data->nVectors * sizeof(char*)); + source_data->Strings = NULL; } /** Free computed vectors, if they exist. **/ @@ -1460,11 +1850,19 @@ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) if (cluster_data->Name != NULL) nmSysFree(cluster_data->Name); /** Free computed data, if it exists. **/ - if (cluster_data->Labels != NULL) + if (cluster_data->Clusters != NULL) { const unsigned int nVectors = cluster_data->SourceData->nVectors; - nmFree(cluster_data->Labels, nVectors * sizeof(unsigned int)); - cluster_data->Labels = NULL; + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + pCluster cluster = &cluster_data->Clusters[i]; + nmFree(cluster->Strings, cluster->Size * sizeof(char*)); + nmFree(cluster->Vectors, cluster->Size * sizeof(pVector)); + } + nmFree(cluster_data->Clusters, nVectors * sizeof(Cluster)); + nmFree(cluster_data->Sims, nVectors * sizeof(double)); + cluster_data->Clusters = NULL; + cluster_data->Sims = NULL; } /** Free subclusters recursively. **/ @@ -1517,23 +1915,23 @@ static void ci_FreeNodeData(pNodeData node_data) if (node_data->ParamList != NULL) expFreeParamList(node_data->ParamList); /** Free parsed clusters, if they exist. **/ - if (node_data->Clusters != NULL) + if (node_data->ClusterDatas != NULL) { /*** This data is cached, so we should NOT free it! *** The caching system is responsible for the memory. ***/ - nmFree(node_data->Clusters, node_data->nClusters * sizeof(pClusterData)); - node_data->Clusters = NULL; + nmFree(node_data->ClusterDatas, node_data->nClusterDatas * sizeof(pClusterData)); + node_data->ClusterDatas = NULL; } /** Free parsed searches, if they exist. **/ - if (node_data->Searches != NULL) + if (node_data->SearchDatas != NULL) { /*** This data is cached, so we should NOT free it! *** The caching system is responsible for the memory. ***/ - nmFree(node_data->Searches, node_data->nSearches * sizeof(pSearchData)); - node_data->Searches = NULL; + nmFree(node_data->SearchDatas, node_data->nSearchDatas * sizeof(pSearchData)); + node_data->SearchDatas = NULL; } /** Free data source, if one exists. **/ @@ -1554,6 +1952,18 @@ static void ci_FreeNodeData(pNodeData node_data) nmFree(node_data, sizeof(NodeData)); } +/** Frees all caches for all cluster driver instances. **/ +static void ci_FreeCaches(void) + { + /*** Free caches in reverse of the order they are created in case + *** cached data relies on its source during the freeing process. + ***/ + check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, ci_CacheFreeSearch, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, ci_CacheFreeCluster, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, ci_CacheFreeSourceData, NULL)); /* Failure ignored. */ + } + + /** ================ Deep Size Computation Functions ================ **/ /** ANCHOR[id=sizing] **/ // LINK #functions @@ -1574,10 +1984,10 @@ static unsigned int ci_SizeOfSourceData(pSourceData source_data) if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); if (source_data->AttrName != NULL) size += strlen(source_data->AttrName) * sizeof(char); - if (source_data->Data != NULL) + if (source_data->Strings != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) - size += strlen(source_data->Data[i]) * sizeof(char); + size += strlen(source_data->Strings[i]) * sizeof(char); size += source_data->nVectors * sizeof(char*); } if (source_data->Vectors != NULL) @@ -1607,7 +2017,18 @@ static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursi { unsigned int size = 0u; if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); - if (cluster_data->Labels != NULL) size += cluster_data->SourceData->nVectors * sizeof(unsigned int); + if (cluster_data->Clusters != NULL) + { + const unsigned int nVectors = cluster_data->SourceData->nVectors; + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + const unsigned int cluster_size = cluster_data->Clusters[i].Size; + size += cluster_size * sizeof(char*); + size += cluster_size * sizeof(pVector); + } + size += nVectors * sizeof(Cluster); + size += nVectors * sizeof(double); + } if (cluster_data->SubClusters != NULL) { if (recursive) @@ -1663,43 +2084,13 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** If the vectors are already computed, we're done. **/ if (source_data->Vectors != NULL) return 0; - /** Handle error case that happens if memory optimizations break. **/ - if (source_data->Data != NULL) - { - /*** We have data, but not vectors, which means that this function ran - *** before, but the vectors were cleared by ci_GCSourceData(). This - *** should only happen if the vectors will not be needed again. Thus, - *** clearly something has gone wrong. - ***/ - fprintf(stderr, "ERROR:" - "\tci_computeSourceData() invoked on source data \"%s\" where\n" - "\tvectors were previously freed. There is likely a bug in\n" - "\tci_GCSourceData() which caused it to free vectors when we\n" - "\tstill needed them.\n", - source_data->Name - ); - fprintf(stderr, "Resolution:\n" - "\tThe original data will be dropped and refetched, and the\n" - "\tthe vectors will be recomputed, avoiding possible issues\n" - "\tfrom stale data.\n" - ); - - /** Drop source_data->Data. **/ - for (unsigned int i = 0u; i < source_data->nVectors; i++) - nmSysFree(source_data->Data[i]); - nmFree(source_data->Data, source_data->nVectors * sizeof(char*)); - source_data->Data = NULL; - source_data->nVectors = 0; - } - - /** Record the date and time. **/ - /** Even if this computation fails, we may want this information. **/ - check(objCurrentDate(&source_data->DateComputed)); - /** Time to play shoots-and-ladders in an error-handling jungle of gotos. **/ bool successful = false; int ret; + /** Record the date and time. **/ + if (!check(objCurrentDate(&source_data->DateComputed))) goto end; + /** Open the source path specified by the .cluster file. **/ tprintf("Openning...\n"); pObject obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); @@ -1712,7 +2103,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) source_data->AttrName, source_data->SourcePath ); - successful = false; goto end; } @@ -1730,14 +2120,15 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) obj->Driver->Name, source_data->SourcePath ); - successful = false; goto end_close; } /** Initialize an xarray to store the retrieved data. **/ XArray data_xarray, vector_xarray; - check(xaInit(&data_xarray, 64)); - check(xaInit(&vector_xarray, 64)); + memset(&data_xarray, 0, sizeof(XArray)); + memset(&vector_xarray, 0, sizeof(XArray)); + if (!check(xaInit(&data_xarray, 64))) goto end_close_query; + if (!check(xaInit(&vector_xarray, 64))) goto end_free_data; /** Fetch data and build vectors. **/ tprintf("Skips: "); @@ -1804,7 +2195,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (strlen(val) == 0) { tprintf("_"); - check(fflush(stdout)); + check(fflush(stdout)); /* Failure ignored. */ continue; } @@ -1826,34 +2217,41 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { /** Skip pVector with no pairs. **/ tprintf("."); - check(fflush(stdout)); + check(fflush(stdout)); /* Failure ignored. */ ca_free_vector(vector); continue; } /** Store value. **/ char* dup_val = check_ptr(nmSysStrdup(val)); - check_strict(xaAddItem(&data_xarray, (void*)dup_val)); - check_strict(xaAddItem(&vector_xarray, (void*)vector)); + if (dup_val == NULL) goto end_free_data; + if (!check_neg(xaAddItem(&data_xarray, (void*)dup_val))) goto end_free_data; + if (!check_neg(xaAddItem(&vector_xarray, (void*)vector))) goto end_free_data; /** Clean up. **/ - check(objClose(entry)); + ret = objClose(entry); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to close object entry (error code %d).", ret); + // ret = ret; // Fall-through: Failure ignored. + } } tprintf("\nData aquired.\n"); source_data->nVectors = vector_xarray.nItems; /** Trim data and store data. **/ const size_t data_size = source_data->nVectors * sizeof(char*); - source_data->Data = check_ptr(nmMalloc(data_size)); - memcpy(source_data->Data, data_xarray.Items, data_size); - check(xaDeInit(&data_xarray)); + source_data->Strings = check_ptr(nmMalloc(data_size)); + if (source_data->Strings == NULL) goto end_free_data; + memcpy(source_data->Strings, data_xarray.Items, data_size); + check(xaDeInit(&data_xarray)); /* Failure ignored. */ data_xarray.nAlloc = 0; /** Trim data and store vectors. **/ const size_t vectors_size = source_data->nVectors * sizeof(pVector); source_data->Vectors = check_ptr(nmMalloc(vectors_size)); memcpy(source_data->Vectors, vector_xarray.Items, vectors_size); - check(xaDeInit(&vector_xarray)); + check(xaDeInit(&vector_xarray)); /* Failure ignored. */ vector_xarray.nAlloc = 0; /** Success. **/ @@ -1864,21 +2262,21 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { for (unsigned int i = 0u; i < data_xarray.nItems; i++) nmSysFree(data_xarray.Items[i]); - check(xaDeInit(&data_xarray)); + check(xaDeInit(&data_xarray)); /* Failure ignored. */ } if (vector_xarray.nAlloc != 0) { for (unsigned int i = 0u; i < vector_xarray.nItems; i++) ca_free_vector(vector_xarray.Items[i]); - check(xaDeInit(&vector_xarray)); + check(xaDeInit(&vector_xarray)); /* Failure ignored. */ } - // end_close_query: + end_close_query: ret = objQueryClose(query); if (ret != 0) { mssErrorf(0, "Cluster", "Failed to close query (error code %d).", ret); - // ret = ret; // Fall-through: Continue through failure. + // ret = ret; // Fall-through: Failure ignored. } end_close: @@ -1886,11 +2284,11 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (ret != 0) { mssErrorf(0, "Cluster", "Failed to close object driver (error code %d).", ret); - // ret = ret; // Fall-through: Continue through failure. + // ret = ret; // Fall-through: Failure ignored. } end: - if (!successful) mssErrorf(0, "Cluster", "Vector computation failed."); + if (!successful) mssErrorf(0, "Cluster", "SourceData computation failed."); return (successful) ? 0 : -1; } @@ -1910,36 +2308,58 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { /** If the clusters are alreadyd computed, we're done. **/ - if (cluster_data->Labels != NULL) return 0; + if (cluster_data->Clusters != NULL) return 0; /** Make source data available. **/ pSourceData source_data = node_data->SourceData; - /** We need the vectors to compute clusters. **/ + /** We need the SourceData vectors to compute clusters. **/ if (ci_ComputeSourceData(source_data, node_data->ParamList->Session) != 0) { - mssErrorf(0, "Cluster", "Vectors not found."); + mssErrorf(0, "Cluster", "Failed to compute SourceData."); goto err; } /** Record the date and time. **/ - /** Even if this computation fails, we may want this information. **/ - check(objCurrentDate(&cluster_data->DateComputed)); + if (!check(objCurrentDate(&cluster_data->DateComputed))) goto err; /** Allocate static memory for finding clusters. **/ - const size_t labels_size = source_data->nVectors * sizeof(unsigned int); - cluster_data->Labels = check_ptr(nmMalloc(labels_size)); + const size_t clusters_size = cluster_data->nClusters * sizeof(Cluster); + cluster_data->Clusters = check_ptr(nmMalloc(clusters_size)); + if (cluster_data->Clusters == NULL) goto err; + memset(cluster_data->Clusters, 0, clusters_size); + const size_t sims_size = source_data->nVectors * sizeof(double); + cluster_data->Sims = check_ptr(nmMalloc(sims_size)); + if (cluster_data->Sims == NULL) goto err_free_clusters; + memset(cluster_data->Sims, 0, sims_size); /** Execute clustering. **/ switch (cluster_data->ClusterAlgorithm) { case ALGORITHM_NONE: - case ALGORITHM_SLIDING_WINDOW: /* Clusters are not computed separately for performance reasons. */ + { tprintf("Applying no clustering...\n"); - memset(cluster_data->Labels, 0u, labels_size); + /** Put all the data into one cluster. **/ + pCluster first_cluster = &cluster_data->Clusters[0]; + first_cluster->Size = source_data->nVectors; + first_cluster->Strings = check_ptr(nmMalloc(source_data->nVectors * sizeof(char*))); + if (first_cluster->Strings == NULL) goto err_free_sims; + first_cluster->Vectors = check_ptr(nmMalloc(source_data->nVectors * sizeof(pVector))); + if (first_cluster->Vectors == NULL) goto err_free_sims; + memcpy(first_cluster->Strings, source_data->Strings, source_data->nVectors * sizeof(char*)); + memcpy(first_cluster->Vectors, source_data->Vectors, source_data->nVectors * sizeof(pVector)); + break; + } + + case ALGORITHM_SLIDING_WINDOW: + /** Computed in each search for efficiency. **/ + tprintf("Skipping sliding window clustering...\n"); + memset(cluster_data->Clusters, 0, clusters_size); break; case ALGORITHM_KMEANS: + { + tprintf("Applying kmeans clustering...\n"); /** Check for unimplemented similarity measures. **/ if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) { @@ -1947,25 +2367,64 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) "The similarity meausre \"%s\" is not implemented.", ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) ); - goto err; + goto err_free_sims; } - /** kmeans expects clusters to be initialized. **/ - memset(cluster_data->Labels, 0u, labels_size); + /** Allocate lables. Note: kmeans does not require us to initialize them. **/ + const size_t lables_size = source_data->nVectors * sizeof(unsigned int); + unsigned int* labels = check_ptr(nmMalloc(lables_size)); + if (labels == NULL) goto err_free_sims; + /** Run kmeans. **/ tprintf("Running kmeans\n"); Timer timer_i, *timer = timer_start(timer_init(&timer_i)); - ca_kmeans( + const bool successful = check(ca_kmeans( source_data->Vectors, source_data->nVectors, - cluster_data->Labels, - cluster_data->NumClusters, + cluster_data->nClusters, cluster_data->MaxIterations, - cluster_data->MinImprovement - ); + cluster_data->MinImprovement, + labels, + cluster_data->Sims + )); timer_stop(timer); - tprintf("Done after %.4lf.\n", timer_get(timer)); + tprintf("Clustering done after %.4lf.\n", timer_get(timer)); + if (!successful) goto err_free_sims; + + /** Convert the labels into clusters. **/ + + /** Allocate space for clusters. **/ + XArray indexes_in_cluster[cluster_data->nClusters]; + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + if (!check(xaInit(&indexes_in_cluster[i], 8))) goto err_free_sims; + + /** Iterate through each label and add the index of the specified cluster to the xArray. **/ + for (unsigned long long i = 0llu; i < source_data->nVectors; i++) + if (!check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i))) goto err_free_sims; + nmFree(labels, lables_size); /* Free unused data. */ + + /** Iterate through each cluster, store it, and free the xArray. **/ + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + pXArray indexes_in_this_cluster = &indexes_in_cluster[i]; + pCluster cluster = &cluster_data->Clusters[i]; + cluster->Size = indexes_in_this_cluster->nItems; + cluster->Strings = check_ptr(nmMalloc(cluster->Size * sizeof(char*))); + if (cluster->Strings == NULL) goto err_free_sims; + cluster->Vectors = check_ptr(nmMalloc(cluster->Size * sizeof(pVector))); + if (cluster->Vectors == NULL) goto err_free_sims; + for (unsigned int j = 0u; j < cluster->Size; j++) + { + const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[j]; + cluster->Strings[j] = source_data->Strings[index]; + cluster->Vectors[j] = source_data->Vectors[index]; + } + check(xaDeInit(indexes_in_this_cluster)); /* Failure ignored. */ + } + + /** k-means is done. **/ break; + } default: mssErrorf(1, "Cluster", @@ -1975,9 +2434,26 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) goto err; } + /** Success. **/ tprintf("Clustering done.\n"); return 0; + err_free_sims: + nmFree(cluster_data->Sims, sims_size); + cluster_data->Sims = NULL; + + err_free_clusters: + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + pCluster cluster = &cluster_data->Clusters[i]; + if (cluster->Strings != NULL) nmFree(cluster->Strings, cluster->Size * sizeof(char*)); + else break; + if (cluster->Vectors != NULL) nmFree(cluster->Vectors, cluster->Size * sizeof(pVector)); + else break; + } + nmFree(cluster_data->Clusters, clusters_size); + cluster_data->Clusters = NULL; + err: mssErrorf(0, "Cluster", "Cluster computation failed for \"%s\".", cluster_data->Name); return -1; @@ -2003,11 +2479,8 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** If the clusters are already computed, we're done. **/ if (search_data->Dups != NULL) return 0; - /** Extract structs. **/ + /** We need the cluster data to be computed before we search it. **/ pClusterData cluster_data = search_data->Source; - pSourceData source_data = node_data->SourceData; - - /** We need the clusters to be able to search them. **/ ret = ci_ComputeClusterData(cluster_data, node_data); if (ret != 0) { @@ -2026,35 +2499,112 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } /** Record the date and time. **/ - /** Even if this computation fails, we may want this information. **/ - check(objCurrentDate(&search_data->DateComputed)); + if (!check(objCurrentDate(&search_data->DateComputed))) goto err; - /** Execute the search. **/ - tprintf("Invoking ca_search.\n"); + tprintf("Invoking search.\n"); Timer timer_i, *timer = timer_start(timer_init(&timer_i)); - pXArray dups_temp = ca_search( - source_data->Vectors, - source_data->nVectors, - cluster_data->Labels, - search_data->Threshold - ); + /** Execute the search using the specified source and comparison function. **/ + pXArray dups = NULL, dups_temp = NULL; + switch (search_data->SimilarityMeasure) + { + case SIMILARITY_COSINE: + { + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) + { + dups_temp = check_ptr(ca_sliding_search( + (void**)cluster_data->SourceData->Vectors, + cluster_data->SourceData->nVectors, + cluster_data->MaxIterations, /* Window size. */ + ca_cos_compare, + search_data->Threshold, + dups + )); + } + else + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + dups_temp = check_ptr(ca_complete_search( + (void**)cluster_data->Clusters[i].Vectors, + cluster_data->Clusters[i].Size, + ca_cos_compare, + search_data->Threshold, + dups + )); + if (dups_temp == NULL) goto err; + else dups = dups_temp; + } + } + break; + } + + case SIMILARITY_LEVENSHTEIN: + { + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) + { + dups_temp = check_ptr(ca_sliding_search( + (void**)cluster_data->SourceData->Vectors, + cluster_data->SourceData->nVectors, + cluster_data->MaxIterations, /* Window size. */ + ca_lev_compare, + search_data->Threshold, + dups + )); + } + else + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + dups_temp = check_ptr(ca_complete_search( + (void**)cluster_data->Clusters[i].Strings, + cluster_data->Clusters[i].Size, + ca_lev_compare, + search_data->Threshold, + dups + )); + if (dups_temp == NULL) goto err; + else dups = dups_temp; + } + } + break; + } + + default: + mssErrorf(1, "Cluster", + "Unknown similarity meansure \"%s\".", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err; + } timer_stop(timer); if (dups_temp == NULL) goto err; - tprintf("ca_search done after %.4lf.\n", timer_get(timer)); + else dups = dups_temp; + tprintf("Search done after %.4lf.\n", timer_get(timer)); /** Store dups. **/ - search_data->nDups = dups_temp->nItems; - search_data->Dups = (dups_temp->nItems == 0) + search_data->nDups = dups->nItems; + search_data->Dups = (dups->nItems == 0) ? check_ptr(nmMalloc(0)) - : ci_xaToTrimmedArray(dups_temp); + : ci_xaToTrimmedArray(dups); /** Free unused data. **/ tprintf("Cleanup.\n"); - check(xaFree(dups_temp)); + check(xaFree(dups)); /* Failure ignored. */ + /** Success. **/ return 0; err: + if (dups != NULL) + { + for (unsigned int i = 0u; i < dups->nItems; i++) + { + if (dups->Items[i] != NULL) nmFree(dups->Items[i], sizeof(Dup)); + else break; + } + check(xaFree(dups)); /* Failure ignored. */ + } + mssErrorf(0, "Cluster", "Search computation failed for \"%s\".", search_data->Name); return -1; } @@ -2126,9 +2676,7 @@ static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData { pParam param = (pParam)node_data->Params[i]; if (strcmp(param->Name, attr_name) != 0) continue; - - tprintf("Param found: Parsing...\n"); - + /** Parameter found. **/ if (param->Value == NULL) return 1; if (param->Value->Flags & DATA_TF_NULL) return 1; @@ -2138,14 +2686,16 @@ static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData return -1; } - tprintf("Param found: Copying...\n"); /** Return param value. **/ - objCopyData(&(param->Value->Data), val, datatype); + if (!check(objCopyData(&(param->Value->Data), val, datatype))) goto err; return 0; } - /** Param not found. **/ - tprintf("Param not found.\n"); + err: + mssErrorf(1, "Cluster", + "Failed to get parameter %s : %s", + attr_name, ci_TypeToStr(datatype) + ); return -1; } @@ -2164,10 +2714,10 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData // LINK #functions /*** Opens a new cluster driver instance by parsing a `.cluster` file found - *** at the path provided in obj. + *** at the path provided in parent. *** - *** @param obj The object being opened, including the path, session, and - *** other necessary information. + *** @param parent The parent of the object to be openned, including useful + *** information such as the pathname, session, etc. *** @param mask Driver permission mask (unused). *** @param sys_type ? (unused) *** @param usr_type The object system file type being openned. Should always @@ -2178,30 +2728,30 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData *** @returns A pDriverData struct representing a driver instance, or *** NULL if an error occures. ***/ -void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) +void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { - tprintf("Warning: clusterOpen(\"%s\") is under active development.\n", ci_file_name(obj)); + tprintf("Warning: clusterOpen(\"%s\") is under active development.\n", ci_file_name(parent)); - /** If CREAT and EXCL are specified, create it and fail if it already exists. **/ + /** If CREAT and EXCL are specified, exclusively create it, failing if the file already exists. **/ pSnNode node_struct = NULL; - bool can_create = (obj->Mode & O_CREAT) && (obj->SubPtr == obj->Pathname->nElements); - if (can_create && (obj->Mode & O_EXCL)) + bool can_create = (parent->Mode & O_CREAT) && (parent->SubPtr == parent->Pathname->nElements); + if (can_create && (parent->Mode & O_EXCL)) { - node_struct = snNewNode(obj->Prev, usr_type); + node_struct = snNewNode(parent->Prev, usr_type); if (node_struct == NULL) { - mssErrorf(0, "Cluster", "Failed to EXCL create new node struct."); + mssErrorf(0, "Cluster", "Failed to exclusively create new node struct."); goto err; } } /** Read the node if it exists. **/ if (node_struct == NULL) - node_struct = snReadNode(obj->Prev); + node_struct = snReadNode(parent->Prev); - /** If we can't read, create it (if allowed). **/ + /** If we can't read it, create it (if allowed). **/ if (node_struct == NULL && can_create) - node_struct = snNewNode(obj->Prev, usr_type); + node_struct = snNewNode(parent->Prev, usr_type); /** If there still isn't a node, fail early. **/ if (node_struct == NULL) @@ -2210,37 +2760,40 @@ void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, goto err; } - /** Parse node data. **/ - pNodeData node_data = ci_ParseNodeData(node_struct->Data, obj); + /** Magic. **/ + ASSERTMAGIC(node_struct, MGK_STNODE); + ASSERTMAGIC(node_struct->Data, MGK_STRUCTINF); + + /** Parse node data from the node_struct. **/ + pNodeData node_data = ci_ParseNodeData(node_struct->Data, parent); if (node_data == NULL) { - mssErrorf(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(obj)); + mssErrorf(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(parent)); goto err; } - node_data->Node = node_struct; - node_data->Node->OpenCnt++; /** Allocate driver instance data. **/ pDriverData driver_data = check_ptr(nmMalloc(sizeof(DriverData))); + if (driver_data == NULL) goto err_free_node; memset(driver_data, 0, sizeof(DriverData)); driver_data->NodeData = node_data; /** Detect target from path. **/ - tprintf("Parsing node path: %d %d\n", obj->SubPtr, obj->SubCnt); obj->SubCnt = 0; - char* target_name = obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt++, 1); + tprintf("Parsing node path: %d %d\n", parent->SubPtr, parent->SubCnt); parent->SubCnt = 0; + char* target_name = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); if (target_name == NULL) { /** Target found: Root **/ tprintf("Found target: Root.\n"); driver_data->TargetType = TARGET_ROOT; driver_data->TargetData = (void*)driver_data->NodeData->SourceData; - return (void*)driver_data; /* Sucess. */ + return (void*)driver_data; /* Success. */ } /** Search clusters. **/ - for (unsigned int i = 0u; i < node_data->nClusters; i++) + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) { - pClusterData cluster = node_data->Clusters[i]; + pClusterData cluster = node_data->ClusterDatas[i]; if (strcmp(cluster->Name, target_name) != 0) continue; /** Target found: Cluster **/ @@ -2251,7 +2804,7 @@ void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, while (true) { /** Decend one path part deeper into the path. **/ - const char* path_part = obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt++, 1); + const char* path_part = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); /** If the path does not go any deeper, we're done. **/ if (path_part == NULL) @@ -2278,13 +2831,13 @@ void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, continue_descent:; } - return (void*)driver_data; /* Sucess. */ + return (void*)driver_data; /* Success. */ } /** Search searches. **/ - for (unsigned int i = 0u; i < node_data->nSearches; i++) + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) { - pSearchData search = node_data->Searches[i]; + pSearchData search = node_data->SearchDatas[i]; if (strcmp(search->Name, target_name) != 0) continue; /** Target found: Search **/ @@ -2292,25 +2845,40 @@ void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, driver_data->TargetData = (void*)search; /** Check for extra, invalid path parts. **/ - char* extra_data = obj_internal_PathPart(obj->Pathname, obj->SubPtr + obj->SubCnt++, 1); + char* extra_data = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); if (extra_data != NULL) { mssErrorf(1, "Cluster", "Unknown path part %s.", extra_data); goto err_free_node; } - tprintf("Found target search: %s %d %d\n", search->Name, obj->SubPtr, obj->SubCnt); - return (void*)driver_data; /* Sucess. */ + tprintf("Found target search: %s %d %d\n", search->Name, parent->SubPtr, parent->SubCnt); + return (void*)driver_data; /* Success. */ } /** We were unable to find the requested cluster or search. **/ - mssErrorf(1, "Cluster", "\"%s\" is not the name of a declaired cluster or search.", target_name); + mssErrorf(1, "Cluster", "\"%s\" is not the name of a declared cluster or search.", target_name); + + /** Attempt to give a hint. **/ + { + const unsigned int n_targets = node_data->nClusterDatas + node_data->nSearchDatas; + char* target_names[n_targets]; + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + target_names[i] = node_data->ClusterDatas[i]->Name; + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + target_names[i + node_data->nClusterDatas] = node_data->SearchDatas[i]->Name; + ci_TryHint(target_name, target_names, n_targets); + } /** Error cleanup. **/ err_free_node: - ci_FreeNodeData(node_data); - nmFree(driver_data, sizeof(DriverData)); + if (node_data != NULL) ci_FreeNodeData(node_data); + if (driver_data != NULL) nmFree(driver_data, sizeof(DriverData)); err: + mssErrorf(0, "Cluster", + "Failed to open cluster file \"%s\" at: %s", + ci_file_name(parent), ci_file_path(parent) + ); return NULL; } @@ -2362,6 +2930,7 @@ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) { tprintf("Warning: clusterOpenQuery() is under active development.\n"); pClusterQuery cluster_query = check_ptr(nmMalloc(sizeof(ClusterQuery))); + if (cluster_query == NULL) return NULL; cluster_query->DriverData = (pDriverData)inf_v; cluster_query->RowIndex = 0u; return cluster_query; @@ -2404,9 +2973,9 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) if (ret != 0) { mssErrorf(0, "Cluster", "Internal cluster computation failed."); - return NULL; + return NULL; } - data_amount = cluster_query->DriverData->NodeData->SourceData->nVectors; + data_amount = target->nClusters; break; } @@ -2418,7 +2987,7 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) if (ret != 0) { mssErrorf(0, "Cluster", "Internal search computation failed."); - return NULL; + return NULL; } data_amount = target->nDups; break; @@ -2442,12 +3011,13 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) if (cluster_query->RowIndex >= data_amount) return NULL; /** Create the result struct. **/ - pDriverData driver_data = nmMalloc(sizeof(DriverData)); - assert(driver_data != NULL); + pDriverData driver_data = check_ptr(nmMalloc(sizeof(DriverData))); + if (driver_data == NULL) return NULL; memcpy(driver_data, cluster_query->DriverData, sizeof(DriverData)); driver_data->TargetType = new_target_type; driver_data->TargetIndex = cluster_query->RowIndex++; + /** Success. **/ return driver_data; } @@ -2531,9 +3101,7 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "num_clusters") == 0 || strcmp(attr_name, "max_iterations") == 0) return DATA_T_INTEGER; - if (strcmp(attr_name, "min_improvement") == 0 - || strcmp(attr_name, "average_similarity") == 0 - || strcmp(attr_name, "size") == 0) + if (strcmp(attr_name, "min_improvement") == 0) return DATA_T_DOUBLE; break; @@ -2546,12 +3114,8 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) break; case TARGET_CLUSTER_ENTRY: - if (strcmp(attr_name, "id") == 0) - return DATA_T_INTEGER; - if (strcmp(attr_name, "val") == 0) - return DATA_T_STRING; - if (strcmp(attr_name, "sim") == 0) - return DATA_T_DOUBLE; + if (strcmp(attr_name, "items") == 0) + return DATA_T_STRINGVEC; break; case TARGET_SEARCH_ENTRY: @@ -2604,9 +3168,9 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val return DATA_T_UNAVAILABLE; } - /** Performance shortcut for frequently requested attributes: val, val1, val2, and sim. **/ + /** Performance shortcut for frequently requested attributes: val1, val2, and sim. **/ if ( - (attr_name[0] == 'v' && datatype == DATA_T_STRING) /* val, val1, val2 : String */ + (attr_name[0] == 'v' && datatype == DATA_T_STRING) /* val1, val2 : String */ || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ ) goto handle_targets; @@ -2771,15 +3335,15 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val } if (strcmp(attr_name, "num_clusters") == 0) { - if (target->NumClusters > INT_MAX) - fprintf(stderr, "Warning: num_clusters value of %u exceeds INT_MAX (%d).\n", target->NumClusters, INT_MAX); - val->Integer = (int)target->NumClusters; + if (target->nClusters > INT_MAX) + fprintf(stderr, "Warning: 'num_clusters' value of %u exceeds INT_MAX (%d).\n", target->nClusters, INT_MAX); + val->Integer = (int)target->nClusters; return 0; } if (strcmp(attr_name, "max_iterations") == 0) { if (target->MaxIterations > INT_MAX) - fprintf(stderr, "Warning: max_iterations value of %u exceeds INT_MAX (%d).\n", target->MaxIterations, INT_MAX); + fprintf(stderr, "Warning: 'max_iterations' value of %u exceeds INT_MAX (%d).\n", target->MaxIterations, INT_MAX); val->Integer = (int)target->MaxIterations; return 0; } @@ -2788,12 +3352,6 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val val->Double = target->MinImprovement; return 0; } - if (strcmp(attr_name, "average_similarity") == 0 - || strcmp(attr_name, "size") == 0) - { - mssErrorf(1, "Cluster", "average_similarity is not implemented."); - return -1; - } break; } @@ -2822,21 +3380,22 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val { pClusterData target = (pClusterData)driver_data->TargetData; - if (strcmp(attr_name, "id") == 0) - { - val->Integer = (int)target->Labels[driver_data->TargetIndex]; - return 0; - } - if (strcmp(attr_name, "val") == 0) + if (strcmp(attr_name, "items") == 0) { - val->String = driver_data->NodeData->SourceData->Data[driver_data->TargetIndex]; + /** Static variable to prevent leaking StringVec from previous calls. **/ + static StringVec* vec = NULL; + if (vec != NULL) nmFree(vec, sizeof(StringVec)); + + /** Allocate and initiallize the requested data. **/ + pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; + val->StringVec = vec = check_ptr(nmMalloc(sizeof(StringVec))); + if (val->StringVec == NULL) return -1; + val->StringVec->nStrings = target_cluster->Size; + val->StringVec->Strings = target_cluster->Strings; + + /** Success. **/ return 0; } - if (strcmp(attr_name, "sim") == 0) - { - mssErrorf(1, "Cluster", "Cluster entry similarity is not supported."); - return -1; - } break; } @@ -2847,23 +3406,29 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val if (strcmp(attr_name, "id1") == 0) { - val->Integer = (int)target_dup->id1; + unsigned int value = target_dup->id1; + if (value > INT_MAX) + fprintf(stderr, "Warning: id1 value of %u exceeds INT_MAX (%d).\n", value, INT_MAX); + val->Integer = (int)value; return 0; } if (strcmp(attr_name, "id2") == 0) { - val->Integer = (int)target_dup->id2; + unsigned int value = target_dup->id2; + if (value > INT_MAX) + fprintf(stderr, "Warning: id2 value of %u exceeds INT_MAX (%d).\n", value, INT_MAX); + val->Integer = (int)value; return 0; } if (strcmp(attr_name, "val1") == 0) { - val->String = driver_data->NodeData->SourceData->Data[target_dup->id1]; + val->String = driver_data->NodeData->SourceData->Strings[target_dup->id1]; // val->Integer = (int)target_dup->id1; return 0; } if (strcmp(attr_name, "val2") == 0) { - val->String = driver_data->NodeData->SourceData->Data[target_dup->id2]; + val->String = driver_data->NodeData->SourceData->Strings[target_dup->id2]; // val->Integer = (int)target_dup->id2; return 0; } @@ -2914,9 +3479,10 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Malloc presentation hints struct. **/ pObjPresentationHints hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); + if (hints == NULL) goto err; memset(hints, 0, sizeof(ObjPresentationHints)); - /** Hints that are the same for all fields */ + /** Hints that are the same for all fields **/ hints->GroupID = -1; hints->VisualLength2 = 1; hints->Style |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; @@ -2924,18 +3490,20 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Temporary param list for compiling expressions. **/ pParamObjects tmp_list = check_ptr(expCreateParamList()); + if (hints == NULL) goto err; + /** Search for the requested attribute through attributes common to all instances. **/ if (strcmp(attr_name, "name") == 0) { hints->Length = 32; hints->VisualLength = 16; - goto end; + goto success; } if (strcmp(attr_name, "annotation") == 0) { hints->Length = 36; hints->VisualLength = 36; - goto end; + goto success; } if (strcmp(attr_name, "inner_type") == 0 || strcmp(attr_name, "inner_type") == 0 @@ -2944,18 +3512,24 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb || strcmp(attr_name, "last_modification") == 0) { hints->VisualLength = 30; - goto end; + goto success; } + /** Handle date created and date computed. */ if (strcmp(attr_name, "date_created") == 0 || strcmp(attr_name, "date_computed") == 0) { - hints->Length = 24; - hints->VisualLength = 20; - hints->Format = nmSysStrdup("datetime"); - goto end; + if (driver_data->TargetType == TARGET_CLUSTER || driver_data->TargetType == TARGET_SEARCH) + { + hints->Length = 24; + hints->VisualLength = 20; + hints->Format = nmSysStrdup("datetime"); + goto success; + } + else goto unknown_attribute; } + /** Search by target type. **/ switch (driver_data->TargetType) { case TARGET_ROOT: @@ -2964,14 +3538,14 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = _PC_PATH_MAX; hints->VisualLength = 64; hints->FriendlyName = "Source Path"; - goto end; + goto success; } if (strcmp(attr_name, "attr_name") == 0) { hints->Length = 255; hints->VisualLength = 32; hints->FriendlyName = "Attribute Name"; - goto end; + goto success; } break; @@ -2986,7 +3560,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 8; hints->VisualLength = 4; hints->FriendlyName = nmSysStrdup("Number of Clusters"); - goto end; + goto success; } if (strcmp(attr_name, "min_improvement") == 0) { @@ -2999,7 +3573,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 16; hints->VisualLength = 8; hints->FriendlyName = nmSysStrdup("Minimum Improvement Threshold"); - goto end; + goto success; } if (strcmp(attr_name, "max_iterations") == 0) { @@ -3012,31 +3586,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 8; hints->VisualLength = 4; hints->FriendlyName = nmSysStrdup("Maximum Number of Clustering Iterations"); - goto end; - } - if (strcmp(attr_name, "average_similarity") == 0) - { - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - - /** Other hints. **/ - hints->Length = 16; - hints->VisualLength = 8; - hints->FriendlyName = nmSysStrdup("Average Similarity"); - goto end; - } - if (strcmp(attr_name, "size") == 0) - { - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - - /** Other hints. **/ - hints->Length = 16; - hints->VisualLength = 8; - hints->FriendlyName = nmSysStrdup("Average Cluster Size"); - goto end; + goto success; } if (strcmp(attr_name, "algorithm") == 0) { @@ -3059,7 +3609,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 24; hints->VisualLength = 20; hints->FriendlyName = nmSysStrdup("Clustering Algorithm"); - goto end; + goto success; } /** Fall-through: Start of overlapping region. **/ @@ -3085,7 +3635,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 32; hints->VisualLength = 20; hints->FriendlyName = nmSysStrdup("Similarity Measure"); - goto end; + goto success; } /** End of overlapping region. **/ @@ -3096,7 +3646,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 64; hints->VisualLength = 32; hints->FriendlyName = nmSysStrdup("Source Cluster Name"); - goto end; + goto success; } if (strcmp(attr_name, "threshold") == 0) { @@ -3108,7 +3658,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 16; hints->VisualLength = 8; hints->FriendlyName = nmSysStrdup("Similarity Threshold"); - goto end; + goto success; } break; @@ -3132,7 +3682,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - goto end; + goto success; } if (strcmp(attr_name, "val") == 0) { @@ -3140,7 +3690,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 255; hints->VisualLength = 32; hints->FriendlyName = nmSysStrdup("Value"); - goto end; + goto success; } if (strcmp(attr_name, "sim") == 0) { @@ -3152,7 +3702,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 16; hints->VisualLength = 8; hints->FriendlyName = nmSysStrdup("Similarity"); - goto end; + goto success; } break; } @@ -3177,7 +3727,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - goto end; + goto success; } if (strcmp(attr_name, "val1") == 0 || strcmp(attr_name, "val2") == 0) { @@ -3185,7 +3735,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 255; hints->VisualLength = 32; hints->FriendlyName = nmSysStrdup("Value"); - goto end; + goto success; } if (strcmp(attr_name, "sim") == 0) { @@ -3197,7 +3747,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb hints->Length = 16; hints->VisualLength = 8; hints->FriendlyName = nmSysStrdup("Similarity"); - goto end; + goto success; } break; } @@ -3207,14 +3757,26 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb goto err; } + /** Unknown attribute. **/ + unknown_attribute:; + char* name; + clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); + mssErrorf(1, "Cluster", + "Unknown attribute '%s' for cluster object %s (target type: %u, \"%s\").", + attr_name, driver_data->NodeData->SourceData->Name, driver_data->TargetType, name + ); - end: - check(expFreeParamList(tmp_list)); - return hints; - + /** Error cleanup. **/ err: + if (tmp_list != NULL) check(expFreeParamList(tmp_list)); /* Failure ignored. */ + if (hints != NULL) nmFree(hints, sizeof(ObjPresentationHints)); mssErrorf(0, "Cluster", "Failed execute generate presentation hints."); return NULL; + + /** Success. **/ + success: + check(expFreeParamList(tmp_list)); /* Failure ignored. */ + return hints; } @@ -3255,11 +3817,11 @@ char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt) tprintf("%u) is under active development.\n", i); switch (driver_data->TargetType) { - case TARGET_ROOT: return (i < nATTR_ROOT) ? ATTR_ROOT[i] : END_OF_ATTRIBUTES; - case TARGET_CLUSTER: return (i < nATTR_CLUSTER) ? ATTR_CLUSTER[i] : END_OF_ATTRIBUTES; - case TARGET_SEARCH: return (i < nATTR_SEARCH) ? ATTR_SEARCH[i] : END_OF_ATTRIBUTES; - case TARGET_CLUSTER_ENTRY: return (i < nATTR_CLUSTER_ENTRY) ? ATTR_CLUSTER_ENTRY[i] : END_OF_ATTRIBUTES; - case TARGET_SEARCH_ENTRY: return (i < nATTR_SEARCH_ENTRY) ? ATTR_SEARCH_ENTRY[i] : END_OF_ATTRIBUTES; + case TARGET_ROOT: return ATTR_ROOT[i]; + case TARGET_CLUSTER: return ATTR_CLUSTER[i]; + case TARGET_SEARCH: return ATTR_SEARCH[i]; + case TARGET_CLUSTER_ENTRY: return ATTR_CLUSTER_ENTRY[i]; + case TARGET_SEARCH_ENTRY: return ATTR_SEARCH_ENTRY[i]; default: mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); return NULL; @@ -3292,7 +3854,7 @@ int clusterInfo(void* inf_v, pObjectInfo info) switch (driver_data->TargetType) { case TARGET_ROOT: - info->nSubobjects = node_data->nClusters + node_data->nSearches; + info->nSubobjects = node_data->nClusterDatas + node_data->nSearchDatas; info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; @@ -3384,11 +3946,9 @@ char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt) ***/ char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt) { - tprintf("Warning: clusterGetNextMethod("); + tprintf("Warning: clusterGetNextMethod() is under active development."); pDriverData driver_data = (pDriverData)inf_v; - const unsigned int i = driver_data->TargetMethodIndex++; - tprintf("%u) is under active development.\n", i); - return (i < nMETHOD_NAME) ? METHOD_NAME[i] : END_OF_METHODS; + return METHOD_NAME[driver_data->TargetMethodIndex++]; } @@ -3439,7 +3999,9 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) bytes = ci_SizeOfSearchData(search_data); break; } - default: assert(false); + default: + mssErrorf(0, "Cluster", "Unknown type_id %u.", *type_id_ptr); + return -1; } /** Increment total bytes. **/ @@ -3539,62 +4101,70 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx if (strcmp(param->String, "show") == 0) { show = true; - path = ci_file_path(driver_data->NodeData->Obj); + path = ci_file_path(driver_data->NodeData->Parent); } if (strcmp(param->String, "show_all") == 0) show = true; if (show) { /** Print cache info table. **/ + int ret = 0; unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; + bool failed = false; printf("\nShowing cache for "); if (path != NULL) printf("\"%s\":\n", path); else printf("all files:\n"); printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); - xhForEach(&ClusterCaches.SourceCache, ci_PrintEntry, (void*[]){&i, &source_bytes, path}); i++; - xhForEach(&ClusterCaches.ClusterCache, ci_PrintEntry, (void*[]){&i, &cluster_bytes, path}); i++; - xhForEach(&ClusterCaches.SearchCache, ci_PrintEntry, (void*[]){&i, &search_bytes, path}); i++; + failed |= !check(xhForEach( + &ClusterDriverCaches.SourceDataCache, + ci_PrintEntry, + (void*[]){&i, &source_bytes, path} + )); + i++; + failed |= !check(xhForEach( + &ClusterDriverCaches.ClusterDataCache, + ci_PrintEntry, + (void*[]){&i, &cluster_bytes, path} + )); + i++; + failed |= !check(xhForEach( + &ClusterDriverCaches.SearchDataCache, + ci_PrintEntry, + (void*[]){&i, &search_bytes, path} + )); + if (failed) + { + mssErrorf(0, "Cluster", "Unexpected error occured while showhing caches."); + ret = -1; + } /** Print stats. **/ char buf[16]; printf("\nCache Stats:\n"); printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); - const int n_sources = ClusterCaches.SourceCache.nItems; - snprint_bytes(buf, sizeof(buf), source_bytes); - printf("%-8s %-4d %-12s\n", "Source", n_sources, buf); - const int n_clusters = ClusterCaches.ClusterCache.nItems; - snprint_bytes(buf, sizeof(buf), cluster_bytes); - printf("%-8s %-4d %-12s\n", "Cluster", n_clusters, buf); - const int n_searches = ClusterCaches.SearchCache.nItems; - snprint_bytes(buf, sizeof(buf), search_bytes); - printf("%-8s %-4d %-12s\n", "Search", n_searches, buf); - snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes); - printf("%-8s %-4d %-12s\n\n", "Total", n_sources + n_clusters + n_searches, buf); - return 0; - } - - /** drop and drop_all. **/ - bool drop = false; - if (strcmp(param->String, "drop") == 0) - { - show = true; - path = ci_file_path(driver_data->NodeData->Obj); + printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprint_bytes(buf, sizeof(buf), source_bytes)); + printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprint_bytes(buf, sizeof(buf), cluster_bytes)); + printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprint_bytes(buf, sizeof(buf), search_bytes)); + printf("%-8s %-4d %-12s\n\n", "Total", + ClusterDriverCaches.SourceDataCache.nItems + ClusterDriverCaches.ClusterDataCache.nItems + ClusterDriverCaches.SearchDataCache.nItems, + snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes) + ); + + return ret; } - if (strcmp(param->String, "drop_all") == 0) drop = true; - if (drop) + /** drop_all. **/ + if (strcmp(param->String, "drop_all") == 0) { + /** Print info. **/ printf("\nDropping cache for "); if (path != NULL) printf("\"%s\":\n", path); else printf("all files:\n"); - /*** Free caches in reverse of the order they are created in case - *** cached data relies on its source during the freeing process. - ***/ - xhClearKeySafe(&ClusterCaches.SearchCache, ci_CacheFreeSearch, path); - xhClearKeySafe(&ClusterCaches.ClusterCache, ci_CacheFreeCluster, path); - xhClearKeySafe(&ClusterCaches.SourceCache, ci_CacheFreeSourceData, path); - printf("Cache dropped.\n"); + /** Free caches. **/ + ci_FreeCaches(); + + tprintf("Cache dropped.\n"); return 0; } @@ -3613,7 +4183,23 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx mssErrorf(0, "Cluster", "Failed execute command."); return -1; } - + + +/*** Frees caches when the driver is unregistered. + *** + *** This function does not free either of the given parameters. + *** + *** @param object_driver The driver instance which was registered being unregistered. (unused) + *** @param session The session being closed. (unused) + *** Returns + ***/ +int clusterUnregister(pObjDriver object_driver, pObjSession session) + { + ci_FreeCaches(); + return 0; + } + + /** ================ Unimplemented Functions ================ **/ /** ANCHOR[id=unimplemented] **/ // LINK #functions @@ -3682,38 +4268,33 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt) *** - Initializing global data needed for the driver. *** *** @returns 0 if successful, or - *** a negative value if an error occured. + *** -1 if an error occured. ***/ int clusterInitialize(void) { - int ret; /** Initialize library. **/ ca_init(); /** Allocate the driver. **/ - pObjDriver drv = (pObjDriver)nmMalloc(sizeof(ObjDriver)); - if (drv == NULL) return -1; + pObjDriver drv = (pObjDriver)check_ptr(nmMalloc(sizeof(ObjDriver))); + if (drv == NULL) goto err; memset(drv, 0, sizeof(ObjDriver)); /** Initialize globals. **/ - memset(&ClusterCaches, 0, sizeof(ClusterCaches)); - ret = xhInit(&ClusterCaches.SourceCache, 251, 0); - if (ret < 0) return ret; - ret = xhInit(&ClusterCaches.ClusterCache, 251, 0); - if (ret < 0) return ret; - ret = xhInit(&ClusterCaches.SearchCache, 251, 0); - if (ret < 0) return ret; + memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); + if (!check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0))) goto err; + if (!check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0))) goto err; + if (!check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0))) goto err; /** Setup the structure. **/ - strcpy(drv->Name, "clu - Clustering Driver"); - drv->Capabilities = OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; // OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; - ret = xaInit(&(drv->RootContentTypes), 1); - if (ret < 0) return ret; - ret = xaAddItem(&(drv->RootContentTypes), "system/cluster"); - if (ret < 0) return ret; + if (check_ptr(strcpy(drv->Name, "clu - Clustering Driver")) == NULL) goto err; + if (!check(xaInit(&(drv->RootContentTypes), 1))) goto err; + if (!check_neg(xaAddItem(&(drv->RootContentTypes), "system/cluster"))) goto err; + drv->Capabilities = OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; /* TODO: Greg, double check these are correct. */ /** Setup the function references. **/ drv->Open = clusterOpen; + drv->OpenChild = NULL; drv->Close = clusterClose; drv->Create = clusterCreate; drv->Delete = clusterDelete; @@ -3734,9 +4315,12 @@ int clusterInitialize(void) drv->GetFirstMethod = clusterGetFirstMethod; drv->GetNextMethod = clusterGetNextMethod; drv->ExecuteMethod = clusterExecuteMethod; - drv->Commit = clusterCommit; - drv->Info = clusterInfo; drv->PresentationHints = clusterPresentationHints; + drv->Info = clusterInfo; + drv->Commit = clusterCommit; + drv->GetQueryCoverageMask = NULL; + drv->GetQueryIdentityPath = NULL; + drv->Unregister = clusterUnregister; /** Register some structures. **/ nmRegister(sizeof(ClusterData), "ClusterData"); @@ -3745,37 +4329,37 @@ int clusterInitialize(void) nmRegister(sizeof(NodeData), "ClusterNodeData"); nmRegister(sizeof(DriverData), "ClusterDriverData"); nmRegister(sizeof(ClusterQuery), "ClusterQuery"); - nmRegister(sizeof(ClusterCaches), "ClusterCaches"); + nmRegister(sizeof(ClusterDriverCaches), "ClusterDriverCaches"); /** Print debug size info. **/ - char cluster_size_buf[16]; - char search_size_buf[16]; - char source_size_buf[16]; - char node_size_buf[16]; - char driver_size_buf[16]; - char query_size_buf[16]; - char caches_size_buf[16]; + char buf1[16], buf2[16], buf3[16], buf4[16], buf5[16], buf6[16], buf7[16]; tprintf( "Cluster driver struct sizes:\n" + " > sizeof(SourceData): %s\n" " > sizeof(ClusterData): %s\n" " > sizeof(SearchData): %s\n" - " > sizeof(SourceData): %s\n" " > sizeof(NodeData): %s\n" " > sizeof(DriverData): %s\n" " > sizeof(ClusterQuery): %s\n" - " > sizeof(ClusterCaches): %s\n", - snprint_bytes(cluster_size_buf, sizeof(cluster_size_buf), sizeof(ClusterData)), - snprint_bytes(search_size_buf, sizeof(search_size_buf), sizeof(SearchData)), - snprint_bytes(source_size_buf, sizeof(source_size_buf), sizeof(SourceData)), - snprint_bytes(node_size_buf, sizeof(node_size_buf), sizeof(NodeData)), - snprint_bytes(driver_size_buf, sizeof(driver_size_buf), sizeof(DriverData)), - snprint_bytes(query_size_buf, sizeof(query_size_buf), sizeof(ClusterQuery)), - snprint_bytes(caches_size_buf, sizeof(caches_size_buf), sizeof(ClusterCaches)) + " > sizeof(ClusterDriverCaches): %s\n", + snprint_bytes(buf1, sizeof(buf1), sizeof(SourceData)), + snprint_bytes(buf2, sizeof(buf2), sizeof(ClusterData)), + snprint_bytes(buf3, sizeof(buf3), sizeof(SearchData)), + snprint_bytes(buf4, sizeof(buf4), sizeof(NodeData)), + snprint_bytes(buf5, sizeof(buf5), sizeof(DriverData)), + snprint_bytes(buf6, sizeof(buf6), sizeof(ClusterQuery)), + snprint_bytes(buf7, sizeof(buf7), sizeof(ClusterDriverCaches)) ); /** Register the driver. **/ - ret = objRegisterDriver(drv); - if (ret < 0) return ret; + if (!check(objRegisterDriver(drv))) goto err; + /** Success. **/ return 0; + + /** Error cleanup. **/ + err: + if (drv != NULL) nmFree(drv, sizeof(ObjDriver)); + fprintf(stderr, "Error: Failed to initialize cluster driver.\n"); + return -1; } diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index 5ef492de3..6b09a8586 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -1271,6 +1271,13 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) } else if (!strcmp(cmdname,"quit")) { + /** Loop through each driver and call their unregister handler, if they have one. **/ + for (unsigned int i = 0u; i < OSYS.Drivers.nItems; i++) + { + pObjDriver cur = (pObjDriver)OSYS.Drivers.Items[i]; + if (cur->Unregister != NULL) cur->Unregister(cur, s); + } + mlxCloseSession(ls); return 1; } From a861fb4a2f55241e5bfa1c2ac15c888ff2eccab8 Mon Sep 17 00:00:00 2001 From: Israel Date: Tue, 28 Oct 2025 10:58:21 -0600 Subject: [PATCH 005/361] Upgrade memory handling in the cluster driver. Improve edge case logic in comparison functions. Remove unregister driver function. Clean up exp_functions.c. --- centrallix-lib/include/clusters.h | 8 + centrallix-lib/src/clusters.c | 18 +- centrallix/expression/exp_functions.c | 2151 ++----------------------- centrallix/include/obj.h | 1 - centrallix/osdrivers/objdrv_cluster.c | 246 ++- centrallix/test_obj.c | 7 - 6 files changed, 326 insertions(+), 2105 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index d8b7f97c6..bddd0800c 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -79,6 +79,14 @@ int ca_kmeans( unsigned int* labels, double* vector_sims); +/** Vector helper macros. **/ +#define ca_is_empty(vector) (vector[0] == -CA_NUM_DIMS) +#define ca_has_no_pairs(vector) \ + ({ \ + __typeof__ (vector) _v = (vector); \ + _v[0] == -172 && _v[1] == 11 && _v[2] == -78; \ + }) + /** Comparison functions, for ca_search(). **/ double ca_cos_compare(void* v1, void* v2); double ca_lev_compare(void* str1, void* str2); diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 90599269c..864ff36eb 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -514,12 +514,18 @@ static unsigned int edit_dist(const char* str1, const char* str2, const size_t s ***/ double ca_cos_compare(void* v1, void* v2) { - /** Input validation checks. **/ - if (v1 == NULL || v2 == NULL) return 0.0; if (v1 == v2) return 1.0; + /** Input validation checks. **/ + const pVector vec1 = v1, vec2 = v2; + const bool v1_empty = (vec1 == NULL || ca_is_empty(vec1) || ca_has_no_pairs(vec1)); + const bool v2_empty = (vec2 == NULL || ca_is_empty(vec2) || ca_has_no_pairs(vec2)); + if (v1_empty && v2_empty) return 1.0; + if (v1_empty && !v2_empty) return 0.0; + if (!v1_empty && v2_empty) return 0.0; + /** Return the sparse similarity. **/ - return sparse_similarity((const pVector)v1, (const pVector)v2); + return sparse_similarity(vec1, vec2); } /*** Compares two strings using their levenstien edit distance to compute a @@ -544,12 +550,12 @@ double ca_lev_compare(void* str1, void* str2) if (str1 == NULL || str2 == NULL) return 0.0; if (str1 == str2) return 1.0; - /** Compute string length. **/ + /** Handle string length. **/ const size_t len1 = strlen(str1); const size_t len2 = strlen(str2); - - /** Empty strings are identical, avoiding a divide by zero. */ if (len1 == 0lu && len2 == 0lu) return 1.0; + if (len1 != 0lu && len2 == 0lu) return 0.0; + if (len1 != 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ const unsigned int dist = edit_dist((const char*)str1, (const char*)str2, len1, len2); diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index a8e16ecc7..4f9ffa563 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -67,6 +67,7 @@ #include "cxlib/mtlexer.h" #include "cxlib/mtsession.h" #include "cxlib/newmalloc.h" +#include "cxlib/util.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "cxss/cxss.h" @@ -3996,2021 +3997,177 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress return 0; } -/* See centrallix-sysdoc/string_comparison.md for more information. */ -int exp_fn_levenshtein(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - if (!i0 || !i1) +/*** Computes cosine or levenshtien similarity between two strings. These two + *** tasks have a large amount of overlapping logic (mostly error checking), + *** so doing them with one function greatly reduces code duplocation. + *** + *** @param tree The tree resulting from this function. + *** @param objlist The evaluation "scope", including available variables. + *** @param maybe_str1 Possibly the first string. + *** @param maybe_str2 Possibly the second string. + *** @param u1 Unused parameter. + *** @param is_cos Whether to compute cosine or levenshtien. + ***/ +static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1, bool is_cos) + { + const char fn_name[] = "cos_cmp"; + + /** Check number of arguments. **/ + const int num_params = tree->Children.nItems; + if (num_params != 2) { - mssError(1,"EXP","levenshtein() requires two parameters"); - return -1; + mssErrorf(1, "EXP", "%s(?) expects 2 parameters, got %d parameters.", fn_name, num_params); + return -1; } - - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) + if (maybe_str1 == NULL || maybe_str2 == NULL || u1 != NULL) { - tree->DataType = DATA_T_INTEGER; - tree->Flags |= EXPR_F_NULL; - return 0; + mssErrorf(1, "EXP", "%s(?) expects 2 parameters.", fn_name); + return -1; } - - if ((i0->DataType != DATA_T_STRING) || (i1->DataType != DATA_T_STRING)) + + /** Magic checks. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + ASSERTMAGIC(maybe_str1, MGK_EXPRESSION); + ASSERTMAGIC(maybe_str2, MGK_EXPRESSION); + + /** Check object list. **/ + if (objlist == NULL) { - mssError(1,"EXP","levenshtein() requires two string parameters"); - return -1; + mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); + return -1; } - - // for all i and j, d[i,j] will hold the Levenshtein distance between - // the first i characters of s and the first j characters of t - int length1 = strlen(i0->String); - int length2 = strlen(i1->String); - //int levMatrix[length1+1][length2+1]; - int (*levMatrix)[length1+1][length2+1] = nmSysMalloc(sizeof(*levMatrix)); - int i; - int j; - //set each element in d to zero - for (i = 0; i < length1; i++) - { - for (j = 0; j < length2; j++) - { - (*levMatrix)[i][j] = 0; - } - } - - // source prefixes can be transformed into empty string by - // dropping all characters - for (i = 0; i <= length1; i++) - { - (*levMatrix)[i][0] = i; - } - - // target prefixes can be reached from empty source prefix - // by inserting every character - for (j = 0; j <= length2; j++) - { - (*levMatrix)[0][j] = j; - } + ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - for (i = 1; i <= length1; i++) - { - for (j = 1; j <= length2; j++) - { - if (i0->String[i-1] == i1->String[j-1]) - { - (*levMatrix)[i][j] = (*levMatrix)[i-1][j-1]; - } - else - { - int value1 = (*levMatrix)[i - 1][j] + 1; - int value2 = (*levMatrix)[i][j-1] + 1; - int value3 = (*levMatrix)[i-1][j-1] + 1; - (*levMatrix)[i][j] = (value1 < value2) ? - ((value1 < value3) ? value1 : value3) : - (value2 < value3) ? value2 : value3; - } - } - } - tree->DataType = DATA_T_INTEGER; - tree->Integer = (*levMatrix)[length1][length2]; - nmSysFree(levMatrix); - return 0; - } - -/* See centrallix-sysdoc/string_comparison.md for more information. */ -int exp_fn_lev_compare(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - - if (!i0 || !i1) + /** Extract str1. **/ + if (maybe_str1->Flags & EXPR_F_NULL) { - mssError(1,"EXP","lev_compare() requires two or three parameters"); - return -1; + mssErrorf(1, "EXP", "%s(NULL, ...) str1 cannot be NULL.", fn_name); + return -1; } - - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL) || (i2 && (i2->Flags & EXPR_F_NULL))) + if (maybe_str1->DataType != DATA_T_STRING) { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; + mssErrorf(1, "EXP", "%s(\?\?\?, ..) str1 should be a string.", fn_name); + return -1; } - - if ((i0->DataType != DATA_T_STRING) || (i1->DataType != DATA_T_STRING) || (i2 && i2->DataType != DATA_T_INTEGER)) + char* str1 = maybe_str1->String; + if (str1 == NULL) { - mssError(1,"EXP","lev_compare() requires two string and one optional integer parameters"); - return -1; + mssErrorf(1, "EXP", + "%s(nothing?, ...) expected string from str1 (of datatype DataType = " + "DATA_T_STRING), but the String was NULL or did not exist!", + fn_name + ); + return -1; } - exp_fn_levenshtein(tree, objlist, i0, i1, i2); - //!!! I am not checking for errors here, because IN THEORY we have two strings... if we don't, big uh-oh. - int lev_dist = tree->Integer; - - int length1 = strlen(i0->String); - int length2 = strlen(i1->String); - - double clamped_dist = 1.0; - - if (length1 == 0 || length2 == 0) //empty string + /** Extract str2. **/ + if (maybe_str2->Flags & EXPR_F_NULL) { - clamped_dist = 0.5; - } - else //normal case - { - int max_len = (length1 > length2) ? length1 : length2; - clamped_dist = ((double) lev_dist) / max_len; - - if (abs(length1-length2) == lev_dist) //only inserts. Maybe substring. - { - clamped_dist /= 2; - } - - //use max_field_width if it was provided as a sensible value. If not, don't use it. - double max_field_width = i2?(i2->Integer):0; - if (max_field_width && max_field_width >= max_len) { - double mod = (lev_dist + max_field_width * 3/4) / max_field_width; - if (mod < 1) { //don't make clamped_dist bigger - clamped_dist *= mod; - } - } + mssErrorf(1, "EXP", "%s(\"%s\", NULL) str2 cannot be NULL.", fn_name, str1); + return -1; } - - - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = 1.0 - clamped_dist; - return 0; -} - -/* - * hash_char_pair - * This method creates an vector table index based a given character pair. The characters are represented - * as their ASCII code points. - * - * Parameters: - * num1 : first ASCII code point (double) - * num2 : second ASCII code point (double) - * - * Returns: - * vector table index (integer) - */ -int exp_fn_i_hash_char_pair(double num1, double num2) - { - int func_result = round(((num1 * num1 * num1) + (num2 * num2 * num2)) * ((num1+1)/(num2+1))) -1; - return func_result % EXP_VECTOR_TABLE_SIZE; - } - - -/* - * exp_fn_i_frequency_table - * This method creates a vector frequency table based on a string of characters. - * - * Parameters: - * table : integer pointer to vector frequency table (unsigned short) - * term : the string of characters (char*) - * - * Returns: - * 0 - * - * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_i_frequency_table - */ -int exp_fn_i_frequency_table(unsigned short *table, char *term) - { - int i; - // Initialize hash table with 0 values - for (i = 0; i < EXP_VECTOR_TABLE_SIZE; i++) + if (maybe_str2->DataType != DATA_T_STRING) { - table[i] = 0; + mssErrorf(1, "EXP", "%s(\"%s\", \?\?\?) str2 should be a string.", fn_name, str1); + return -1; } - - int j = -1; - for(i = 0; i < strlen(term) + 1; i++) + char* str2 = maybe_str2->String; + if (str2 == NULL) { - // If latter character is punctuation or whitespace, skip it - if (ispunct(term[i]) || isspace(term[i])) - { - continue; - } - - double temp1 = 0.0; - double temp2 = 0.0; - - // If previous character is null - if (j == -1) - { - temp1 = 96; - } - - // Else character is not null - else - { - temp1 = (int)tolower(term[j]); - } - - // If latter character is null - if (i == strlen(term)) + mssErrorf(1, "EXP", + "%s(\"%s\", nothing?) expected string from str2 (of datatype DataType = " + "DATA_T_STRING), but the String was NULL or did not exist!", + fn_name, str1 + ); + return -1; + } + + /** Handle either cos_cmp or lev_cmp. **/ + if (is_cos) + { /* cos_cmp */ + int ret; + + /** Build vectors. **/ + const pVector v1 = check_ptr(ca_build_vector(str1)); + const pVector v2 = check_ptr(ca_build_vector(str2)); + if (v1 == NULL || v2 == NULL) { - temp2 = 96; + mssErrorf(1, "EXP", + "%s(\"%s\", \"%s\") - Failed to build vectors.", + fn_name, str1, str2 + ); + ret = -1; } - - // Else character is not null else { - temp2 = (int)tolower(term[i]); - } - - // Else character is not null // If either character is a number, reassign the code point - if (temp1 >= 48 && temp1 <= 57) - { - temp1 += 75; - } - - if (temp2 >= 48 && temp2 <= 57) - { - temp2 += 75; + tree->Types.Double = ca_cos_compare(v1, v2); + tree->DataType = DATA_T_DOUBLE; + ret = 0; } - - // Hash the character pair into an index - int index = exp_fn_i_hash_char_pair(temp1, temp2); - - // Increment Frequency Table value by number from 0 to 13 - table[index] += ((unsigned short)temp1 + (unsigned short)temp2) % 13 + 1; - - // Move j up to latter character before incrementing i - j = i; - + + if (v1 != NULL) ca_free_vector(v1); + if (v2 != NULL) ca_free_vector(v2); + return ret; } - - return 0; - - } - -/* - * exp_fn_i_dot_product - * This method calculautes the dot product of two vectors. - * - * Parameters: - * dot_product : the place where the result is stored (double) - * r_freq_table1 : the first vector (unsigned short) - * r_freq_table2 : the second vector (unsigned short) - * - * Returns: - * 0 - * - * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_i_dot_product - */ -int exp_fn_i_dot_product(double *dot_product, unsigned short *r_freq_table1, unsigned short *r_freq_table2) - { - int i; - for (i = 0; i < EXP_VECTOR_TABLE_SIZE; i++) - { - *dot_product = *dot_product + ((double)r_freq_table1[i] * (double)r_freq_table2[i]); + else + { /* lev_cmp */ + tree->Types.Double = ca_lev_compare(str1, str2); + tree->DataType = DATA_T_DOUBLE; + return 0; } - return 0; + return -1; } -/* - * exp_fn_i_magnitude - * This method calculates the magnitude of a vector - * - * Parameters: - * magnitude : the place where the result is stored (double) - * r_freq_table : the vector (unsigned short) - * - * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_i_magnitude - */ -int exp_fn_i_magnitude(double *magnitude, unsigned short *r_freq_table) +/*** Computes cosine similarity between two strings. + *** + *** @param tree The tree resulting from this function. + *** @param objlist The evaluation "scope", including available variables. + *** @param maybe_str1 Possibly the first string. + *** @param maybe_str2 Possibly the second string. + *** @param u1 Unused parameter. + ***/ +int exp_fn_cos_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) { - int i; - for (i = 0; i < EXP_VECTOR_TABLE_SIZE; i++) - { - *magnitude = *magnitude + ((double)r_freq_table[i] * (double)r_freq_table[i]); - } - *magnitude = sqrt(*magnitude); - return 0; + return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, true); } -/* - * exp_fn_cos_compare - * This method calculates the cosine similarity of two vector frequency tables - * See centrallix-sysdoc/string_comparison.md for more information. - * - * Parameters: - * tree : structure where output is stored - * objlist : unused - * i0 : first data entry (pExpression) - * i1 : second data entry (pExpression) - * i2 : unused - * - * Returns: - * 0 - * - * LINK ../../centrallix-sysdoc/string_comparison.md#exp_fn_similarity - */ -int exp_fn_cos_compare(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +/*** Computes levenshtein similarity by normalizing the levenshtein edit + *** distance between two strings with the length of the longer string. + *** + *** @param tree The tree resulting from this function. + *** @param objlist The evaluation "scope", including available variables. + *** @param maybe_str1 Possibly the first string. + *** @param maybe_str2 Possibly the second string. + *** @param u1 Unused parameter. + ***/ +int exp_fn_lev_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) { - // Ensure function receives two non-null parameters - if (!i0 || !i1) - { - mssError(1,"EXP","cos_compare() requires two parameter."); - return -1; - } - - // Ensure value passed in both parameters is not null - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) - { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; - } - - // Ensure both parameters contain string values - if ((i0->DataType != DATA_T_STRING) || (i1->DataType != DATA_T_STRING)) - { - mssError(1,"EXP","cos_compare() requires two string parameters."); - return -1; - } - - //If the two strings are identical, don't bother running cosine compare - if (strcmp(i0->String, i1->String) == 0) - { - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = 1.0; - return 0; - } - - // Allocate frequency tables (arrays of integers) for each term - unsigned short *table1 = nmMalloc(EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - unsigned short *table2 = nmMalloc(EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - - if (table1 == NULL || table2 == NULL) - { - mssError(1,"EXP","Memory allocation failed."); - return -1; - } - - // Calculate frequency tables for each term - exp_fn_i_frequency_table(table1, i0->String); - exp_fn_i_frequency_table(table2, i1->String); - - // Calculate dot product - double dot_product = 0; - exp_fn_i_dot_product(&dot_product, table1, table2); - - // Calculate magnitudes of each relative frequency vector - double magnitude1 = 0; - double magnitude2 = 0; - exp_fn_i_magnitude(&magnitude1, table1); - exp_fn_i_magnitude(&magnitude2, table2); - - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = dot_product / (magnitude1 * magnitude2); - nmFree(table1, EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - nmFree(table2, EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - - return 0; + return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, false); } -// /*** ========================= -// *** DUPE SECTION -// *** By: Israel Fuller -// *** Last Updated: September, 2025 -// *** -// *** This section of the file deals with finding duplocates. -// ***/ - -// /*** @brief Returns the smaller of two values. -// *** -// *** @param a The first value. -// *** @param b The second value. -// *** @return The smaller of the two values. -// *** -// *** @note This macro uses GNU C extensions and is type-safe. -// ***/ -// #define min(a, b) ({ \ -// __typeof__ (a) _a = (a); \ -// __typeof__ (b) _b = (b); \ -// (_a < _b) ? _a : _b; \ -// }) - -// /*** @brief Returns the larger of two values. -// *** -// *** @param a The first value. -// *** @param b The second value. -// *** @return The larger of the two values. -// *** -// *** @note This macro uses GNU C extensions and is type-safe. -// ***/ -// #define max(a, b) ({ \ -// __typeof__ (a) _a = (a); \ -// __typeof__ (b) _b = (b); \ -// (_a > _b) ? _a : _b; \ -// }) - -// /** The character used to create a pair with the first and last characters of a string. **/ -// #define EXP_BOUNDARY_CHAR ('a' - 1) - -// /*** Helpful error handling function. **/ -// void mssErrorf(int clr, char* module, const char* format, ...); - -// /*** Gets the hash, representing a pair of ASCII characters, represented by unsigned ints. -// *** -// *** @param num1 The first character in the pair. -// *** @param num1 The second character in the pair. -// *** @returns The resulting hash. -// ***/ -// unsigned int exp_fn_get_char_pair_hash(const unsigned int num1, const unsigned int num2) -// { -// if (num1 == EXP_BOUNDARY_CHAR && num2 == EXP_BOUNDARY_CHAR) -// { -// mssErrorf(1, "EXP", -// "exp_fn_get_char_pair_hash(%u, %u) - Warning: Pair of boundary characters.", -// num1, num2 -// ); -// } -// const double sum = (num1 * num1 * num1) + (num2 * num2 * num2); -// const double scale = ((double)num1 + 1.0) / ((double)num2 + 1.0); -// const unsigned int hash = (unsigned int)round(sum * scale) - 1u; -// return hash % EXP_NUM_DIMS; -// } - -// /*** Builds a vector using a string. -// *** -// *** Vectors are based on the frequencies of character pairs in the string. -// *** Space characters and punctuation characters (see code for list) are ignored, -// *** and all characters are converted to lowercase. Character 96, which is just -// *** before 'a' in the ASCII table (and maps to '`') is used to make pairs on the -// *** start and end of strings. The only supported characters for the passed char* -// *** are spaces, punctuation, uppercase and lowercase letters, and numbers. -// *** -// *** This results in the following modified ASCII table: -// *** ```csv -// *** #, char, #, char, #, char -// *** 97, a, 109, m, 121, y -// *** 98, b, 110, n, 122, z -// *** 99, c, 111, o, 123, 0 -// *** 100, d, 112, p, 124, 1 -// *** 101, e, 113, q, 125, 2 -// *** 102, f, 114, r, 126, 3 -// *** 103, g, 115, s, 127, 4 -// *** 104, h, 116, t, 128, 5 -// *** 105, i, 117, u, 129, 6 -// *** 106, j, 118, v, 130, 7 -// *** 107, k, 119, w, 131, 8 -// *** 108, l, 120, x, 132, 9 -// *** ``` -// *** Thus, any number from 96 (the start/end character) to 132 ('9') is a valid -// *** input to get_char_pair_hash(). -// *** -// *** After hashing each character pair, we add some number from 1 to 13 to the -// *** coresponding dimention. However, for most names, this results in a lot of -// *** zeros and a FEW positive numbers. Thus, after creating the dense vector, -// *** we convert it to a sparse vector in which a negative number replaces a run -// *** of that many zeros. Consider the following example: -// *** -// *** Dense Vector: `[1,0,0,0,3,0]` -// *** -// *** Sparse Vector: `[1,-3,3,-1]` -// *** -// *** Using these sparse vectors greatly reduces the required memory and gives -// *** aproximately an x5 boost to performance when traversing vectors, at the -// *** cost of more algorithmically complex code. -// *** -// *** @param str The string to be divided into pairs and hashed to make the vector. -// *** @returns The sparse vector built using the hashed character pairs. -// ***/ -// int* build_vector(char* str) { -// /** Allocate space for a dense vector. **/ -// unsigned int dense_vector[EXP_NUM_DIMS] = {0u}; - -// /** j is the former character, i is the latter. **/ -// const unsigned int num_chars = (unsigned int)strlen(str); -// for (unsigned int j = 65535u, i = 0u; i <= num_chars; i++) -// { -// /** isspace: space, \n, \v, \f, \r **/ -// if (isspace(str[i])) continue; - -// /** ispunct: !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ **/ -// if (ispunct(str[i]) && str[i] != EXP_BOUNDARY_CHAR) continue; - -// /*** iscntrl (0-8): SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS -// *** (14-31): SO, SI, DLE, DC1-4, NAK, SYN, ETB, CAN -// *** EM, SUB, ESC, FS, GS, RS, US -// ***/ -// if (iscntrl(str[i]) && i != num_chars) { -// mssErrorf(1, "EXP", -// "build_vector(%s) - Warning: Skipping unknown character #%u.\n", -// str, (unsigned int)str[i] -// ); -// continue; -// } - -// /** First and last character should fall one before 'a' in the ASCII table. **/ -// unsigned int temp1 = (j == 65535u) ? EXP_BOUNDARY_CHAR : (unsigned int)tolower(str[j]); -// unsigned int temp2 = (i == num_chars) ? EXP_BOUNDARY_CHAR : (unsigned int)tolower(str[i]); - -// /** Shift numbers to the end of the lowercase letters. **/ -// if ('0' <= temp1 && temp1 <= '9') temp1 += 75u; -// if ('0' <= temp2 && temp2 <= '9') temp2 += 75u; - -// /** Hash the character pair into an index (dimension). **/ -// /** Note that temp will be between 97 ('a') and 132 ('9'). **/ -// unsigned int dim = exp_fn_get_char_pair_hash(temp1, temp2); - -// /** Increment the dimension of the dense vector by a number from 1 to 13. **/ -// dense_vector[dim] += (temp1 + temp2) % 13u + 1u; - -// j = i; -// } - -// /** Count how much space is needed for a sparse vector. **/ -// bool zero_prev = false; -// size_t size = 0u; -// for (unsigned int dim = 0u; dim < EXP_NUM_DIMS; dim++) -// { -// if (dense_vector[dim] == 0u) -// { -// size += (zero_prev) ? 0u : 1u; -// zero_prev = true; -// } -// else -// { -// size++; -// zero_prev = false; -// } -// } - -// /*** Check compression size. -// *** If this check fails, I doubt anything will break. However, the longest -// *** word I know (supercalifragilisticexpialidocious) has only 35 character -// *** pairs, so it shouldn't reach half this size (and it'd be even shorter -// *** if the hash generates at least one collision). -// *** -// *** Bad vector compression will result in degraded performace and increased -// *** memory usage, and likely also indicates a bug or modified assumption -// *** elsewhere in the code. -// *** -// *** If this warning is ever generated, it's definitely worth investigating. -// ***/ -// const size_t expected_max_size = 64u; -// if (size > expected_max_size) -// { -// mssErrorf(1, "EXP" -// "build_vector(%s) - Warning: Sparse vector larger than expected.\n" -// " > Size: %lu\n" -// " > #Dims: %u\n", -// str, -// size, -// EXP_NUM_DIMS -// ); -// } - -// /** Allocate space for sparse vector. **/ -// const size_t sparse_vector_size = size * sizeof(int); -// int* sparse_vector = (int*)nmSysMalloc(sparse_vector_size); -// if (sparse_vector == NULL) { -// mssErrorf(1, "EXP", -// "build_vector(%s) - nmSysMalloc(%lu) failed.", -// str, sparse_vector_size -// ); -// return NULL; -// } - -// /** Convert the dense vector above to a sparse vector. **/ -// unsigned int j = 0u, sparse_idx = 0u; -// while (j < EXP_NUM_DIMS) -// { -// if (dense_vector[j] == 0u) -// { -// /*** Count and store consecutive zeros, except the first one, -// *** which we already know is zero. -// ***/ -// unsigned int zero_count = 1u; -// j++; -// while (j < EXP_NUM_DIMS && dense_vector[j] == 0u) -// { -// zero_count++; -// j++; -// } -// sparse_vector[sparse_idx++] = (int)-zero_count; -// } -// else -// { -// /** Store the value. **/ -// sparse_vector[sparse_idx++] = (int)dense_vector[j++]; -// } -// } - -// return sparse_vector; -// } - -// /*** Compute the magnitude of a sparsely allocated vector. -// *** -// *** @param vector The vector. -// *** @returns The computed magnitude. -// ***/ -// double exp_fn_magnitude_sparse(const int* vector) -// { -// unsigned int magnitude = 0u; -// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) -// { -// const int val = vector[i++]; - -// /** Negative val represents -val 0s in the array, so skip that many values. **/ -// if (val < 0) dim += (unsigned)(-val); - -// /** We have a param_value, so square it and add it to the magnitude. **/ -// else { magnitude += (unsigned)(val * val); dim++; } -// } -// return sqrt((double)magnitude); -// } - -// /*** Compute the magnitude of a densely allocated centroid. -// *** -// *** @param centroid The centroid. -// *** @returns The computed magnitude. -// ***/ -// double exp_fn_magnitude_dense(const double* centroid) -// { -// double magnitude = 0.0; -// for (int i = 0; i < EXP_NUM_DIMS; i++) -// magnitude += centroid[i] * centroid[i]; -// return sqrt(magnitude); -// } - -// /*** Parse a token from a sparsely allocated vector and write the param_value and -// *** number of remaining values to the passed locations. -// *** -// *** @param token The sparse vector token being parsed. -// *** @param remaining The location to save the remaining number of characters. -// *** @param param_value The location to save the param_value of the token. -// ***/ -// void exp_fn_parse_token(const int token, unsigned int* remaining, unsigned int* param_value) { -// if (token < 0) -// { -// /** This run contains -token zeros. **/ -// *remaining = (unsigned)(-token); -// *param_value = 0u; -// } -// else -// { -// /** This run contains one param_value. **/ -// *remaining = 1u; -// *param_value = (unsigned)(token); -// } -// } - -// /*** Calculate the similarity on sparcely allocated vectors. Comparing -// *** any string to an empty string should always return 0.5 (untested). -// *** -// *** @param v1 Sparse vector #1. -// *** @param v2 Sparse vector #2. -// *** @returns Similarity between 0 and 1 where -// *** 1 indicates identical and -// *** 0 indicates completely different. -// ***/ -// double exp_fn_sparse_similarity(const int* v1, const int* v2) -// { -// /** Calculate dot product. **/ -// unsigned int vec1_remaining = 0u, vec2_remaining = 0u; -// unsigned int dim = 0u, i1 = 0u, i2 = 0u, dot_product = 0u; -// while (dim < EXP_NUM_DIMS) -// { -// unsigned int val1 = 0u, val2 = 0u; -// if (vec1_remaining == 0u) exp_fn_parse_token(v1[i1++], &vec1_remaining, &val1); -// if (vec2_remaining == 0u) exp_fn_parse_token(v2[i2++], &vec2_remaining, &val2); - -// /*** Accumulate the dot_product. If either vector is 0 here, -// *** the total is 0 and this statement does nothing. -// ***/ -// dot_product += val1 * val2; - -// /** Consume overlap from both runs. **/ -// unsigned int overlap = min(vec1_remaining, vec2_remaining); -// vec1_remaining -= overlap; -// vec2_remaining -= overlap; -// dim += overlap; -// } - -// /** Optional optimization to speed up nonsimilar vectors. **/ -// if (dot_product == 0u) return 0.0; - -// /** Return the difference score. **/ -// return (double)dot_product / (exp_fn_magnitude_sparse(v1) * exp_fn_magnitude_sparse(v2)); -// } - -// /*** Calculate the difference on sparcely allocated vectors. Comparing -// *** any string to an empty string should always return 0.5 (untested). -// *** -// *** @param v1 Sparse vector #1. -// *** @param v2 Sparse vector #2. -// *** @returns Similarity between 0 and 1 where -// *** 1 indicates completely different and -// *** 0 indicates identical. -// ***/ -// #define exp_fn_sparse_dif(v1, v2) (1.0 - exp_fn_sparse_similarity(v1, v2)) - -// /*** Calculate the similarity between a sparsely allocated vector -// *** and a densely allocated centroid. Comparing any string to an -// *** empty string should always return 0.5 (untested). -// *** -// *** @param v1 Sparse vector #1. -// *** @param c1 Dense centroid #2. -// *** @returns Similarity between 0 and 1 where -// *** 1 indicates identical and -// *** 0 indicates completely different. -// ***/ -// double exp_fn_sparse_similarity_c(const int* v1, const double* c2) -// { -// /** Calculate dot product. **/ -// double dot_product = 0.0; -// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) -// { -// const int val = v1[i++]; - -// /** Negative val represents -val 0s in the array, so skip that many values. **/ -// if (val < 0) dim += (unsigned)(-val); - -// /** We have a param_value, so square it and add it to the magnitude. **/ -// else dot_product += (double)val * c2[dim++]; -// } - -// /** Return the difference score. **/ -// return dot_product / (exp_fn_magnitude_sparse(v1) * exp_fn_magnitude_dense(c2)); -// } - -// /*** Calculate the difference between a sparsely allocated vector -// *** and a densely allocated centroid. Comparing any string to an -// *** empty string should always return 0.5 (untested). -// *** -// *** @param v1 Sparse vector #1. -// *** @param c1 Dense centroid #2. -// *** @returns Difference between 0 and 1 where -// *** 1 indicates completely different and -// *** 0 indicates identical. -// ***/ -// #define exp_fn_sparse_dif_c(v1, c2) (1.0 - exp_fn_sparse_similarity_c(v1, c2)) - -// /*** Calculate the average size of all clusters in a set of vectors. -// *** -// *** @param vectors The vectors of the dataset (allocated sparsely). -// *** @param num_vectors The number of vectors in the dataset. -// *** @param labels The clusters to which vectors are assigned. -// *** @param centroids The locations of the centroids (allocated densely). -// *** @param num_clusters The number of centroids (k). -// *** @returns The average cluster size. -// ***/ -// double exp_fn_get_cluster_size( -// int** vectors, -// const unsigned int num_vectors, -// unsigned int* labels, -// double centroids[][EXP_NUM_DIMS], -// const unsigned int num_clusters -// ) -// { -// double cluster_sums[num_clusters]; -// unsigned int cluster_counts[num_clusters]; -// for (unsigned int i = 0u; i < num_clusters; i++) -// cluster_sums[i] = 0.0; -// memset(cluster_counts, 0, sizeof(cluster_counts)); - -// /** Sum the difference from each vector to its cluster centroid. **/ -// for (unsigned int i = 0u; i < num_vectors; i++) -// { -// const unsigned int label = labels[i]; -// cluster_sums[label] += exp_fn_sparse_dif_c(vectors[i], centroids[label]); -// cluster_counts[label]++; -// } - -// /** Add up the average cluster size. **/ -// double cluster_total = 0.0; -// unsigned int num_valid_clusters = 0u; -// for (unsigned int label = 0u; label < num_clusters; label++) -// { -// const unsigned int cluster_count = cluster_counts[label]; -// if (cluster_count == 0u) continue; - -// cluster_total += cluster_sums[label] / cluster_count; -// num_valid_clusters++; -// } - -// /** Return average sizes. **/ -// return cluster_total / num_valid_clusters; -// } - -// /*** Compute the param_value for `k` (number of clusters), given a dataset of with -// *** a size of `n`. -// *** -// *** The following table shows data sizes vs.selected cluster size. In testing, -// *** these numbers tended to givea good balance of accuracy and dulocates detected. -// *** -// *** ```csv -// *** Data Size, Actual -// *** 10k, 12 -// *** 100k, 33 -// *** 1M, 67 -// *** 4M, 93 -// *** ``` -// *** -// *** This function is not intended for datasets smaller than (`n < ~2000`). -// *** These should be handled using complete search. -// *** -// *** LaTeX Notation: \log_{36}\left(n\right)^{3.1}-8 -// *** -// *** @param n The size of the dataset. -// *** @returns k, the number of clusters to use. -// *** -// *** Complexity: `O(1)` -// ***/ -// unsigned int exp_fn_compute_k(const unsigned int n) -// { -// return (unsigned)max(2, pow(log(n) / log(36), 3.2) - 8); -// } - -// /*** Executes the k-means clustering algorithm. Selects NUM_CLUSTERS random -// *** vectors as initial centroids. Then points are assigned to the nearest -// *** centroid, after which centroids are moved to the center of their points. -// *** -// *** @param vectors The vectors to cluster. -// *** @param num_vectors The number of vectors to cluster. -// *** @param labels Stores the final cluster identities of the vectors after -// *** clustering is completed. -// *** @param centroids Stores the locations of the centroids used for the clusters -// *** of the data. -// *** @param iterations The number of iterations that actually executed is stored -// *** here. Leave this NULL if you don't care. -// *** @param max_iter The max number of iterations. -// *** @param num_clusters The number of clusters to generate. -// *** -// *** @attention - Assumes: num_vectors is the length of vectors. -// *** @attention - Assumes: num_clusters is the length of labels. -// *** -// *** @attention - Issue: At larger numbers of clustering iterations, some -// *** clusters have a size of negative infinity. In this implementation, -// *** the bug is mitigated by setting a small number of max iterations, -// *** such as 16 instead of 100. -// *** @attention - Issue: Clusters do not apear to improve much after the first -// *** iteration, which puts the efficacy of the algorithm into question. This -// *** may be due to the uneven density of a typical dataset. However, the -// *** clusters still offer useful information. -// *** -// *** Complexity: -// *** -// *** - `O(kd + k + i*(k + n*(k+d) + kd))` -// *** -// *** - `O(kd + k + ik + ink + ind + ikd)` -// *** -// *** - `O(nk + nd)` -// ***/ -// void exp_fn_kmeans( -// int** vectors, -// const unsigned int num_vectors, -// unsigned int* labels, -// const unsigned int num_clusters, -// const unsigned int max_iter -// ) -// { -// // const size_t centroids_size = num_clusters * sizeof(double*); -// // const size_t centroid_size = EXP_NUM_DIMS * sizeof(double); -// // double** centroids = (double**)nmMalloc(centroids_size); -// // if (centroids == NULL) -// // { -// // fprintf(stderr, "exp_fn_kmeans() - nmMalloc(%u) failed.\n", centroids_size); -// // return; -// // } -// // for (int i = 0; i < num_clusters; i++) -// // { -// // double* centroid = centroids[i] = (double*)nmMalloc(centroid_size); -// // if (centroid == NULL) -// // { -// // fprintf(stderr, "exp_fn_kmeans() - nmMalloc(%u) failed.\n", centroid_size); -// // return; -// // } -// // memset(centroids[i], 0, centroid_size); -// // } -// double centroids[num_clusters][EXP_NUM_DIMS]; -// memset(centroids, 0, sizeof(centroids)); - -// /** Select random vectors to use as the initial centroids. **/ -// srand(time(NULL)); -// for (unsigned int i = 0u; i < num_clusters; i++) -// { -// // Pick a random vector. -// const unsigned int random_index = (unsigned int)rand() % num_vectors; - -// // Sparse copy the vector into a densely allocated centroid. -// double* centroid = centroids[i]; -// const int* vector = vectors[random_index]; -// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) -// { -// const int token = vector[i++]; -// if (token > 0) centroid[dim++] = (double)token; -// else for (unsigned int j = 0u; j < -token; j++) centroid[dim++] = 0.0; -// } -// } - -// /** Allocate memory for new centroids. **/ -// double new_centroids[num_clusters][EXP_NUM_DIMS]; - -// /** Main exp_fn_kmeans loop. **/ -// double old_average_cluster_size = 1.0; -// unsigned int cluster_counts[num_clusters]; -// for (unsigned int iter = 0u; iter < max_iter; iter++) -// { -// bool changed = false; - -// /** Reset new centroids. **/ -// for (unsigned int i = 0u; i < num_clusters; i++) -// { -// cluster_counts[i] = 0u; -// for (unsigned int dim = 0; dim < EXP_NUM_DIMS; dim++) -// new_centroids[i][dim] = 0.0; -// } - -// /** Assign each point to the nearest centroid. **/ -// for (unsigned int i = 0u; i < num_vectors; i++) -// { -// const int* vector = vectors[i]; -// double min_dist = DBL_MAX; -// unsigned int best_centroid_label = 0u; - -// // Find nearest centroid. -// for (unsigned int j = 0u; j < num_clusters; j++) -// { -// const double dist = exp_fn_sparse_dif_c(vector, centroids[j]); -// if (dist < min_dist) -// { -// min_dist = dist; -// best_centroid_label = j; -// } -// } - -// /** Update label to new centroid, if necessary. **/ -// if (labels[i] != best_centroid_label) -// { -// labels[i] = best_centroid_label; -// changed = true; -// } - -// /** Accumulate values for new centroid calculation. **/ -// double* best_centroid = new_centroids[best_centroid_label]; -// for (unsigned int i = 0u, dim = 0u; dim < EXP_NUM_DIMS;) -// { -// const int val = vector[i++]; -// if (val < 0) dim += (unsigned)(-val); -// else best_centroid[dim++] += (double)val; -// } -// cluster_counts[best_centroid_label]++; -// } - -// /** Stop if centroids didn't change. **/ -// if (!changed) break; - -// /** Update centroids. **/ -// for (unsigned int i = 0u; i < num_clusters; i++) -// { -// if (cluster_counts[i] == 0u) continue; -// double* centroid = centroids[i]; -// const double* new_centroid = new_centroids[i]; -// const unsigned int cluster_count = cluster_counts[i]; -// for (unsigned int dim = 0u; dim < EXP_NUM_DIMS; dim++) -// centroid[dim] = new_centroid[dim] / cluster_count; -// } - -// /** Print cluster size for debugging. **/ -// const double average_cluster_size = exp_fn_get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters); - -// /** Is there enough improvement? **/ -// const double improvement = old_average_cluster_size - average_cluster_size; -// if (improvement < KMEANS_IMPROVEMENT_THRESHOLD) break; -// old_average_cluster_size = average_cluster_size; -// } - -// // Free unused memory. -// // for (int i = 0; i < num_clusters; i++) { -// // nmFree(centroids[i], centroid_size); -// // } -// // nmFree(centroids, centroids_size); -// } - -// /** Duplocate information. **/ -// typedef struct -// { -// unsigned int id1; -// unsigned int id2; -// double similarity; -// } -// Dup, *pDup; - -// /*** Runs complete search to find duplocates if `num_vectors < MAX_COMPLETE_SEARCH` -// *** and runs a search using k-means clustering on larger amounts of data. -// *** -// *** @param vectors Array of precomputed frequency vectors for all dataset strings. -// *** @param num_vectors The number of vectors to be scanned. -// *** @param dupe_threshold The similarity threshold, below which dups are ignored. -// *** @returns The duplicates in pDup structs. -// ***/ -// pXArray lightning_search(int** vectors, const unsigned int num_vectors, const double dupe_threshold) -// { -// /** Allocate space for dups. **/ -// const size_t guess_size = num_vectors * 2u; -// pXArray dups = xaNew(guess_size); -// if (dups == NULL) -// { -// mssErrorf(1, "EXP", "lightning_search() - xaNew(%lu) failed.", guess_size); -// return NULL; -// } - -// /** Descide which algorithm to use. **/ -// if (num_vectors <= MAX_COMPLETE_SEARCH) -// { /** Do a complete search. **/ -// for (unsigned int i = 0u; i < num_vectors; i++) -// { -// const int* v1 = vectors[i]; -// for (unsigned int j = i + 1u; j < num_vectors; j++) -// { -// const int* v2 = vectors[j]; -// const double similarity = exp_fn_sparse_similarity(v1, v2); -// if (similarity > dupe_threshold) // Dup found! -// { -// Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); -// if (dup == NULL) -// { -// mssErrorf(1, "EXP", "lightning_search() - nmMalloc(%lu) failed.", sizeof(Dup)); -// goto err_free_dups; -// } - -// dup->id1 = i; -// dup->id2 = j; -// dup->similarity = similarity; -// xaAddItem(dups, (void*)dup); -// } -// } -// } -// } -// else -// { /** Do a k-means search. **/ -// /** Define constants for the algorithm. **/ -// const unsigned int max_iter = 64u; /** Hardcode value because idk. **/ -// const unsigned int num_clusters = exp_fn_compute_k(num_vectors); - -// /** Allocate static memory for finding clusters. **/ -// unsigned int labels[num_vectors]; -// memset(labels, 0u, sizeof(labels)); - -// /** Execute kmeans clustering. **/ -// exp_fn_kmeans(vectors, num_vectors, labels, num_clusters, max_iter); - -// /** Find duplocates in clusters. **/ -// for (unsigned int i = 0u; i < num_vectors; i++) -// { -// const int* v1 = vectors[i]; -// const unsigned int label = labels[i]; -// for (unsigned int j = i + 1u; j < num_vectors; j++) -// { -// if (labels[j] != label) continue; -// const int* v2 = vectors[j]; -// const double similarity = exp_fn_sparse_similarity(v1, v2); -// if (similarity > dupe_threshold) /* Dup found! */ -// { -// Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); -// if (dup == NULL) -// { -// mssErrorf(1, "EXP", -// "lightning_search() - nmMalloc(%lu) failed.", -// sizeof(Dup) -// ); -// goto err_free_dups; -// } - -// dup->id1 = i; -// dup->id2 = j; -// dup->similarity = similarity; -// xaAddItem(dups, (void*)dup); -// } -// } -// } -// } - -// /** Done **/ -// return dups; - -// /** Free dups. **/ -// err_free_dups:; -// const size_t num_dups = dups->nItems; -// for (unsigned int i = 0u; i < num_dups; i++) -// { -// nmFree(dups->Items[i], sizeof(Dup)); -// dups->Items[i] = NULL; -// } -// xaDeInit(dups); -// return NULL; -// } - -// /*** Computes Levenshtein distance between two strings. -// *** -// *** @param str1 The first string. -// *** @param str2 The second string. -// *** @param length1 The length of the first string. -// *** @param length1 The length of the first string. -// *** -// *** @attention - Tip: Pass 0 for the length of either string to infer it -// *** using the null terminating character. Thus, strings with no null -// *** terminator are supported if you pass explicit lengths. -// *** -// *** Complexity: O(length1 * length2). -// *** -// *** @see centrallix-sysdoc/string_comparison.md -// ***/ -// unsigned int exp_fn_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) -// { -// /*** lev_matrix: -// *** For all i and j, d[i][j] will hold the Levenshtein distance between -// *** the first i characters of s and the first j characters of t. -// *** -// *** As they say, no dynamic programming algorithm is complete without a -// *** matrix that you fill out and it has the answer in the final location. -// ***/ -// const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; -// const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; -// unsigned int lev_matrix[str1_len + 1][str2_len + 1]; - -// /*** Base case #0: -// *** Transforming an empty string into an empty string has 0 cost. -// ***/ -// lev_matrix[0][0] = 0u; - -// /*** Base case #1: -// *** Any source prefixe can be transformed into an empty string by -// *** dropping each character. -// ***/ -// for (unsigned int i = 1u; i <= str1_len; i++) -// lev_matrix[i][0] = i; - -// /*** Base case #2: -// *** Any target prefixes can be transformed into an empty string by -// *** inserting each character. -// ***/ -// for (unsigned int j = 1u; j <= str2_len; j++) -// lev_matrix[0][j] = j; - -// /** General Case **/ -// for (unsigned int i = 1u; i <= str1_len; i++) -// { -// for (unsigned int j = 1u; j <= str2_len; j++) -// { -// /** Equal characters need no changes. **/ -// if (str1[i - 1] == str2[j - 1]) -// lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; - -// /*** We need to make a change, so use the opereration with the -// *** lowest cost out of delete, insert, replace, or swap. -// ***/ -// else -// { -// unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; -// unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; -// unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; - -// /** If a swap is possible, calculate the cost. **/ -// bool can_swap = ( -// i > 1 && j > 1 && -// str1[i - 1] == str2[j - 2] && -// str1[i - 2] == str2[j - 1] -// ); -// unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; - -// // Find the best operation. -// lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); -// } -// } -// } - -// return lev_matrix[str1_len][str2_len]; -// } - -// /*** Runs complete search to find duplocates in phone numbers using the -// *** levenshtein min edit distance algorithm. -// *** -// *** @param dataset An array of characters for all dataset strings. -// *** @param dataset_size The number of phone numbers to be scanned. -// *** @param dupe_threshold The similarity threshold, below which dups are ignored. -// *** @returns The duplicates in pDup structs. -// ***/ -// pXArray phone_search(char dataset[][10u], const unsigned int dataset_size, const double dupe_threshold) -// { -// /** Allocate space for dups. **/ -// const size_t guess_size = dataset_size * 2u; -// pXArray dups = xaNew(guess_size); -// if (dups == NULL) -// { -// mssErrorf(1, "EXP", "phone_search() - xaNew(%lu) failed.", guess_size); -// return NULL; -// } - -// /** Search for dups using edit distance. **/ -// for (unsigned int i = 0u; i < dataset_size; i++) -// { -// const char* v1 = dataset[i]; -// for (unsigned int j = i + 1u; j < dataset_size; j++) -// { -// const char* v2 = dataset[j]; -// const unsigned int dist = exp_fn_edit_dist(v1, v2, 10u, 10u); -// const double similarity = (double)dist / 10.0; -// if (similarity > dupe_threshold) /* Dup found! */ -// { -// Dup* dup = (Dup*)nmMalloc(sizeof(Dup)); -// if (dup == NULL) -// { -// mssErrorf(1, "EXP", "phone_search() - nmMalloc(%lu) failed.", sizeof(Dup)); - -// /** Free data before returning. **/ -// const size_t num_dups = dups->nItems; -// for (unsigned int i = 0u; i < num_dups; i++) -// { -// void* dup = dups->Items[i]; -// nmFree(dup, sizeof(Dup)); -// } -// xaDeInit(dups); -// return NULL; -// } - -// dup->id1 = i; -// dup->id2 = j; -// dup->similarity = similarity; -// xaAddItem(dups, (void*)dup); -// } -// } -// } - -// return dups; -// } - -// /*** Usage: get_dups(, , ) -// *** data is assumed to contain only the following characters: -// *** (Data containing ` or control characters is undefined.) -// *** \n\v\f\r 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij -// *** klmnopqrstuvwxyz!"#$%&'()*+,-./:;<=>?@[\]^_{|}~ -// ***/ -// int exp_fn_get_dups_general(pExpression tree, pParamObjects objlist, pExpression maybe_dup_threshold, pExpression maybe_out_file_path, pExpression maybe_data, const char* fn_name, bool is_phone_numbers) -// { -// /** Check number of arguments. **/ -// if (!maybe_dup_threshold || !maybe_out_file_path || !maybe_data) -// { -// mssErrorf(1, "EXP", "%s(?) expects 3 parameters.", fn_name); -// return -1; -// } -// const int num_params = tree->Children.nItems; -// if (num_params != 3) -// { -// mssErrorf(1, "EXP", "%s(?) expects 3 parameter, got %d.", fn_name, num_params); -// return -1; -// } - -// /** Magic checks. **/ -// ASSERTMAGIC(tree, MGK_EXPRESSION); -// ASSERTMAGIC(maybe_dup_threshold, MGK_EXPRESSION); -// ASSERTMAGIC(maybe_out_file_path, MGK_EXPRESSION); -// ASSERTMAGIC(maybe_data, MGK_EXPRESSION); - -// /** Check object list. **/ -// if (!objlist) -// { -// mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); -// return -1; -// } -// ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - -// /** Extract dup_threshold. **/ -// if (maybe_dup_threshold->Flags & EXPR_F_NULL) -// { -// mssErrorf(1, "EXP", "%s(NULL, ...) dup_threshold cannot be NULL.", fn_name); -// return -1; -// } -// if (maybe_dup_threshold->DataType != DATA_T_DOUBLE) -// { -// mssErrorf(1, "EXP", "%s(?, ...) dup_threshold must be a doube.", fn_name); -// return -1; -// } -// double dup_threshold = maybe_dup_threshold->Types.Double; -// if (isnan(dup_threshold)) -// { -// mssErrorf(1, "EXP", "%s(NAN, ...) dup_threshold cannot be NAN.", fn_name); -// return -1; -// } -// if (dup_threshold <= 0 || 1 <= dup_threshold) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, ...) dup_threshold must be between 0 and 1 (exclusive).", -// fn_name, dup_threshold -// ); -// return -1; -// } - -// /** Extract output file path. **/ -// if (maybe_out_file_path->Flags & EXPR_F_NULL) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, NULL, ...) out_file_path cannot be NULL.", -// fn_name, dup_threshold -// ); -// return -1; -// } -// if (maybe_out_file_path->DataType != DATA_T_STRING) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \?\?\?, ...) out_file_path should be a string.", -// fn_name, dup_threshold -// ); -// return -1; -// } -// char* out_file_path = maybe_out_file_path->String; -// if (out_file_path == NULL) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, nothing?, ...) expected string from out_file_path " -// "(of type DataType = DATA_T_STRING), but the String was NULL " -// "or did not exist!", -// fn_name, dup_threshold -// ); -// return -1; -// } -// size_t out_path_len = strlen(out_file_path); -// if (out_path_len == 0u) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", ...) out_file_path cannot be an empty string.", -// fn_name, dup_threshold, out_file_path -// ); -// return -1; -// } -// const size_t max_len = BUFSIZ - 48u; -// if (out_path_len >= max_len) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", ...) out_file_path length (%lu) > max length (%lu).", -// fn_name, dup_threshold, out_file_path, out_path_len, max_len -// ); -// return -1; -// } -// if (strncmp(out_file_path + (out_path_len - 4u), ".csv", 4u) != 0) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", ...) out_file_path must end in .csv, " -// "because the output file is a csv.", -// fn_name, dup_threshold, out_file_path -// ); -// return -1; -// } - -// /** Extract dataset string. **/ -// if (maybe_data->Flags & EXPR_F_NULL) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", NULL) data cannot be NULL.", -// fn_name, dup_threshold, out_file_path -// ); -// return -1; -// } -// if (maybe_data->DataType != DATA_T_STRING) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \?\?\?) data must be a string.", -// fn_name, dup_threshold, out_file_path -// ); -// return -1; -// } -// char* data = maybe_data->String; -// if (data == NULL) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \?\?\?) expected string from data " -// "(of type DataType = DATA_T_STRING), but the String " -// "was NULL or did not exist!", -// fn_name, dup_threshold, out_file_path -// ); -// return -1; -// } -// if (strlen(data) == 0u) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"%s\") data cannot be an empty string.", -// fn_name, dup_threshold, out_file_path, data -// ); -// return -1; -// } - -// /** Check number of entries in the dataset. **/ -// size_t dataset_size = 1; -// for (char* buf = data; *buf != '\0'; buf++) -// if (*buf == SEPARATOR_CHAR) dataset_size++; - -// /** Verify dataset is reasonable size. **/ -// if (dataset_size == 1) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"\?\?\?\") Expected data to contain multiple " -// "values separated by \""SEPARATOR"\", but data was: \"%s\"", -// fn_name, dup_threshold, out_file_path, data -// ); -// return -1; -// } - -// /** Parse strs out of the data into the dataset. **/ -// size_t count = 0u; -// char* token = strtok(data, SEPARATOR); -// char* dataset[dataset_size]; -// memset(dataset, 0, sizeof(dataset)); -// while (token && count < dataset_size) -// { -// char* new_token = strdup(token); -// if (new_token == NULL) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") Failed to copy token \"%s\" from data.", -// fn_name, dup_threshold, out_file_path, token -// ); -// goto err_free_dataset; -// } -// dataset[count++] = new_token; -// token = strtok(NULL, SEPARATOR); -// } - -// /** Allocate memory to store dups. **/ -// pXArray dups; - -// /** Handle phone numbers. **/ -// if (is_phone_numbers) -// { -// /*** Phone number strings are always 10 characters long. Thus, they -// *** are NOT NULL TERMINATED because we can assume the length. -// ***/ -// unsigned int num_phone_numbers = 0u; -// char phone_numbers[dataset_size][10u]; - -// /** Parse the dataset. **/ -// for (unsigned int i = 0u; i < dataset_size; i++) -// { -// char* maybe_phone_number = dataset[i]; - -// /** Verify length can be a valid phone number. **/ -// const size_t len = strlen(maybe_phone_number); -// if (len < 10u) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") is too short. (skipped)", -// fn_name, dup_threshold, out_file_path, maybe_phone_number -// ); -// continue; -// } -// if (len > 18u) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") is too long. (skipped)", -// fn_name, dup_threshold, out_file_path, maybe_phone_number -// ); -// continue; -// } - -// /** Parse phone number. **/ -// char buf[11u], cur_char = maybe_phone_number[0]; -// unsigned int j = ((cur_char == '+') ? 2u : -// ((cur_char == '1') ? 1u : 0u)); -// unsigned int number_len = 0u; -// while (cur_char != '\0' && number_len <= 10u) -// { -// cur_char = maybe_phone_number[j]; - -// if ( -// cur_char == '-' || -// cur_char == ' ' || -// cur_char == '(' || -// cur_char == ')' -// ) continue; -// else if (!isdigit(cur_char)) -// { -// /** Unknown character. **/ -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") contains unexpected character '%c'. (skipped)", -// fn_name, dup_threshold, out_file_path, maybe_phone_number, cur_char -// ); -// goto next_phone_number; -// } - -// /** Add the character to the phone number. */ -// buf[number_len] = cur_char; -// number_len++; - -// /** Advance to next number. **/ -// j++; -// } - -// /** Check number of digits. **/ -// if (number_len < 10u) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") has less than 10 digits. (skipped)", -// fn_name, dup_threshold, out_file_path, maybe_phone_number -// ); -// continue; -// } -// if (number_len > 10u) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") \"Phone number\" (\"%s\") has more than 10 digits. (skipped)", -// fn_name, dup_threshold, out_file_path, maybe_phone_number -// ); -// continue; -// } - -// /** Copy valid phone number (with no null-terminator). **/ -// memcpy(phone_numbers[num_phone_numbers++], buf, 10u); - -// next_phone_number:; -// } - -// /** Invoke phone number search to find dups in the processed data. **/ -// dups = phone_search(phone_numbers, num_phone_numbers, dup_threshold); -// } - -// /** Handle text. **/ -// else -// { -// /** Build vectors from the strs in the dataset. **/ -// const size_t vectors_size = dataset_size * sizeof(int*); -// int** vectors = (int**)nmMalloc(vectors_size); -// if (vectors == NULL) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") - nmMalloc(%lu) failed.", -// fn_name, dup_threshold, out_file_path, vectors_size -// ); -// goto err_free_dataset; -// } -// for (size_t i = 0; i < dataset_size; i++) -// { -// const int* vector = vectors[i] = build_vector(dataset[i]); -// if (vector == NULL) -// { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") - build_vector(%s) failed.", -// fn_name, dup_threshold, out_file_path, dataset[i] -// ); -// goto err_free_vectors; -// } -// if (vector[0] == -EXP_NUM_DIMS) { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") - build_vector(%s) produced no character pairs.", -// fn_name, dup_threshold, out_file_path, dataset[i] -// ); -// goto err_free_vectors; -// } -// } - -// /** Invoke lightning search to find dups using the vectors. **/ -// dups = lightning_search(vectors, dataset_size, dup_threshold); -// if (dups == NULL) { -// mssErrorf(1, "EXP", -// "%s(%lg, \"%s\", \"...\") - lightning_search() failed.", -// fn_name, dup_threshold, out_file_path -// ); -// goto err_free_vectors; -// } - -// /** Free unused memory. **/ -// for (size_t i = 0; i < dataset_size; i++) -// { -// nmSysFree(vectors[i]); -// vectors[i] = NULL; -// } -// nmFree(vectors, vectors_size); -// vectors = NULL; -// goto search_done; - -// /** Free vectors, if needed. **/ -// err_free_vectors: -// if (vectors != NULL) -// { -// for (size_t i = 0; i < dataset_size; i++) -// { -// if (vectors[i] == NULL) break; -// nmSysFree(vectors[i]); -// vectors[i] = NULL; -// } -// nmFree(vectors, vectors_size); -// vectors = NULL; -// } -// goto err_free_dataset; - -// search_done:; -// } - -// /** Check number of dups found. **/ -// const int num_dups = dups->nItems; - -// // Hack where we hardcode the path to the root directory because trying to -// // track it down is way too hard. -// const char root_path[] = "/usr/local/src/cx-git/centrallix-os"; - -// /** Create output file path. **/ -// char out_path[BUFSIZ]; -// snprintf(memset(out_path, 0, sizeof(out_path)), sizeof(out_path), "%s/%s", root_path, out_file_path); - -// /** Write output file. **/ -// FILE* file = fopen(out_path, "w"); -// if (file == NULL) -// { -// perror("Failed to open file."); -// mssErrorf(1, "EXP", -// "%s(%lg, \"...\", ...) failed to open file: %s", -// fn_name, dup_threshold, out_path -// ); -// goto err_free_dups; -// } -// const int setvbuf_ret = setvbuf(file, NULL, _IOFBF, (1000 * 1000)); -// if (setvbuf_ret != 0) -// { -// perror("Failed to set buffering on file."); -// mssErrorf(1, "EXP", -// "%s(%lg, \"...\", ...) failed to set buffering on file: %d, %s", -// fn_name, dup_threshold, setvbuf_ret, out_path -// ); -// goto err_close_file; -// } - -// /** Write CSV header. **/ -// fprintf(file, "id1,id2,sim\n"); - -// /*** If no data was written, make sure there is at least one row in the -// *** output file since assuming this file has data makes the sql faster. -// ***/ -// if (num_dups == 0u) -// fprintf(file, "error,undefined,0.0\n"); - -// /** Write CSV data rows. **/ -// else -// { -// for (unsigned int i = 0u; i < num_dups; i++) -// { -// Dup* data = (Dup*)dups->Items[i]; -// fprintf(file, "%s,%s,%.8lf\n", dataset[data->id1], dataset[data->id2], data->similarity); -// nmFree(data, sizeof(Dup)); /* Free unused data. */ -// dups->Items[i] = NULL; -// } -// } - -// /** Free unused data. **/ -// for (unsigned int i = 0u; i < dataset_size; i++) -// { -// free(dataset[i]); -// dataset[i] = NULL; -// } -// xaDeInit(dups); -// dups = NULL; - -// /** Close file. **/ -// const int fclose_ret = fclose(file); -// if (fclose_ret != 0) -// { -// perror("Failed to close file."); -// mssErrorf(1, "EXP", -// "%s(%lg, \"...\") failed to close file: %d, %s", -// fn_name, dup_threshold, fclose_ret, out_path -// ); -// goto err_free_dataset; -// } -// file = NULL; - -// /** Success. **/ -// tree->DataType = DATA_T_INTEGER; -// tree->Integer = (int)num_dups; -// return 0; - -// /** Error cases. **/ - -// /** Close file, if needed. **/ -// err_close_file: -// if (file != NULL) -// { -// const int fclose_ret = fclose(file); -// if (fclose_ret != 0) -// { -// char dbl_buf[DBL_BUF_SIZE]; -// snprintf(dbl_buf, sizeof(dbl_buf), "%lg", dup_threshold); -// perror("Failed to close file."); -// mssErrorf(1, "EXP", -// "%s(%s, \"...\") failed to close file: %d, %s", -// fn_name, dbl_buf, fclose_ret, out_path -// ); -// } -// } - -// /** Free dups, if needed. **/ -// err_free_dups: -// if (dups != NULL) -// { -// for (unsigned int i = 0u; i < num_dups; i++) -// { -// nmFree(dups->Items[i], sizeof(Dup)); -// dups->Items[i] = NULL; -// } -// xaDeInit(dups); -// dups = NULL; -// } - -// /** Free dataset, if needed. **/ -// err_free_dataset: -// for (unsigned int i = 0u; i < dataset_size; i++) -// { -// if (dataset[i] == NULL) break; -// free(dataset[i]); -// dataset[i] = NULL; -// } - -// return -1; -// } - -// int exp_fn_get_dups(pExpression tree, pParamObjects objlist, pExpression p1, pExpression p2, pExpression p3) -// { -// return exp_fn_get_dups_general(tree, objlist, p1, p2, p3, "get_dups", false); -// } - -// int exp_fn_get_dups_phone(pExpression tree, pParamObjects objlist, pExpression p1, pExpression p2, pExpression p3) -// { -// return exp_fn_get_dups_general(tree, objlist, p1, p2, p3, "get_dups_phone", true); -// } - -// /** Magic values. **/ -// #define EXP_NUM_FIELDS 7 -// #define EXP_INDEX_FIRST_NAME 0 -// #define EXP_INDEX_FIRST_NAME_METAPHONE 1 -// #define EXP_INDEX_LAST_NAME 2 -// #define EXP_INDEX_LAST_NAME_METAPHONE 3 -// #define EXP_INDEX_EMAIL 4 -// #define EXP_INDEX_PHONE 5 -// #define EXP_INDEX_ADDRESS 6 - -// /** No-op function. **/ -// int exp_fn_do_nothing() { return 0; } - -// /*** Function to add parameters to private storage so that more than 3 parameters can be passed. -// *** Currently, doubles are the only supported param type. -// *** -// *** Usage: param(, , ) : R, -// *** where: V : Double -// *** -// *** @param tree Return param_value. -// *** @param objlist Function scope. -// *** @param maybe_array The 1st param, should be NULL or another call to param(). -// *** @param maybe_param_name The 2nd param, should be a string for the name of the param. -// *** @param maybe_param_value The 3rd param, should be the param_value of the param being set. -// ***/ -// int exp_fn_param(pExpression tree, pParamObjects objlist, pExpression maybe_param_name, pExpression maybe_param_value, pExpression maybe_array) { -// // Verify arg number. -// if (!maybe_param_name || !maybe_param_value) -// { -// mssErrorf(1, "EXP", "param(?) expects two or three parameters."); -// return -1; -// } - -// // Magic checks. -// ASSERTMAGIC(tree, MGK_EXPRESSION); -// ASSERTMAGIC(maybe_param_name, MGK_EXPRESSION); -// ASSERTMAGIC(maybe_param_value, MGK_EXPRESSION); -// ASSERTMAGIC(maybe_array, MGK_EXPRESSION); - -// // Check object list. -// if (!objlist) -// { -// mssErrorf(1, "EXP", "param(\?\?\?) no object list?"); -// return -1; -// } -// ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - -// // Extract param name. -// if (maybe_param_name->Flags & EXPR_F_NULL) -// { -// mssErrorf(1, "EXP", "param(NULL, ...) param_name cannot be null."); -// return -1; -// } -// if (maybe_param_name->DataType != DATA_T_STRING) -// { -// mssErrorf(1, "EXP", "param(?, ...) param_name must be a string."); -// return -1; -// } -// const char* param_name = maybe_param_name->String; - -// // Extract param value. -// if (maybe_param_value->Flags & EXPR_F_NULL) -// { -// mssErrorf(1, "EXP", "param(\"%s\", NULL, ...) param_value cannot be null.", param_name); -// return -1; -// } -// if (maybe_param_value->DataType != DATA_T_DOUBLE) -// { -// mssErrorf(1, "EXP", "param(\"%s\", ?, ...) param_value must be a doube.", param_name); -// return -1; -// } -// double param_value = maybe_param_value->Types.Double; - -// // Verify the value being set. -// // TODO: Replace with hashmap. -// signed int index = -1; -// if (strcmp(param_name, "first_name") == 0) index = EXP_INDEX_FIRST_NAME; -// else if (strcmp(param_name, "first_name_metaphone") == 0) index = EXP_INDEX_FIRST_NAME_METAPHONE; -// else if (strcmp(param_name, "last_name") == 0) index = EXP_INDEX_LAST_NAME; -// else if (strcmp(param_name, "last_name_metaphone") == 0) index = EXP_INDEX_LAST_NAME_METAPHONE; -// else if (strcmp(param_name, "email") == 0) index = EXP_INDEX_EMAIL; -// else if (strcmp(param_name, "phone") == 0) index = EXP_INDEX_PHONE; -// else if (strcmp(param_name, "address") == 0) index = EXP_INDEX_ADDRESS; -// if (index == -1) -// { -// mssErrorf(1, "EXP", -// "param(\"%s\", %lf, ...) invalid field name %s.", -// param_name, param_value, param_name -// ); -// return -1; -// } - -// // Extract array. -// double* array; -// if (!maybe_array || maybe_array->Flags & EXPR_F_NULL) -// { -// const size_t size = EXP_NUM_FIELDS * sizeof(double); -// void* PrivateData = tree->PrivateData = memset(nmSysMalloc(size), 0, size); -// tree->PrivateDataFinalize = exp_fn_do_nothing; // DON'T FREE MY DATA UNTIL I'M READY. - -// array = (double*)PrivateData; -// for (unsigned int i = 0u; i < EXP_NUM_FIELDS; i++) array[i] = NAN; -// } -// else if ( -// maybe_array->DataType == DATA_T_ARRAY && -// maybe_array->PrivateData != NULL && -// !strcmp(maybe_array->Name, "param") -// ) -// { -// tree->PrivateData = maybe_array->PrivateData; -// tree->PrivateDataFinalize = exp_fn_do_nothing; // DON'T FREE MY DATA UNTIL I'M READY. -// array = (double*)maybe_array->PrivateData; -// } -// else -// { -// mssErrorf(1, "EXP", "param(\"%s\", %lf, ...) if provided, array must be from a call to param().", param_name, param_value); -// return -1; -// } - -// // Warn on previous data. -// double old_value = array[index]; -// if (!isnan(old_value)) -// { -// fprintf(stderr, -// "Warning: Overwriting field '%s'(@ index %d) with %lf (was %lf).\n", -// param_name, index, param_value, old_value -// ); -// } - -// // Set param_value. -// array[index] = param_value; - -// // Done -// tree->DataType = DATA_T_ARRAY; -// tree->Integer = 0; -// tree->Types.Double = 0.0; -// return 0; -// } - -// int exp_fn_get_sim(pExpression tree, pParamObjects objlist, pExpression maybe_fields, pExpression unused1, pExpression unused2) -// { -// if (!maybe_fields || unused1 || unused2) -// { -// mssErrorf(1, "EXP", "get_sim(param(...)) expects one parameter, from param()."); -// return -1; -// } - -// // Magic checks. -// ASSERTMAGIC(tree, MGK_EXPRESSION); -// ASSERTMAGIC(maybe_fields, MGK_EXPRESSION); - -// // Check object list. -// if (!objlist) -// { -// mssErrorf(1, "EXP", "get_sim(\?\?\?) no object list?"); -// return -1; -// } -// ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - -// // Verify arg. -// if (maybe_fields->Flags & EXPR_F_NULL) -// { -// mssErrorf(1, "EXP", "get_sim(NULL) fields from param() cannot be NULL."); -// return -1; -// } -// if (maybe_fields->DataType != DATA_T_ARRAY || maybe_fields->PrivateData == NULL) -// { -// mssErrorf(1, "EXP", "get_sim(\?\?\?) expects arg 0 to be fields from a call to param()."); -// return -1; -// } - -// // Extract arg(s?). -// double* fields = (double*)maybe_fields->PrivateData; - -// const double first_name = fields[EXP_INDEX_FIRST_NAME]; -// if (isnan(first_name)) -// { -// mssErrorf(1, "EXP", "get_sim(...) first_name similarity not set."); -// return -1; -// } - -// const double first_name_metaphone = fields[EXP_INDEX_FIRST_NAME_METAPHONE]; -// if (isnan(first_name_metaphone)) -// { -// mssErrorf(1, "EXP", "get_sim(...) first_name_metaphone similarity not set."); -// return -1; -// } - -// const double last_name = fields[EXP_INDEX_LAST_NAME]; -// if (isnan(last_name)) -// { -// mssErrorf(1, "EXP", "get_sim(...) last_name similarity not set."); -// return -1; -// } - -// const double last_name_metaphone = fields[EXP_INDEX_LAST_NAME_METAPHONE]; -// if (isnan(last_name_metaphone)) -// { -// mssErrorf(1, "EXP", "get_sim(...) last_name_metaphone similarity not set."); -// return -1; -// } - -// const double email = fields[EXP_INDEX_EMAIL]; -// if (isnan(email)) -// { -// mssErrorf(1, "EXP", "get_sim(...) email similarity not set."); -// return -1; -// } - -// const double phone = fields[EXP_INDEX_PHONE]; -// if (isnan(phone)) -// { -// mssErrorf(1, "EXP", "get_sim(...) phone similarity not set."); -// return -1; -// } - -// const double address = fields[EXP_INDEX_ADDRESS]; -// if (isnan(address)) -// { -// mssErrorf(1, "EXP", "get_sim(...) address similarity not set."); -// return -1; -// } - -// char* primary; -// char* secondary; -// meta_double_metaphone("text", &primary, &secondary); -// printf("Primary: %s, secondary: %s\n", primary, secondary); - -// // Print args. -// printf( -// "Sims:\n" -// "\tfirst_name: %lf\n" -// "\tfirst_name_metaphone: %lf\n" -// "\tlast_name: %lf\n" -// "\tlast_name_metaphone: %lf\n" -// "\temail: %lf\n" -// "\tphone: %lf\n" -// "\taddress: %lf\n", -// first_name, -// first_name_metaphone, -// last_name, -// last_name_metaphone, -// email, -// phone, -// address -// ); - -// // Compute total. -// const double first_name_total = max(first_name * 1.0, first_name_metaphone * 0.9); -// const double last_name_total = max(last_name * 1.0, last_name_metaphone * 0.9); -// double total = (first_name_total * last_name_total) * 0.6 + email * 0.2 + address * 0.2; - -// // Clean up. -// nmSysFree(fields); - -// // Return total. -// tree->DataType = DATA_T_DOUBLE; -// tree->Types.Double = total; -// return 0; -// } - - +/*** Computes double metaphone. + *** + *** @param tree The tree resulting from this function. + *** @param objlist The evaluation "scope", including available variables. + *** @param maybe_str Possibly the string passed to double metaphone. + *** @param u1 Unused parameter. + *** @param u2 Unused parameter. + ***/ int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression maybe_str, pExpression u1, pExpression u2) { const char fn_name[] = "double_metaphone"; /** Check number of arguments. **/ - if (!maybe_str || u1 || u2) + const int num_params = tree->Children.nItems; + if (num_params != 1) { - mssErrorf(1, "EXP", "%s(?) expects 1 parameter.", fn_name); + mssErrorf(1, "EXP", "%s(?) expects 1 parameter, got %d parameters.", fn_name, num_params); return -1; } - const int num_params = tree->Children.nItems; - if (num_params != 1) + if (maybe_str == NULL || u1 != NULL || u2 != NULL) { - mssErrorf(1, "EXP", "%s(?) expects 1 parameter, got %d.", fn_name, num_params); + mssErrorf(1, "EXP", "%s(?) expects 1 parameter.", fn_name); return -1; } @@ -6019,7 +4176,7 @@ int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression ASSERTMAGIC(maybe_str, MGK_EXPRESSION); /** Check object list. **/ - if (!objlist) + if (objlist == NULL) { mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); return -1; @@ -6041,9 +4198,8 @@ int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression if (str == NULL) { mssErrorf(1, "EXP", - "%s(nothing?) expected string from str " - "(of type DataType = DATA_T_STRING), but the String " - "was NULL or did not exist!", + "%s(nothing?) expected string from str (of datatype DataType = " + "DATA_T_STRING), but the String was NULL or did not exist!", fn_name ); return -1; @@ -6056,18 +4212,15 @@ int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression } /** Compute DoubleMetaphone. **/ - char* primary; - char* secondary; - meta_double_metaphone( - str, - memset(&primary, 0, sizeof(primary)), - memset(&secondary, 0, sizeof(secondary)) - ); + char* primary = NULL; + char* secondary = NULL; + meta_double_metaphone(str, &primary, &secondary); /** Process result. **/ const size_t primary_length = strlen(primary); const size_t secondary_length = strlen(secondary); - char* result = nmSysMalloc(primary_length + 1u + secondary_length + 1u); + char* result = check_ptr(nmSysMalloc(primary_length + 1u + secondary_length + 1u)); + if (result == NULL) return -1; sprintf(result, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); /** Return the result. **/ @@ -6076,13 +4229,6 @@ int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression return 0; } -// // Clean up. -// #undef min -// #undef max - -// // END OF DUPE SECTION -// // =================== - /* * exp_fn_argon2id * This method hashes a given password using the Argon2 algorithm (ID variant) @@ -6205,7 +4351,9 @@ int exp_fn_argon2id(pExpression tree, pParamObjects objlist, pExpression passwor int exp_internal_DefineFunctions() { - + /** Initialize library **/ + ca_init(); + /** Function list for EXPR_N_FUNCTION nodes **/ xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); xhAdd(&EXP.Functions, "user_name", (char*)exp_fn_user_name); @@ -6260,9 +4408,6 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "log10", (char*)exp_fn_log10); xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); - xhAdd(&EXP.Functions, "levenshtein", (char*)exp_fn_levenshtein); /* Only used in its own tests. */ - xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); /* Only used in its own tests. */ - xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); xhAdd(&EXP.Functions, "to_hex", (char*)exp_fn_to_hex); @@ -6271,19 +4416,19 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); /** Duplicate Detection **/ - // xhAdd(&EXP.Functions, "get_dups", (char*)exp_fn_get_dups); - // xhAdd(&EXP.Functions, "get_dups_phone", (char*)exp_fn_get_dups_phone); - // xhAdd(&EXP.Functions, "no_op", (char*)exp_fn_do_nothing); - // xhAdd(&EXP.Functions, "do_nothing", (char*)exp_fn_do_nothing); - // xhAdd(&EXP.Functions, "param", (char*)exp_fn_param); - // xhAdd(&EXP.Functions, "total_sim", (char*)exp_fn_get_sim); + xhAdd(&EXP.Functions, "cos_cmp", (char*)exp_fn_cos_cmp); + xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_cmp); + xhAdd(&EXP.Functions, "cosine_compare", (char*)exp_fn_cos_cmp); + xhAdd(&EXP.Functions, "lev_cmp", (char*)exp_fn_lev_cmp); + xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_cmp); + xhAdd(&EXP.Functions, "levenshtein_compare", (char*)exp_fn_lev_cmp); xhAdd(&EXP.Functions, "double_metaphone", (char*)exp_fn_double_metaphone); /** Windowing **/ xhAdd(&EXP.Functions, "row_number", (char*)exp_fn_row_number); xhAdd(&EXP.Functions, "dense_rank", (char*)exp_fn_dense_rank); xhAdd(&EXP.Functions, "lag", (char*)exp_fn_lag); - + /** Aggregate **/ xhAdd(&EXP.Functions, "count", (char*)exp_fn_count); xhAdd(&EXP.Functions, "avg", (char*)exp_fn_avg); @@ -6293,9 +4438,9 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "first", (char*)exp_fn_first); xhAdd(&EXP.Functions, "last", (char*)exp_fn_last); xhAdd(&EXP.Functions, "nth", (char*)exp_fn_nth); - + /** Reverse functions **/ xhAdd(&EXP.ReverseFunctions, "isnull", (char*)exp_fn_reverse_isnull); - + return 0; } diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index 54d4c988a..045d57f85 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -192,7 +192,6 @@ typedef struct _OSD int (*Commit)(); int (*GetQueryCoverageMask)(); int (*GetQueryIdentityPath)(); - int (*Unregister)(); } ObjDriver, *pObjDriver; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 4acfc8579..c10c6fca6 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -72,7 +72,7 @@ ***/ /** Pure Laziness **/ -// #define ENABLE_TPRINTF +#define ENABLE_TPRINTF /** Debugging **/ #ifndef ENABLE_TPRINTF @@ -243,7 +243,7 @@ void** ci_xaToTrimmedArray(pXArray arr) } const size_t arr_size = arr->nItems * sizeof(void*); - void** result = check_ptr(nmMalloc(arr_size)); + void** result = check_ptr(nmSysMalloc(arr_size)); memcpy(result, arr->Items, arr_size); return result; } @@ -675,7 +675,7 @@ static void ci_FreeSourceData(pSourceData source_data); static void ci_FreeClusterData(pClusterData cluster_data, bool recursive); static void ci_FreeSearchData(pSearchData search_data); static void ci_FreeNodeData(pNodeData node_data); -static void ci_FreeCaches(void); +static void ci_ClearCaches(void); /** Deep Size Computation Functions. **/ // LINK #sizing @@ -718,7 +718,6 @@ static void ci_CacheFreeSourceData(pXHashEntry entry, void* path); static void ci_CacheFreeCluster(pXHashEntry entry, void* path); static void ci_CacheFreeSearch(pXHashEntry entry, void* path); int clusterExecuteMethod(void* inf_v, char* methodname, pObjData param, pObjTrxTree oxt); -int clusterUnregister(pObjDriver object_driver, pObjSession session); /** Unimplemented DriverFunctions. **/ // LINK #unimplemented @@ -1539,7 +1538,6 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) if (path == NULL) goto err; /** Allocate node struct data. **/ - // pNodeData node_data = NodeData |> sizeof() |> nmMalloc() |> check_ptr(); pNodeData node_data = check_ptr(nmMalloc(sizeof(NodeData))); if (node_data == NULL) goto err; memset(node_data, 0, sizeof(NodeData)); @@ -1678,7 +1676,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) /** Itterate over each param in the structure file. **/ node_data->nParams = param_infs.nItems; const size_t params_size = node_data->nParams * sizeof(pParam); - node_data->Params = check_ptr(nmMalloc(params_size)); + node_data->Params = check_ptr(nmSysMalloc(params_size)); if (node_data->Params == NULL) goto err_free_arrs; memset(node_data->Params, 0, params_size); for (unsigned int i = 0u; i < node_data->nParams; i++) @@ -1755,7 +1753,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) if (node_data->nClusterDatas > 0) { const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); - node_data->ClusterDatas = check_ptr(nmMalloc(clusters_size)); + node_data->ClusterDatas = check_ptr(nmSysMalloc(clusters_size)); if (node_data->ClusterDatas == NULL) goto err_free_arrs; memset(node_data->ClusterDatas, 0, clusters_size); for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) @@ -1773,7 +1771,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) if (node_data->nSearchDatas > 0) { const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); - node_data->SearchDatas = check_ptr(nmMalloc(searches_size)); + node_data->SearchDatas = check_ptr(nmSysMalloc(searches_size)); if (node_data->SearchDatas == NULL) goto err_free_arrs; memset(node_data->SearchDatas, 0, searches_size); for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) @@ -1810,17 +1808,39 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) /** @param source_data A pSourceData struct, freed by this function. **/ static void ci_FreeSourceData(pSourceData source_data) { + /** Guard segfault. **/ + if (source_data == NULL) + { + fprintf(stderr, "Call to ci_FreeSourceData(NULL);\n"); + return; + } + /** Free top level attributes, if they exist. **/ - if (source_data->Name != NULL) nmSysFree(source_data->Name); - if (source_data->SourcePath != NULL) nmSysFree(source_data->SourcePath); - if (source_data->AttrName != NULL) nmSysFree(source_data->AttrName); + if (source_data->Name != NULL) + { + nmSysFree(source_data->Name); + source_data->Name = NULL; + } + if (source_data->SourcePath != NULL) + { + nmSysFree(source_data->SourcePath); + source_data->SourcePath = NULL; + } + if (source_data->AttrName != NULL) + { + nmSysFree(source_data->AttrName); + source_data->AttrName = NULL; + } /** Free fetched data, if it exists. **/ if (source_data->Strings != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) + { nmSysFree(source_data->Strings[i]); - nmFree(source_data->Strings, source_data->nVectors * sizeof(char*)); + source_data->Strings[i] = NULL; + } + nmSysFree(source_data->Strings); source_data->Strings = NULL; } @@ -1828,13 +1848,17 @@ static void ci_FreeSourceData(pSourceData source_data) if (source_data->Vectors != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) + { ca_free_vector(source_data->Vectors[i]); - nmFree(source_data->Vectors, source_data->nVectors * sizeof(pVector)); + source_data->Vectors[i] = NULL; + } + nmSysFree(source_data->Vectors); source_data->Vectors = NULL; } - /** Free the source_data struct. **/ + /** Free the source data struct. **/ nmFree(source_data, sizeof(SourceData)); + source_data = NULL; } @@ -1846,21 +1870,33 @@ static void ci_FreeSourceData(pSourceData source_data) ***/ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) { - /** Free top level cluster data. **/ - if (cluster_data->Name != NULL) nmSysFree(cluster_data->Name); + /** Guard segfault. **/ + if (cluster_data == NULL) + { + fprintf(stderr, "Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + return; + } + + /** Free attribute data. **/ + if (cluster_data->Name != NULL) + { + nmSysFree(cluster_data->Name); + cluster_data->Name = NULL; + } /** Free computed data, if it exists. **/ if (cluster_data->Clusters != NULL) { - const unsigned int nVectors = cluster_data->SourceData->nVectors; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { pCluster cluster = &cluster_data->Clusters[i]; - nmFree(cluster->Strings, cluster->Size * sizeof(char*)); - nmFree(cluster->Vectors, cluster->Size * sizeof(pVector)); + nmSysFree(cluster->Strings); + nmSysFree(cluster->Vectors); + cluster->Strings = NULL; + cluster->Vectors = NULL; } - nmFree(cluster_data->Clusters, nVectors * sizeof(Cluster)); - nmFree(cluster_data->Sims, nVectors * sizeof(double)); + nmSysFree(cluster_data->Clusters); + nmSysFree(cluster_data->Sims); cluster_data->Clusters = NULL; cluster_data->Sims = NULL; } @@ -1871,14 +1907,18 @@ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) if (recursive) { for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + { ci_FreeClusterData(cluster_data->SubClusters[i], recursive); + cluster_data->SubClusters[i] = NULL; + } } - nmFree(cluster_data->SubClusters, cluster_data->nSubClusters * sizeof(void*)); + nmSysFree(cluster_data->SubClusters); cluster_data->SubClusters = NULL; } - /** Free the cluster struct. **/ + /** Free the cluster data struct. **/ nmFree(cluster_data, sizeof(ClusterData)); + cluster_data = NULL; } @@ -1886,15 +1926,35 @@ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) /** @param search_data A pSearchData struct, freed by this function. **/ static void ci_FreeSearchData(pSearchData search_data) { - if (search_data->Name != NULL) nmSysFree(search_data->Name); + /** Guard segfault. **/ + if (search_data == NULL) + { + fprintf(stderr, "Call to ci_FreeSearchData(NULL);\n"); + return; + } + + /** Free attribute data. **/ + if (search_data->Name != NULL) + { + nmSysFree(search_data->Name); + search_data->Name = NULL; + } + + /** Free computed data. **/ if (search_data->Dups != NULL) { for (unsigned int i = 0; i < search_data->nDups; i++) + { nmFree(search_data->Dups[i], sizeof(Dup)); - nmFree(search_data->Dups, search_data->nDups * sizeof(void*)); + search_data->Dups[i] = NULL; + } + nmSysFree(search_data->Dups); search_data->Dups = NULL; } + + /** Free the search data struct. **/ nmFree(search_data, sizeof(SearchData)); + search_data = NULL; } @@ -1902,6 +1962,13 @@ static void ci_FreeSearchData(pSearchData search_data) /** @param node_data A pNodeData struct, freed by this function. **/ static void ci_FreeNodeData(pNodeData node_data) { + /** Guard segfault. **/ + if (node_data == NULL) + { + fprintf(stderr, "Call to ci_FreeNodeData(NULL);\n"); + return; + } + /** Free parsed params, if they exist. **/ if (node_data->Params != NULL) { @@ -1909,28 +1976,36 @@ static void ci_FreeNodeData(pNodeData node_data) { if (node_data->Params[i] == NULL) break; paramFree(node_data->Params[i]); + node_data->Params[i] = NULL; } - nmFree(node_data->Params, node_data->nParams * sizeof(pParam)); + nmSysFree(node_data->Params); + node_data->Params = NULL; } - if (node_data->ParamList != NULL) expFreeParamList(node_data->ParamList); + if (node_data->ParamList != NULL) + { + expFreeParamList(node_data->ParamList); + node_data->ParamList = NULL; + } /** Free parsed clusters, if they exist. **/ if (node_data->ClusterDatas != NULL) { - /*** This data is cached, so we should NOT free it! - *** The caching system is responsible for the memory. + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. ***/ - nmFree(node_data->ClusterDatas, node_data->nClusterDatas * sizeof(pClusterData)); + nmSysFree(node_data->ClusterDatas); node_data->ClusterDatas = NULL; } /** Free parsed searches, if they exist. **/ if (node_data->SearchDatas != NULL) { - /*** This data is cached, so we should NOT free it! - *** The caching system is responsible for the memory. + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. ***/ - nmFree(node_data->SearchDatas, node_data->nSearchDatas * sizeof(pSearchData)); + nmSysFree(node_data->SearchDatas); node_data->SearchDatas = NULL; } @@ -1942,18 +2017,20 @@ static void ci_FreeNodeData(pNodeData node_data) ***/ if (node_data->SourceData != NULL) { - /*** This data is cached, so we should NOT free it! - *** The caching system is responsible for the memory. + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. ***/ node_data->SourceData = NULL; } /** Free the node data. **/ nmFree(node_data, sizeof(NodeData)); + node_data = NULL; } -/** Frees all caches for all cluster driver instances. **/ -static void ci_FreeCaches(void) +/** Frees all data in caches for all cluster driver instances. **/ +static void ci_ClearCaches(void) { /*** Free caches in reverse of the order they are created in case *** cached data relies on its source during the freeing process. @@ -2207,13 +2284,13 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) successful = false; goto end_free_data; } - if (vector[0] == -CA_NUM_DIMS) + if (ca_is_empty(vector)) { mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", val); successful = false; goto end_free_data; } - if (vector[0] == -172 && vector[1] == 11 && vector[2] == -78) + if (ca_has_no_pairs(vector)) { /** Skip pVector with no pairs. **/ tprintf("."); @@ -2241,7 +2318,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Trim data and store data. **/ const size_t data_size = source_data->nVectors * sizeof(char*); - source_data->Strings = check_ptr(nmMalloc(data_size)); + source_data->Strings = check_ptr(nmSysMalloc(data_size)); if (source_data->Strings == NULL) goto end_free_data; memcpy(source_data->Strings, data_xarray.Items, data_size); check(xaDeInit(&data_xarray)); /* Failure ignored. */ @@ -2249,7 +2326,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Trim data and store vectors. **/ const size_t vectors_size = source_data->nVectors * sizeof(pVector); - source_data->Vectors = check_ptr(nmMalloc(vectors_size)); + source_data->Vectors = check_ptr(nmSysMalloc(vectors_size)); memcpy(source_data->Vectors, vector_xarray.Items, vectors_size); check(xaDeInit(&vector_xarray)); /* Failure ignored. */ vector_xarray.nAlloc = 0; @@ -2325,11 +2402,11 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate static memory for finding clusters. **/ const size_t clusters_size = cluster_data->nClusters * sizeof(Cluster); - cluster_data->Clusters = check_ptr(nmMalloc(clusters_size)); + cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); if (cluster_data->Clusters == NULL) goto err; memset(cluster_data->Clusters, 0, clusters_size); const size_t sims_size = source_data->nVectors * sizeof(double); - cluster_data->Sims = check_ptr(nmMalloc(sims_size)); + cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); if (cluster_data->Sims == NULL) goto err_free_clusters; memset(cluster_data->Sims, 0, sims_size); @@ -2342,9 +2419,9 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Put all the data into one cluster. **/ pCluster first_cluster = &cluster_data->Clusters[0]; first_cluster->Size = source_data->nVectors; - first_cluster->Strings = check_ptr(nmMalloc(source_data->nVectors * sizeof(char*))); + first_cluster->Strings = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(char*))); if (first_cluster->Strings == NULL) goto err_free_sims; - first_cluster->Vectors = check_ptr(nmMalloc(source_data->nVectors * sizeof(pVector))); + first_cluster->Vectors = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(pVector))); if (first_cluster->Vectors == NULL) goto err_free_sims; memcpy(first_cluster->Strings, source_data->Strings, source_data->nVectors * sizeof(char*)); memcpy(first_cluster->Vectors, source_data->Vectors, source_data->nVectors * sizeof(pVector)); @@ -2372,7 +2449,7 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate lables. Note: kmeans does not require us to initialize them. **/ const size_t lables_size = source_data->nVectors * sizeof(unsigned int); - unsigned int* labels = check_ptr(nmMalloc(lables_size)); + unsigned int* labels = check_ptr(nmSysMalloc(lables_size)); if (labels == NULL) goto err_free_sims; /** Run kmeans. **/ @@ -2409,9 +2486,9 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) pXArray indexes_in_this_cluster = &indexes_in_cluster[i]; pCluster cluster = &cluster_data->Clusters[i]; cluster->Size = indexes_in_this_cluster->nItems; - cluster->Strings = check_ptr(nmMalloc(cluster->Size * sizeof(char*))); + cluster->Strings = check_ptr(nmSysMalloc(cluster->Size * sizeof(char*))); if (cluster->Strings == NULL) goto err_free_sims; - cluster->Vectors = check_ptr(nmMalloc(cluster->Size * sizeof(pVector))); + cluster->Vectors = check_ptr(nmSysMalloc(cluster->Size * sizeof(pVector))); if (cluster->Vectors == NULL) goto err_free_sims; for (unsigned int j = 0u; j < cluster->Size; j++) { @@ -2584,7 +2661,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Store dups. **/ search_data->nDups = dups->nItems; search_data->Dups = (dups->nItems == 0) - ? check_ptr(nmMalloc(0)) + ? check_ptr(nmSysMalloc(0)) : ci_xaToTrimmedArray(dups); /** Free unused data. **/ @@ -4162,7 +4239,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx else printf("all files:\n"); /** Free caches. **/ - ci_FreeCaches(); + ci_ClearCaches(); tprintf("Cache dropped.\n"); return 0; @@ -4185,21 +4262,6 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx } -/*** Frees caches when the driver is unregistered. - *** - *** This function does not free either of the given parameters. - *** - *** @param object_driver The driver instance which was registered being unregistered. (unused) - *** @param session The session being closed. (unused) - *** Returns - ***/ -int clusterUnregister(pObjDriver object_driver, pObjSession session) - { - ci_FreeCaches(); - return 0; - } - - /** ================ Unimplemented Functions ================ **/ /** ANCHOR[id=unimplemented] **/ // LINK #functions @@ -4272,9 +4334,6 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt) ***/ int clusterInitialize(void) { - /** Initialize library. **/ - ca_init(); - /** Allocate the driver. **/ pObjDriver drv = (pObjDriver)check_ptr(nmMalloc(sizeof(ObjDriver))); if (drv == NULL) goto err; @@ -4320,36 +4379,47 @@ int clusterInitialize(void) drv->Commit = clusterCommit; drv->GetQueryCoverageMask = NULL; drv->GetQueryIdentityPath = NULL; - drv->Unregister = clusterUnregister; /** Register some structures. **/ + nmRegister(sizeof(SourceData), "ClusterSourceData"); + nmRegister(sizeof(Cluster), "Cluster"); nmRegister(sizeof(ClusterData), "ClusterData"); nmRegister(sizeof(SearchData), "ClusterSearch"); - nmRegister(sizeof(SourceData), "ClusterSourceData"); nmRegister(sizeof(NodeData), "ClusterNodeData"); nmRegister(sizeof(DriverData), "ClusterDriverData"); nmRegister(sizeof(ClusterQuery), "ClusterQuery"); nmRegister(sizeof(ClusterDriverCaches), "ClusterDriverCaches"); /** Print debug size info. **/ - char buf1[16], buf2[16], buf3[16], buf4[16], buf5[16], buf6[16], buf7[16]; - tprintf( - "Cluster driver struct sizes:\n" - " > sizeof(SourceData): %s\n" - " > sizeof(ClusterData): %s\n" - " > sizeof(SearchData): %s\n" - " > sizeof(NodeData): %s\n" - " > sizeof(DriverData): %s\n" - " > sizeof(ClusterQuery): %s\n" - " > sizeof(ClusterDriverCaches): %s\n", - snprint_bytes(buf1, sizeof(buf1), sizeof(SourceData)), - snprint_bytes(buf2, sizeof(buf2), sizeof(ClusterData)), - snprint_bytes(buf3, sizeof(buf3), sizeof(SearchData)), - snprint_bytes(buf4, sizeof(buf4), sizeof(NodeData)), - snprint_bytes(buf5, sizeof(buf5), sizeof(DriverData)), - snprint_bytes(buf6, sizeof(buf6), sizeof(ClusterQuery)), - snprint_bytes(buf7, sizeof(buf7), sizeof(ClusterDriverCaches)) - ); +// char buf1[16], buf2[16], buf3[16], buf4[16], buf5[16], buf6[16], buf7[16], buf8[16]; +// tprintf( +// "Cluster driver struct sizes:\n" +// " > sizeof(SourceData): %s\n" +// " > sizeof(Cluster): %s\n" +// " > sizeof(ClusterData): %s\n" +// " > sizeof(SearchData): %s\n" +// " > sizeof(NodeData): %s\n" +// " > sizeof(DriverData): %s\n" +// " > sizeof(ClusterQuery): %s\n" +// " > sizeof(ClusterDriverCaches): %s\n", +// snprint_bytes(buf1, sizeof(buf1), sizeof(SourceData)), +// snprint_bytes(buf2, sizeof(buf2), sizeof(Cluster)), +// snprint_bytes(buf3, sizeof(buf3), sizeof(ClusterData)), +// snprint_bytes(buf4, sizeof(buf4), sizeof(SearchData)), +// snprint_bytes(buf5, sizeof(buf5), sizeof(NodeData)), +// snprint_bytes(buf6, sizeof(buf6), sizeof(DriverData)), +// snprint_bytes(buf7, sizeof(buf7), sizeof(ClusterQuery)), +// snprint_bytes(buf8, sizeof(buf8), sizeof(ClusterDriverCaches)) +// ); + +// pVector v = ca_build_vector(""); +// const unsigned int len = ca_sparse_len(v); +// fprintf(stderr, "Vector (x%d): [%d", len, v[0]); +// for (unsigned int i = 1u; i < len; i++) +// { +// fprintf(stderr, ", %d", v[i]); +// } +// fprintf(stderr, "]\n"); /** Register the driver. **/ if (!check(objRegisterDriver(drv))) goto err; diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index 6b09a8586..5ef492de3 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -1271,13 +1271,6 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) } else if (!strcmp(cmdname,"quit")) { - /** Loop through each driver and call their unregister handler, if they have one. **/ - for (unsigned int i = 0u; i < OSYS.Drivers.nItems; i++) - { - pObjDriver cur = (pObjDriver)OSYS.Drivers.Items[i]; - if (cur->Unregister != NULL) cur->Unregister(cur, s); - } - mlxCloseSession(ls); return 1; } From b4634f3cbd1410c147e8b7cb6a6f3ac1fb89ad96 Mon Sep 17 00:00:00 2001 From: Israel Date: Thu, 30 Oct 2025 14:48:10 -0600 Subject: [PATCH 006/361] Begin adding query files to search for duplicates. Simplify dataqa_duplicates component in preparation for making it the boundary into our new duplicate system. Add exp functions: sparse_eql(), ln(), and logn(). Fix bugs in comparison functions. Make minor tweaks to objdrv_cluster.c. --- centrallix-lib/include/clusters.h | 2 + centrallix-lib/src/clusters.c | 19 ++- centrallix-os/cluster-schema.cluster | 2 +- centrallix-os/file.cluster | 4 +- centrallix/expression/exp_functions.c | 209 ++++++++++++++++++++++++++ centrallix/osdrivers/objdrv_cluster.c | 100 ++++++------ 6 files changed, 283 insertions(+), 53 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index bddd0800c..288f81714 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -33,6 +33,7 @@ /************************************************************************/ #include +#include #ifdef CXLIB_INTERNAL #include "xarray.h" @@ -90,6 +91,7 @@ int ca_kmeans( /** Comparison functions, for ca_search(). **/ double ca_cos_compare(void* v1, void* v2); double ca_lev_compare(void* str1, void* str2); +bool ca_eql(pVector v1, pVector v2); void* ca_most_similar( void* target, diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 864ff36eb..ef1222873 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -184,7 +184,7 @@ pVector ca_build_vector(const char* str) if (size > expected_max_size) { fprintf(stderr, - "cli_build_vector(%s) - Warning: Sparse vector larger than expected.\n" + "cli_build_vector(\"%s\") - Warning: Sparse vector larger than expected.\n" " > Size: %lu\n" " > #Dims: %u\n", str, @@ -555,7 +555,7 @@ double ca_lev_compare(void* str1, void* str2) const size_t len2 = strlen(str2); if (len1 == 0lu && len2 == 0lu) return 1.0; if (len1 != 0lu && len2 == 0lu) return 0.0; - if (len1 != 0lu && len2 != 0lu) return 0.0; + if (len1 == 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ const unsigned int dist = edit_dist((const char*)str1, (const char*)str2, len1, len2); @@ -567,6 +567,21 @@ double ca_lev_compare(void* str1, void* str2) return normalized_similarity; } +/*** Check if two sparse vectors are identical. + *** + *** @param v1 The first vector. + *** @param v2 The second vector. + *** @returns true if they are equal, + *** false if any element is different. + ***/ +bool ca_eql(pVector v1, pVector v2) + { + const unsigned int len = ca_sparse_len(v1); + for (unsigned int i = 0u; i < len; i++) + if (v1[i] != v2[i]) return false; + return true; + } + /*** Calculate the average size of all clusters in a set of vectors. *** *** @param vectors The vectors of the dataset (allocated sparsely). diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index 9f11c1636..e87ae6b5f 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -38,8 +38,8 @@ file_name "system/cluster" search_name "system/search" { source : string ⊂ [cluster_name, ...] - threshold : double && 0.0 < x < 1.0 // optimization. similarity_measure : "cosine" | "levenshtein" + threshold : double && 0.0 < x < 1.0 // optimization. } ... } diff --git a/centrallix-os/file.cluster b/centrallix-os/file.cluster index 078a39fcc..95eacfee0 100644 --- a/centrallix-os/file.cluster +++ b/centrallix-os/file.cluster @@ -54,14 +54,14 @@ file_name "system/cluster" dups "cluster/search" { source = kmeans_cluster; - threshold = 0.75; similarity_measure = "cosine"; + threshold = 0.75; } dups2 "cluster/search" { source = no_clustering; - threshold = 0.75; similarity_measure = "cosine"; + threshold = 0.75; } } diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 4f9ffa563..751e07297 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -3288,6 +3288,101 @@ int exp_fn_log10(pExpression tree, pParamObjects objlist, pExpression i0, pExpre } +int exp_fn_log_natural(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) + { + double n; + + if (!i0) + { + mssError(1, "EXP", "ln() requires a number as its first parameter"); + goto error; + } + if (i0->Flags & EXPR_F_NULL) + { + tree->DataType = DATA_T_DOUBLE; + tree->Flags |= EXPR_F_NULL; + return 0; + } + switch(i0->DataType) + { + case DATA_T_INTEGER: + n = i0->Integer; + break; + case DATA_T_DOUBLE: + n = i0->Types.Double; + break; + case DATA_T_MONEY: + n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); + break; + default: + mssError(1, "EXP", "ln() requires a number as its first parameter"); + goto error; + } + if (n < 0) + { + mssError(1, "EXP", "ln(): cannot compute the logarithm of a negative number"); + goto error; + } + tree->DataType = DATA_T_DOUBLE; + tree->Types.Double = log(n); + return 0; + + error: + return -1; + } + + +int exp_fn_log_base_n(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) + { + double n, p; + + if (!i0 || !i1) + { + mssError(1, "EXP", "logn() requires numbers as its first and second parameters"); + goto error; + } + if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) + { + tree->DataType = DATA_T_DOUBLE; + tree->Flags |= EXPR_F_NULL; + return 0; + } + switch(i0->DataType) + { + case DATA_T_INTEGER: + n = i0->Integer; + break; + case DATA_T_DOUBLE: + n = i0->Types.Double; + break; + case DATA_T_MONEY: + n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); + break; + default: + mssError(1, "EXP", "logn() requires a number as its first parameter"); + goto error; + } + switch(i1->DataType) + { + case DATA_T_INTEGER: + p = i1->Integer; + break; + case DATA_T_DOUBLE: + p = i1->Types.Double; + break; + default: + mssError(1, "EXP", "logn() requires an integer or double as its second parameter"); + goto error; + } + tree->DataType = DATA_T_DOUBLE; + tree->Types.Double = log(n) / log(p); + return 0; + + error: + return -1; + } + + int exp_fn_power(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { double n, p; @@ -4008,6 +4103,7 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress *** @param maybe_str2 Possibly the second string. *** @param u1 Unused parameter. *** @param is_cos Whether to compute cosine or levenshtien. + *** @returns 0 for success, -1 for failure. ***/ static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1, bool is_cos) { @@ -4126,6 +4222,7 @@ static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe *** @param maybe_str1 Possibly the first string. *** @param maybe_str2 Possibly the second string. *** @param u1 Unused parameter. + *** @returns 0 for success, -1 for failure. ***/ int exp_fn_cos_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) { @@ -4140,12 +4237,121 @@ int exp_fn_cos_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_st *** @param maybe_str1 Possibly the first string. *** @param maybe_str2 Possibly the second string. *** @param u1 Unused parameter. + *** @returns 0 for success, -1 for failure. ***/ int exp_fn_lev_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, false); } + +/*** Comparse two strings to see if their sparse vectors are equal. + *** + *** @param tree The tree resulting from this function. + *** @param objlist The evaluation "scope", including available variables. + *** @param maybe_str1 Possibly the first string. + *** @param maybe_str2 Possibly the second string. + *** @param u1 Unused parameter. + *** @returns 0 for success, -1 for failure. + ***/ +static int exp_fn_sparse_eql(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1, bool is_cos) + { + const char fn_name[] = "sparse_compare"; + + /** Check number of arguments. **/ + const int num_params = tree->Children.nItems; + if (num_params != 2) + { + mssErrorf(1, "EXP", "%s(?) expects 2 parameters, got %d parameters.", fn_name, num_params); + return -1; + } + if (maybe_str1 == NULL || maybe_str2 == NULL || u1 != NULL) + { + mssErrorf(1, "EXP", "%s(?) expects 2 parameters.", fn_name); + return -1; + } + + /** Magic checks. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + ASSERTMAGIC(maybe_str1, MGK_EXPRESSION); + ASSERTMAGIC(maybe_str2, MGK_EXPRESSION); + + /** Check object list. **/ + if (objlist == NULL) + { + mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); + return -1; + } + ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); + + /** Extract str1. **/ + if (maybe_str1->Flags & EXPR_F_NULL) + { + mssErrorf(1, "EXP", "%s(NULL, ...) str1 cannot be NULL.", fn_name); + return -1; + } + if (maybe_str1->DataType != DATA_T_STRING) + { + mssErrorf(1, "EXP", "%s(\?\?\?, ..) str1 should be a string.", fn_name); + return -1; + } + char* str1 = maybe_str1->String; + if (str1 == NULL) + { + mssErrorf(1, "EXP", + "%s(nothing?, ...) expected string from str1 (of datatype DataType = " + "DATA_T_STRING), but the String was NULL or did not exist!", + fn_name + ); + return -1; + } + + /** Extract str2. **/ + if (maybe_str2->Flags & EXPR_F_NULL) + { + mssErrorf(1, "EXP", "%s(\"%s\", NULL) str2 cannot be NULL.", fn_name, str1); + return -1; + } + if (maybe_str2->DataType != DATA_T_STRING) + { + mssErrorf(1, "EXP", "%s(\"%s\", \?\?\?) str2 should be a string.", fn_name, str1); + return -1; + } + char* str2 = maybe_str2->String; + if (str2 == NULL) + { + mssErrorf(1, "EXP", + "%s(\"%s\", nothing?) expected string from str2 (of datatype DataType = " + "DATA_T_STRING), but the String was NULL or did not exist!", + fn_name, str1 + ); + return -1; + } + + /** Build vectors. **/ + int ret; + const pVector v1 = check_ptr(ca_build_vector(str1)); + const pVector v2 = check_ptr(ca_build_vector(str2)); + if (v1 == NULL || v2 == NULL) + { + mssErrorf(1, "EXP", + "%s(\"%s\", \"%s\") - Failed to build vectors.", + fn_name, str1, str2 + ); + ret = -1; + } + else + { + tree->Integer = (ca_eql(v1, v2)) ? 1 : 0; + tree->DataType = DATA_T_INTEGER; + ret = 0; + } + + if (v1 != NULL) ca_free_vector(v1); + if (v2 != NULL) ca_free_vector(v2); + return ret; + } + /*** Computes double metaphone. *** *** @param tree The tree resulting from this function. @@ -4406,6 +4612,8 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "hash", (char*)exp_fn_hash); xhAdd(&EXP.Functions, "hmac", (char*)exp_fn_hmac); xhAdd(&EXP.Functions, "log10", (char*)exp_fn_log10); + xhAdd(&EXP.Functions, "ln", (char*)exp_fn_log_natural); + xhAdd(&EXP.Functions, "logn", (char*)exp_fn_log_base_n); xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); @@ -4422,6 +4630,7 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "lev_cmp", (char*)exp_fn_lev_cmp); xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_cmp); xhAdd(&EXP.Functions, "levenshtein_compare", (char*)exp_fn_lev_cmp); + xhAdd(&EXP.Functions, "sparse_eql", (char*)exp_fn_sparse_eql); xhAdd(&EXP.Functions, "double_metaphone", (char*)exp_fn_double_metaphone); /** Windowing **/ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index c10c6fca6..6d1aaf313 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -71,8 +71,8 @@ *** https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors ***/ -/** Pure Laziness **/ -#define ENABLE_TPRINTF +/** Pure Laziness. **/ +// #define ENABLE_TPRINTF /** Debugging **/ #ifndef ENABLE_TPRINTF @@ -815,7 +815,7 @@ static int ci_ParseAttribute( if (datatype != exp->DataType) { mssErrorf(1, "Cluster", - "Expected \"%s\" : %s, but got type %s.", + "Expected ['%s' : %s], but got type %s.", attr_name, ci_TypeToStr(datatype), ci_TypeToStr(exp->DataType) ); goto err; @@ -826,7 +826,7 @@ static int ci_ParseAttribute( if (ret != 0) { mssErrorf(1, "Cluster", - "Failed to get \"%s\" : %s using expression \"%s\" (error code %d).", + "Failed to get ['%s' : %s] using expression \"%s\" (error code %d).", attr_name, ci_TypeToStr(datatype), exp->Name, ret ); goto err; @@ -1215,7 +1215,7 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) if (strcmp(group_type, "cluster/cluster") != 0) { fprintf(stderr, - "Warning: Unknown group \"%s\" : \"%s\" in cluster \"%s\".\n", + "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", name, group_type, inf->Name ); continue; @@ -1364,13 +1364,13 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) if (search_data->Name == NULL) goto err_free_search; if (!check(objCurrentDate(&search_data->DateCreated))) goto err_free_search; - /** Get source. **/ - char* source_name; - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_name), node_data->ParamList, true, true) != 0) return NULL; + /** Get source cluster. **/ + char* source_cluster_name; + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), node_data->ParamList, true, true) != 0) return NULL; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { pClusterData cluster_data = node_data->ClusterDatas[i]; - if (strcmp(source_name, cluster_data->Name) == 0) + if (strcmp(source_cluster_name, cluster_data->Name) == 0) { /** Source found. **/ search_data->Source = cluster_data; @@ -1384,13 +1384,13 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) if (search_data->Source == NULL) { /** Print error. **/ - mssErrorf(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_name, search_data->Name); + mssErrorf(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); /** Attempt to give a hint. **/ char* cluster_names[node_data->nClusterDatas]; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) cluster_names[i] = node_data->ClusterDatas[i]->Name; - ci_TryHint(source_name, cluster_names, node_data->nClusterDatas); + ci_TryHint(source_cluster_name, cluster_names, node_data->nClusterDatas); /** Fail. **/ goto err_free_search; @@ -1457,7 +1457,7 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) char* group_type = check_ptr(sub_inf->UsrType); if (group_type == NULL) goto err_free_search; fprintf(stderr, - "Warning: Unknown group \"%s\" : \"%s\" in search \"%s\".\n", + "Warning: Unknown group [\"%s\" : \"%s\"] in search \"%s\".\n", name, group_type, inf->Name ); break; @@ -1513,7 +1513,7 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) ci_FreeSearchData(search_data); err: - mssErrorf(0, "Cluster", "Failed to parse search from group \"%s\".", inf->Name); + mssErrorf(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); return NULL; } @@ -2174,9 +2174,9 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (obj == NULL) { mssErrorf(0, "Cluster", - "Failed to open object driver:" - " > Attribute: \"%s\" : String\n" - " > Source Path: %s", + "Failed to open object driver:\n" + " > Attribute: ['%s' : String]\n" + " > Source Path: \"%s\"", source_data->AttrName, source_data->SourcePath ); @@ -2190,9 +2190,9 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { mssErrorf(0, "Cluster", "Failed to open query:\n" - " > Attribute: \"%s\" : String\n" + " > Attribute: ['%s' : String]\n" " > Driver Used: %s\n" - " > Source Path: %s", + " > Source Path: \"%s\"", source_data->AttrName, obj->Driver->Name, source_data->SourcePath @@ -2221,9 +2221,9 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { mssErrorf(0, "Cluster", "Failed to get type for %uth entry:\n" - " > Attribute: \"%s\" : String\n" + " > Attribute: '%s' : String\n" " > Driver Used: %s\n" - " > Source Path: %s", + " > Source Path: \"%s\"", i, source_data->AttrName, obj->Driver->Name, @@ -2235,9 +2235,9 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { mssErrorf(1, "Cluster", "Type for %uth entry was not a string:\n" - " > Attribute: \"%s\" : %s!!\n" + " > Attribute: ['%s' : %s]\n" " > Driver Used: %s\n" - " > Source Path: %s", + " > Source Path: \"%s\"", i, source_data->AttrName, ci_TypeToStr(datatype), obj->Driver->Name, @@ -2254,9 +2254,9 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) tprintf("\n"); mssErrorf(0, "Cluster", "Failed to value for %uth entry:\n" - " > Attribute: \"%s\" : String\n" + " > Attribute: ['%s' : String]\n" " > Driver Used: %s\n" - " > Source Path: %s\n" + " > Source Path: \"%s\"\n" " > Error code: %d", i, source_data->AttrName, @@ -2264,7 +2264,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) source_data->SourcePath, ret ); - successful = false; goto end_free_data; } @@ -2393,7 +2392,7 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** We need the SourceData vectors to compute clusters. **/ if (ci_ComputeSourceData(source_data, node_data->ParamList->Session) != 0) { - mssErrorf(0, "Cluster", "Failed to compute SourceData."); + mssErrorf(0, "Cluster", "ClusterData computation failed due to missing SourceData."); goto err; } @@ -2532,7 +2531,7 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) cluster_data->Clusters = NULL; err: - mssErrorf(0, "Cluster", "Cluster computation failed for \"%s\".", cluster_data->Name); + mssErrorf(0, "Cluster", "ClusterData computation failed for \"%s\".", cluster_data->Name); return -1; } @@ -2561,7 +2560,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) ret = ci_ComputeClusterData(cluster_data, node_data); if (ret != 0) { - mssErrorf(0, "Cluster", "Search computation failed due to missing clusters."); + mssErrorf(0, "Cluster", "SearchData computation failed due to missing clusters."); goto err; } @@ -2608,7 +2607,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) search_data->Threshold, dups )); - if (dups_temp == NULL) goto err; + if (dups_temp == NULL) goto err_free; else dups = dups_temp; } } @@ -2639,7 +2638,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) search_data->Threshold, dups )); - if (dups_temp == NULL) goto err; + if (dups_temp == NULL) goto err_free; else dups = dups_temp; } } @@ -2651,10 +2650,10 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) "Unknown similarity meansure \"%s\".", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); - goto err; + goto err_free; } timer_stop(timer); - if (dups_temp == NULL) goto err; + if (dups_temp == NULL) goto err_free; else dups = dups_temp; tprintf("Search done after %.4lf.\n", timer_get(timer)); @@ -2671,7 +2670,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Success. **/ return 0; - err: + err_free: if (dups != NULL) { for (unsigned int i = 0u; i < dups->nItems; i++) @@ -2682,7 +2681,8 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) check(xaFree(dups)); /* Failure ignored. */ } - mssErrorf(0, "Cluster", "Search computation failed for \"%s\".", search_data->Name); + err: + mssErrorf(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); return -1; } @@ -2770,7 +2770,7 @@ static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData err: mssErrorf(1, "Cluster", - "Failed to get parameter %s : %s", + "Failed to get parameter ['%s' : %s]", attr_name, ci_TypeToStr(datatype) ); return -1; @@ -3040,7 +3040,7 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_ROOT: mssErrorf(1, "Cluster", "Querying the root node of a cluster file is not allowed."); fprintf(stderr, " > Hint: Try / or /\n"); - return NULL; + goto err; case TARGET_CLUSTER: { @@ -3049,8 +3049,8 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ret = ci_ComputeClusterData(target, cluster_query->DriverData->NodeData); if (ret != 0) { - mssErrorf(0, "Cluster", "Internal cluster computation failed."); - return NULL; + mssErrorf(0, "Cluster", "Failed to compute ClusterData for query."); + goto err; } data_amount = target->nClusters; break; @@ -3063,8 +3063,8 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ret = ci_ComputeSearchData(target, cluster_query->DriverData->NodeData); if (ret != 0) { - mssErrorf(0, "Cluster", "Internal search computation failed."); - return NULL; + mssErrorf(0, "Cluster", "Failed to compute SearchData for query."); + goto err; } data_amount = target->nDups; break; @@ -3073,29 +3073,33 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_CLUSTER_ENTRY: case TARGET_SEARCH_ENTRY: mssErrorf(1, "Cluster", "Querying a query result is not allowed."); - return NULL; + goto err; default: mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); - return NULL; + goto err; } tprintf("Fetch Index: %u/16 (total: %u)\n", cluster_query->RowIndex, data_amount); - /** Cap results to 16 for faster debugging. TODO: Remove. **/ - data_amount = min(data_amount, 16); + /** Cap results to 16 for faster debugging. TODO: Israel - Remove. **/ +// data_amount = min(data_amount, 16); /** Check that the requested data exists, returning null if we've reached the end of the data. **/ if (cluster_query->RowIndex >= data_amount) return NULL; /** Create the result struct. **/ pDriverData driver_data = check_ptr(nmMalloc(sizeof(DriverData))); - if (driver_data == NULL) return NULL; + if (driver_data == NULL) goto err; memcpy(driver_data, cluster_query->DriverData, sizeof(DriverData)); driver_data->TargetType = new_target_type; driver_data->TargetIndex = cluster_query->RowIndex++; /** Success. **/ return driver_data; + + err: + mssErrorf(0, "Cluster", "Failed to fetch query result."); + return NULL; } @@ -3259,7 +3263,7 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val if (datatype != expected_datatype) { mssErrorf(1, "Cluster", - "Type mismatch: Accessing attribute '%s' : %s as type %s.", + "Type mismatch: Accessing attribute ['%s' : %s] as type %s.", attr_name, ci_TypeToStr(expected_datatype), ci_TypeToStr(datatype) ); return -1; @@ -4168,7 +4172,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx if (param->String == NULL) { mssErrorf(1, "Cluster", - "param : \"show\" | \"show_all\" | \"drop_all\" is required for the cache method." + "[param : \"show\" | \"show_all\" | \"drop_all\"] is required for the cache method." ); goto err; } @@ -4247,7 +4251,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx /** Unknown parameter. **/ mssErrorf(1, "Cluster", - "Expected param : \"show\" | \"show_all\" | \"drop_all\" the cache method, but got: \"%s\"", + "Expected [param : \"show\" | \"show_all\" | \"drop_all\"] for the cache method, but got: \"%s\"", param->String ); goto err; From 63a4dc224ae126d38c6902d118d2e92872d2490a Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 7 Nov 2025 11:10:31 -0700 Subject: [PATCH 007/361] Add warning for providing an invalid parameter. Modify cluster files to use string keys. Build vectors fully sparsely. Add ca_fprint_vector(). Add snprint_llu(). Add exp_fn_trim(). Update exp_fn_cmp(). Organize exp function definitions by group. Add statistics tracking to cluster driver. Reduce minimum hint threshold. Add array handling to ci_xaToTrimmedArray(). Update timer to handle multiple starts and stops properly. --- centrallix-lib/include/clusters.h | 13 +- centrallix-lib/include/util.h | 4 +- centrallix-lib/src/clusters.c | 321 ++++++++---- centrallix-lib/src/util.c | 37 +- centrallix-os/cluster-schema.cluster | 9 +- centrallix/expression/exp_functions.c | 325 ++++++++---- centrallix/osdrivers/objdrv_cluster.c | 698 +++++++++++++++----------- 7 files changed, 896 insertions(+), 511 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 288f81714..8338cd5e0 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -41,7 +41,11 @@ #include "cxlib/xarray.h" #endif -#define CA_NUM_DIMS 251 /* aka. The vector table size. */ +/*** 2147483629 is the signed int max, and is also a prime number. + *** Using this value ensures that the longest run of 0s will not + *** cause an int underflow with the current encoding scheme. + ***/ +#define CA_NUM_DIMS 251 //2147483629 /* aka. The vector table size. */ /// LINK ../../centrallix-sysdoc/string_comparison.md#cosine_charsets /** The character used to create a pair with the first and last characters of a string. **/ @@ -55,8 +59,8 @@ typedef double* pCentroid; /* Dense centroid. */ /** Duplocate information. **/ typedef struct { - unsigned int id1; - unsigned int id2; + void* key1; + void* key2; double similarity; } Dup, *pDup; @@ -70,6 +74,7 @@ typedef struct pVector ca_build_vector(const char* str); unsigned int ca_sparse_len(const pVector vector); +void ca_print_vector(const pVector vector); void ca_free_vector(pVector sparse_vector); int ca_kmeans( pVector* vectors, @@ -105,12 +110,14 @@ pXArray ca_sliding_search( const unsigned int window_size, const double (*similarity)(void*, void*), const double dupe_threshold, + void** maybe_keys, pXArray dups); pXArray ca_complete_search( void** data, const unsigned int num_data, const double (*similarity)(void*, void*), const double dupe_threshold, + void** maybe_keys, pXArray dups); #endif /* End of .h file. */ diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index dd821767f..1f286cc26 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -25,11 +25,12 @@ extern "C" { unsigned int strtoui(const char *nptr, char **endptr, int base); char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes); + char* snprint_llu(char* buf, size_t buflen, unsigned long long value); void fprint_mem(FILE* out); typedef struct { - double start, end; + double start, total; } Timer, *pTimer; @@ -38,6 +39,7 @@ extern "C" { pTimer timer_start(pTimer timer); pTimer timer_stop(pTimer timer); double timer_get(pTimer timer); + pTimer timer_reset(pTimer timer); void timer_de_init(pTimer timer); void timer_free(pTimer timer); diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index ef1222873..d61a558c7 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -50,18 +50,31 @@ /*** Gets the hash, representing a pair of ASCII characters, represented by unsigned ints. *** Thank you to professor John Delano for this hashing algorithm. *** - *** @param num1 The first character in the pair. - *** @param num1 The second character in the pair. + *** @param c1 The first character in the pair. + *** @param c2 The second character in the pair. *** @returns The resulting hash. ***/ -static unsigned int hash_char_pair(const unsigned int num1, const unsigned int num2) +static unsigned int hash_char_pair(const char c1, const char c2) { - const double sum = (num1 * num1 * num1) + (num2 * num2 * num2); - const double scale = ((double)num1 + 1.0) / ((double)num2 + 1.0); + const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); + const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); const unsigned int hash = (unsigned int)round(sum * scale) - 1u; return hash % CA_NUM_DIMS; } +typedef struct + { + unsigned char c1, c2; + unsigned int hash; + } + CharPair, *pCharPair; + +static int charpair_cmp(const void *p1, const void *p2) + { + const CharPair *a = p1, *b = p2; + return a->hash - b->hash; + } + /*** Builds a vector using a string. *** *** Vectors are based on the frequencies of character pairs in the string. @@ -109,123 +122,185 @@ static unsigned int hash_char_pair(const unsigned int num1, const unsigned int n ***/ pVector ca_build_vector(const char* str) { - /** Allocate space for a dense vector. **/ - unsigned int dense_vector[CA_NUM_DIMS] = {0u}; - - /** j is the former character, i is the latter. **/ - const unsigned int num_chars = (unsigned int)strlen(str); - for (unsigned int j = 65535u, i = 0u; i <= num_chars; i++) + char chars[strlen(str) + 2u]; + unsigned int num_chars = 0u; + chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ + for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) { - /** isspace: space, \n, \v, \f, \r **/ - if (isspace(str[i])) continue; - - /** ispunct: !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ **/ - if (ispunct(str[i]) && str[i] != CA_BOUNDARY_CHAR) continue; + unsigned char c = *char_ptr; - /*** iscntrl (0-8): NULL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS - *** (14-31): SO, SI, DLE, DC1-4, NAK, SYN, ETB, CAN EM, - *** SUB, ESC, FS, GS, RS, US - ***/ - if (iscntrl(str[i]) && i != num_chars) - { - fprintf(stderr, - "ca_build_vector(%s) - Warning: Skipping unknown character #%u.\n", - str, (unsigned int)str[i] - ); - continue; - } + /** Always consider boundary character in string. **/ + if (c == CA_BOUNDARY_CHAR) goto skip_checks; - /** First and last character should fall one before 'a' in the ASCII table. **/ - unsigned int temp1 = (j == 65535u) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[j]); - unsigned int temp2 = (i == num_chars) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[i]); + /** Ignore insignificant characters: spaces and punctuation. **/ + if (isspace(c)) continue; /* space, \n, \v, \f, \r */ + if (ispunct(c)) continue; /* !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ */ + skip_checks: /** Shift numbers to the end of the lowercase letters. **/ - if ('0' <= temp1 && temp1 <= '9') temp1 += 75u; - if ('0' <= temp2 && temp2 <= '9') temp2 += 75u; + if ('0' <= c && c <= '9') c += 75u; - /** Hash the character pair into an index (dimension). **/ - /** Note that temp will be between 97 ('a') and 132 ('9'). **/ - unsigned int dim = hash_char_pair(temp1, temp2); - - /** Increment the dimension of the dense vector by a number from 1 to 13. **/ - dense_vector[dim] += (temp1 + temp2) % 13u + 1u; - - j = i; + /** Store the character. **/ + chars[num_chars++] = tolower(c); } + chars[num_chars++] = CA_BOUNDARY_CHAR; /* Ending boundary character. */ - /** Count how much space is needed for a sparse vector. **/ - bool zero_prev = false; - size_t size = 0u; - for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) + /** Compute char pairs. **/ + CharPair char_pairs[num_chars]; + const unsigned int num_pairs = num_chars - 1u; + for (unsigned int i = 0u; i < num_pairs; i++) { - if (dense_vector[dim] == 0u) - { - size += (zero_prev) ? 0u : 1u; - zero_prev = true; - } - else - { - size++; - zero_prev = false; - } + /** Store characters. **/ + char_pairs[i].c1 = chars[i]; + char_pairs[i].c2 = chars[i + 1]; + + /** Hash the character pair into an index (dimension). **/ + /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ + char_pairs[i].hash = hash_char_pair(chars[i], chars[i + 1]); } - /*** Check compression size. - *** If this check fails, I doubt anything will break. However, the longest - *** word I know (supercalifragilisticexpialidocious) has only 35 character - *** pairs, so it shouldn't reach half this size (and it'd be even shorter - *** if the hash generates at least one collision). - *** - *** Bad vector compression will result in degraded performace and increased - *** memory usage. This indicates a likely bug in the code. Thus, if this - *** warning is ever generated, it is definitely worth investigating. - ***/ - const size_t expected_max_size = 64u; - if (size > expected_max_size) - { - fprintf(stderr, - "cli_build_vector(\"%s\") - Warning: Sparse vector larger than expected.\n" - " > Size: %lu\n" - " > #Dims: %u\n", - str, - size, - CA_NUM_DIMS - ); - } + /** Sort char_pairs by hash value. **/ + qsort(char_pairs, num_pairs, sizeof(CharPair), charpair_cmp); - /** Allocate space for sparse vector. **/ - const size_t sparse_vector_size = size * sizeof(int); - pVector sparse_vector = (pVector)check_ptr(nmSysMalloc(sparse_vector_size)); + /** Allocate space for the sparce vector. **/ + pVector sparse_vector = (pVector)check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); if (sparse_vector == NULL) return NULL; - /** Convert the dense vector above to a sparse vector. **/ - unsigned int j = 0u, sparse_idx = 0u; - while (j < CA_NUM_DIMS) - { - if (dense_vector[j] == 0u) - { - /*** Count and store consecutive zeros, except the first one, - *** which we already know is zero. - ***/ - unsigned int zero_count = 1u; - j++; - while (j < CA_NUM_DIMS && dense_vector[j] == 0u) - { - zero_count++; - j++; - } - sparse_vector[sparse_idx++] = (int)-zero_count; - } - else + /** Build the sparse vector. **/ + unsigned int cur = 0u, dim = 0u; + for (unsigned int i = 0u; i < num_pairs;) + { + unsigned int hash = char_pairs[i].hash; + + /** Proceed through the pairs until we find a unique hash. **/ + /** Dividing value by 2 each time reduces the impact of repeated pairs. **/ + int value = 0; + for (; i < num_pairs && char_pairs[i].hash == hash; i++) + value = (value / 2) + ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; + + /** Skip zeros to reach the dimension index specified by the hash. **/ + unsigned int num_zeros = hash - dim; + if (num_zeros > 0u) { - /** Store the value. **/ - sparse_vector[sparse_idx++] = (int)dense_vector[j++]; + sparse_vector[cur++] = (int)-num_zeros; + dim = hash; } + + /** Add the value to the sparse vector. **/ + sparse_vector[cur++] = value; + dim++; } + if (dim != CA_NUM_DIMS) sparse_vector[cur++] = -(CA_NUM_DIMS - dim); - return sparse_vector; + /** Trim extra space wasted by identical hashes. **/ + pVector trimmed_sparse_vector = (pVector)check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); + if (trimmed_sparse_vector == NULL) return NULL; + + return trimmed_sparse_vector; } +// Build vector by converting a dense vector to a sparse one. +//pVector ca_build_vector_old(const char* str) +// { +// /** Allocate space for a dense vector. **/ +// unsigned int dense_vector[CA_NUM_DIMS] = {0u}; +// +// /** j is the former character, i is the latter. **/ +// const unsigned int num_chars = (unsigned int)strlen(str); +// for (unsigned int j = 65535u, i = 0u; i <= num_chars; i++) +// { +// if (isspace(str[i])) continue; +// if (ispunct(str[i]) && str[i] != CA_BOUNDARY_CHAR) continue; +// +// /** First and last character should fall one before 'a' in the ASCII table. **/ +// unsigned int temp1 = (j == 65535u) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[j]); +// unsigned int temp2 = (i == num_chars) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[i]); +// +// /** Shift numbers to the end of the lowercase letters. **/ +// if ('0' <= temp1 && temp1 <= '9') temp1 += 75u; +// if ('0' <= temp2 && temp2 <= '9') temp2 += 75u; +// +// /** Hash the character pair into an index (dimension). **/ +// /** Note that temp will be between 97 ('a') and 132 ('9'). **/ +// unsigned int dim = hash_char_pair(temp1, temp2); +// +// /** Increment the dimension of the dense vector by a number from 1 to 13. **/ +// dense_vector[dim] += (temp1 + temp2) % 13u + 1u; +// +// j = i; +// } +// +// /** Count how much space is needed for a sparse vector. **/ +// bool zero_prev = false; +// size_t size = 0u; +// for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) +// { +// if (dense_vector[dim] == 0u) +// { +// size += (zero_prev) ? 0u : 1u; +// zero_prev = true; +// } +// else +// { +// size++; +// zero_prev = false; +// } +// } +// +// /*** Check compression size. +// *** If this check fails, I doubt anything will break. However, the longest +// *** word I know (supercalifragilisticexpialidocious) has only 35 character +// *** pairs, so it shouldn't reach half this size (and it'd be even shorter +// *** if the hash generates at least one collision). +// *** +// *** Bad vector compression will result in degraded performace and increased +// *** memory usage. This indicates a likely bug in the code. Thus, if this +// *** warning is ever generated, it is definitely worth investigating. +// ***/ +// const size_t expected_max_size = 256u; +// if (size > expected_max_size) +// { +// fprintf(stderr, +// "cli_build_vector(\"%s\") - Warning: Sparse vector larger than expected.\n" +// " > Size: %lu\n" +// " > #Dims: %u\n", +// str, +// size, +// CA_NUM_DIMS +// ); +// } +// +// /** Allocate space for sparse vector. **/ +// const size_t sparse_vector_size = size * sizeof(int); +// pVector sparse_vector = (pVector)check_ptr(nmSysMalloc(sparse_vector_size)); +// if (sparse_vector == NULL) return NULL; +// +// /** Convert the dense vector above to a sparse vector. **/ +// unsigned int dim = 0u, sparse_idx = 0u; +// while (dim < CA_NUM_DIMS) +// { +// if (dense_vector[dim] == 0u) +// { +// /** Count and store consecutive zeros, skipping the first one. **/ +// unsigned int zero_count = 1u; +// dim++; +// while (dim < CA_NUM_DIMS && dense_vector[dim] == 0u) +// { +// zero_count++; +// dim++; +// } +// sparse_vector[sparse_idx++] = (int)-zero_count; +// } +// else +// { +// /** Store the value. **/ +// sparse_vector[sparse_idx++] = (int)dense_vector[dim++]; +// } +// } +// +// return sparse_vector; +// } + /*** Free memory allocated to store a sparse vector. *** *** @param sparse_vector The sparse vector being freed. @@ -256,6 +331,21 @@ unsigned int ca_sparse_len(const pVector vector) return i; } +/*** Print the underlying implementation values sparsely allocated + *** vector (intended for debugging). + *** + *** @param out File to print to. + *** @param vector The vector. + ***/ +void ca_fprint_vector(FILE* out, const pVector vector) + { + const unsigned int len = ca_sparse_len(vector); + fprintf(out, "Vector: [%d", vector[0]); + for (unsigned int i = 1u; i < len; i++) + fprintf(out, ", %d", vector[i]); + fprintf(out, "]"); + } + /*** Compute the magnitude of a sparsely allocated vector. *** *** @param vector The vector. @@ -911,6 +1001,9 @@ void* ca_most_similar( *** the data param and returns their similarity. *** @param threshold The minimum threshold required for a duplocate to be *** included in the returned xArray. + *** @param maybe_keys A pointer to an array of keys, with one key per data. + *** These will be used to fill in the key1 and key2 attributes for each + *** struct. If this variable is null, these values are also left null. *** @param maybe_dups A pointer to an xArray in which dups should be found. *** Pass NULL to allocate a new one. *** @returns An xArray holding all of the duplocates found. If maybe_dups is @@ -922,11 +1015,12 @@ pXArray ca_sliding_search( const unsigned int window_size, const double (*similarity)(void*, void*), const double threshold, - pXArray dups) + void** maybe_keys, + pXArray maybe_dups) { /** Allocate space for dups (if necessary). **/ - const bool allocate_dups = (dups == NULL); - if (allocate_dups) + pXArray dups = maybe_dups; + if (dups == NULL) { /** Guess that we will need space for num_data * 2 dups. **/ const int guess_size = num_data * 2; @@ -955,8 +1049,11 @@ pXArray ca_sliding_search( glyph(find); Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); if (dup == NULL) goto err_free_dups; - dup->id1 = i; - dup->id2 = j; + if (maybe_keys != NULL) + { + dup->key1 = maybe_keys[i]; + dup->key2 = maybe_keys[j]; + } dup->similarity = sim; if (!check_neg(xaAddItem(dups, (void*)dup))) goto err_free_dups; } @@ -973,7 +1070,7 @@ pXArray ca_sliding_search( /** Free the dups we added to the XArray. */ while (dups->nItems > num_starting_dups) nmFree(dups->Items[dups->nItems--], sizeof(Dup)); - if (allocate_dups) check(xaDeInit(dups)); /* Failure ignored. */ + if (maybe_dups == NULL) check(xaDeInit(dups)); /* Failure ignored. */ err: return NULL; @@ -990,6 +1087,9 @@ pXArray ca_sliding_search( *** the data param and returns their similarity. *** @param threshold The minimum threshold required for a duplocate to be *** included in the returned xArray. + *** @param maybe_keys A pointer to an array of keys, with one key per data. + *** These will be used to fill in the key1 and key2 attributes for each + *** struct. If this variable is null, these values are also left null. *** @param maybe_dups A pointer to an xArray in which dups should be found. *** Pass NULL to allocate a new one. *** @returns An xArray holding all of the duplocates found. If maybe_dups is @@ -1000,9 +1100,10 @@ pXArray ca_complete_search( const unsigned int num_data, const double (*similarity)(void*, void*), const double threshold, - pXArray dups) + void** maybe_keys, + pXArray maybe_dups) { - return ca_sliding_search(data, num_data, num_data, similarity, threshold, dups); + return ca_sliding_search(data, num_data, num_data, similarity, threshold, maybe_keys, maybe_dups); } /** Scope cleanup. **/ diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 450c16593..b18361280 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -155,6 +155,32 @@ char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) } #undef nUints +char* snprint_llu(char* buf, size_t buflen, unsigned long long value) + { + if (buflen == 0) return NULL; + if (value == 0) + { + if (buflen > 1) { buf[0] = '0'; buf[1] = '\0'; } + else buf[0] = '\0'; + return buf; + } + + char tmp[32]; + unsigned int ti = 0; + while (value > 0 && ti < sizeof(tmp) - 1) + { + if (ti % 4 == 3) tmp[ti++] = ','; + tmp[ti++] = '0' + (value % 10); + value /= 10; + } + tmp[ti] = '\0'; + + unsigned int outlen = min(ti, buflen - 1u); + for (unsigned int i = 0u; i < outlen; i++) buf[i] = tmp[ti - i - 1]; + buf[outlen] = '\0'; + return buf; + } + void fprint_mem(FILE* out) { FILE* fp = fopen("/proc/self/statm", "r"); @@ -192,7 +218,7 @@ pTimer timer_init(pTimer timer) { if (timer == NULL) return NULL; timer->start = NAN; - timer->end = NAN; + timer->total = 0.0; return timer; } @@ -211,13 +237,18 @@ pTimer timer_start(pTimer timer) pTimer timer_stop(pTimer timer) { if (!timer) return timer; - timer->end = get_time(); + timer->total += get_time() - timer->start; return timer; } double timer_get(pTimer timer) { - return (timer) ? timer->end - timer->start : NAN; + return (timer) ? timer->total : NAN; + } + +pTimer timer_reset(pTimer timer) + { + return timer_init(timer); } void timer_de_init(pTimer timer) {} diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index e87ae6b5f..277e2bb12 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -14,7 +14,8 @@ file_name "system/cluster" ... source : DataSourcePath - attr_name : string ⊂ DataSourcePath/columns + key_attr : string ⊂ DataSourcePath/columns + data_attr : string ⊂ DataSourcePath/columns cluster_name "cluster/cluster" { @@ -54,10 +55,8 @@ file_name "system/cluster" ... /search_name - /{query} - - /id1 : uint < sizeof(source/attr_name) // The id of the first data point. - - /id2 : uint < sizeof(source/attr_name) // The id of the second data point. - - /val1 : string // The value of the first data point. - - /val2 : string // The value of the second data point. + - /key1 : string // The key of the first data point. + - /key2 : string // The key of the second data point. - /sim : double && 0.0 < x <= threshold // The similarity of the two data points. ... diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 751e07297..71f906e3d 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -74,16 +74,30 @@ #include "expression.h" #include "obj.h" -/** Duplocate detection settings. **/ -// #define SEPARATOR "|" -// #define SEPARATOR_CHAR '|' -// #define DBL_BUF_SIZE 16u -// #define USE_PARALLEL_COMPLETE_SEARCH true -// #define MIN_PARALLEL_COMPLETE_SEARCH 1000 -// #define MAX_COMPLETE_SEARCH 50 * 1000 // Default: 100 * 1000 -// #define KMEANS_IMPROVEMENT_THRESHOLD 0.0002 -#define EXP_NUM_DIMS 251 /* aka. The size of the vector table. */ -const int EXP_VECTOR_TABLE_SIZE = EXP_NUM_DIMS; /* Should probably be removed. */ + +/** TODO: I think this should be moved to datatypes. **/ +/** Should maybe replace duplocate functionality elsewhere. **/ +static char* ci_TypeToStr(const int type) + { + switch (type) + { + case DATA_T_UNAVAILABLE: return "Unknown"; + case DATA_T_INTEGER: return "Integer"; + case DATA_T_STRING: return "String"; + case DATA_T_DOUBLE: return "Double"; + case DATA_T_DATETIME: return "DateTime"; + case DATA_T_INTVEC: return "IntVecor"; + case DATA_T_STRINGVEC: return "StringVector"; + case DATA_T_MONEY: return "Money"; + case DATA_T_ARRAY: return "Array"; + case DATA_T_CODE: return "Code"; + case DATA_T_BINARY: return "Binary"; + } + + /** Invalid type. **/ + mssErrorf(1, "Cluster", "Invalid type %d.\n", type); + return "Invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ + } /****** Evaluator functions follow for expEvalFunction ******/ @@ -1239,6 +1253,31 @@ int exp_fn_rtrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpre } +int exp_fn_trim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) + { + int ret; + + /** Invoke left trim. **/ + ret = exp_fn_ltrim(tree, objlist, i0, i1, i2); + if (ret != 0) + { + mssErrorf(0, "EXP", "Failed to left trim (error code: %d).", ret); + return ret; + } + + /** Invoke right trim. **/ + ret = exp_fn_rtrim(tree, objlist, i0, i1, i2); + if (ret != 0) + { + mssErrorf(0, "EXP", "Failed to right trim (error code: %d).", ret); + return ret; + } + + /** Success. **/ + return 0; + } + + int exp_fn_right(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { int n,i; @@ -4105,10 +4144,8 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress *** @param is_cos Whether to compute cosine or levenshtien. *** @returns 0 for success, -1 for failure. ***/ -static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1, bool is_cos) +static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1, const char* fn_name) { - const char fn_name[] = "cos_cmp"; - /** Check number of arguments. **/ const int num_params = tree->Children.nItems; if (num_params != 2) @@ -4138,49 +4175,33 @@ static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe /** Extract str1. **/ if (maybe_str1->Flags & EXPR_F_NULL) { - mssErrorf(1, "EXP", "%s(NULL, ...) str1 cannot be NULL.", fn_name); - return -1; + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_DOUBLE; + return 0; } if (maybe_str1->DataType != DATA_T_STRING) { mssErrorf(1, "EXP", "%s(\?\?\?, ..) str1 should be a string.", fn_name); return -1; } - char* str1 = maybe_str1->String; - if (str1 == NULL) - { - mssErrorf(1, "EXP", - "%s(nothing?, ...) expected string from str1 (of datatype DataType = " - "DATA_T_STRING), but the String was NULL or did not exist!", - fn_name - ); - return -1; - } + char* str1 = check_ptr(maybe_str1->String); /** Extract str2. **/ if (maybe_str2->Flags & EXPR_F_NULL) { - mssErrorf(1, "EXP", "%s(\"%s\", NULL) str2 cannot be NULL.", fn_name, str1); - return -1; + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_DOUBLE; + return 0; } if (maybe_str2->DataType != DATA_T_STRING) { mssErrorf(1, "EXP", "%s(\"%s\", \?\?\?) str2 should be a string.", fn_name, str1); return -1; } - char* str2 = maybe_str2->String; - if (str2 == NULL) - { - mssErrorf(1, "EXP", - "%s(\"%s\", nothing?) expected string from str2 (of datatype DataType = " - "DATA_T_STRING), but the String was NULL or did not exist!", - fn_name, str1 - ); - return -1; - } + char* str2 = check_ptr(maybe_str2->String); /** Handle either cos_cmp or lev_cmp. **/ - if (is_cos) + if (fn_name[0] == 'c') { /* cos_cmp */ int ret; @@ -4215,35 +4236,20 @@ static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe return -1; } -/*** Computes cosine similarity between two strings. - *** - *** @param tree The tree resulting from this function. - *** @param objlist The evaluation "scope", including available variables. - *** @param maybe_str1 Possibly the first string. - *** @param maybe_str2 Possibly the second string. - *** @param u1 Unused parameter. - *** @returns 0 for success, -1 for failure. - ***/ -int exp_fn_cos_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { - return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, true); - } -/*** Computes levenshtein similarity by normalizing the levenshtein edit - *** distance between two strings with the length of the longer string. - *** - *** @param tree The tree resulting from this function. - *** @param objlist The evaluation "scope", including available variables. - *** @param maybe_str1 Possibly the first string. - *** @param maybe_str2 Possibly the second string. - *** @param u1 Unused parameter. - *** @returns 0 for success, -1 for failure. - ***/ +int exp_fn_cos_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) + { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "cos_cmp"); } +int exp_fn_cos_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) + { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "cos_compare"); } +int exp_fn_cosine_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) + { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "cosine_compare"); } int exp_fn_lev_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { - return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, false); - } - + { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "lev_cmp"); } +int exp_fn_lev_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) + { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "lev_compare"); } +int exp_fn_levenshtein_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) + { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "levenshtein_compare"); } + /*** Comparse two strings to see if their sparse vectors are equal. *** @@ -4435,6 +4441,110 @@ int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression return 0; } + +int exp_fn_aggregate_similarities(pExpression tree, pParamObjects objlist) + { + const char fn_name[] = "aggregate_similarities"; + + /** Check number of arguments. **/ + const int num_params = tree->Children.nItems; + if (num_params != 6) + { + mssErrorf(1, "EXP", "%s(?) expects 6 parameters, got %d parameters.", fn_name, num_params); + return -1; + } + + /** Magic checks. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + ASSERTMAGIC(tree->Children.Items[0], MGK_EXPRESSION); + ASSERTMAGIC(tree->Children.Items[1], MGK_EXPRESSION); + ASSERTMAGIC(tree->Children.Items[2], MGK_EXPRESSION); + ASSERTMAGIC(tree->Children.Items[3], MGK_EXPRESSION); + + /** Check object list. **/ + if (objlist == NULL) + { + mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); + return -1; + } + ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); + + /** Extract parameters. **/ + double params[4] = {NAN}; + const char names[4][8] = {"name", "email", "phone", "address"}; + for (unsigned int i = 0; i < 4u; i++) + { + pExpression param = (pExpression)tree->Children.Items[i]; + + /** Ignore null values. **/ + if (param->Flags & EXPR_F_NULL) continue; + + /** Only accept doubles. **/ + if (param->DataType != DATA_T_DOUBLE) + { + mssErrorf(1, "EXP", + "%s() param%u (%s) expected type %s but got %s.", + fn_name, i, names[i], ci_TypeToStr(DATA_T_DOUBLE), ci_TypeToStr(param->DataType) + ); + if (param->DataType == DATA_T_INTEGER) fprintf(stderr, "Value: %d\n", param->Integer); + return -1; + } + + /** Do not accept NaN. **/ + params[i] = param->Types.Double; + if (isnan(params[i])) + { + mssErrorf(1, "EXP", "%s() param%u (%s) cannot be NaN", fn_name, names[i], i); + return -1; + } + } + + char* dup_names[2] = {NULL}; + for (unsigned int i = 0; i < 2u; i++) + { + pExpression param = (pExpression)tree->Children.Items[i + 4u]; + + /** Ignore null values. **/ + if (param->Flags & EXPR_F_NULL) continue; + + /** Only accept doubles. **/ + if (param->DataType != DATA_T_STRING) + { + mssErrorf(1, "EXP", + "%s() param%u expected type %s but got %s.", + fn_name, i, ci_TypeToStr(DATA_T_STRING), ci_TypeToStr(param->DataType) + ); + return -1; + } + + dup_names[i] = param->String; + } + + FILE *f = check_ptr(fopen("/home/israel/exp_log.swift", "a")); + check_neg(fprintf(f, "aggregate_similarities(%g, %g, %g, %g, \"%s\", \"%s\")", params[0], params[1], params[2], params[3], dup_names[0], dup_names[1])); + + /** Compute aggregated similarity. **/ + double name_sim = params[0]; + double email_sim = params[1]; + double phone_sim = params[2]; + double address_sim = params[3]; + + double mean = 0.0, n = 0.0; + if (name_sim > 0.0) { mean += name_sim; n++; } + if (email_sim > 0.0) { mean += email_sim; n++; } + if (phone_sim > 0.0) { mean += phone_sim; n++; } + if (address_sim > 0.0) { mean += address_sim; n++; } + mean /= n; + + /** Success. **/ + tree->DataType = DATA_T_DOUBLE; + tree->Types.Double = mean; + fprintf(f, " = %g\n", tree->Types.Double); + check(fclose(f)); + return 0; + } + + /* * exp_fn_argon2id * This method hashes a given password using the Argon2 algorithm (ID variant) @@ -4557,27 +4667,42 @@ int exp_fn_argon2id(pExpression tree, pParamObjects objlist, pExpression passwor int exp_internal_DefineFunctions() { - /** Initialize library **/ + /** Initialize library. **/ ca_init(); - /** Function list for EXPR_N_FUNCTION nodes **/ - xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); + /** Function list for EXPR_N_FUNCTION nodes. **/ + + /** General. **/ xhAdd(&EXP.Functions, "user_name", (char*)exp_fn_user_name); xhAdd(&EXP.Functions, "convert", (char*)exp_fn_convert); xhAdd(&EXP.Functions, "wordify", (char*)exp_fn_wordify); xhAdd(&EXP.Functions, "abs", (char*)exp_fn_abs); xhAdd(&EXP.Functions, "ascii", (char*)exp_fn_ascii); xhAdd(&EXP.Functions, "condition", (char*)exp_fn_condition); - xhAdd(&EXP.Functions, "charindex", (char*)exp_fn_charindex); - xhAdd(&EXP.Functions, "upper", (char*)exp_fn_upper); - xhAdd(&EXP.Functions, "lower", (char*)exp_fn_lower); - xhAdd(&EXP.Functions, "mixed", (char*)exp_fn_mixed); - xhAdd(&EXP.Functions, "char_length", (char*)exp_fn_char_length); - xhAdd(&EXP.Functions, "datepart", (char*)exp_fn_datepart); xhAdd(&EXP.Functions, "isnull", (char*)exp_fn_isnull); + xhAdd(&EXP.Functions, "eval", (char*)exp_fn_eval); + xhAdd(&EXP.Functions, "truncate", (char*)exp_fn_truncate); + xhAdd(&EXP.Functions, "constrain", (char*)exp_fn_constrain); + xhAdd(&EXP.Functions, "has_endorsement", (char*)exp_fn_has_endorsement); + xhAdd(&EXP.Functions, "rand", (char*)exp_fn_rand); + xhAdd(&EXP.Functions, "nullif", (char*)exp_fn_nullif); + xhAdd(&EXP.Functions, "hash", (char*)exp_fn_hash); + xhAdd(&EXP.Functions, "hmac", (char*)exp_fn_hmac); + xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); + xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); + xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); + + /** Dates. **/ + xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); + xhAdd(&EXP.Functions, "datepart", (char*)exp_fn_datepart); + xhAdd(&EXP.Functions, "dateadd", (char*)exp_fn_dateadd); + xhAdd(&EXP.Functions, "datediff", (char*)exp_fn_datediff); + + /** Strings. **/ xhAdd(&EXP.Functions, "ltrim", (char*)exp_fn_ltrim); xhAdd(&EXP.Functions, "lztrim", (char*)exp_fn_lztrim); xhAdd(&EXP.Functions, "rtrim", (char*)exp_fn_rtrim); + xhAdd(&EXP.Functions, "trim", (char*)exp_fn_trim); xhAdd(&EXP.Functions, "substring", (char*)exp_fn_substring); xhAdd(&EXP.Functions, "right", (char*)exp_fn_right); xhAdd(&EXP.Functions, "ralign", (char*)exp_fn_ralign); @@ -4587,12 +4712,22 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "escape", (char*)exp_fn_escape); xhAdd(&EXP.Functions, "quote", (char*)exp_fn_quote); xhAdd(&EXP.Functions, "substitute", (char*)exp_fn_substitute); - xhAdd(&EXP.Functions, "eval", (char*)exp_fn_eval); + xhAdd(&EXP.Functions, "upper", (char*)exp_fn_upper); + xhAdd(&EXP.Functions, "lower", (char*)exp_fn_lower); + xhAdd(&EXP.Functions, "mixed", (char*)exp_fn_mixed); + xhAdd(&EXP.Functions, "char_length", (char*)exp_fn_char_length); + xhAdd(&EXP.Functions, "charindex", (char*)exp_fn_charindex); + xhAdd(&EXP.Functions, "dateformat", (char*)exp_fn_dateformat); + xhAdd(&EXP.Functions, "moneyformat", (char*)exp_fn_moneyformat); + + /** Numbering systems (e.g. base 16 aka. hex, base 8 aka. octal, etc.). **/ + xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); + xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); + xhAdd(&EXP.Functions, "to_hex", (char*)exp_fn_to_hex); + xhAdd(&EXP.Functions, "from_hex", (char*)exp_fn_from_hex); + + /** Math. **/ xhAdd(&EXP.Functions, "round", (char*)exp_fn_round); - xhAdd(&EXP.Functions, "dateadd", (char*)exp_fn_dateadd); - xhAdd(&EXP.Functions, "datediff", (char*)exp_fn_datediff); - xhAdd(&EXP.Functions, "truncate", (char*)exp_fn_truncate); - xhAdd(&EXP.Functions, "constrain", (char*)exp_fn_constrain); xhAdd(&EXP.Functions, "sin", (char*)exp_fn_sin); xhAdd(&EXP.Functions, "cos", (char*)exp_fn_cos); xhAdd(&EXP.Functions, "tan", (char*)exp_fn_tan); @@ -4604,41 +4739,28 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "square", (char*)exp_fn_square); xhAdd(&EXP.Functions, "degrees", (char*)exp_fn_degrees); xhAdd(&EXP.Functions, "radians", (char*)exp_fn_radians); - xhAdd(&EXP.Functions, "has_endorsement", (char*)exp_fn_has_endorsement); - xhAdd(&EXP.Functions, "rand", (char*)exp_fn_rand); - xhAdd(&EXP.Functions, "nullif", (char*)exp_fn_nullif); - xhAdd(&EXP.Functions, "dateformat", (char*)exp_fn_dateformat); - xhAdd(&EXP.Functions, "moneyformat", (char*)exp_fn_moneyformat); - xhAdd(&EXP.Functions, "hash", (char*)exp_fn_hash); - xhAdd(&EXP.Functions, "hmac", (char*)exp_fn_hmac); xhAdd(&EXP.Functions, "log10", (char*)exp_fn_log10); xhAdd(&EXP.Functions, "ln", (char*)exp_fn_log_natural); xhAdd(&EXP.Functions, "logn", (char*)exp_fn_log_base_n); xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); - xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); - xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); - xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); - xhAdd(&EXP.Functions, "to_hex", (char*)exp_fn_to_hex); - xhAdd(&EXP.Functions, "from_hex", (char*)exp_fn_from_hex); - xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); - xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); - /** Duplicate Detection **/ + /** Duplicate detection. **/ xhAdd(&EXP.Functions, "cos_cmp", (char*)exp_fn_cos_cmp); - xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_cmp); - xhAdd(&EXP.Functions, "cosine_compare", (char*)exp_fn_cos_cmp); + xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); + xhAdd(&EXP.Functions, "cosine_compare", (char*)exp_fn_cosine_compare); xhAdd(&EXP.Functions, "lev_cmp", (char*)exp_fn_lev_cmp); - xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_cmp); - xhAdd(&EXP.Functions, "levenshtein_compare", (char*)exp_fn_lev_cmp); + xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); + xhAdd(&EXP.Functions, "levenshtein_compare", (char*)exp_fn_levenshtein_compare); xhAdd(&EXP.Functions, "sparse_eql", (char*)exp_fn_sparse_eql); + xhAdd(&EXP.Functions, "aggregate_similarities", (char*)exp_fn_aggregate_similarities); xhAdd(&EXP.Functions, "double_metaphone", (char*)exp_fn_double_metaphone); - /** Windowing **/ + /** Windowing. **/ xhAdd(&EXP.Functions, "row_number", (char*)exp_fn_row_number); xhAdd(&EXP.Functions, "dense_rank", (char*)exp_fn_dense_rank); xhAdd(&EXP.Functions, "lag", (char*)exp_fn_lag); - /** Aggregate **/ + /** Aggregate. **/ xhAdd(&EXP.Functions, "count", (char*)exp_fn_count); xhAdd(&EXP.Functions, "avg", (char*)exp_fn_avg); xhAdd(&EXP.Functions, "sum", (char*)exp_fn_sum); @@ -4648,7 +4770,8 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "last", (char*)exp_fn_last); xhAdd(&EXP.Functions, "nth", (char*)exp_fn_nth); - /** Reverse functions **/ + + /** Reverse functions. **/ xhAdd(&EXP.ReverseFunctions, "isnull", (char*)exp_fn_reverse_isnull); return 0; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 6d1aaf313..b3d416668 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -159,7 +160,7 @@ void mssErrorf(int clr, char* module, const char* format, ...) *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -int ci_TypeFromStr(const char* str) +static int ci_TypeFromStr(const char* str) { /** All valid types are non-null strings, at least 2 characters long. **/ if (str == NULL || str[0] == '\0' || str[1] == '\0') return -1; @@ -211,7 +212,7 @@ int ci_TypeFromStr(const char* str) /** TODO: I think this should be moved to datatypes. **/ /** Should maybe replace duplocate functionality elsewhere. **/ -char* ci_TypeToStr(const int type) +static char* ci_TypeToStr(const int type) { switch (type) { @@ -234,17 +235,37 @@ char* ci_TypeToStr(const int type) } /** TODO: I think this should be moved to xarray. **/ -/** Contract: Return value is null iff pXArray has 0 items. **/ -void** ci_xaToTrimmedArray(pXArray arr) +/*** Trims an xArray, returning a new array (with nmSysMalloc). + *** + *** @param arr The array to be trimmed. + *** @param cleanup 0: No clean up. + *** 1: DeInit arr. + *** 2: Free arr. + *** *: Any other value prints a warning and does nothing. + *** @returns The new array, or null if and only if the passed pXArray has 0 items. + ***/ +static void** ci_xaToTrimmedArray(pXArray arr, int array_handling) { - if (arr->nItems == 0) { - mssErrorf(1, "Cluster", "Failed to trim XArray of length 0."); - return NULL; - } - const size_t arr_size = arr->nItems * sizeof(void*); void** result = check_ptr(nmSysMalloc(arr_size)); + if (result == NULL) return NULL; memcpy(result, arr->Items, arr_size); + + /** Handle the array. **/ + switch (array_handling) + { + case 0: break; + case 1: check(xaDeInit(arr)); arr->nAlloc = 0; break; /* Failure ignored. */ + case 2: check(xaFree(arr)); break; /* Failure ignored. */ + default: + /** Uh oh, there might be a memory leak... **/ + fprintf(stderr, + "Warning: ci_xaToTrimmedArray(%p, %d) - Unknown value (%d) for array_handling.\n", + arr, array_handling, array_handling + ); + break; + } + return result; } @@ -376,8 +397,8 @@ char* const ATTR_CLUSTER_ENTRY[] = }; char* const ATTR_SEARCH_ENTRY[] = { - "val1", - "val2", + "key1", + "key2", "sim", END_OF_ARRAY, }; @@ -397,15 +418,20 @@ char* const METHOD_NAME[] = *** *** Memory Stats: *** - Padding: 4 bytes - *** - Total size: 72 bytes + *** - Total size: 80 bytes *** *** @skip --> Attribute Data. *** @param Name The source name, specified in the .cluster file. *** @param Key The key associated with this object in the SourceDataCache. *** @param SourcePath The path to the data source from which to retrieve data. - *** @param AttrName The name of the attribute to get from the data source. + *** @param KeyAttr The name of the attribute to use when getting keys from + *** the SourcePath. + *** @param NameAttr The name of the attribute to use when getting data from + *** the SourcePath. *** *** @skip --> Computed data. + *** @param Strings The keys for each data string strings recieved from the + *** database, allowing them to be lined up again when queried. *** @param Strings The data strings to be clustered and searched, or NULL if *** they have not been fetched from the source. *** @param Vectors The cosine comparison vectors from the fetched data, or @@ -416,14 +442,16 @@ char* const METHOD_NAME[] = *** *** @skip --> Time. *** @param DateCreated The date and time that this object was created and initialized. - *** @param DateComputed The date and time that the Labels field was computed. + *** @param DateComputed The date and time that the computed attributes were computed. ***/ typedef struct _SOURCE { char* Name; char* Key; char* SourcePath; - char* AttrName; + char* KeyAttr; + char* NameAttr; + char** Keys; char** Strings; pVector* Vectors; unsigned int nVectors; @@ -468,7 +496,7 @@ typedef struct *** each clustering iteration. If there is less improvement, the algorithm *** will stop. The "max" in a .cluster file is represented by -inf. *** @param MaxIterations The maximum number of iterations that a clustering - *** algorithm can run for. Note: Sliding window uses this field to store + *** algorithm can run for. Note: Sliding window uses this attribute to store *** the window_size. *** *** @skip --> Relationship Data. @@ -481,12 +509,12 @@ typedef struct *** @param Clusters An array of length num_clusters, NULL if the clusters *** have not yet been computed. *** @param Sims An array of num_vectors elements, where index i stores the - *** similarity of vector i to its assigned cluster. This field is NULL + *** similarity of vector i to its assigned cluster. This attribute is NULL *** if the clusters have not yet been computed. *** *** @skip --> Time. *** @param DateCreated The date and time that this object was created and initialized. - *** @param DateComputed The date and time that the Labels field was computed. + *** @param DateComputed The date and time that the computed attributes were computed. ***/ typedef struct _CLUSTER { @@ -530,7 +558,7 @@ typedef struct _CLUSTER *** *** @skip --> Time. *** @param DateCreated The date and time that this object was created and initialized. - *** @param DateComputed The date and time that the Dups field was computed. + *** @param DateComputed The date and time that the computed attributes were computed. ***/ typedef struct _SEARCH { @@ -653,6 +681,20 @@ struct } ClusterDriverCaches; +struct + { + unsigned long long OpenCalls; + unsigned long long OpenQueryCalls; + unsigned long long FetchCalls; + unsigned long long CloseCalls; + unsigned long long GetTypeCalls; + unsigned long long GetValCalls; + unsigned long long GetValCalls_name; + unsigned long long GetValCalls_key1; + unsigned long long GetValCalls_key2; + unsigned long long GetValCalls_sim; + } ClusterStatistics; + /** ================ Function Declarations ================ **/ /** ANCHOR[id=functions] **/ @@ -661,6 +703,8 @@ struct /** Parsing Functions. **/ // LINK #parsing +static void ci_GiveHint(const char* hint); +static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values); static int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); @@ -741,7 +785,8 @@ static void ci_GiveHint(const char* hint) fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); } -/*** Given the user a hint when they specify an invalid string for a field + +/*** Given the user a hint when they specify an invalid string for an attribute *** where we know the list of valid strings. The hint is only displayed if *** their string is close enough to a valid string. *** @@ -753,11 +798,12 @@ static void ci_GiveHint(const char* hint) ***/ static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) { - char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.5); + char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.25); if (guess == NULL) return false; /* No hint. */ /** Issue hint. **/ ci_GiveHint(guess); + tprintf(" > Similarity: %.4g\n", ca_lev_compare(value, guess)); return true; } @@ -845,7 +891,7 @@ static int ci_ParseAttribute( // LINK #functions -/*** Parses a ClusteringAlgorithm from the algorithm field in the pStructInf +/*** Parses a ClusteringAlgorithm from the algorithm attribute in the pStructInf *** representing some structure with that attribute in a parsed structure file. *** *** @attention - Promises that a failure invokes mssError() at least once. @@ -892,7 +938,7 @@ static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObject // LINK #functions -/*** Parses a SimilarityMeasure from the similarity_measure field in the given +/*** Parses a SimilarityMeasure from the similarity_measure attribute in the given *** pStructInf parameter, which represents some structure with that attribute *** in a parsed structure file. *** @@ -951,78 +997,70 @@ static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects ***/ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) { - char* buf; + char* buf = NULL; + + /** Allocate SourceData. **/ + pSourceData source_data = check_ptr(nmMalloc(sizeof(SourceData))); + if (source_data == NULL) goto err_free; + memset(source_data, 0, sizeof(SourceData)); + + /** Initialize obvious values for SourceData. **/ + source_data->Name = check_ptr(nmSysStrdup(inf->Name)); + if (source_data->Name == NULL) goto err_free; + if (!check(objCurrentDate(&source_data->DateCreated))) goto err_free; /** Get source. **/ - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; - char* source_path = check_ptr(nmSysStrdup(buf)); - if (source_path == NULL) goto err; + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; + source_data->SourcePath = check_ptr(nmSysStrdup(buf)); + if (source_data->SourcePath == NULL) goto err_free; - /** Get attribute name. **/ - if (ci_ParseAttribute(inf, "attr_name", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err; - char* attr_name = check_ptr(nmSysStrdup(buf)); - if (attr_name == NULL) goto err_free_path; + /** Get the attribute name to use when querying keys from the source. **/ + if (ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; + source_data->KeyAttr = check_ptr(nmSysStrdup(buf)); + if (source_data->KeyAttr == NULL) goto err_free; + + /** Get the attribute name to use for querying data from the source. **/ + if (ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; + source_data->NameAttr = check_ptr(nmSysStrdup(buf)); + if (source_data->NameAttr == NULL) goto err_free; /** Create cache entry key. **/ - const size_t len = strlen(path) + strlen(source_path) + strlen(attr_name) + 3lu; - char* key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (key == NULL) goto err_free_attr; - snprintf(key, len, "%s?%s:%s", path, source_path, attr_name); + const size_t len = strlen(path) + strlen(source_data->SourcePath) + strlen(source_data->KeyAttr) + strlen(source_data->NameAttr) + 5lu; + source_data->Key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (source_data->Key == NULL) goto err_free; + snprintf(source_data->Key, len, "%s?%s->%s:%s", path, source_data->SourcePath, source_data->KeyAttr, source_data->NameAttr); /** Check for a cached version. **/ - pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, key); + pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, source_data->Key); if (source_maybe != NULL) { /** Cache hit. **/ - tprintf("# source: \"%s\"\n", key); + tprintf("# source: \"%s\"\n", source_data->Key); /** Cause an imediate invalid read if cache was incorrectly freed. **/ tprintf("--> Name: %s\n", source_maybe->Name); /** Free data we don't need. **/ - nmSysFree(source_path); - nmSysFree(attr_name); - nmSysFree(key); + nmSysFree(source_data->Key); + ci_FreeSourceData(source_data); /** Return the cached source data. **/ return source_maybe; } - /** Cache miss: Create a new source data object. **/ - pSourceData source_data = check_ptr(nmMalloc(sizeof(SourceData))); - if (source_data == NULL) goto err_free_key; - memset(source_data, 0, sizeof(SourceData)); - source_data->Key = key; - source_data->SourcePath = source_path; - source_data->AttrName = attr_name; - source_data->Name = check_ptr(nmSysStrdup(inf->Name)); - if (source_data->Name == NULL) goto err_free_source; - if (!check(objCurrentDate(&source_data->DateCreated))) goto err_free_source; - - /** Add the new object to the cache for next time. **/ - tprintf("+ source: \"%s\"\n", key); - if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, key, (void*)source_data))) - goto err_free_source; + /** Cache miss: Add the new object to the cache for next time. **/ + tprintf("+ source: \"%s\"\n", source_data->Key); + if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->Key, (void*)source_data))) + goto err_free; /** Success. **/ return source_data; /** Error handling. **/ - err_free_source: - ci_FreeSourceData(source_data); - nmSysFree(key); - goto err; - - err_free_key: - nmSysFree(key); - - err_free_attr: - nmSysFree(attr_name); - - err_free_path: - nmSysFree(source_path); + err_free: + if (source_data->Key != NULL) nmSysFree(source_data->Key); + if (source_data != NULL) ci_FreeSourceData(source_data); - err: mssErrorf(0, "Cluster", "Failed to parse source data from group \"%s\" in file: %s", inf->Name, path @@ -1241,10 +1279,7 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) } } cluster_data->nSubClusters = sub_clusters.nItems; - cluster_data->SubClusters = (cluster_data->nSubClusters > 0u) ? - (pClusterData*)ci_xaToTrimmedArray(&sub_clusters) - : NULL; /* No sub-clusters. */ - check(xaDeInit(&sub_clusters)); /* Failure ignored. */ + cluster_data->SubClusters = (pClusterData*)ci_xaToTrimmedArray(&sub_clusters, 1); /** Create the cache key. **/ parsing_done:; @@ -1592,7 +1627,8 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) /** Valid attribute names. **/ char* attrs[] = { "source", - "attr_name", + "key_attr", + "data_attr", }; const unsigned int nattrs = sizeof(attrs) / sizeof(char*); @@ -1695,16 +1731,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) /** Check each provided param to see if the user provided value. **/ for (unsigned int j = 0u; j < num_provided_params; j++) { - pStruct provided_param = provided_params[j]; - if (provided_param == NULL) - { - mssErrorf(1, "Cluster", "Provided param struct cannot be NULL."); - fprintf(stderr, - "Debug info: obj->Pathname->OpenCtl[%d]->SubInf[%u] is NULL", - parent->SubPtr - 1, j - ); - goto err_free_arrs; - } + pStruct provided_param = check_ptr(provided_params[j]); /* Failure ignored. */ /** If this provided param value isn't for the param, ignore it. **/ if (strcmp(provided_param->Name, param->Name) != 0) continue; @@ -1724,7 +1751,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) ); goto err_free_arrs; } - tprintf("Found provided value for %s, which is now %d\n", param->Name, param->Value->Data.Integer); + tprintf("Found provided value for %s of type %s\n", param->Name, ci_TypeToStr(param->Type)); /** Provided value successfully handled, we're done. **/ break; @@ -1744,6 +1771,28 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) check(xaDeInit(¶m_infs)); /* Failure ignored. */ param_infs.nAlloc = 0; + /** Iterate over provided parameters and warn the user if they specified a parameter that does not exist. **/ + for (unsigned int i = 0u; i < num_provided_params; i++) + { + pStruct provided_param = check_ptr(provided_params[i]); /* Failure ignored. */ + char* provided_name = provided_param->Name; + + /** Look to see if this provided param actually exists for this driver instance. **/ + for (unsigned int j = 0u; j < node_data->nParams; j++) + if (strcmp(provided_name, node_data->Params[j]->Name) == 0) + goto next_provided_param; + + /** This param doesn't exist, warn the user and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, ci_file_name(parent)); + char** param_names = check_ptr(nmSysMalloc(node_data->nParams * sizeof(char*))); + for (unsigned int j = 0u; j < node_data->nParams; j++) + param_names[j] = node_data->Params[j]->Name; + ci_TryHint(provided_name, param_names, node_data->nParams); + nmSysFree(param_names); + + next_provided_param:; + } + /** Parse source data. **/ node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); if (node_data->SourceData == NULL) goto err_free_arrs; @@ -1826,10 +1875,15 @@ static void ci_FreeSourceData(pSourceData source_data) nmSysFree(source_data->SourcePath); source_data->SourcePath = NULL; } - if (source_data->AttrName != NULL) + if (source_data->KeyAttr != NULL) + { + nmSysFree(source_data->KeyAttr); + source_data->KeyAttr = NULL; + } + if (source_data->NameAttr != NULL) { - nmSysFree(source_data->AttrName); - source_data->AttrName = NULL; + nmSysFree(source_data->NameAttr); + source_data->NameAttr = NULL; } /** Free fetched data, if it exists. **/ @@ -2060,7 +2114,8 @@ static unsigned int ci_SizeOfSourceData(pSourceData source_data) unsigned int size = 0u; if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); - if (source_data->AttrName != NULL) size += strlen(source_data->AttrName) * sizeof(char); + if (source_data->KeyAttr != NULL) size += strlen(source_data->KeyAttr) * sizeof(char); + if (source_data->NameAttr != NULL) size += strlen(source_data->NameAttr) * sizeof(char); if (source_data->Strings != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) @@ -2175,9 +2230,9 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { mssErrorf(0, "Cluster", "Failed to open object driver:\n" - " > Attribute: ['%s' : String]\n" - " > Source Path: \"%s\"", - source_data->AttrName, + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n", + source_data->KeyAttr, source_data->NameAttr, source_data->SourcePath ); goto end; @@ -2190,85 +2245,86 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { mssErrorf(0, "Cluster", "Failed to open query:\n" - " > Attribute: ['%s' : String]\n" - " > Driver Used: %s\n" - " > Source Path: \"%s\"", - source_data->AttrName, - obj->Driver->Name, - source_data->SourcePath + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name ); goto end_close; } /** Initialize an xarray to store the retrieved data. **/ - XArray data_xarray, vector_xarray; + XArray key_xarray, data_xarray, vector_xarray; + memset(&key_xarray, 0, sizeof(XArray)); memset(&data_xarray, 0, sizeof(XArray)); memset(&vector_xarray, 0, sizeof(XArray)); - if (!check(xaInit(&data_xarray, 64))) goto end_close_query; + if (!check(xaInit(&key_xarray, 64))) goto end_close_query; + if (!check(xaInit(&data_xarray, 64))) goto end_free_data; if (!check(xaInit(&vector_xarray, 64))) goto end_free_data; /** Fetch data and build vectors. **/ tprintf("Skips: "); - unsigned int i = 0u; while (true) { pObject entry = objQueryFetch(query, O_RDONLY); if (entry == NULL) break; /* Done. */ - /** Type checking. **/ - const int datatype = objGetAttrType(entry, source_data->AttrName); - if (datatype == -1) + /** Data value: Type checking. **/ + const int data_datatype = objGetAttrType(entry, source_data->NameAttr); + if (data_datatype == -1) { mssErrorf(0, "Cluster", "Failed to get type for %uth entry:\n" - " > Attribute: '%s' : String\n" - " > Driver Used: %s\n" - " > Source Path: \"%s\"", - i, - source_data->AttrName, - obj->Driver->Name, - source_data->SourcePath + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name ); goto end_free_data; } - if (datatype != DATA_T_STRING) + if (data_datatype != DATA_T_STRING) { mssErrorf(1, "Cluster", "Type for %uth entry was not a string:\n" - " > Attribute: ['%s' : %s]\n" - " > Driver Used: %s\n" - " > Source Path: \"%s\"", - i, - source_data->AttrName, ci_TypeToStr(datatype), - obj->Driver->Name, - source_data->SourcePath + " > Attribute: ['%s':'%s' : %s]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, ci_TypeToStr(data_datatype), + source_data->SourcePath, + obj->Driver->Name ); goto end_free_data; } - /** Get value from database. **/ - char* val; - ret = objGetAttrValue(entry, source_data->AttrName, DATA_T_STRING, POD(&val)); + /** Data value: Get value from database. **/ + char* data; + ret = objGetAttrValue(entry, source_data->NameAttr, DATA_T_STRING, POD(&data)); if (ret != 0) { tprintf("\n"); mssErrorf(0, "Cluster", "Failed to value for %uth entry:\n" - " > Attribute: ['%s' : String]\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" " > Driver Used: %s\n" - " > Source Path: \"%s\"\n" - " > Error code: %d", - i, - source_data->AttrName, - obj->Driver->Name, + " > Error code: %d\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, source_data->SourcePath, + obj->Driver->Name, ret ); goto end_free_data; } /** Skip empty strings. **/ - if (strlen(val) == 0) + if (strlen(data) == 0) { tprintf("_"); check(fflush(stdout)); /* Failure ignored. */ @@ -2276,16 +2332,16 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Convert the string to a vector. **/ - pVector vector = ca_build_vector(val); + pVector vector = ca_build_vector(data); if (vector == NULL) { - mssErrorf(1, "Cluster", "Failed to build vectors for string \"%s\".", val); + mssErrorf(1, "Cluster", "Failed to build vectors for string \"%s\".", data); successful = false; goto end_free_data; } if (ca_is_empty(vector)) { - mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", val); + mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); successful = false; goto end_free_data; } @@ -2298,10 +2354,66 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) continue; } - /** Store value. **/ - char* dup_val = check_ptr(nmSysStrdup(val)); - if (dup_val == NULL) goto end_free_data; - if (!check_neg(xaAddItem(&data_xarray, (void*)dup_val))) goto end_free_data; + + /** Key value: Type checking. **/ + const int key_datatype = objGetAttrType(entry, source_data->KeyAttr); + if (key_datatype == -1) + { + mssErrorf(0, "Cluster", + "Failed to get type for key on %uth entry:\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name + ); + goto end_free_data; + } + if (key_datatype != DATA_T_STRING) + { + mssErrorf(1, "Cluster", + "Type for key on %uth entry was not a string:\n" + " > Attribute: ['%s':'%s' : %s]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, ci_TypeToStr(key_datatype), + source_data->SourcePath, + obj->Driver->Name + ); + goto end_free_data; + } + + /** key value: Get value from database. **/ + char* key; + ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); + if (ret != 0) + { + tprintf("\n"); + mssErrorf(0, "Cluster", + "Failed to value for key on %uth entry:\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n" + " > Error code: %d\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name, + ret + ); + goto end_free_data; + } + + /** Store values. **/ + char* key_dup = check_ptr(nmSysStrdup(key)); + if (key_dup == NULL) goto end_free_data; + char* data_dup = check_ptr(nmSysStrdup(data)); + if (data_dup == NULL) goto end_free_data; + if (!check_neg(xaAddItem(&key_xarray, (void*)key_dup))) goto end_free_data; + if (!check_neg(xaAddItem(&data_xarray, (void*)data_dup))) goto end_free_data; if (!check_neg(xaAddItem(&vector_xarray, (void*)vector))) goto end_free_data; /** Clean up. **/ @@ -2314,26 +2426,39 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) } tprintf("\nData aquired.\n"); source_data->nVectors = vector_xarray.nItems; + if (source_data->nVectors == 0) + { + mssErrorf(0, "Cluster", + "Data source path did not contain any valid data:\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name + ); + } - /** Trim data and store data. **/ - const size_t data_size = source_data->nVectors * sizeof(char*); - source_data->Strings = check_ptr(nmSysMalloc(data_size)); + /** Trim and store: keys, data, and vectors. **/ + source_data->Keys = (char**)check_ptr(ci_xaToTrimmedArray(&key_xarray, 1)); + source_data->Strings = (char**)check_ptr(ci_xaToTrimmedArray(&data_xarray, 1)); + source_data->Vectors = (int**)check_ptr(ci_xaToTrimmedArray(&vector_xarray, 1)); + if (source_data->Keys == NULL) goto end_free_data; if (source_data->Strings == NULL) goto end_free_data; - memcpy(source_data->Strings, data_xarray.Items, data_size); - check(xaDeInit(&data_xarray)); /* Failure ignored. */ - data_xarray.nAlloc = 0; - - /** Trim data and store vectors. **/ - const size_t vectors_size = source_data->nVectors * sizeof(pVector); - source_data->Vectors = check_ptr(nmSysMalloc(vectors_size)); - memcpy(source_data->Vectors, vector_xarray.Items, vectors_size); - check(xaDeInit(&vector_xarray)); /* Failure ignored. */ - vector_xarray.nAlloc = 0; + if (source_data->Vectors == NULL) goto end_free_data; /** Success. **/ + fprintf(stderr, "[SourceData: %s] Compute done.\n", source_data->Name); successful = true; end_free_data: + if (key_xarray.nAlloc != 0) + { + for (unsigned int i = 0u; i < vector_xarray.nItems; i++) + nmSysFree(key_xarray.Items[i]); + check(xaDeInit(&key_xarray)); /* Failure ignored. */ + } if (data_xarray.nAlloc != 0) { for (unsigned int i = 0u; i < data_xarray.nItems; i++) @@ -2464,7 +2589,7 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) cluster_data->Sims )); timer_stop(timer); - tprintf("Clustering done after %.4lf.\n", timer_get(timer)); + tprintf("Clustering done after %.4lfs.\n", timer_get(timer)); if (!successful) goto err_free_sims; /** Convert the labels into clusters. **/ @@ -2511,7 +2636,7 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) } /** Success. **/ - tprintf("Clustering done.\n"); + fprintf(stderr, "[ClusterData: %s] Compute done.\n", cluster_data->Name); return 0; err_free_sims: @@ -2564,16 +2689,6 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) goto err; } - /** Check for unimplemented similarity measures. **/ - if (search_data->SimilarityMeasure != SIMILARITY_COSINE) - { - mssErrorf(1, "Cluster", - "The similarity meausre \"%s\" is not implemented.", - ci_SimilarityMeasureToString(search_data->SimilarityMeasure) - ); - goto err; - } - /** Record the date and time. **/ if (!check(objCurrentDate(&search_data->DateComputed))) goto err; @@ -2593,6 +2708,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) cluster_data->MaxIterations, /* Window size. */ ca_cos_compare, search_data->Threshold, + (void**)cluster_data->SourceData->Keys, dups )); } @@ -2605,6 +2721,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) cluster_data->Clusters[i].Size, ca_cos_compare, search_data->Threshold, + (void**)cluster_data->SourceData->Keys, dups )); if (dups_temp == NULL) goto err_free; @@ -2624,6 +2741,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) cluster_data->MaxIterations, /* Window size. */ ca_lev_compare, search_data->Threshold, + (void**)cluster_data->SourceData->Keys, dups )); } @@ -2636,6 +2754,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) cluster_data->Clusters[i].Size, ca_lev_compare, search_data->Threshold, + (void**)cluster_data->SourceData->Keys, dups )); if (dups_temp == NULL) goto err_free; @@ -2655,19 +2774,16 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) timer_stop(timer); if (dups_temp == NULL) goto err_free; else dups = dups_temp; - tprintf("Search done after %.4lf.\n", timer_get(timer)); + tprintf("Search done after %.4lfs.\n", timer_get(timer)); /** Store dups. **/ search_data->nDups = dups->nItems; search_data->Dups = (dups->nItems == 0) ? check_ptr(nmSysMalloc(0)) - : ci_xaToTrimmedArray(dups); - - /** Free unused data. **/ - tprintf("Cleanup.\n"); - check(xaFree(dups)); /* Failure ignored. */ + : ci_xaToTrimmedArray(dups, 2); /** Success. **/ + fprintf(stderr, "[SearchData: %s] Compute done.\n", search_data->Name); return 0; err_free: @@ -2808,6 +2924,7 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { tprintf("Warning: clusterOpen(\"%s\") is under active development.\n", ci_file_name(parent)); + ClusterStatistics.OpenCalls++; /** If CREAT and EXCL are specified, exclusively create it, failing if the file already exists. **/ pSnNode node_struct = NULL; @@ -2973,6 +3090,7 @@ int clusterClose(void* inf_v, pObjTrxTree* oxt) { tprintf("Warning: clusterClose() is under active development.\n"); pDriverData driver_data = (pDriverData)inf_v; + ClusterStatistics.CloseCalls++; /** Entries are shallow copies so we shouldn't do a deep free. **/ if (driver_data->TargetType == TARGET_CLUSTER_ENTRY @@ -3005,6 +3123,7 @@ int clusterClose(void* inf_v, pObjTrxTree* oxt) ***/ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) { + ClusterStatistics.OpenQueryCalls++; tprintf("Warning: clusterOpenQuery() is under active development.\n"); pClusterQuery cluster_query = check_ptr(nmMalloc(sizeof(ClusterQuery))); if (cluster_query == NULL) return NULL; @@ -3029,7 +3148,8 @@ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { int ret; - tprintf("Warning: clusterQueryFetch() is under active development.\n"); + ClusterStatistics.FetchCalls++; +// tprintf("Warning: clusterQueryFetch() is under active development.\n"); pClusterQuery cluster_query = (pClusterQuery)qy_v; /** Ensure that the data being fetched exists and is computed. **/ @@ -3114,7 +3234,7 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ***/ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) { - tprintf("Warning: clusterQueryClose() is under active development.\n"); +// tprintf("Warning: clusterQueryClose() is under active development.\n"); nmFree(qy_v, sizeof(ClusterQuery)); return 0; @@ -3134,6 +3254,7 @@ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; + ClusterStatistics.GetTypeCalls++; /** Guard possible segfault. **/ if (attr_name == NULL) @@ -3142,8 +3263,8 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) return DATA_T_UNAVAILABLE; } - /** Performance shortcut for frequently requested attributes: val, val1, val2, and sim. **/ - if (attr_name[0] == 'v' || attr_name[0] == 's') goto handle_targets; + /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ + if (attr_name[0] == 'k' || attr_name[0] == 's') goto handle_targets; /** Debug info. **/ if (oxt == NULL) tprintf(" > "); @@ -3171,7 +3292,8 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { case TARGET_ROOT: if (strcmp(attr_name, "source") == 0 - || strcmp(attr_name, "attr_name") == 0) + || strcmp(attr_name, "data_attr") == 0 + || strcmp(attr_name, "key_attr") == 0) return DATA_T_STRING; break; @@ -3200,11 +3322,8 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) break; case TARGET_SEARCH_ENTRY: - if (strcmp(attr_name, "id1") == 0 - || strcmp(attr_name, "id2") == 0) - return DATA_T_INTEGER; - if (strcmp(attr_name, "val1") == 0 - || strcmp(attr_name, "val2") == 0) + if (strcmp(attr_name, "key1") == 0 + || strcmp(attr_name, "key2") == 0) return DATA_T_STRING; if (strcmp(attr_name, "sim") == 0) return DATA_T_DOUBLE; @@ -3241,6 +3360,7 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; + ClusterStatistics.GetValCalls++; /** Guard possible segfault. **/ if (attr_name == NULL) @@ -3249,9 +3369,8 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val return DATA_T_UNAVAILABLE; } - /** Performance shortcut for frequently requested attributes: val1, val2, and sim. **/ - if ( - (attr_name[0] == 'v' && datatype == DATA_T_STRING) /* val1, val2 : String */ + /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ + if ((attr_name[0] == 'k' && datatype == DATA_T_STRING) /* key1, key2 : string */ || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ ) goto handle_targets; @@ -3272,6 +3391,7 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val /** Handle name and annotation. **/ if (strcmp(attr_name, "name") == 0) { + ClusterStatistics.GetValCalls_name++; switch (driver_data->TargetType) { case TARGET_ROOT: @@ -3336,7 +3456,7 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val case TARGET_ROOT: case TARGET_CLUSTER_ENTRY: case TARGET_SEARCH_ENTRY: - /** Field is not defined for this target type. **/ + /** Attribute is not defined for this target type. **/ return -1; case TARGET_CLUSTER: @@ -3356,7 +3476,7 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val case TARGET_ROOT: case TARGET_CLUSTER_ENTRY: case TARGET_SEARCH_ENTRY: - /** Field is not defined for this target type. **/ + /** Attribute is not defined for this target type. **/ return -1; case TARGET_CLUSTER: @@ -3393,9 +3513,14 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val val->String = ((pSourceData)driver_data->TargetData)->SourcePath; return 0; } - if (strcmp(attr_name, "attr_name") == 0) + if (strcmp(attr_name, "key_attr") == 0) + { + val->String = ((pSourceData)driver_data->TargetData)->KeyAttr; + return 0; + } + if (strcmp(attr_name, "name_attr") == 0) { - val->String = ((pSourceData)driver_data->TargetData)->AttrName; + val->String = ((pSourceData)driver_data->TargetData)->NameAttr; return 0; } break; @@ -3460,6 +3585,7 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val case TARGET_CLUSTER_ENTRY: { pClusterData target = (pClusterData)driver_data->TargetData; + pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; if (strcmp(attr_name, "items") == 0) { @@ -3468,7 +3594,6 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val if (vec != NULL) nmFree(vec, sizeof(StringVec)); /** Allocate and initiallize the requested data. **/ - pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; val->StringVec = vec = check_ptr(nmMalloc(sizeof(StringVec))); if (val->StringVec == NULL) return -1; val->StringVec->nStrings = target_cluster->Size; @@ -3485,37 +3610,22 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val pSearchData target = (pSearchData)driver_data->TargetData; pDup target_dup = target->Dups[driver_data->TargetIndex]; - if (strcmp(attr_name, "id1") == 0) - { - unsigned int value = target_dup->id1; - if (value > INT_MAX) - fprintf(stderr, "Warning: id1 value of %u exceeds INT_MAX (%d).\n", value, INT_MAX); - val->Integer = (int)value; - return 0; - } - if (strcmp(attr_name, "id2") == 0) - { - unsigned int value = target_dup->id2; - if (value > INT_MAX) - fprintf(stderr, "Warning: id2 value of %u exceeds INT_MAX (%d).\n", value, INT_MAX); - val->Integer = (int)value; - return 0; - } - if (strcmp(attr_name, "val1") == 0) + if (strcmp(attr_name, "sim") == 0) { - val->String = driver_data->NodeData->SourceData->Strings[target_dup->id1]; - // val->Integer = (int)target_dup->id1; + ClusterStatistics.GetValCalls_sim++; + val->Double = target_dup->similarity; return 0; } - if (strcmp(attr_name, "val2") == 0) + if (strcmp(attr_name, "key1") == 0) { - val->String = driver_data->NodeData->SourceData->Strings[target_dup->id2]; - // val->Integer = (int)target_dup->id2; + ClusterStatistics.GetValCalls_key1++; + val->String = target_dup->key1; return 0; } - if (strcmp(attr_name, "sim") == 0) + if (strcmp(attr_name, "key2") == 0) { - val->Double = target_dup->similarity; + ClusterStatistics.GetValCalls_key2++; + val->String = target_dup->key2; return 0; } break; @@ -3542,10 +3652,10 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val /*** Create a new presentation hints object, describing this attribute on the *** provided cluster driver instance. *** - *** Note: expCompileExpression() and nmSysStrdup() are run unchecked because - *** the worst case senario is that the fields are set to null and ignored, - *** which I consider to be better than ending the script because one of - *** them failed. + *** Note: Failures from nmSysStrdup() and several others are ignored because + *** the worst case senario is that the attributes are set to null, which + *** will cause them to be ignored. I consider that to be better than than + *** throwing an error that could unnecessarily disrupt normal usage. *** *** @param inf_v The driver instance to be read. *** @param attr_name The name of the requested attribute. @@ -3563,7 +3673,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb if (hints == NULL) goto err; memset(hints, 0, sizeof(ObjPresentationHints)); - /** Hints that are the same for all fields **/ + /** Hints that are the same for all attributes. **/ hints->GroupID = -1; hints->VisualLength2 = 1; hints->Style |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; @@ -3604,7 +3714,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb { hints->Length = 24; hints->VisualLength = 20; - hints->Format = nmSysStrdup("datetime"); + hints->Format = check_ptr(nmSysStrdup("datetime")); /* Failure ignored. */ goto success; } else goto unknown_attribute; @@ -3618,14 +3728,21 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb { hints->Length = _PC_PATH_MAX; hints->VisualLength = 64; - hints->FriendlyName = "Source Path"; + hints->FriendlyName = check_ptr(nmSysStrdup("Source Path")); /* Failure ignored. */ goto success; } - if (strcmp(attr_name, "attr_name") == 0) + if (strcmp(attr_name, "key_attr") == 0) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = "Attribute Name"; + hints->FriendlyName = check_ptr(nmSysStrdup("Key Attribute Name")); /* Failure ignored. */ + goto success; + } + if (strcmp(attr_name, "data_attr") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Data Attribute Name")); /* Failure ignored. */ goto success; } break; @@ -3640,7 +3757,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - hints->FriendlyName = nmSysStrdup("Number of Clusters"); + hints->FriendlyName = check_ptr(nmSysStrdup("Number of Clusters")); /* Failure ignored. */ goto success; } if (strcmp(attr_name, "min_improvement") == 0) @@ -3653,7 +3770,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = nmSysStrdup("Minimum Improvement Threshold"); + hints->FriendlyName = check_ptr(nmSysStrdup("Minimum Improvement Threshold")); /* Failure ignored. */ goto success; } if (strcmp(attr_name, "max_iterations") == 0) @@ -3666,15 +3783,15 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - hints->FriendlyName = nmSysStrdup("Maximum Number of Clustering Iterations"); + hints->FriendlyName = check_ptr(nmSysStrdup("Maximum Iterations")); /* Failure ignored. */ goto success; } if (strcmp(attr_name, "algorithm") == 0) { /** Enum values. **/ - check(xaInit(&(hints->EnumList), nClusteringAlgorithms)); + check(xaInit(&(hints->EnumList), nClusteringAlgorithms)); /* Failure ignored. */ for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) - check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); + check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); /* Failure ignored. */ /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -3689,7 +3806,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 24; hints->VisualLength = 20; - hints->FriendlyName = nmSysStrdup("Clustering Algorithm"); + hints->FriendlyName = check_ptr(nmSysStrdup("Clustering Algorithm")); /* Failure ignored. */ goto success; } /** Fall-through: Start of overlapping region. **/ @@ -3698,9 +3815,9 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb if (strcmp(attr_name, "similarity_measure") == 0) { /** Enum values. **/ - check(xaInit(&(hints->EnumList), nSimilarityMeasures)); + check(xaInit(&(hints->EnumList), nSimilarityMeasures)); /* Failure ignored. */ for (unsigned int i = 0u; i < nSimilarityMeasures; i++) - check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); + check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); /* Failure ignored. */ /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; @@ -3715,7 +3832,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 32; hints->VisualLength = 20; - hints->FriendlyName = nmSysStrdup("Similarity Measure"); + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Measure")); /* Failure ignored. */ goto success; } @@ -3726,7 +3843,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb { hints->Length = 64; hints->VisualLength = 32; - hints->FriendlyName = nmSysStrdup("Source Cluster Name"); + hints->FriendlyName = check_ptr(nmSysStrdup("Source Cluster Name")); /* Failure ignored. */ goto success; } if (strcmp(attr_name, "threshold") == 0) @@ -3738,39 +3855,22 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = nmSysStrdup("Similarity Threshold"); + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Threshold")); /* Failure ignored. */ goto success; } break; case TARGET_CLUSTER_ENTRY: { - pClusterData target = (pClusterData)driver_data->TargetData; + pClusterData target = (pClusterData)check_ptr(driver_data->TargetData); + if (target == NULL) goto err; - if (strcmp(attr_name, "id") == 0) - { - pSourceData source_data = (pSourceData)target->SourceData; - - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - if (source_data->Vectors != NULL) - { - char buf[16u]; - snprintf(buf, sizeof(buf), "%u", source_data->nVectors); - hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - } - - /** Other hints. **/ - hints->Length = 8; - hints->VisualLength = 4; - goto success; - } - if (strcmp(attr_name, "val") == 0) + if (strcmp(attr_name, "items") == 0) { /** Other hints. **/ - hints->Length = 255; - hints->VisualLength = 32; - hints->FriendlyName = nmSysStrdup("Value"); + hints->Length = 65536; + hints->VisualLength = 256; + hints->FriendlyName = check_ptr(nmSysStrdup("Cluster Data")); /* Failure ignored. */ goto success; } if (strcmp(attr_name, "sim") == 0) @@ -3782,7 +3882,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = nmSysStrdup("Similarity"); + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ goto success; } break; @@ -3790,32 +3890,21 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb case TARGET_SEARCH_ENTRY: { - pSearchData target = (pSearchData)driver_data->TargetData; + pSearchData target = (pSearchData)check_ptr(driver_data->TargetData); + if (target == NULL) goto err; - if (strcmp(attr_name, "id1") == 0 || strcmp(attr_name, "id2") == 0) + if (strcmp(attr_name, "key1") == 0) { - pSourceData source_data = (pSourceData)target->Source->SourceData; - - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - if (source_data->Vectors != NULL) - { - char buf[16u]; - snprintf(buf, sizeof(buf), "%u", source_data->nVectors); - hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - } - - /** Other hints. **/ - hints->Length = 8; - hints->VisualLength = 4; + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Key 1")); /* Failure ignored. */ goto success; } - if (strcmp(attr_name, "val1") == 0 || strcmp(attr_name, "val2") == 0) + if (strcmp(attr_name, "key2") == 0) { - /** Other hints. **/ hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = nmSysStrdup("Value"); + hints->FriendlyName = check_ptr(nmSysStrdup("Key 2")); /* Failure ignored. */ goto success; } if (strcmp(attr_name, "sim") == 0) @@ -3827,7 +3916,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = nmSysStrdup("Similarity"); + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ goto success; } break; @@ -3841,7 +3930,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Unknown attribute. **/ unknown_attribute:; char* name; - clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); + check(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); /* Failure ignored. */ mssErrorf(1, "Cluster", "Unknown attribute '%s' for cluster object %s (target type: %u, \"%s\").", attr_name, driver_data->NodeData->SourceData->Name, driver_data->TargetType, name @@ -4174,7 +4263,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx mssErrorf(1, "Cluster", "[param : \"show\" | \"show_all\" | \"drop_all\"] is required for the cache method." ); - goto err; + goto err; } /** show and show_all. **/ @@ -4256,6 +4345,34 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx ); goto err; } + + if (strcmp(method_name, "stat") == 0) + { + unsigned long long ExpectedOpenCalls = 10666; + unsigned long long ExpectedOpenQueryCalls = 10665; + unsigned long long ExpectedFetchCalls = 3368007; + unsigned long long ExpectedCloseCalls = 3368007; + unsigned long long ExpectedGetTypeCalls = 26664164; + unsigned long long ExpectedGetValCalls = 15021419; + unsigned long long ExpectedGetValCalls_name = 3368008; + unsigned long long ExpectedGetValCalls_key1 = 3357342; + unsigned long long ExpectedGetValCalls_key2 = 1574; + unsigned long long ExpectedGetValCalls_sim = 8283829; + char buf[12]; + printf("Cluster Driver Statistics:\n"); + printf(" Stat Name Value\n"); + printf(" OpenCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls), snprint_llu(buf, sizeof(buf), ExpectedOpenCalls), ClusterStatistics.OpenCalls / ExpectedOpenCalls * 100.0); + printf(" OpenQueryCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls), snprint_llu(buf, sizeof(buf), ExpectedOpenQueryCalls), ClusterStatistics.OpenQueryCalls / ExpectedOpenQueryCalls * 100.0); + printf(" FetchCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls), snprint_llu(buf, sizeof(buf), ExpectedFetchCalls), ClusterStatistics.FetchCalls / ExpectedFetchCalls * 100.0); + printf(" CloseCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls), snprint_llu(buf, sizeof(buf), ExpectedCloseCalls), ClusterStatistics.CloseCalls / ExpectedCloseCalls * 100.0); + printf(" GetTypeCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls), snprint_llu(buf, sizeof(buf), ExpectedGetTypeCalls), ClusterStatistics.GetTypeCalls / ExpectedGetTypeCalls * 100.0); + printf(" GetValCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls), ClusterStatistics.GetValCalls / ExpectedGetValCalls * 100.0); + printf(" GetValCalls_name %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_name), ClusterStatistics.GetValCalls_name / ExpectedGetValCalls_name * 100.0); + printf(" GetValCalls_key1 %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_key1), ClusterStatistics.GetValCalls_key1 / ExpectedGetValCalls_key1 * 100.0); + printf(" GetValCalls_key2 %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_key2), ClusterStatistics.GetValCalls_key2 / ExpectedGetValCalls_key2 * 100.0); + printf(" GetValCalls_sim %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_sim), ClusterStatistics.GetValCalls_sim / ExpectedGetValCalls_sim * 100.0); + return 0; + } /** Unknown parameter. **/ mssErrorf(1, "Cluster", "Unknown command: \"%s\"", method_name); @@ -4343,17 +4460,20 @@ int clusterInitialize(void) if (drv == NULL) goto err; memset(drv, 0, sizeof(ObjDriver)); - /** Initialize globals. **/ + /** Initialize caches. **/ memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); if (!check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0))) goto err; if (!check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0))) goto err; if (!check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0))) goto err; + /** Initialize statistics. **/ + memset(&ClusterStatistics, 0, sizeof(ClusterStatistics)); + /** Setup the structure. **/ if (check_ptr(strcpy(drv->Name, "clu - Clustering Driver")) == NULL) goto err; - if (!check(xaInit(&(drv->RootContentTypes), 1))) goto err; - if (!check_neg(xaAddItem(&(drv->RootContentTypes), "system/cluster"))) goto err; - drv->Capabilities = OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; /* TODO: Greg, double check these are correct. */ + if (!check(xaInit(&drv->RootContentTypes, 1))) goto err; + if (!check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster"))) goto err; + drv->Capabilities = OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; /* TODO: Greg, are these correct? Should I add any others? */ /** Setup the function references. **/ drv->Open = clusterOpen; @@ -4415,15 +4535,17 @@ int clusterInitialize(void) // snprint_bytes(buf7, sizeof(buf7), sizeof(ClusterQuery)), // snprint_bytes(buf8, sizeof(buf8), sizeof(ClusterDriverCaches)) // ); - -// pVector v = ca_build_vector(""); -// const unsigned int len = ca_sparse_len(v); -// fprintf(stderr, "Vector (x%d): [%d", len, v[0]); -// for (unsigned int i = 1u; i < len; i++) -// { -// fprintf(stderr, ", %d", v[i]); -// } -// fprintf(stderr, "]\n"); +// + // 'st' (7: 13) collides with 'an' (7: 11) +// char* str1 = "This is a very long string of text"; +// char* str2 = "This is a very long string of textttttttttttt"; +// pVector v1 = ca_build_vector(str1); +// pVector v2 = ca_build_vector(str2); +// ca_fprint_vector(stdout, v1); printf("\n"); +// ca_fprint_vector(stdout, v2); printf("\n"); +// fprintf(stderr, "'%s' ?= '%s' -> %g\n", str1, str2, ca_cos_compare(v1, v2)); +// ca_free_vector(v1); +// ca_free_vector(v2); /** Register the driver. **/ if (!check(objRegisterDriver(drv))) goto err; From 4b656a4a407468296395b25ac11433eebdcb928f Mon Sep 17 00:00:00 2001 From: Israel Date: Thu, 13 Nov 2025 16:50:35 -0700 Subject: [PATCH 008/361] Improve exp_functions() to use central schema verification. Re-add Levenshtein to exp_functions. Publish edit_dist() in the cluster library. Fix mistakes in cluster driver function signatures. Fix spelling mistakes. Add detail to an error message in the lexer. Remove unused .cluster files. Clean up cluster-schema.cluster. Clean up other unused junk. --- centrallix-doc/Widgets/widgets.xml | 2 +- centrallix-lib/include/clusters.h | 7 + centrallix-lib/include/glyph.h | 6 +- centrallix-lib/include/util.h | 56 +- centrallix-lib/src/clusters.c | 32 +- centrallix-lib/src/mtlexer.c | 6 +- centrallix-lib/src/util.c | 4 +- centrallix-lib/src/xhash.c | 6 +- centrallix-os/cluster-schema.cluster | 111 -- centrallix-os/file.cluster | 67 - centrallix-os/file2.cluster | 42 - centrallix-sysdoc/OSDriver_Authoring.md | 1222 +++++++++++++----- centrallix-sysdoc/string_comparison.md | 101 -- centrallix-sysdoc/string_similarity.md | 63 +- centrallix/expression/exp_double_metaphone.c | 30 +- centrallix/expression/exp_functions.c | 513 +++----- centrallix/osdrivers/objdrv_cluster.c | 167 +-- 17 files changed, 1200 insertions(+), 1235 deletions(-) delete mode 100644 centrallix-os/file.cluster delete mode 100644 centrallix-os/file2.cluster delete mode 100644 centrallix-sysdoc/string_comparison.md diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index b6b50afde..f38f178d0 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -3731,7 +3731,7 @@ myTabControl "widget/tab" The title of the column to be displayed in the header row. - The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. + The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. width of the column. diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 8338cd5e0..05480e742 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -71,11 +71,17 @@ typedef struct nmRegister(sizeof(pCentroid), "pCentroid"); \ nmRegister(pCentroidSize, "Centroid"); \ nmRegister(sizeof(Dup), "Dup") + +/** Edit distance function. **/ +unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); +/** Vector functions. **/ pVector ca_build_vector(const char* str); unsigned int ca_sparse_len(const pVector vector); void ca_print_vector(const pVector vector); void ca_free_vector(pVector sparse_vector); + +/** Kmeans function. **/ int ca_kmeans( pVector* vectors, const unsigned int num_vectors, @@ -98,6 +104,7 @@ double ca_cos_compare(void* v1, void* v2); double ca_lev_compare(void* str1, void* str2); bool ca_eql(pVector v1, pVector v2); +/** Similarity search functions. **/ void* ca_most_similar( void* target, void** data, diff --git a/centrallix-lib/include/glyph.h b/centrallix-lib/include/glyph.h index 5f78eab5d..cfafd3946 100644 --- a/centrallix-lib/include/glyph.h +++ b/centrallix-lib/include/glyph.h @@ -35,8 +35,8 @@ #include -/** Uncomment to use glyphs. **/ -/** TODO: Israel - Comment this out. **/ +/** Uncomment to activate glyphs. **/ +/** Should not be enabled in production code on the master branch. */ // #define ENABLE_GLYPHS #ifdef ENABLE_GLYPHS @@ -50,7 +50,7 @@ *** *** @param name The symbol name of the visualizer. *** @param str The string printed for the visualization. - *** @param interval The number of invokations of glyph() required to print. + *** @param interval The number of invocations of glyph() required to print. *** @param flush Whether to flush on output. ***/ #define glyph_init(name, str, interval, flush) \ diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 1f286cc26..0f2685039 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -50,7 +50,10 @@ extern "C" { #ifndef __cplusplus #include -/** TODO: Greg, is the __typeof__ syntax from GCC a portability concern? **/ +/*** TODO: Greg - Can we assume this code will always be compiled with GCC? + *** If not, then the __typeof__, __LINE__, and __FILE__ syntaxes might be a + *** portability concern. + ***/ /*** @brief Returns the smaller of two values. *** @@ -58,7 +61,7 @@ extern "C" { *** @param b The second value. *** @return The smaller of the two values. *** - *** @note This macro uses GCC extensions to enusre type safety. + *** @note This macro uses GCC extensions to ensure type safety. ***/ #define min(a, b) \ ({ \ @@ -73,7 +76,7 @@ extern "C" { *** @param b The second value. *** @return The larger of the two values. *** - *** @note This macro uses GCC extensions to enusre type safety. + *** @note This macro uses GCC extensions to ensure type safety. ***/ #define max(a, b) \ ({ \ @@ -151,53 +154,6 @@ void print_diagnostics(int code, const char* function_name, const char* file_nam _r; \ }) -/** Pattern for printing a binary int using printf(). **/ -#define INT_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" - -/*** Converts an int to the values that should be passed to printf() for the - *** INT_TO_BINARY_PATTERN pattern. - *** - *** @attention - Double evaluation is NOT HANDLED so int_val will be evaluted - *** 32 times when this macro is used. Ensure that evaluation of the value - *** passed for int_val does not have important side effects! - *** - *** @param int_val The int to be printed. - *** @returns Values for printf(). - ***/ -#define INT_TO_BINARY(int_val) \ - ((int_val) & 0b10000000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b01000000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00100000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00010000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00001000000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000100000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000010000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000001000000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000100000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000010000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000001000000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000100000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000010000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000001000000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000100000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000010000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000001000000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000100000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000010000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000001000000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000100000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000010000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000001000000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000100000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000010000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000001000000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000100000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000010000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000001000 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000000100 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000000010 ? '1' : '0'), \ - ((int_val) & 0b00000000000000000000000000000001 ? '1' : '0') - #endif /* __cplusplus */ #endif /* UTILITY_H */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index d61a558c7..4a96b6ca1 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -104,7 +104,7 @@ static int charpair_cmp(const void *p1, const void *p2) *** input to get_char_pair_hash(). *** *** After hashing each character pair, we add some number from 1 to 13 to the - *** coresponding dimention. However, for most names, this results in a lot of + *** coresponding dimension. However, for most names, this results in a lot of *** zeros and a FEW positive numbers. Thus, after creating the dense vector, *** we convert it to a sparse vector in which a negative number replaces a run *** of that many zeros. Consider the following example: @@ -114,7 +114,7 @@ static int charpair_cmp(const void *p1, const void *p2) *** Sparse pVector: `[1,-3,3,-1]` *** *** Using these sparse vectors greatly reduces the required memory and gives - *** aproximately an x5 boost to performance when traversing vectors, at the + *** approximately an x5 boost to performance when traversing vectors, at the *** cost of more algorithmically complex code. *** *** @param str The string to be divided into pairs and hashed to make the vector. @@ -162,7 +162,7 @@ pVector ca_build_vector(const char* str) /** Sort char_pairs by hash value. **/ qsort(char_pairs, num_pairs, sizeof(CharPair), charpair_cmp); - /** Allocate space for the sparce vector. **/ + /** Allocate space for the sparse vector. **/ pVector sparse_vector = (pVector)check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); if (sparse_vector == NULL) return NULL; @@ -403,7 +403,7 @@ static void parse_vector_token(const int token, unsigned int* remaining, unsigne } } -/*** Calculate the similarity on sparcely allocated vectors. Comparing +/*** Calculate the similarity on sparsely allocated vectors. Comparing *** any string to an empty string should always return 0.5 (untested). *** *** @param v1 Sparse vector #1. @@ -442,7 +442,7 @@ static double sparse_similarity(const pVector v1, const pVector v2) return (double)dot_product / (magnitude_sparse(v1) * magnitude_sparse(v2)); } -/*** Calculate the difference on sparcely allocated vectors. Comparing +/*** Calculate the difference on sparsely allocated vectors. Comparing *** any string to an empty string should always return 0.5 (untested). *** *** @param v1 Sparse vector #1. @@ -511,7 +511,7 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 *** @skip *** LINK ../../centrallix-sysdoc/string_comparison.md#levenshtein ***/ -static unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) +unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) { /*** lev_matrix: *** For all i and j, d[i][j] will hold the Levenshtein distance between @@ -554,7 +554,7 @@ static unsigned int edit_dist(const char* str1, const char* str2, const size_t s if (str1[i - 1] == str2[j - 1]) lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; - /*** We need to make a change, so use the opereration with the + /*** We need to make a change, so use the oppereration with the *** lowest cost out of delete, insert, replace, or swap. ***/ else @@ -587,7 +587,7 @@ static unsigned int edit_dist(const char* str1, const char* str2, const size_t s return result; } -/*** Compares two strings using their cosie simiarity, returning a value +/*** Compares two strings using their cosie similarity, returning a value *** between `0.0` (completely different) and `1.0` (identical). If either *** OR BOTH strings are NULL, this function returns `0.0`. *** @@ -618,7 +618,7 @@ double ca_cos_compare(void* v1, void* v2) return sparse_similarity(vec1, vec2); } -/*** Compares two strings using their levenstien edit distance to compute a +/*** Compares two strings using their Levenshtein edit distance to compute a *** similarity between `0.0` (completely different) and `1.0` (identical). *** If both strings are empty, this function returns `1.0` (identical). If *** either OR BOTH strings are NULL, this function returns `0.0`. @@ -722,7 +722,7 @@ static double get_cluster_size( *** a size of `n`. *** *** The following table shows data sizes vs.selected cluster size. In testing, - *** these numbers tended to givea good balance of accuracy and dulocates detected. + *** these numbers tended to give a good balance of accuracy and duplicates detected. *** *** ```csv *** Data Size, Actual @@ -771,7 +771,7 @@ unsigned int compute_k(const unsigned int n) *** clusters have a size of negative infinity. In this implementation, *** the bug is mitigated by setting a small number of max iterations, *** such as 16 instead of 100. - *** @attention - Issue: Clusters do not apear to improve much after the first + *** @attention - Issue: Clusters do not appear to improve much after the first *** iteration, which puts the efficacy of the algorithm into question. This *** may be due to the uneven density of a typical dataset. However, the *** clusters still offer useful information. @@ -962,7 +962,7 @@ int ca_kmeans( *** @param similarity A function which takes two data items of the type *** of the data param and returns their similarity. *** @param threshold The minimum similarity threshold. If the most similar - *** data does not meet this threshold, the funciton returns NULL. + *** data does not meet this threshold, the function returns NULL. *** @returns A pointer to the most similar piece of data found in the data *** array, or NULL if the most similar data did not meet the threshold. ***/ @@ -988,10 +988,10 @@ void* ca_most_similar( } -/*** Runs a sliding search over the povided data, comparing each element to +/*** Runs a sliding search over the provided data, comparing each element to *** the following `window_size` elements, invoking the passed comparison *** function just under `window_size * num_data` times. If any comparison - *** yeilds a similarity greater than the threshold, it is stored in the + *** yields a similarity greater than the threshold, it is stored in the *** xArray returned by this function. *** *** @param data The data to be searched. @@ -1076,9 +1076,9 @@ pXArray ca_sliding_search( return NULL; } -/*** Runs a complete search over the povided data, comparing each element to +/*** Runs a complete search over the provided data, comparing each element to *** each other element, invoking the passed comparison function `num_data^2` - *** times. If any comparison yeilds a similarity greater than the threshold, + *** times. If any comparison yields a similarity greater than the threshold, *** it is stored in the xArray returned by this function. *** *** @param data The data to be searched. diff --git a/centrallix-lib/src/mtlexer.c b/centrallix-lib/src/mtlexer.c index e92ea49ff..39a69cc15 100644 --- a/centrallix-lib/src/mtlexer.c +++ b/centrallix-lib/src/mtlexer.c @@ -7,6 +7,7 @@ #include #include #include + #include "newmalloc.h" #include "mtask.h" #include "mtlexer.h" @@ -907,7 +908,9 @@ mlxNextToken(pLxSession this) } else { - mssError(1,"MLX","Unexpected character encountered"); + char buf[4]; + snprintf(buf, sizeof(buf), "%c", ch); // mssError() does not support %c. + mssError(1, "MLX", "Unexpected character encountered: '%s'", buf); this->TokType = MLX_TOK_ERROR; break; } @@ -1305,4 +1308,3 @@ mlxSetOffset(pLxSession this, unsigned long new_offset) return 0; } - diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index b18361280..f60349a74 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -85,7 +85,7 @@ unsigned int strtoui(const char *nptr, char **endptr, int base){ } /*** Detects the optimal number of threads to use on this system. - *** Note: Multithreading is not currently supported, so this funciton + *** Note: Multithreading is not currently supported, so this function *** will always return 1, for now. *** *** @returns The number of threads that should be used on this system. @@ -120,7 +120,7 @@ static char* units_metric[nUnits] = {"bytes", "KB", "MB", "GB"}; *** *** @param buf The buffer to which new text will be written, using snprintf(). *** @param buf_size The amount of space in the buffer, passed to snprintf(). - *** It is recomended to have at least 12 characters available. + *** It is recommended to have at least 12 characters available. *** @param bytes The number of bytes, which will be formatted and written *** to the buffer.. *** @returns buf, for chaining. diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index 32a4a35eb..46ef3a6fb 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -295,11 +295,11 @@ xhClear(pXHashTable this, int (*free_fn)(), void* free_arg) *** @param this The affected hash table. *** @param callback_fn A callback function to be called on each hash table *** entry. It takes 2 parameters: the current hash table entry and a void* - *** argument specified using each_arg. If any invokation of the callback + *** argument specified using each_arg. If any invocation of the callback *** function returns a value other than 0, xhForEach() will immediately *** fail, returning that value as the error code. - *** @param each_arg An aditional argument which will be passed to each - *** invokation of the callback function. + *** @param each_arg An additional argument which will be passed to each + *** invocation of the callback function. *** @returns 0 if the function executes successfully. *** 1 if the callback function is NULL. *** n (where n != 0) if the callback function returns n. diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index 277e2bb12..4113a339a 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -59,114 +59,3 @@ file_name "system/cluster" - /key2 : string // The key of the second data point. - /sim : double && 0.0 < x <= threshold // The similarity of the two data points. ... - -// Other notes - -// This means centrallix scripts will have to chose when to switch -// from complete search to clustered search. I think this is a good -// thing, because that feels like a higher-level responsibility. - -// Invoke file: -// select * from /file.cluster - -// Driver-authoring.md -// Comprehend stparse.c (lib vs. centrallix?) -// Design what a .cluster file looks like. -// -// Figure out how to invoke the object system. - -// Random queries - -// Names -SELECT CONCAT(p_given_name, ' ', p_surname) AS full_name, - COUNT(*) AS num_dups -FROM p_partner -WHERE p_given_name is not null -AND p_surname is not null -AND p_given_name != "" -AND p_surname != "" -AND p_given_name != " " -AND p_surname != " " -GROUP BY full_name -ORDER BY num_dups DESC -LIMIT 1; -// Result: Ine Bradley with 4 dups - -// Phone Numbers -SELECT CONCAT(ci.p_phone_country, ci.p_phone_area_city, ci.p_contact_data) AS phone_number, - COUNT(*) AS num_dups -FROM p_partner AS p -JOIN p_contact_info AS ci - ON p.p_partner_key = ci.p_partner_key -WHERE ci.p_contact_data != ' ' -AND ci.p_contact_data != '' -AND (ci.p_contact_type = 'P' OR ci.p_contact_type = 'C') -GROUP BY phone_number -ORDER BY num_dups DESC -LIMIT 1; -// Result: 1813762-2274 with 2 dups - -// Emails and Addresses -SELECT CONCAT(ci.p_contact_data, ' ', - l.p_in_care_of, ' ', - l.p_address_1, ' ', - l.p_address_2, ' ', - l.p_address_3, ' ', - l.p_city, ' ', - l.p_state_province, ' ', - l.p_country_code, ' ', - l.p_postal_code) AS email_and_address, - COUNT(*) AS duplicate_count -FROM p_partner AS p -JOIN p_contact_info AS ci - ON p.p_partner_key = ci.p_partner_key -JOIN p_location AS l - ON p.p_partner_key = l.p_partner_key -WHERE ci.p_contact_type = 'E' -GROUP BY email_and_address -ORDER BY duplicate_count DESC -LIMIT 1; -// Result: richard.aypofblcsg@iipr.yeen with 2 dups - -// Email -SELECT ci.p_contact_data AS email, - COUNT(*) AS duplicate_count -FROM p_partner AS p -JOIN p_contact_info AS ci - ON p.p_partner_key = ci.p_partner_key -WHERE ci.p_contact_type = 'E' -GROUP BY email -ORDER BY duplicate_count DESC -LIMIT 1; - -// Result: uoehtbtjvqh20@ltirs.zese with 2 dups - -// Address -SELECT CONCAT(l.p_in_care_of, ' ', - l.p_address_1, ' ', - l.p_address_2, ' ', - l.p_address_3, ' ', - l.p_city, ' ', - l.p_state_province, ' ', - l.p_country_code, ' ', - l.p_postal_code) AS address, - COUNT(*) AS duplicate_count -FROM p_partner AS p -JOIN p_location AS l - ON p.p_partner_key = l.p_partner_key -WHERE l.p_address_1 != ' ' -GROUP BY address -ORDER BY duplicate_count DESC -LIMIT 1; -// Result: "742 1ben Sc E Adams FL US 49152" with 4 - - -// Output to dataset -INTO OUTFILE '/var/lib/mysql/db_output.csv' -LINES TERMINATED BY '|' - -// Output to CSV -INTO OUTFILE '/var/lib/mysql/db_output.csv' -FIELDS TERMINATED BY ',' -ENCLOSED BY '"' -LINES TERMINATED BY '\n'; diff --git a/centrallix-os/file.cluster b/centrallix-os/file.cluster deleted file mode 100644 index 95eacfee0..000000000 --- a/centrallix-os/file.cluster +++ /dev/null @@ -1,67 +0,0 @@ -$Version=2$ -file_name "system/cluster" - { - // Developer can specify parameters to improve file reuseability. - // TIP: Improve performance by declairing frequently used parameters first. - k "cluster/parameter" { type = integer; style=notnull; } - str "cluster/parameter" { type = string; } - int "cluster/parameter" { type = integer; default = runserver(:parameters:k); } - dbl "cluster/parameter" { type = double; default=4.2; } - // conversion "cluster/parameter" { type=double; default=4; } - - null_str "cluster/parameter" { type = string; default = null; } - null_int "cluster/parameter" { type = integer; default = null; } - null_dbl "cluster/parameter" { type = double; default = null; } - - // We calculate k in a centrallix script using: - // k = max(2, pow(log(n) / log(36), 3.2) - 8) - // where n is the number of records passed. - - // Specify the data source at the top of the file. - // How do we pass distinct data? Should the driver - // handle that for us? - source = "/apps/kardia/data/Kardia_DB/p_partner/rows"; - attr_name = p_given_name; // runserver(:parameters:str) - - // Multiple data sources when? - - // Clustering object specifies properties for clustering. - kmeans_cluster "cluster/cluster" - { - algorithm = "k-means"; - similarity_measure = "cosine"; - // window_size = 16; - num_clusters = runserver(:parameters:k); - min_improvement = 0.0001; - max_iterations = 48; - - // Create subclusters. (Not implemented) - sub_cluster "cluster/cluster" - { - algorithm = "none"; - similarity_measure = "cosine"; - num_clusters = 7; - min_improvement = "max"; - } - } - - // Complete search. - no_clustering "cluster/cluster" - { - algorithm = "none"; - } - - dups "cluster/search" - { - source = kmeans_cluster; - similarity_measure = "cosine"; - threshold = 0.75; - } - - dups2 "cluster/search" - { - source = no_clustering; - similarity_measure = "cosine"; - threshold = 0.75; - } - } diff --git a/centrallix-os/file2.cluster b/centrallix-os/file2.cluster deleted file mode 100644 index a55c37f85..000000000 --- a/centrallix-os/file2.cluster +++ /dev/null @@ -1,42 +0,0 @@ -$Version=2$ -file_name "system/cluster" - { - // Developer can specify parameters to improve file reuseability. - // TIP: Improve performance by declairing frequently used parameters first. - k "cluster/parameter" { type = integer; style=notnull; } - str "cluster/parameter" { type = string; default="k-means"; } - int "cluster/parameter" { type=integer; default=:parameters:k; } - dbl "cluster/parameter" { type=double; default=4.2; } - // conversion "cluster/parameter" { type=double; default=4; } - - null_str "cluster/parameter" { type = string; default = null; } - null_int "cluster/parameter" { type = integer; default = null; } - null_dbl "cluster/parameter" { type = double; default = null; } - - // We calculate k in a centrallix script using: - // k = max(2, pow(log(n) / log(36), 3.2) - 8) - // where n is the number of records passed. - - // Specify the data source at the top of the file. - // How do we pass distinct data? Should the driver - // handle that for us? - source = "/apps/kardia/data/Kardia_DB/p_partner/rows"; - attr_name = "p_given_name"; - - // Clustering object specifies properties for clustering. - kmeans_cluster "cluster/cluster" - { - algorithm = "k-means"; - similarity_measure = "cosine"; - num_clusters = :parameters:k; - min_improvement = 0.0001; - max_iterations = 48; - } - - dups "cluster/search" - { - source = kmeans_cluster; - threshold = 0.75; - similarity_measure = "cosine"; - } - } diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 5755d15c5..d00c192f6 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -1,52 +1,76 @@ # ObjectSystem Driver Interface -Author: Greg Beeley -Date: January 13, 1999 +**Author**: Greg Beeley -Updated: March 9, 2011 +**Date**: January 13, 1999 -License: Copyright (C) 2001-2011 LightSys Technology Services. See LICENSE.txt for more information. +**Updated**: November 27, 2025 + +**License**: Copyright (C) 2001-2011 LightSys Technology Services. See LICENSE.txt for more information. ## Table of Contents - [ObjectSystem Driver Interface](#objectsystem-driver-interface) - [Table of Contents](#table-of-contents) - [I Introduction](#i-introduction) - [II Interface](#ii-interface) - - [A. Initialization](#a--initialization) - - [B. Opening And Closing Objects](#b--opening-and-closing-objects) - - [C. Creating and Deleting Objects.](#c--creating-and-deleting-objects) - - [D. Reading and Writing Object Content.](#d--reading-and-writing-object-content) - - [E. Querying for Child Objects.](#e--querying-for-child-objects) - - [F. Managing Object Attributes](#f--managing-object-attributes) - - [G. Managing Object Methods](#g--managing-object-methods) + - [Function: Open](#function-open) + - [Function: OpenChild()](#function-openchild) + - [Function: Close()](#function-close) + - [Function: Create()](#function-create) + - [Function: Delete()](#function-delete) + - [Function: DeleteObj()](#function-deleteobj) + - [Function: Read()](#function-read) + - [Function: Write()](#function-write) + - [Function: OpenQuery()](#function-openquery) + - [Function: QueryDelete()](#function-querydelete) + - [Function: QueryFetch()](#function-queryfetch) + - [Function: QueryCreate()](#function-querycreate) + - [Function: QueryClose()](#function-queryclose) + - [Function: GetAttrType()](#function-getattrtype) + - [Function: GetAttrValue()](#function-getattrvalue) + - [Function: GetFirstAttr()](#function-getfirstattr--getnextattr) + - [Function: GetNextAttr()](#function-getfirstattr--getnextattr) + - [Function: SetAttrValue()](#function-setattrvalue) + - [Function: AddAttr()](#function-addattr) + - [Function: OpenAttr()](#function-openattr) + - [Function: GetFirstMethod()](#function-getfirstmethod--getnextmethod) + - [Function: GetNextMethod()](#function-getfirstmethod--getnextmethod) + - [Function: ExecuteMethod()](#function-executemethod) + - [Function: PresentationHints()](#function-presentationhints) + - [Function: Info()](#function-info) + - [Function: Commit()](#function-commit) + - [Function: GetQueryCoverageMask()](#function-getquerycoveragemask) + - [Function: GetQueryIdentityPath()](#function-getqueryidentitypath) - [III Reading the Node Object](#iii-reading-the-node-object) - - [pSnNode snReadNode(pObject obj)](#psnnode-snreadnodepobject-obj) - - [pSnNode snNewNode(pObject obj, char* content_type)](#psnnode-snnewnodepobject-obj-char-content_type) - - [int snWriteNode(pSnNode node)](#int-snwritenodepsnnode-node) - - [int snDeleteNode(pSnNode node)](#int-sndeletenodepsnnode-node) - - [int snGetSerial(pSnNode node)](#int-sngetserialpsnnode-node) - - [pStructInf stParseMsg(pFile inp_fd, int flags)](#pstructinf-stparsemsgpfile-inp_fd-int-flags) - - [pStructInf stParseMsgGeneric(void* src, int (*read_fn)(), int flags)](#pstructinf-stparsemsggenericvoid-src-int-read_fn-int-flags) - - [int stGenerateMsg(pFile out_fd, pStructInf info, int flags)](#int-stgeneratemsgpfile-out_fd-pstructinf-info-int-flags) - - [int stGenerateMsgGeneric(void* dst, int (*write_fn)(), pStructInf info, int flags)](#int-stgeneratemsggenericvoid-dst-int-write_fn-pstructinf-info-int-flags) - - [pStructInf stCreateStruct(char* name, char* type)](#pstructinf-stcreatestructchar-name-char-type) - - [pStructInf stAddAttr(pStructInf inf, char* name)](#pstructinf-staddattrpstructinf-inf-char-name) - - [pStructInf stAddGroup(pStructInf inf, char* name, char* type)](#pstructinf-staddgrouppstructinf-inf-char-name-char-type) - - [int stAddValue(pStructInf inf, char* strval, int intval)](#int-staddvaluepstructinf-inf-char-strval-int-intval) - - [pStructInf stLookup(pStructInf inf, char* name)](#pstructinf-stlookuppstructinf-inf-char-name) - - [int stAttrValue(pStructInf inf, int* intval, char** strval, int nval)](#int-stattrvaluepstructinf-inf-int-intval-char-strval-int-nval) - - [int stFreeInf(pStructInf this)](#int-stfreeinfpstructinf-this) + - [Module: st_node](#module-st_node) + - [st_node: snReadNode()](#st_node-snreadnode) + - [st_node: snNewNode()](#st_node-snnewnode) + - [st_node: snWriteNode()](#st_node-snwritenode) + - [st_node: snDelete()](#st_node-sndeletenode) + - [st_node: snGetSerial()](#st_node-sngetserial) + - [st_node: snGetLastModification()](#st_node-sngetlastmodification) + - [Module: stparse](#module-stparse) + - [stparse: stStructType()](#stparse-ststructtype) + - [stparse: stLookup()](#stparse-stlookup) + - [stparse: stAttrValue()](#stparse-stattrvalue) + - [stparse: stGetExpression()](#stparse-stgetexpression) + - [stparse: stCreateStruct()](#stparse-stcreatestruct) + - [stparse: stAddAttr()](#stparse-staddattr) + - [stparse: stAddGroup()](#stparse-staddgroup) + - [stparse: stAddValue()](#stparse-staddvalue) + - [stparse: stFreeInf()](#stparse-stfreeinf) + - [stparse: Using Fields Directly](#stparse-using-fields-directly) - [IV Memory Management in Centrallix](#iv-memory-management-in-centrallix) - - [void* nmMalloc(int size)](#void-nmmallocint-size) - - [void nmFree(void* ptr, int size)](#void-nmfreevoid-ptr-int-size) - - [void nmStats()](#void-nmstats) - - [void nmRegister(int size, char* name)](#void-nmregisterint-size-char-name) - - [void nmDebug()](#void-nmdebug) - - [void nmDeltas()](#void-nmdeltas) - - [void* nmSysMalloc(int size)](#void-nmsysmallocint-size) - - [void nmSysFree(void* ptr)](#void-nmsysfreevoid-ptr) - - [void* nmSysRealloc(void* ptr, int newsize)](#void-nmsysreallocvoid-ptr-int-newsize) - - [char* nmSysStrdup(const char* str)](#char-nmsysstrdupconst-char-str) + - [nmMalloc()](#nmmalloc) + - [nmFree()](#nmfree) + - [nmStats()](#nmstats) + - [nmRegister()](#nmregister) + - [nmDebug()](#nmdebug) + - [nmDeltas()](#nmdeltas) + - [nmSysMalloc()](#nmsysmalloc) + - [nmSysRealloc()](#nmsysrealloc) + - [nmSysStrdup()](#nmsysstrdup) + - [nmSysFree()](#nmsysfree) - [V Other Utility Modules](#v-other-utility-modules) - [A. XArray (XA) - Arrays](#axarray-xa---arrays) - [xaInit(pXArray this, int init_size)](#xainitpxarray-this-int-init_size) @@ -115,520 +139,1028 @@ License: Copyright (C) 2001-2011 LightSys Technology Services. See LICENSE.txt - [B. Object attribute enumeration, getting, and setting.](#bobject-attribute-enumeration-getting-and-setting) - [C. Object querying (for subobjects)](#cobject-querying-for-subobjects) + + ## I Introduction -An objectsystem driver's purpose is to provide access to a particular type of local or network data/resource, and to organize that data in a tree- structured heirarchy that can be integrated into the Centrallix's ObjectSystem. This tree structure will vary based on the data being presented, but will fit the basic ObjectSystem model of a heirarchy of objects, each having attributes, perhaps some methods, and possibly content. +An objectsystem driver's purpose is to provide access to a particular type of local or network data/resource. Specific information about the resource to be accessed (such as credentials for a database, queries for selecting data, the auth token for an API, etc.) is stored in a file that is openned by the relevant driver. For example, the query driver (defined in `objdrv_query.c`) opens `.qy` files, which store one or more ObjectSQL queries used to fetch data. -Each objectsystem driver will implement this subtree structure rooted at what is called the "node" object. The node has a specifically recognizable object type which the ObjectSystem Management Layer uses to determine which OS Driver to pass control to. Normally, the 'node' object is a UNIX file either with a particular extension registered with the OSML, or a UNIX file residing in a directory containing a '.type' file, which contains the explicit object type for all objects in that directory without recognizable extensions. +When the object system starts up, each driver registers one or more type names that it supports (e.g. `"system/query"` for the query driver). When a file is openned, the object system uses the file's type name to select which driver to use. It finds this type name with one of two strategies. If the file has an extension (e.g. `example.qy`), that extension can be mapped to a type name using `types.cfg` (e.g. `.qy` maps to `"system/query"`). Althernatively, the file may reside in a directory containing a `.type` file which explicitly specifies the type name for all files in that directory without recognizable extensions. -Normally, objectsystem drivers will be able to manage any number of 'node' objects and the subtrees rooted at them. Each 'node' object will normally relate to a particular instance of a network resource, or in some cases, a group of resources that are easily enumerated. For example, a POP3 server would be a network resource that an OS driver could be written for. If the network had multiple POP3 servers, then that one OS driver would be able to access each of them using different node objects. However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could optionally design the driver to list the POP3 servers under a single node for the whole network. +Once a file is openned, the driver should organize provided data into a tree-structured hierarchy, which becomes part of the path used by Centrallix's ObjectSystem. For example, when opening `example.qy` in the ObjectSystem, the driver makes `/rows` and `/columns` available, allowing for paths such as `/apps/data/example.qy/rows`. The root of a driver's tree (`example.qy`) is called the driver's "node" object, and most paths traverse the root nodes of multiple drivers. A driver author is free to define any manner of tree structures for representing data available within their driver. However, the structure should fit the basic ObjectSystem model of a hierarchy of objects, each having attributes, and optionally some methods and/or content. -The structure of the subtree beneath the node object is entirely up to the drivers' author to determine; the OSML does not impose any structural restrictions on such subtrees. +A driver can be openned multiple times, leading one driver to have multiple "node" objects, also called instances. Typically, each "node" object relates to a particular instance of a network resource. For example, an instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. -Here is one example of an OS Driver's node object and subtree (this is for the Sybase OS Driver, objdrv_sybase.c): +The structure of the subtree beneath the node object is entirely up to the drivers' author to determine; the OSML does not impose any structural restrictions on such subtrees. Each object within this structure (e.g. `/example.qy`) can have three types of readable data: +- Child objects (e.g. `/rows`) which can have their own data. +- Content, which can be read similar to reading a file. +- Query data, allowing the object to be queried for information. -``` -OMSS_DB (type = application/sybase) +Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradime, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traversal of its child objects, although queries are not required to be implemented this way. + +Below is an example of the Sybase driver's node object and its subtrees of child objects (defined in `objdrv_sybase.c`): + +```sh +OMSS_DB (type = "application/sybase") | - +--- JNetHelp (type = system/table) - | | - | +--- columns (type = system/table-columns) - | | | - | | +--- document_id (type = system/column) - | | | - | | +--- parent_id (type = system/column) - | | | - | | +--- title (type = system/column) - | | | - | | +--- content (type = system/column) - | | - | +--- rows (type = system/table-rows) - | | - | +--- 1 (type = system/row) - | | - | +--- 2 (type = system/row) + +----- JNetHelp (type = "system/table") + | | + | +----- columns (type = "system/table-columns") + | | | + | | +----- document_id (type = "system/column") + | | | + | | +----- parent_id (type = "system/column") + | | | + | | +----- title (type = "system/column") + | | | + | | +----- content (type = "system/column") + | | + | +----- rows (type = "system/table-rows") + | | + | +----- 1 (type = "system/row") + | | + | +----- 2 (type = "system/row") | - +--- Partner (type = system/table) + +----- Partner (type = "system/table") ``` (... and so forth) -In this case the node object would contain the information necessary to access the database, such as server name, database name, max connections to pool, and so forth. More about the node object and managing its parameters will be discussed later in this document. +In this case, the `OMSS_DB` file becomes the driver's node object. This file would contain the information necessary to access the database, such as server name, database name, max connections to pool, and so forth. -OS Drivers support several primary areas of functionality: opening and closing objects, reading and writing object content (if the object has content), setting and viewing object attributes, executing object methods, and querying an object's child objects based on name and/or attribute values. Drivers will also support the creation and deletion of objects and/or a set of child objects. +OS Drivers support several primary areas of functionality: +- Opening and closing objects. +- Creating and deleting node objects (optional). +- Reading and writing object content (optional). +- Getting and (optionally) setting object attributes. +- Executing object methods (optional). +- Querying data attributes (optional). -## II Interface -This section describes the standard interface between the OSML and the ObjectSystem driver itself. +Using the example above, we can query from the database using a statement like `select :title from /OMSS_DB/JNetHelp/rows`, which will open a sybase driver instance, then open a query and repeatedly fetch rows, getting the `title` attribute from each row. -### A. Initialization -Each OS Driver will have an initialization function, normally named xxxInitialize() where 'xxx' is the driver's abbreviative prefix. This prefix should be attached to each and every function within the OS driver for consistency and project management. Normally 'xxx' is two to four characters, all lowercase. This initialization function is called when the Centrallix starts up, and at least at the present time, this initial call to the OS driver must be manually added to the appropriate startup code, currently found in 'centrallix.c'. - -Within the initialization function, the driver should initialize all necessary global variables and register itself with the OSML. Global variables should all be placed inside a single global 'struct', which is normally named similarly to the driver's prefix, except normally in all uppercase. Under no circumstances should global variables be accessed outside of the module, except via the module's functions. - -To register with the OSML, the driver must first allocate an ObjDriver structure and fill in its contents. +## II Interface +This section describes the standard interface between the OSML and the ObjectSystem driver itself. Every driver should implement certain required functions. (**Note**: Many drivers "implement" some required functions to simply fail with a not implemented or not supported error. For example, most database drivers "implement" `Read()` and `Write()` this way because database content should be queried, not read). Various optional functions are also available, which a driver is not required to implement. + + +The driver should implement an `Initialize()` function, as well as the following (* indicates required functions): +| Function Name | Description +| --------------------------------------------------------- | ------------ +| [Open](#function-open)* | Opens a new driver instance object on a given root node. +| [OpenChild](#function-openchild) | ??? +| [Close](#function-close)* | Close an open object created by either `Open()` or `QueryFetch()`. +| [Create](#function-create) | Create a new driver root node object. +| [Delete](#function-delete) | Delete an existing driver root node object. +| [DeleteObj](#function-deleteobj)* | ??? +| [OpenQuery](#function-openquery)** | Start a new query for child objects of a given object. +| [QueryDelete](#function-querydelete) | Delete specific objects from a query's result set. +| [QueryFetch](#function-queryfetch)** | Open the next child object in the query's result set. +| [QueryCreate](#function-querycreate) | ??? +| [QueryClose](#function-queryclose)** | Close an open query. +| [Read](#function-read)* | Read content from the object. +| [Write](#function-write)* | Write content to the object. +| [GetAttrType](#function-getattrtype)* | Get the type of a given object's attribute. +| [GetAttrValue](#function-getattrvalue)* | Get the value of a given object's attribute. +| [GetFirstAttr](#function-getfirstattr--getnextattr)* | Get the name of the object's first attribute. +| [GetNextAttr](#function-getfirstattr--getnextattr)* | Get the name of the object's next attribute. +| [SetAttrValue](#function-setattrvalue) | Set the value of an object's attribute. +| [AddAttr](#function-addattr) | Add a new attribute to an object. +| [OpenAttr](#function-openattr) | Open an attribute as if it were an object with content. +| [GetFirstMethod](#function-getfirstmethod--getnextmethod) | Get the name of an object's first method. +| [GetNextMethod](#function-getfirstmethod--getnextmethod) | Get the name of an object's next method. +| [ExecuteMethod](#function-executemethod) | Execute a method with a given name and optional parameter string. +| [PresentationHints](#function-presentationhints) | Get info about an object's attributes. +| [Info](#function-info)* | Get info about an object instance. +| [Commit](#function-commit) | Commit changes made to an object. +| [GetQueryCoverageMask](#function-getquerycoveragemask) | ??? +| [GetQueryIdentityPath](#function-getqueryidentitypath) | ??? + +_*Function is always required._ + +_**Function is required to support queries._ + + +--- +### Abbreviative Prefix +Each OS Driver will have an abbreviation prefix, such as `qy` for the query driver or `sydb` for the sybase database driver. This prefix should be prepended to the start of every public function name within the OS driver for consistency and scope management (e.g. `qyInitialize()`, `sydbQueryFetch()`, etc.). Normally, a driver's abbreviation prefix is two to four characters, all lowercase and may be the same as a file extension the driver supports. However, this is not an absolute requirement (see the cluster driver in `objdrv_cluster.c` which supports `.cluster` files using an abbreviation prefix of `cluster`). + +This document uses `xxx` to refer to an unspecified abbreviative prefix. + +--- +### Internal Functions +It is highly likely that driver authors will find shared functionality in the following functions, or wish to abstract out functionality from any of them for a variety of reasons. When creating additional internal functions in this way, they should be named using the convention of `xxx_internal_FunctionName()`, or possibly `xxxi_FunctionName()` for short. + +--- +### Function: Initialize ```c - pObjDriver drv; - - drv = (pObjDriver)nmMalloc(sizeof(ObjDriver)); +/*** @returns 0 if successful, or + *** -1 if an error occurred. + ***/ +int xxxInitialize(void) ``` +- ⚠️ **Warning**: Currently, the success/failure of this function is ignored by the caller. +- 📖 **Note**: Unlike other functions defined in the driver, each driver author must manually add this call to the start up code, found in the `cxDriverInit()` function in `centrallix.c`. -This involves setting a large number of fields to the appropriate entry points within the OS Driver, as well as telling the OSML what object type(s) are handled by the driver and giving the OSML a description of the driver. A list of the required entry point functions / fields follows: - -| Function/Field | Description -| -------------------- | ------------ -| Open | Function that the OSML calls when the user opens an object managed by this driver. -| Close | Close an open object. -| Create | Create a new object. -| Delete | Delete an existing object. -| OpenQuery | Start a query for child objects. -| QueryDelete | Delete all objects in the query result set. -| QueryFetch | Open the next child object in the query's result set. -| QueryClose | Close an open query. -| Read | Read content from the object. -| Write | Write content to the object. -| GetAttrType | Get the type of an object's attribute. -| GetAttrValue | Get the value of an object's attribute. -| GetFirstAttr | Get the first attribute associated with the object. -| GetNextAttr | Get the next attribute associated with the object. -| SetAttrValue | Set the value of an attribute. -| AddAttr | Add a new attribute to an object. -| OpenAttr | Open an attribute as if it were an object with content. -| GetFirstMethod | Get the first method of the object. -| GetNextMethod | Get the next method of an object. -| ExecuteMethod | Execute a method with an optional string parameter. - -The only method that can be set to NULL is the QueryDelete method, in which case the OSML will call QueryFetch() and Delete() in succession. However, if the underlying network resource has the capability of intelligently deleting objects matching the query's criteria, this method should be implemented (as with a database server). - -Another field in the driver structure is the Capabilities field. This field is a bitmask, and can currently contain zero or more of the following options: +The initialization function is called when the Centrallix starts up, and should register the driver with the OSML and initialize necessary global variables. It is recommended to place global variables in a single global 'struct' that is named with the driver's prefix in all uppercase. Global variables should **NOT** be accessed from outside the driver. Instead, the driver should define functions to access them, allowing it to abstract details away from other drivers. -- OBJDRV_C_FULLQUERY: Indicates that this objectsystem driver will intelligently process the query's expression tree specified in the OpenQuery call, and will only return objects that match that expression. If this flag is missing, the OSML will filter objects returned by QueryFetch so that the calling user does not get objects that do not match the query. Typically this is set by database server drivers. +To register itself with the OSML, the driver should first allocate an ObjDriver structure and initialize its contents: - THE ABOVE IS OUT-OF-DATE. From now on, a driver can determine whether to handle the Where and OrderBy on a per-query basis, by setting values in the ObjQuery structure used when opening a new query. This is because a driver may be able to handle Where and OrderBy for some object listings but not for others. +```c +pObjDriver drv = (pObjDriver)nmMalloc(sizeof(ObjDriver)); +if (drv == NULL) goto error_handling; +memset(drv, 0, sizeof(ObjDriver)); +... +``` -- OBJDRV_C_TRANS: Indicates that this objectsystem driver requires transaction management by the OSML's transaction layer (the OXT layer). OS drivers that require this normally are those that for some reason cannot complete operations in independence from one another. For example, with a database driver, the creation of a new row object and the setting of its attributes must be done as one operation, although the operation requires several calls from the end user's process. The OXT allows for the grouping of objectsystem calls so that the os driver does not have to complete them independently, but instead can wait until several calls have been made before actually completing the operation. +To initialize this struct, the driver must: +- Provide a name (in `drv->Name`). +- Provide an array of supported root node types (in `drv->RootContentTypes`). +- Provide capability flags (in `drv->Capabilities`). +- Provide function pointers to implemented functions (see [II Interface](#ii-interface) for a list). -The 'Name' field should be filled in with a description of the OS driver, with a maximum length of 63 characters (plus the string null terminator). Normally, the 2-4 letter prefix of the driver is included at the beginning of 'Name', such as "UXD - UNIX filesystem driver". +#### Name +The `name` field is a 64 character buffer (allowing names up to 63 characters, with a null terminator). It usually follows the format of the driver abbreviation prefix (in all uppercase), followed by a dash, followed by a descriptive name for the driver. -Finally, the 'RootContentTypes' field is an XArray containing a list of strings, each of which specifies the node object types that the driver will handle. Such types are added to this XArray using the normal XArray utility functions, such as: +For example: ```c - xaInit(&drv->RootContentTypes, 16); - xaAddItem(&drv->RootContentTypes, "system/file"); - xaAddItem(&drv->RootContentTypes, "system/directory"); +if (strcpy(drv->Name, "SYBD - Sybase Database Driver") == NULL) goto error_handling; ``` -When the structure has been filled out, the os driver should call the OSML to register itself, using the objRegisterDriver function: +#### RootContentTypes +The `RootContentTypes` field is an XArray containing a list of strings, representing the type names that the driver can open. This should only include types the driver will handle as root nodes, not other objects created by the driver. Thus, the sybase driver would include `"application/sybase"`, but not `"system/table"`. +For example: ```c - objRegisterDriver(drv); +if (xaInit(&(drv->RootContentTypes), 2) != 0) goto error_handling; +if (xaAddItem(&(drv->RootContentTypes), "application/sybase") < 0) goto error_handling; +if (xaAddItem(&(drv->RootContentTypes), ""system/query"") < 0) goto error_handling; ``` -The initialization function should return 0 to indicate success, or -1 on failure. Currently, initialization success/failure is not verified by lsmain.c. +- 📖 **Note**: To make a specific file extension (like `.qy`) open in a driver, edit `types.cfg` to map that file extension to an available root content type supported by the driver (such as `"system/query"`). -The driver should NOT nmFree() the allocated driver structure unless the objRegisterDriver() routine fails (returns -1). +#### Capabilities +The capabilities field is a bitmask which can contain zero or more of the following flags: -Note that the RootContentTypes handled by the driver should only include the types of the objects this driver will handle as node objects. For instance, the Sybase database access driver uses "application/sybase" as its top level type. It won't register such things as "system/table". +- `OBJDRV_C_FULLQUERY`: Indicates that this objectsystem driver will intelligently process the query's expression tree specified in the OpenQuery call, and will only return objects that match that expression. If this flag is missing, the OSML will filter objects returned by QueryFetch so that the calling user does not get objects that do not match the query. Typically this is set by database server drivers. + - > **THE ABOVE IS OUT-OF-DATE** (May 16th, 2022): A driver can now determine whether to handle the Where and OrderBy on a per-query basis, by setting values in the ObjQuery structure used when opening a new query. This allows a because a driver to handle Where and OrderBy for some object listings but not others. -### B. Opening And Closing Objects -As an overview, the normal procedure for the open routine to follow is this: +- `OBJDRV_C_TRANS`: Indicates that this objectsystem driver requires transaction management by the OSML's transaction layer (the OXT layer). OS drivers that require this normally are those that for some reason cannot complete operations in independence from one another. For example, with a database driver, the creation of a new row object and the setting of its attributes must be done as one operation, although the operation requires several calls from the end user's process. The OXT allows for the grouping of objectsystem calls so that the os driver does not have to complete them independently, but instead can wait until several calls have been made before actually completing the operation. -1. Access the node object, or create it, depending on whether the object already exists as well as the open mode flags indicated by the end-user. -2. Upon successful node object access, determine what additional components of the pathname are to be handled by this driver, and verify that they can be opened, depending on the object's open mode (CREAT, EXCL, etc.) -3. If it hasn't been already, allocate a structure that will represent this open object and contain information about it and how we're to handle it. It should include a pointer to the node object. -4. Perform any operations inherent in the open process that have not already been performed (such as reading database table information, etc., when a db table's row is being accessed). -5. Return a pointer to the structure allocated in (3) as a void pointer. The OSML will pass this pointer back to the driver on subsequent calls that involve this object. +#### Registering the Driver Struct +When all values within the structure have been initialized, the driver should call the OSML to register itself, using the `objRegisterDriver()` function: -The first basic part of the OS driver consists of the Open and Close routines, normally named 'xxxOpen' and 'xxxClose' within the driver, where 'xxx' is the driver's prefix. The Close routine is normally fairly simple, but the Open routine is one of the most complicated routines in a typical OS driver, for the Open routine must parse the subtree pathname beneath the node object. For example, if the node object had a pathname like: - -```sh - /datasources/OMSS_DB +```c +if (objRegisterDriver(drv) != 0) goto error_handling; ``` -and the user opened an object called: -```sh - /datasources/OMSS_DB/JNetHelp/rows/1 +--- +### Function: Open() +```c +void* xxxOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); ``` -the OS driver would have to determine what the subtree pathname 'JNetHelp/rows/1' means, since this path will mean different things to different os drivers. - -The Open routine also must determine whether the object already exists or not, and if not, whether to create a new object. This logic is largely dependent on the `obj->Mode` flags, as if `O_CREAT` is included, the driver must attempt to create the object if it does not already exist, and if `O_EXCL` is included, the driver must refuse to open the object if it already exists, as with the UNIX `open()` system call semantics. +The `Open()` function opens a given file to create a new driver instance. This procedure normally includes the following steps: -Finally, if the os driver specified a capability of `OBJDRV_C_TRANS`, it must pay attention to the current state of the end-user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). The transaction layer will be discussed in depth later in this document. +1. Access or create the node object, depending on specified flags and whether or not it already exists. +2. Parse additional contents of the path after the root node. +3. Allocate a structure that will represent the open object, including a pointer to the node object. +4. Perform other opening operations (such as reading database table information, etc., when a db table's row is being accessed). +5. Return a pointer to the node instance as a void pointer. This pointer will be passed as `void* inf_v` to the driver in subsequent calls involving this object (except the Query functions, discussed below). -As a part of the Open process, the OS driver will normally allocate an internal structure to represent the current open object, and will return that structure as a `void*` data type in the return value. This pointer will be then passed to each of the other driver entry point functions, with the exception of QueryFetch, QueryDelete, and Query- Close, which will be discussed later. +- 📖 **Note - Transactions**: If the os driver specified the `OBJDRV_C_TRANS` capability, it must respect the current state of the user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). This is handled using the transaction tree parameter (`oxt : pObjTrxTree*`). The transaction later is discussed in depth in the ??? section. + -The Open() routine is called with five parameters: +#### Accessing the Node Object +If `O_CREAT` and `O_EXCL` are both specified in `parent->Mode`, the driver should **only** create a new file and fail if the file already exists (refusing to open and read it). Otherwise, the driver should read an existing file, or create one if it does not exist and `O_CREAT` is specified, failing if no file can be read or created. -- `obj` (pObject) - This is a pointer to the Object sturcture maintained by the OSML. This structure will contain some important fields for processing the open() request. +#### Parsing Path Contents +The task of parsing the provided path into the subtree beneath its root node is one of the more complex operations for a driver. For example, the path to a driver's root node might be `/datasources/OMSS_DB` and the user opens an object called `/datasources/OMSS_DB/JNetHelp/rows/1`. In this case, the OS driver must parse the meaning of the subtree path `JNetHelp/rows/1`, storing the data targetted by the user into the driver instance to allow later method calls to access the correct data. - - `obj->Mode` is a bitmask of the O_* flags, which include `O_RDONLY`, `O_WRONLY`, `O_RDWR`, `O_CREAT`, `O_TRUNC`, and `O_EXCL`. +#### Parameters +The `Open()` routine is called with five parameters: - - `obj->Pathname` is a Pathname structure which contains the complete parsed pathname for the object. This structure is defined in the file `include/obj.h`, and has a buffer for the pathname as well as an array of pointers to the pathname's components. The function `obj_internal_PathPart()` can be used to obtain at will any component or series of components of the pathname. +- `obj : pObject`: A pointer to the Object structure maintained by the OSML. This structure includes some useful fields: + + - `obj->Mode : int`: A bitmask of the O_* flags, which include: `O_RDONLY` (read only), `O_WRONLY` (write only), `O_RDWR` (read/write), `O_CREAT` (create), `O_TRUNC` (truncate), and `O_EXCL` (exclusive, see above). + + - `obj->Pathname : pPathname`: A pointer to a Pathname struct (defined in `include/obj.h`) which contains the complete parsed pathname for the object. This provides a buffer for the pathname as well as an array of pointers to the pathname's components. The function `obj_internal_PathPart()` can be used to obtain at will any component or series of components of the pathname. - - `obj->Pathname->OpenCtl[]` contains parameters to the open() operation. Frequently these params provide additional information on how to open the object. The use of these parameters is determined by the author of the objectsystem driver. The parameters are those passed in normal URL fasion (?param=value, etc.). Typically, the only OpenCtl of interest is going to be `obj->Pathname->OpenCtl[obj->SubPtr]` (see below for SubPtr meaning). + - `obj->Pathname->OpenCtl : pStruct[]`: Parameters for the open() operation, as defined by the driver author. These are specified in the path in a similar way to URLs (`example.qy?param1=value¶m2=other_value`). Drivers typically only use `obj->Pathname->OpenCtl[obj->SubPtr]` (see SubPtr below) to retrieve their own parameters, ignoring parameters passed to other drivers in the path. - - `obj->SubPtr` is the number of components in the path that are a part of the node object's path. For example, in the above path of '/datasources/OMSS_DB', the path would be internally represented as './datasources/ OMSS_DB', and the SubPtr would be 3. + - `obj->SubPtr : short`: The number of components in the path that are a part of the path to the root node object, including the `.` for the top level directory. For example, in the above path of `/data/file.csv`, the path would be internally represented as `./ data/ file.csv`, so SubPtr is 3. - - `obj->SubCnt` reflects the number of components of the path which are under the control of the current driver. This includes the node object, so SubCnt will always be at least 1. For example, when opening '/data/file.csv/rows/1', and the driver in question is the CSV driver, SubPtr would be 3 (includes an "invisible" first component), from '/data/file.csv', and SubCnt would be 3, from 'file.csv/rows/1'. The driver will need to SET THE SUBCNT value in its Open function. SubPtr is already set. + - `obj->SubCnt : short`: _The driver should set this value_ to show the number of components it controls. This includes the root node object, so `SubCnt` will always be at least 1. For example, when opening `/data/file.csv/rows/1`, the CSV driver will read the `SubPtr` of 3 (see above), representing `./ data/ file.csv`. It will then set a `SubCnt` of 3, representing that it will control `file.csv /rows /1`. (The driver only sets `SubCnt`, `SubPtr` is provided.) - - `obj->Prev` is the underlying object as opened by the next-lower-level driver. It is the duty of this driver to parse the content of that object and do something meaningful with it. + - `obj->Prev : pObject`: The underlying object as opened by the next-lower-level driver. The file can be accessed and parsed by calling functions and passing this pointer to them (such as the st_parse functions, see below). **DO NOT attempt to open the file directly with a call like `fopen()`,** as this would require hard coding the path to the root directory of the object system, which *will* break if the code runs on another machine. - - `obj->Prev->Flags` contains some critical information about the underlying object. If it contains the flag `OBJ_F_CREATED`, then the underlying object was just created by this open() operation. In that case, this driver is expected to create the node with snNewNode() (see later in this document) as long as obj->Mode contains O_CREAT. + - `obj->Prev->Flags : short`: Contains some useful flags about the underlying object, such as: + - `OBJ_F_CREATED`: The underlying object was just created by this open() operation. In that case, this driver is expected to create the node with `snNewNode()` (see later in this document) as long as `obj->Mode` contains `O_CREAT`. + -- `mask` (int) - Indicates the security mask to be given to the object if it is being created. Typically, this will only apply to files and directories. The values are the same as UNIX chmod() type values. +- `mask : int`: The permission mask to be given to the object, if it is being created. Typically, this will only apply to files and directories, so most drivers can ignore it. The values are the same as the UNIX [octal digit permissions](https://en.wikipedia.org/wiki/Chmod#:~:text=Octal%20digit%20permission) used for the `chmod()` command. -- `systype` (pContentType) - This param indicates the content type of the node object as determined by the OSML. The ContentType structure is defined in include/ obj.h, and includes among other things the name of the content type. For example, for the reporting driver, this type would be "system/report". +- `sys_type : pContentType`: Indicates the content type of the node object as determined by the OSML. The ContentType structure is defined in `include/obj.h`. `sys_type->Name` lists the name of the content type (e.g. `"system/query"` for the query driver). + -- `usrtype` (char*) - This param is the requested object type by the user and is normally used when creating a new object, though under some circumstances it may change the way the open operates on an existing object. For example, the reporting driver can change whether it generates HTML report text or plaintext reports based on usrtype being either "text/html" or "text/plain". +- `usr_type : char*`: The object type requested by the user. This is normally used when creating a new object, though some drivers also use it when opening an existing object. For example, the reporting driver generates HTML report text or plaintext reports if `usr_type` is `"text/html"` or `"text/plain"` (respectively). -- `oxt` (pObjTrxTree*) - This param is only used by object drivers that specified a capability of OBJDRV_C_TRANS. More on this field later. For non-transaction-aware drivers, this field can be safely ignored. +- `oxt : pObjTrxTree*`: The transaction tree, used when the driver specifies the `OBJDRV_C_TRANS` capability. More on this field later. Non-transaction-aware drivers can safely ignore this field. + + 📖 **Note**: Yes, this param *is* a pointer to a pointer. Essentially, a pointer passed by reference. - Yes, this param *is* a pointer to a pointer. Essentially, a pointer passed by reference. -The Open routine should return its internal structure pointer on success, or `NULL` on failure. It is normal to allocate one such structure per Open call, and for the structure to point, among other things, to shared data describing the node object. Accessing the node object is described later in this document. +The `Open()` routine should return a pointer to an internal driver structure on success, or `NULL` on failure. It is normal to allocate one such structure per `Open()` call, and for one of the structure fields to point to shared data describing the node object. Accessing the node object is described later in this document. -It is important to know what kinds of fields normally are placed in the allocated data structure returned by Open. These fields are all determined by the driver author, but here are a few typical ones that are helpful to have ("inf" is the pointer to the structure here): +While driver instance structures may vary, some fields are common in most drivers (`inf` is the pointer to the structure here): | Field | Type | Description | ---------- | --------- | ------------ -| inf->Obj | pObject | This is a copy of the 'obj' pointer passed to the Open routine. -| inf->Mask | int | The 'mask' argument passed to Open. -| inf->Node | pSnNode | A pointer to the node object, as returned from snNewNode() or snReadNode(), or if structure files aren't being used as the node content type, a pointer to whatever structure contains information about the node object. +| inf->Obj | pObject | A copy of the `obj` pointer passed to `Open()`. +| inf->Mask | int | The `mask` argument passed to `Open()`. +| inf->Node | pSnNode | A pointer to the node object. This can come from `snNewNode()` or `snReadNode()` (for structure files), or other node struct information. + + +--- +### Function: OpenChild() +*(Optional)* +```c +void* xxxOpenChild(void* inf_v, pObject obj, char* child_name, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); +``` +**No documentation provided.** + +--- +### Function: Close() +```c +int xxxClose(void* inf_v, pObjTrxTree* oxt); +``` +The close function closes a driver instance, freeing all allocated data and releasing all shared memory such as open connections, files, or other driver instances. The driver must ensure that all memory allocated by originally opening the object (or allocated by other functions that may be called on an open object) is properly deallocated. This includes the internal structure returned by `Open()`, or by `QueryFetch()`, which is passed in as `inf_v`. The driver may also need to decrement the Open Count (`node->OpenCnt--`) if it had to increment this value during `Open()`. Before doing so, it should also perform a `snWriteNode()` to write any modified node information to the node object. -The Close() routine is called with two parameters: +- 📖 **Note**: Remember that the passed driver instance may originally be from a call to `Open()` or a call to `QueryFetch()`. + +- 📖 **Note**: Even if close fails, the object should still be closed in whatever way is possible. The end-user should deal with the resulting situation by reviewing the `mssError()` messages left by the driver. + +- 📖 **Note**: Information may be left unfreed if it is stored in a cache for later use. + +The `Close()` routine is called with two parameters: | Param | Type | Description | ------ | ------------ | ------------ -| inf_v | void* | This param is the pointer that the Open routine returned. Normally the driver will cast the void* parameter to some other structure pointer to access the object's information. -| oxt | pObjTrxTree* | The transaction tree pointer. - -The Close routine should return 0 on success or -1 on failure. The os driver must make sure it properly deallocates the memory used by originally opening the object, such as the internal structure returned by open and passed in as inf_v. +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -Note the semantics of a Close failure - the object should still be closed in whatever way is still meaningful. The end-user must deal with the situation by reviewing the returned mssError messages. +The Close routine should return 0 on success or -1 on failure. -Before exiting, the Close routine should make sure it decrements the Open Count (node->OpenCnt--). Before doing so, it should also perform a snWriteNode() to write any modified node information back to the node object. -### C. Creating and Deleting Objects. -The Create and Delete functions are used for creating and deleting objects. Normally, the os driver will process the Pathname in the same manner for Create and Delete as for Open, thus such functionality could be placed in another function. +### Function: Create() +```c +int xxxCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); +``` +The `Create()` function is used to create a new object, and uses the same parameters and return value as `Open()` (documented in detail above). This often means adding a new file to the file system to represent the object. Many drivers do not implement this and recommend that driver end-users create files using a standard text editor or programatically using more general means, such as general structure file generation. If implemented, this function frequently requires very similar path parsing functionality to `Open()`. -As a side note, within Centrallix, the standard function naming convention is to use `xxx_internal_FunctionName()` for functions that are more or less internal to the module and not a part of any standard interface. +- 📖 **Note**: For many drivers, the `Create()` function calls the driver's `Open()` function with `O_CREAT`, then calls its `Close()` function, although some drivers may manage this differently. -The Create routine has parameters identical to the Open routine. It should return 0 on success and -1 on error. -The Delete routine is passed the following parameters: +### Function: Delete() +```c +int clusterDelete(pObject obj, pObjTrxTree* oxt); +``` +The `Delete()` function is used to delete an object, which often means removing a file from the file system. The Delete routine is passed the following parameters: | Param | Type | Description | ------ | ------------- | ------------ | obj | pObject | The Object structure pointer, used in the same way as in Open and Delete. -| oxt | pObjTrxTree* | The transaction tree pointer. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. Delete should return 0 on success and -1 on failure. -For many objectsystem drivers, the Create function simply calls the driver's internal Open() with O_CREAT and then its internal Close, although some drivers could manage Create differently from Open. -### D. Reading and Writing Object Content. -Some, but not all, objects will have content. If the object does or can have content, the driver should handle these functions as is appropriate. Otherwise, the driver should return a failure code (-1) from these functions. +### Function: DeleteObj() +```c +int xxxDeleteObj(void* inf_v, pObjTrxTree* oxt); +``` +**No documentation provided.** + + +### Function: Read() +```c +int xxxRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt); +``` + + +The `Read()` function reads content from objects that have content, similar to reading content from a file. If the object does or can have content, the driver should handle these functions as is appropriate. Otherwise, the driver should return a failure code (-1) and call `mssError()` in these functions. -The Read routine reads content from the object, as if reading from a file. The parameters passed are almost identical to those used in the fdRead command in MTASK: +The parameters passed are intentionally similar to the `fdRead()` function in `mtask.c`: | Parameter | Type | Description | --------- | ------------- | ------------ -| inf_v | void* | The generic pointer to the structure returned from Open(). -| buffer | char* | The destination buffer for the data being read in. -| maxcnt | int | The maximum number of bytes to read into the buffer. -| flags | int | Either 0 or FD_U_SEEK, in which case the user is specifying the seek offset for the read in the 5th argument. Of course, not all objects will be seekable, and furthermore, some of the objects handled by the driver may have full or limited seek functionality, even though others may not. -| arg | int | Extra argument, currently only used to specify an optional seek offset. -| oxt | pObjTrxTree* | The transaction tree pointer. +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| buffer | char* | The buffer where read data should be stored. +| max_cnt | int | The maximum number of bytes to read into the buffer. +| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 5th argument (`arg`). +| arg | int | Extra argument, currently only used to specify the optional seek offset. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -The Write routine is very similar, except that instead of 'maxcnt', the third argument is 'cnt', and specifies how much data is in the buffer waiting to be written. +- 📖 **Note**: Not all objects can be seekable and some of the objects handled by the driver may have limited seek functionality, even if others do not. Each of these routines should return -1 on failure and return the number of bytes read/written on success. At end of file or on device hangup, 0 should be returned once, and then subsequent calls should return -1. -### E. Querying for Child Objects. -Many objects will have the capability of having sub-objects beneath them, called child objects. In such a case, the parent object becomes a directory of sorts, even though the parent object may also have content, something which is somewhat foreign in the standard filesystem world, but is common for web servers, where opening a directory returns the file 'index.html' on many occasions. -To enumerate a parent object's child objects, the query functions are used. A query may have a specific criteria so that only objects having certain attributes will be listed. As mentioned earlier in this document, a driver may or may not choose to intelligently handle those criteria. The driver has the option of always enumerating all child objects via its query functions, and allowing the OSML filter them and only return to the user the objects that match the criteria. But it also can do the filtering itself or, more typically, pass the filtering on to the source of the data the driver manages, as with a database server. +### Function: Write() +```c +int xxxWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt); +``` + +The `Write()` function is very similar to the `Read()` function above, allowing the caller to write data to objects of supporting drivers with content. However, the third argument (`max_cnt`) is replaced with `cnt`, specifying the number of bytes of data in the buffer that should be written. -The query mechanism can also be used to delete a set of child objects, optionally matching a certain criteria. The QueryDelete method may be left NULL in the ObjDriver structure if the driver does not implement full query support, in which case the OSML will iterate through the query results and delete the objects one by one. -The first main function for handling queries is OpenQuery. This function is passed three arguments: +### Function: OpenQuery() +```c +void* xxxOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); +``` +The `OpenQuery()` function opens a new query instance struct for fetching query results from a specific driver instance. Queries are often used to enumerate an object's child objects, although this is not a requirement. Queries may include specific criteria, and the driver may decide to intelligently handle them (either manually or, more often, by passing them on to a lower level driver or database) or simply to enumerating all results with its query functions. In the latter case, the OSML layer will filter results and only return objects that match the criteria to the user. -- `inf_v` (void*) The value returned from Open for this object. +`OpenQuery()` is passed three parameters: +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| query | pObjQuery | A query structure created by the object system. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -- `query` (pObjQuery) The query structure setup by the OSML. It will contain several key fields: +The `query : pObjQuery` parameter contains several useful fields: +| Parameter | Type | Description +| --------------- | ----------------------- | ------------ +| query->QyText | char* | The text specifying the criteria (i.e., the WHERE clause, in Centrallix SQL syntax). +| query->Tree | void* (pExpression) | The compiled expression tree. This expression evaluates to a nonzero value for `true` if the where clause is satisfied, or zero for `false` if it is not. +| query->SortBy[] | void*[] (pExpression[]) | An array of expressions giving the various components of the sorting criteria. +| query->Flags | int | The driver should set and/or clear the `OBJ_QY_F_FULLQUERY` and `OBJ_QY_F_FULLSORT` flags, if needed. - - `query->QyText`: the text of the criteria (i.e., the WHERE clause, in Centrallix SQL syntax) +The `OBJ_QY_F_FULLQUERY` flag indicates that the driver will handle the full WHERE clause specified in `query->Tree`. - - `query->Tree`: the compiled expression tree, which evaluates to nonzero for true or zero for false as the WHERE clause condition. +The `OBJ_QY_F_FULLSORT` flag indicates that the driver will handle all sorting for the data specified in `query->SortBy[]`. - - `query->SortBy[]`: an array of expressions giving the various components of the sorting criteria. +If the driver can easily handle sorting/selection (as when querying an database), it should set these flags. Otherwise, it should let the OSML handle the ORDER BY and WHERE conditions to avoid unnecessary work for the driver author. - - `query->Flags`: the driver should set and/or clear the flags `OBJ_QY_F_FULLQUERY` and `OBJ_QY_F_FULLSORT` if need be. The former indicates that the driver is willing to handle the full WHERE clause (the query->Tree). The latter indicates that the driver is willing to handle the sorting of the data as well (in query->SortBy[]). If the driver can easily have the sorting/selection done (as when querying an RDBMS), it should set these flags. Otherwise, it should let the OSML take care of the ORDER BY and WHERE conditions. +The `OpenQuery()` function returns a `void*` for the query instance struct, which will be passed to the other query functions (`QueryDelete()`, `QueryFetch()`, and `QueryClose()`). This structure normally points to the driver instance struct to allow easy access to queried data. `OpenQuery()` returns `NULL` if the object does not support queries or if an error occurs, in which case `mssError()` should be called before returning. -- `oxt` (pObjTrxTree*) The transaction tree pointer. -The OpenQuery function should return a void* value, which will within the driver point to a structure used for managing the query. This structure will normally have a pointer to the inf_v value returned by Open as well, since inf_v is never passed to QueryFetch, QueryDelete or QueryClose. OpenQuery should return NULL if the object does not support queries or if some other error condition occurs that will prevent the execution of the query. +### Function: QueryDelete() +*(Optional)* +```c +int xxxQueryDelete(void* qy_v, pObjTrxTree* oxt); +``` + +Deletes results in the query result set, optionally matching a certain criteria. `QueryDelete()` is passed two parameters: -Once the query is underway with OpenQuery, the user will either start fetching the results with QueryFetch, or will issue a delete operation with QueryDelete. +| Parameter | Type | Description +| --------- | ------------- | ------------ +| qy_v | void* | A query instance pointer (returned from `QueryOpen()`). +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -The QueryFetch routine should return an inf_v pointer to the child object, or NULL if no more child objects are to be returned by the query. Some drivers may be able to use their internal Open function to generate the newly opened object, although others will directly allocate the inf_v structure and fill it in based on the current queried child object. QueryFetch will be passed these parameters: +`QueryDelete()` returns 0 to indicate a successful deletion, or -1 to indicate failure, in which case `mssError()` should be called before returning. -| Parameter | Type | Description -| ---------- | -------------- | ------------ -| qy_v | void* | The value returned by OpenQuery. -| obj | pObject | The newly-created object structure that the OSML is using to track the newly queried child object. -| mode | int | The open mode for the new object, as with obj->Mode in Open(). -| oxt | pObjTrxTree* | The transaction tree pointer. +If a delete is needed and this method is not implemented, the OSML will iterate through the query results and delete the objects one by one. -All object drivers will need to add an element to the obj->Pathname structure to indicate the path to the child object being returned. This will involve a process somewhat like this: (given that new_name is the new object's name, qy is the current query structure, which contains a field 'Parent' that points to the inf_v originally returned by Open, and where the inf_v contains a field Obj that points to the Object structure containing a Pathname structure) +### Function: QueryFetch() ```c - int cnt; +void* xxxQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt); +``` +The `QueryFetch()` function fetches a driver instance pointer (aka. an `inf_v` pointer) to a child object, or `NULL` if there are no more child objects. It may be helpful to think of `QueryFetch()` as similar to an alternate form of `Open()`, even if your driver does not implement the functionality to `Open()` every object that can be found with `QueryFetch()`. In fact, some drivers may use an internal `Open()` function to generate the opened objects. + +`QueryFetch()` takes four parameters: + +| Parameter | Type | Description +| ---------- | ------------- | ------------ +| qy_v | void* | A query instance struct (returned by `OpenQuery()`). +| obj | pObject | An object structure that the OSML uses to track the newly queried child object. +| mode | int | The open mode for the new object, the same as `obj->Mode` in `Open()`. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + + +The driver should add an element to the `obj->Pathname` structure to indicate the path of the returned child object. This will involve a process somewhat like this, where: +- `new_name : char*` is the new object's name. +- `qy : pMyDriversQueryInf` is the current query structure. +- `qy->Parent->Obj->Pathname : pPathname` points to the affected Pathname struct. + +```c + int count; pObject obj; char* new_name; pMyDriversQueryInf qy; - /** Build the filename. **/ - cnt = snprintf(obj->Pathname->Pathbuf, 256, "%s/%s", - qy->Parent->Obj->Pathname->Pathbuf,new_name); - if (cnt < 0 || cnt >= 256) return NULL; - obj->Pathname->Elements[obj->Pathname->nElements++] = - strrchr(obj->Pathname->Pathbuf,'/')+1; + /** Build the new filename. **/ + count = snprintf(obj->Pathname->Pathbuf, 256, "%s/%s", qy->Parent->Obj->Pathname->Pathbuf, new_name); + if (count < 0 || 256 <= count) return NULL; + obj->Pathname->Elements[obj->Pathname->nElements++] = strrchr(obj->Pathname->Pathbuf, '/') + 1; +``` + +### Function: QueryCreate() +```c +void* xxxQueryCreate(void* qy_v, pObject new_obj, char* name, int mode, int permission_mask, pObjTrxTree *oxt); ``` + +**No documentation provided.** + -QueryDelete is passed the qy_v void* parameter, and an oxt parameter. It should return 0 on successful deletion, and -1 on failure. +### Function: QueryClose() +```c +int xxxQueryClose(void* qy_v, pObjTrxTree* oxt); +``` +The close function closes a query instance, freeing all allocated data and releasing all shared memory such as open connections, files, or other driver instances. This function operates very similarly to `Close()`, documented in detail above. The query should be closed, whether or not `QueryFetch()` has been called enough times to enumerate all of the query results. -QueryClose is also passed qy_v and oxt. It should close the query, whether or not QueryFetch has been called enough times to enumerate all of the query results. -### F. Managing Object Attributes -All objects will have at least some attributes. Five attributes are mandatory: 'name', 'content_type', 'inner_type', 'outer_type', and 'annotation'. All compliant drivers must implement these five attributes, all of which have a data type of DATA_T_STRING. +### Object Attributes +All objects can have attributes, and there are five required attributes that all drivers must implement (explained below). Currently, the OS specification includes support for the following data types: -- DATA_T_INTEGER - 32-bit signed integer. -- DATA_T_STRING - Zero-terminated ASCII string. -- DATA_T_DOUBLE - Double-precision floating point. -- DATA_T_DATETIME - date/time structure. -- DATA_T_MONEY - money data type. +| Name | Description +| ----------------- | ------------ +| `DATA_T_INTEGER` | 32-bit signed integer. +| `DATA_T_STRING` | Null-terminated ASCII string. +| `DATA_T_DOUBLE` | Double-precision floating point number. +| `DATA_T_DATETIME` | Date/time structure. +| `DATA_T_MONEY` | Money structure. + +See `datatypes.h` for more information. + +For `true`/`false` or `on`/`off` attributes, use `DATA_T_INTEGER` where 0 indicates `false` and 1 indicates `true`. + +The following five attributes are required (all are of type `DATA_T_STRING`): + +| Attribute | Description +| ------------ | ------------ +| name | The name of the object, just as it appears in any directory listing. The name of the object must always be unique for its directory. +| annotation | A short description of the object. While users may not assign annotations to all objects, each object should be able to have an annotation. For example, in the Sybase driver, annotations for rows are created by assigning an 'expression' to the table in question, such as `first_name + last_name` for a people table. +| content_type | The type of the object's content, given as a MIME-type. Specify `"system/void"` if the object does not have content. +| inner_type | An alias for 'content_type'. Both should be supported. +| outer_type | This is the type of the object itself (the container). Specify `"system/row"` for objects that can be queried. + +The `last_modification : DATA_T_DATETIME` attribute is a sixth, optional attribute that may be useful in some situations. This attribute should indicate the last time that the object's content was modified or updated. + + + + +### Function: GetAttrType() +```c +int xxxGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); +``` +The `GetAttrType()` function returns DATA_T_xxx value for the datatype of the requested. It takes three parameters: + +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| attr_name | char* | The name of the attribute to be queried. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +This function should return `DATA_T_UNAVAILABLE` if the requested attribute does not exist on the driver instance. It should return -1 to indicate an error, in which case `mssError()` should be called before returning. + +For example, calling the following on any driver should return `DATA_T_STRING`. +```c +int datatype = driver->GetAttrType(inf_v, 'name', oxt); +``` + + +### Function: GetAttrValue() +```c +int xxxGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +``` +The `GetAttrValue()` function takes four parameters: + +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| attr_name | char* | The name of the attribute to be queried. +| val | pObjData | A pointer to a location where the value of the attribute should be stored. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -True/false or on/off attributes should be treated as DATA_T_INTEGER for the time being with values of 0 and 1. +The value pointer should be handled in different ways, depending on the type: +- For `DATA_T_INTEGER` types, it is assumed to point to a 32-bit integer where the value should be written. +- For `DATA_T_STRING` types, it is assumed to point to an empty `char*` location where a pointer to a string should be written. +- For `DATA_T_DOUBLE` types, it is assumed to point to a double value where the double should be written. +- For `DATA_T_DATETIME` types, it is assumed to point to an empty `pDateTime` where a pointer to a date time struct (see `obj.h`) should be written. -Here is a description of the functionality of the five mandatory attributes: +In this way, integer and double values are returned by value, and string or datetime values are returned by reference. Items returned by reference are guaranteed to be valid until either the object is closed, or another call to `GetAttrValue()` or `SetAttrValue()` call is made on the same driver (which ever happens first). -| Attribute | Description -| -------------- | ------------ -| 'name' | This attribute indicates the name of the object, just as it should appear in any directory listing. The name of the object must be unique for the directory it is in. -| 'content_type' | This is the type of the object's content, given as a MIME-type. -| 'annotation' | This is an annotation for the object. While users may not assign annotations to all objects, each object should be able to have an annotation. Normally the annotation is a short description of what the object is. For the Sybase driver, annotations for rows are created by assigning an 'expression' to the table in question, such as 'first_name + last_name' for a people table. -| 'inner_type' | An alias for 'content_type'. Both should be supported. -| 'outer_type' | This is the type of the object itself (the container). +This function should return -1 on a non-existent attribute, 0 on success, and 1 if the value is `NULL` or undefined / unset. -A sixth attribute is not mandatory, but is useful if the object might have content that could in turn be a node object (be interpreted by another driver). This attribute is 'last_modification', of type DATA_T_DATETIME, and should indicate when the object's content was last updated or modified. +- 📖 **Note**: The caller of this function can use the POD(x) macro to typecast appropriate pointers to the pObjData pointer, passed to this function. The ObjData structure is a UNION type of structure, allowing easy manipulation of data of various types. See `datatypes.h` for more information. -The first function to be aware of is the GetAttrType function. This routine takes the inf_v pointer, the name of the attribute in question, and the oxt* pointer. It should return the DATA_T_xxx value for the data type of the attribute. +- 📖 **Note**: In legacy code, a typecasted void* was used instead of a pObjData pointer used today. This method was binary compatible the current solution because the pObjData is a pointer to a struct union. See `datatypes.h` for more information. -Next is the GetAttrValue function, which takes four parameters: the inf_v pointer, the name of the attribute, a void pointer pointing to where the attribute's value will be put, and the oxt* pointer. The way the value pointer is handled depends on the data type. For DATA_T_INTEGER types, the value pointer is assumed to be pointing to a 32-bit integer where the integer value can be written. For DATA_T_ STRING types, the value pointer is assumed to be pointing to an empty pointer location where a pointer to the string can be stored. For DATA_T_DATETIME types, the value pointer is assumed to be pointing to an empty pointer where a pointer to a date time structure (from obj.h) can be stored. And for double values, the value pointer points to a double value where the double will be stored. In this way, integer and double values are returned from GetAttrValue by value, and string or datetime values are returned from GetAttrValue by reference. Items returned by reference must be guaranteed to be valid until the object is closed, or another GetAttrValue or SetAttrValue call is made. This function should return -1 on a non-existent attribute, 0 on success, and 1 if the value is NULL or unset. -UPDATE ON GETATTR/SETATTR: These functions now, instead of taking a void* pointer for the value, take a pObjData pointer, which points to an ObjData structure. The POD(x) macro can be used to typecast appropriate pointers to a pObjData pointer. The ObjData structure is a UNION type of structure, allowing easy manipulation of data of various types. See 'datatypes.h'. Note that this is binary compatible with the old way of using a typecasted void pointer. +### Function: SetAttrValue() +```c +int xxxSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +``` +The `SetAttrValue()` function is the same as `GetAttrValue()`, however it sets the value by reading it from the `val` parameter instead of getting the value by writing it to the `val` parameter. The return value is also identical, and `mssError()` should be invoked on failure, or if setting attributes programatically is not implemented. -The SetAttrValue function works much the same way as GetAttrValue, just with the information moving in the opposite direction. The third parameter, void* value, is treated in the same manner. -The GetFirstAttr and GetNextAttr functions each take two parameters, the inf_v pointer and the oxt* pointer, and are used to iterate through the non-mandatory attributes for the object. GetFirstAttr should return a string naming the first attribute, and GetNextAttr should iterate through subsequent attributes. When the attributes are exhausted, these functions should return NULL. The attributes 'name', 'annotation', and 'content_type' should not be returned. If the object has no other attributes, GetFirstAttr should return NULL. +### Function: GetFirstAttr() & GetNextAttr() +```c +char* xxxGetFirstAttr(void* inf_v, pObjTrxTree* oxt); +char* xxxGetNextAttr(void* inf_v, pObjTrxTree* oxt); +``` +These functions return the names of attributes that can be queried on an object. They both take the same two parameters. -AddAttr is used to add a new attribute to an existing object. Not all objects support this, and many will refuse the operation. The parameters are as follows: void* inf_v, char* attrname, int type, void* value, and pObjTrxTree* oxt. +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +These functions should only return the names of significant values, so `name`, `annotation`, etc. should not be returned from these functions, even though they are required to be valid values for any object. Typically, this is implemented by `GetFirstAttr()` resetting some internal value in the driver `inf_v`, then returning the result of `GetNextAttr()`. `GetNextAttr()` extracts a string from an array or other list of valid attribute names for the object and increments the internal counter. Once the attributes are exhausted, `GetNextAttr()` returns `NULL` and `GetFirstAttr()` can be used to restart and begin querying elements from the start of the list again. If an object has no significant attributes, `GetFirstAttr()` and `GetNextAttr()` both return NULL. -OpenAttr is used to open an attribute for objRead/objWrite as if it were an object with content. Not all object drivers will support this; this routine should return an inf_v pointer for the new descriptor, and takes four parameters: void* inf_v, char* attrname, int mode, and pObjTrxTree* oxt. The mode is used in the same manner as the Open function. -### G. Managing Object Methods -Objects may optionally have methods associated with them. Each method is given a unique name within the object, and can take a single string parameter. Three functions exist for managing methods. +### Function: AddAttr() +```c +int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt); +``` +The `AddAttr()` function adds a new attribute to an existing object. Not all objects support this, and many will refuse the operation. The parameters are the same as those of `GetAttrValue()` and `SetAttrValue()`, documented in detail above. + + +### Function: OpenAttr() +```c +void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt); +``` +The `OpenAttr()` function is used to open an attribute for `objRead()`/`objWrite()` as if it were an object with content. Not all object drivers will support this, and many will refuse the operation. + +This function takes 4 parameters. `inf_v`, `attr_name`, and `oxt` are the same as they are for `GetAttrValue()` and `SetAttrValue()`. `mode` is the same as it is for `Open()`. This function should return an `inf_v` pointer for the new descriptor (similar to `Open()` and `QueryFetch()` above). + + +### Function: ExecuteMethod() +```c +int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); +``` +The `ExecuteMethod()` function is used to execute a method on an object. This feature is rarely used, but some drivers have created methods for actions like dropping their cache or printing debug information. Each method has a unique name within that object, and can take a single string parameter. + +The `ExecuteMethod()` function takes four parameters: + +| Parameter | Type | Description +| ----------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| method_name | char* | The name of the method to be executed. +| param | pObjData | A pointer to a location where the string value of the param is stored. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +- 📖 **Note**: The `pObjData` type of the `param` parameter makes it possible that other types of parameters could be supported in the future, however, this is not currently implemented. + +The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. + + +### Function: GetFirstMethod() & GetNextMethod() +```c +char* xxxGetFirstMethod(void* inf_v, pObjTrxTree* oxt); +char* xxxGetNextMethod(void* inf_v, pObjTrxTree* oxt); +``` +These functions work the same as `GetFirstAttr()` and `GetNextAttr()` (respectively), except that they return the method names instead of the attribute names. + + +### Function: PresentationHints() +```c +pObjPresentationHints xxxPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt); +``` +The `PresentationHints()` function allows the caller to request extra information about a specific attribute on a specific driver instance object. Most of this information is intended to be used for displaying the attribute in a user interface, although it can also be useful for general data validation. As such, many drivers may not implement this function. + +The `PresentationHints()` function takes three parameters: + +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| attr_name | char* | The name of the requested attribute. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +The returns a new pObjPresentationHints struct on success, or NULL to indicate an error, in which case `mssError()` should be called before returning. This struct should be allocated using `nmMalloc()`, and memset to zero, like this: +```c +pObjPresentationHints hints = nmMalloc(sizeof(ObjPresentationHints)); +if (hints == NULL) goto error_handling; +memset(hints, 0, sizeof(ObjPresentationHints)); +``` + +The return value, `hints : ObjPresentationHints`, contains the following useful fields which the function should set to give various useful information about the attribute. +- `hints->Constraint : void*`: An expression for determining if a value is valid. +- `hints->DefaultExpr : void*`: An expression defining the default value. +- `hints->MinValue : void*`: An expression defining the minimum valid value. +- `hints->MaxValue : void*`: An expression defining the maximum valid value. +- `hints->EnumList : XArray`: If the attribute is a string enum, this XArray lists the valid string values. +- `hints->EnumQuery : char*`: A query string which enumerates the valid values a string enum attribute. +- `hints->Format : char*`: presentation format - datetime or money +- `hints->AllowChars : char*`: An array of all valid characters for a string attribute, NULL to allow all characters. +- `hints->BadChars : char*`: An array of all invalid characters for a string attribute. +- `hints->Length : int`: The maximum length of data that can be included in a string attribute. +- `hints->VisualLength : int`: The length that the attribute should be displayed if it is show to the user. +- `hints->VisualLength2 : int`: The number of lines to use in a multi-line edit box for the attribute. +- `hints->BitmaskRO : unsigned int`: which bits, if any, in bitmask are read-only +- `hints->Style : int`: Style flags, documented below. +- `hints->StyleMask : int`: A mask for which style flags were set and which were left unset / undefined. +- `hints->GroupID : int`: Used to assign attributes to groups. Use -1 if the attribute is not in a group. +- `hints->GroupName : char*`: The name of the group to which this attribute belongs, or NULL if it is ungrouped or if the group is named elsewhere. +- `hints->OrderID : int`: Used to specify an attribute order. +- `hints->FriendlyName : char*`: Used to specify a "display name" for an attribute (e.g. `n_rows` might have a friendly name of `"Number of Rows"`). Should be `nmSysMalloc()`ed, often using `nmSysStrdup()`. + +- ⚠️ **Warning**: Behavior is undefined if: + - If a character is included in both `hints->AllowChars` and `hints->BadChars`. + - The data is longer than length. + +The `hints->Style` field can be set with several useful flags. To specify that a flag is not set (e.g. to specify explicitly that a field does allow `NULL`s), set the coresponding bit in the `hints->StyleMask` field while leaving the the bit in the `hints->Style` field set to 0. + +The following macros are provided for setting style flags: +- `OBJ_PH_STYLE_BITMASK`: The items in `hints->EnumList` or `hints->EnumQuery` are bitmasked. +- `OBJ_PH_STYLE_LIST`: List-style presentation should be used for the values of an enum attribute. +- `OBJ_PH_STYLE_BUTTONS`: Radio buttons or check boxes should be used for the presentation of enum attribute values. +- `OBJ_PH_STYLE_NOTNULL`: The attribute does not allow `NULL` values. +- `OBJ_PH_STYLE_STRNULL`: An empty string (`""`) should be treated as a `NULL` value. +- `OBJ_PH_STYLE_GROUPED`: The GroupID should be checked and so that fields can be grouped together. +- `OBJ_PH_STYLE_READONLY`: The user is not allowed to modify this attribute. +- `OBJ_PH_STYLE_HIDDEN`: This attribute should be hidden and not presented to the user. +- `OBJ_PH_STYLE_PASSWORD`: Values in this attribute should be hidden, such as for passwords. +- `OBJ_PH_STYLE_MULTILINE`: String values should allow multiline editting. +- `OBJ_PH_STYLE_HIGHLIGHT`: This attribute should be highlighted when presented to the user. +- `OBJ_PH_STYLE_LOWERCASE`: This attribute only allows lowercase characters. +- `OBJ_PH_STYLE_UPPERCASE`: This attribute only allows uppercase characters. +- `OBJ_PH_STYLE_TABPAGE`: Prefer the tab-page layout for grouped fields. +- `OBJ_PH_STYLE_SEPWINDOW`: Prefer separate windows for grouped fields. +- `OBJ_PH_STYLE_ALWAYSDEF`: Always reset the default value when this attribute is modified. +- `OBJ_PH_STYLE_CREATEONLY`: This attribute is writeable only when created, after that it is read only. +- `OBJ_PH_STYLE_MULTISEL`: Multiple select +- `OBJ_PH_STYLE_KEY`: This attribute is a primary key. +- `OBJ_PH_STYLE_APPLYCHG`: Presentation hints should be applied on DataChange instead of on DataModify. + + +### Function: Info() +```c +int xxxInfo(void* inf_v, pObjectInfo info); +``` +The `Info()` function allows the caller to request extra information about a specific driver instance object. It takes two parameters: + +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| info | pObjectInfo | A driver info struct allocated by the caller which the driver sets with information. + +The `pObjectInfo` struct has two fields: `Flags` and `nSubobjects`. This function should set `info->Flags` to 0 (to ensure no uninitialized noise gets into the data), then & it with all of the following flags that apply to that object. +- `OBJ_INFO_F_CAN_HAVE_SUBOBJ` / `OBJ_INFO_F_CANT_HAVE_SUBOBJ`: Indicates that the object can or cannot have subobjects. +- `OBJ_INFO_F_HAS_SUBOBJ` / `OBJ_INFO_F_NO_SUBOBJ`: Indicates that the object has or does not have subobjects. +- `OBJ_INFO_F_SUBOBJ_CNT_KNOWN`: Indicates that we know the number of subobjects. If set, the count should be stored in `info->nSubobjects`. +- `OBJ_INFO_F_CAN_HAVE_CONTENT` / `OBJ_INFO_F_CANT_HAVE_CONTENT`: Indicates that the object can or cannot have content (see `Read()` / `Write()`). +- `OBJ_INFO_F_HAS_CONTENT` / `OBJ_INFO_F_NO_CONTENT`: Indicates that this object does or does not have content (see `Read()` / `Write()`). +- `OBJ_INFO_F_CAN_SEEK_FULL`: Seeking is fully supported (both forwards and backwards) on the object. +- `OBJ_INFO_F_CAN_SEEK_REWIND`: Seeking is only supported with an offset of `0`. +- `OBJ_INFO_F_CANT_SEEK`: Seeking is not supported at all. +- `OBJ_INFO_F_CAN_ADD_ATTR` / `OBJ_INFO_F_CANT_ADD_ATTR`: Indicates that the object does or does not allow attributes to be added with the [AddAttr()](#function-addattr) function. +- `OBJ_INFO_F_SUPPORTS_INHERITANCE`: Indicates that the object supports inheritance through attributes such as `cx__inherit`. See ??? for more information about object inheritance. + +- `OBJ_INFO_F_FORCED_LEAF`: Indicates that the object is forced to be a 'leaf' unless ls__type used. +- `OBJ_INFO_F_TEMPORARY`: Indicates that this is a temporary object without a vaoid pathname. + + +The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. + + +### Function: Commit() +```c +int xxxCommit(void* inf_v, pObjTrxTree *oxt); +``` +**No documentation provided.** + + +### Function: GetQueryCoverageMask() +```c +int xxxGetQueryCoverageMask(pObjQuery this); +``` +**No documentation provided.** + + +### Function: GetQueryIdentityPath() +```c +int xxxGetQueryIdentityPath(pObjQuery this, char* pathbuf, int maxlen); +``` +**No documentation provided.** + -The first two functions, GetFirstMethod and GetNextMethod, work identically to their counterparts dealing with attributes. The third function, ExecuteMethod, starts a method executing. This function takes four parameters: the inf_v pointer, the name of the method, the optional string parameter, and the oxt* pointer. ## III Reading the Node Object -The Node object has content which controls what resource(s) this driver will actually access, so it is important for the driver to access the node object's content. If the driver's node objects are structure files (which is normally the case when dealing with a remote network resource), then the SN module can make opening the node object much more painless. It also performs caching automatically to improve performance. +A driver will commonly configure itself by reading text content from its node object file, at the root of its object subtree. This content may define what resource(s) a driver should provide, how it should access or compute them, and other similar information. Most drivers use the structure file format for their node objects because SN module makes parsing, reading, and writing these files easier. It also performs caching automatically to improve performance. -Note that the Node object will technically ALREADY BE OPEN as an object in the objectsystem. The OSML does that for you. If your driver will not use the SN/ST modules, then it should read the node object via the normal objRead() function, and write it via objWrite(). Your driver should NEVER objClose() the node object! The OSML does that for you. +- 📖 **Note**: The node object will **already be open** as an object in the ObjectSystem: The OSML does this for each driver. If a driver does not use the SN/ST modules, then it should read and write the node object directly with `objRead()` and `objWrite()`. A driver should **NEVER** `objClose()` the node object! The OSML handles that. -An objectsystem driver will commonly configure itself by reading a text file at the root of its object subtree. There are two main modules available for making this easier. +Although using the structure file format may be complex, it allows significant flexibility. Data is structured in hierarchies where each sub-object can have named attributes as well as sub-objects. Centrallix is filled with examples of this, including any `.qy`, `.app`, `.cmp`, or `.cluster` file. -The normal way to manage object parameters is to use a structure file. Structure files are a little more complicated, but allow for arrays of values for a given attribute name, as well as allowing for tree- structured hierarchies of attributes and values. Structure files are accessed via the stparse and st_node modules. The stparse module provides access to the individual attributes and groups of attributes, and the st_node module loads and saves the structure file heirarchies as a whole. The st_node module also provides node caching to reduce disk activity and eliminate repeated parsing of one file. +Structure files are accessed via the st_node (SN) and stparse (SP) modules. The st_node module loads and saves the structure file heirarchies as a whole. It also manages caching to reduce disk activity and eliminate repeated parsing of the same file. The stparse module provides access to the individual attributes and groups of attributes within a node structure file. -For example, if two sessions open two files, '/test1.rpt' and '/test2.rpt' the st_node (SN) module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be re-parsed. The file will be re-parsed if its timestamp changes. +For example, if two sessions open two files, `/test1.rpt` and `/test2.rpt` the st_node module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be re-parsed. The file will be re-parsed if its timestamp changes. + -If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then SN prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the SN module to re-read the structure file defining the node object. Otherwise, the SN module keeps track of the timestamp, and if it changes, the node object is re-read and re-parsed. +If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then st_node prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the st_node module to re-read the structure file defining the node object. Otherwise, the st_node module keeps track of the timestamp, and if it changes, the node object is re-read and re-parsed. -The driver's first course of action to obtain node object data is to open the node object with the SN module. The SN module's functions are listed below: +### Module: st_node +To obtain node object data, the driver should first open the node object with the st_node module. To use this module, include the file `st_node.h`, which provides the following functions (read `st_node.c` for more functions and additional information): -### pSnNode snReadNode(pObject obj) -This function reads a Structure File from the already-open node object which is passed in the "obj" parameter in the xxxOpen() routine. The "obj" parameter has an element, obj->Prev, which is a link to the node object as opened by the previous driver in the OSML's chain of drivers for handling this open(). All you need to know to get the parsed node object is the following: +### st_node: snReadNode() ```c - pSnNode node; +pSnNode snReadNode(pObject obj); +``` +The `snReadNode()` function reads a Structure File from the `obj` parameter, which should be a previously openned object. In a driver's `Open()` function, this is `obj->Prev` (the node object as opened by the previous driver in the OSML's chain of drivers). - node = snReadNode(obj->Prev); +**Usage:** +```c +pSnNode node = snReadNode(obj->Prev); +if (node == NULL) goto error_handling; ``` -The returned node structure is managed by the SN module and need not be nmFree()ed. The only thing that must be done is that the driver should increment the node structure's link count like this: +The returned node structure is managed by the SN module and does not need to be `nmFree()`ed. Instead, the driver should increment the node structure's link count for as long as it intends to use this structure, using `node->OpenCnt++;`. When the structure is no longer needed (e.g. when the driver instance is closed), the driver should decrement the link count. + + +### st_node: snNewNode() +```c +pSnNode snNewNode(pObject obj, char* content_type); +``` +The `snNewNode()` function creates a new node object of the given content type. The open link count should be incremented and decremented when appropriate, as with `snReadNode()`. +**Usage:** ```c - node->OpenCnt++; +pSnNode node = snNewNode(obj->Prev, "system/structure"); +if (node == NULL) goto error_handling; ``` -When closing an object (and thus releasing a reference to the Node structure), the driver should decrement the link count. +In this case, the new structure file will have the type: `"system/structure"`. + +- 📖 **Note**: This function only creates node object content, so the underlying object file must already exist. The OSML should do this for you because the previous driver (`obj->Prev`) creates the underlying object. -### pSnNode snNewNode(pObject obj, char* content_type) -This function creates a new node object with a given content type. The open link count should be incremented as appropriate, as before with snReadNode(). +### st_node: snWriteNode() ```c - pSnNode node; +int snWriteNode(pSnNode node); +``` +The `snWriteNode()` function writes a node's internal data back out to the node file, if the node's status (`node->Status`) is set to `SN_NS_DIRTY`. Otherwise, `snWriteNode()` does nothing. + - node = snNewNode(obj->Prev, "system/structure"); +### st_node: snDelete() +```c +int snDelete(pSnNode node); ``` +The `snDelete()` function deletes a node by removing the node's data from the internal node cache. -The "system/structure" argument is the type that will be assigned to the newly created node object. Note that the underlying object must already exist in order for this to create a node object as that object's content. Normally the OSML does this for you by commanding the previous driver (handling obj->Prev) to create the underlying object in question. +- 📖 **Note**: This does not actually delete the node file. -### int snWriteNode(pSnNode node) -This function writes a node's internal representation back out to the node file. The node's status (node->Status) should be set to SN_NS_DIRTY in order for the write to actually occur. Otherwise, snWriteNode() does nothing. -### int snDeleteNode(pSnNode node) -This function deletes a node file. At this point, does not actually delete the file but instead just removes the node's data structures from the internal node cache. +### st_node: snGetSerial() +```c +int snGetSerial(pSnNode node); +``` +The `snGetSerial()` function returns the serial number of the node. -### int snGetSerial(pSnNode node) -This function returns the serial number of the node. Each time the node is re-read because of modifications to the file or is written via snWriteNode because of modifications to the internal structure, the serial number is increased. This is a good way for a driver to refresh internal information that it caches should it determine a node object has changed. +Each time the node is re-read because of modifications to the node file or is written with because `snWriteNode()` was called after modifications to the internal structure, the serial number is increased. This is a good way for a driver to determine if the node file has changed so it can refresh internal cached data. -The stparse module is used to examine the parsed contents of the node file. A node file using the stparse module (and thus st_node module) has a structure file format; see StructureFile.txt. The file format is a tree structure with objects, subobjects, and attributes. The internal parsed representation is a tree, with each tree node being an object in the structure file, and each node having attributes, each of which is also a tree node. Thus, there are three different node types in the tree representation: the top-level ST_T_STRUCT element, which can contain subgroups and attributes; a mid-level ST_T_SUBGROUP tree node, which has a content type, name, and can contain attributes and other subgroups, and lastly a ST_T_ATTRIB node which contains an attribute name and attribute values, either integer or string, and optional lists of such up to 64 items in length. To use this module, include the file stparse.h. -The following functions are used to manage a parsed structure file: +### st_node: snGetLastModification() +```c +pDateTime snGetLastModification(pSnNode node); +``` +The `snGetLastModification()` function returns the date and time that a file was last modified. This pointer will remain valid as long as the passed `pSnNode` struct remains valid. It is managed by the `st_node` module, so the caller should not free the returned pointer. This function promises not to fail and return `NULL`. -### pStructInf stParseMsg(pFile inp_fd, int flags) -This function is internal-use-only and is used by the st_node module to parse a structure file. -### pStructInf stParseMsgGeneric(void* src, int (*read_fn)(), int flags) -This function is also internal-use-only (unless you want to parse the file manually without st_node's help) and is used to parse the structure file when the structure file isn't being read from an MTASK pFile descriptor. This is always the case, as the structure file data is being read from a pObject pointer. In such a case, src is the pObject pointer and read_fn is objRead(). +### Module: stparse +The stparse module is used to examine the parsed contents of the node file using the structure file format; see [StructureFile.txt](../centrallix-doc/StructureFile.txt). This format is a tree structure with node objects that can each have sub-objects and named attributes. Thus, stparse uses three distinct node types: +- `ST_T_STRUCT`: The top-level node, containing the subtrees and attributes in the file. +- `ST_T_SUBGROUP`: A mid-level type for subobjects within the top-level node. Each subgroup has a content type, name, and may contain attributes and other subgroups. +- `ST_T_ATTRIB`: A bottom-level type for each named attribute. Each attribute has a name and values, either of type integer or string, and optional lists of such up to 64 items in length. -### int stGenerateMsg(pFile out_fd, pStructInf info, int flags) -This function, also internal-use only, is used by the st_node module to write a structure file whose internal representation is given in the 'info' parameter. +To use this module, include the file `stparse.h`, which includes the following functions (read `stparse.c` for more functions and additional information): -### int stGenerateMsgGeneric(void* dst, int (*write_fn)(), pStructInf info, int flags) -This function is stParseMsgGeneric's converse. -### pStructInf stCreateStruct(char* name, char* type) -This function creates a new top-level tree item of type ST_T_STRUCT, with a given name and content-type. +### stparse: stStructType() +```c +int stStructType(pStructInf this); +``` +The `stStructType()` function returns the struct type of the past `pStructInf` parameter, which is either `ST_T_ATTRIB` or `ST_T_SUBGROUP` (see above). -### pStructInf stAddAttr(pStructInf inf, char* name) -This function adds a node of type ST_T_ATTRIB to either a ST_T_STRUCT or ST_T_SUBGROUP type of node, with a given name and no values associated with that name (see AddValue, below). The new attribute tree node is linked under the 'inf' node passed, and is returned. +- ⚠️ **Warning**: The root node of type `ST_T_STRUCT` will return `ST_T_SUBGROUP` from this function. If you wish to avoid this, read `inf->Type` (see [stparse: Using Fields Directly](#stparse-using-fields-directly) for more info). It is unclear whether this behavior is a bug or a feature. I've decided to call it a feature! ;) -### pStructInf stAddGroup(pStructInf inf, char* name, char* type) -This function adds a node of type ST_T_SUBGROUP to either a ST_T_SUBGROUP or ST_T_STRUCT tree node, with a given name and content type (content type such as 'report/query'). -### int stAddValue(pStructInf inf, char* strval, int intval) -This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If 'strval' is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. If the string is non-static, and has a lifetime less than the ST_T_ATTRIB tree node, then the following procedure must be used: +### stparse: stLookup() +```c +pStructInf stLookup(pStructInf inf, char* name); +``` +The `stLookup()` function searches all sub-tree nodes for a group or attribute of the given name and returns a pointer to it or returns `NULL` if no group or attribute was found. + +### stparse: stAttrValue() ```c - char* ptr; - char* nptr; - pStructInf attr_inf; +int stAttrValue(pStructInf inf, int* intval, char** strval, int nval); +``` +This function gets the value of the given attribute in an `ST_T_ATTRIB` node. If the value is an integer, the caller should pass a pointer to an integer where it can be stored. If the value is a string, the caller should pass a pointer to string (aka. a `char*`) where char* for the string can be stored. The unused alternate pointer must be left `NULL`. `nval` can normally be 0, but if the attribute has several values, setting nval to 1, 2, 3, etc., returns the 2nd, 3rd, 4th item, respectively. + +This function returns -1 if the attribute value did not exist, if the wrong type was requested, or if 'inf' was `NULL`. + +It is common practice to use `stLookup()` and `stAttrValue()` or `stGetExpression()` (see below) together to retrieve values, for example (where `inf` is a `pStructInfo` variable from somewhere): - attr_inf = stAddAttr(my_parent_inf, "myattr"); - nptr = (char*)malloc(strlen(ptr)+1); - if (!nptr) go_report_the_error_and_return; - strcpy(nptr, ptr); - stAddValue(attr_inf, nptr, 0); - attr_inf->StrAlloc[0] = 1; +```c +char* ptr; +if (stAttrValue(stLookup(inf, "my_attr"), NULL, &ptr, 0) != 0) + goto error_handling; +printf("The value is: %s\n", ptr); ``` -By following this method (making a copy of the string and then setting the StrAlloc value for that string), when the StructInf tree node is freed by the stparse module, the string will auto- matically be freed as well. -### pStructInf stLookup(pStructInf inf, char* name) -This routine examines all sub-tree-nodes, both group and attribute nodes, for a group or attribute with the given name. If it finds one, it returns a pointer to the sub-node, otherwise NULL. +### stparse: stGetExpression() +```c +pExpression stGetExpression(pStructInf this, int nval); +``` +Returns a pointer to an expression that represents the value of the nval-th element of the given struct. -### int stAttrValue(pStructInf inf, int* intval, char** strval, int nval) -This function returns the value of the given attribute in an ST_T_ATTRIB tree node. If a string value is being returned, pass a pointer to the string pointer. If an integer value is being returned, pass a pointer to an integer. The pointer not being used must be left NULL. 'nval' can normally be 0, but if the attribute has several values, setting nval to 1,2,3, etc., returns the 2nd, 3rd, 4th item, respectively. This routing returns -1 if the attribute value did not exist or if the wrong type was requested. It also returns -1 if 'inf' was NULL. -It is common practice to use the stLookup and stAttrValue functions together to retrieve values, and search for an attribute StructInf and retrieve its value in one operation: +### stparse: stCreateStruct() +```c +pStructInf stCreateStruct(char* name, char* type); +``` +This function creates a new top-level tree item of type `ST_T_STRUCT`, with a given name and content-type. + +### stparse: stAddAttr() ```c - pStructInf inf; - char* ptr; +pStructInf stAddAttr(pStructInf inf, char* name); +``` +This function adds a node of type `ST_T_ATTRIB` to either an `ST_T_STRUCT` or an `ST_T_SUBGROUP` type of node, with a given name and no values (see AddValue, below). The new attribute tree node is linked under the `inf` node passed, and is returned. - if (stAttrValue(stLookup(inf, "myattr"),NULL,&ptr,0) == 0) - { - printf("%s is the value\n", ptr); - } + +### stparse: stAddGroup() +```c +pStructInf stAddGroup(pStructInf inf, char* name, char* type); ``` +This function adds a node of type `ST_T_SUBGROUP` to either an `ST_T_SUBGROUP` or an `ST_T_STRUCT` tree node, with a given name and content type (content type such as `"report/query"`). -### int stFreeInf(pStructInf this) -This function is used to free a StructInf tree node. It will free any sub-nodes first, so if that is not desired, be sure to disconnect them by removing them from the SubInf array and appropriately adjusting the nSubInf counter, and setting the SubInf array position to NULL. This function also disconnects the tree node from its parent, if any, so if the parent is already free()'d, be sure to set the node's Parent pointer to NULL. Any strings marked allocated with the StrAlloc flags will be free()'d. -It is also common practice to bypass the stXxx() functions entirely and access the elements of the StructInf structures themselves. This is not forbidden, and may be done. See the file stparse.h for a description of the structure. For example, +### stparse: stAddValue() +```c +int stAddValue(pStructInf inf, char* strval, int intval); +``` +This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If `strval` is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. If the string is non-static, and has a lifetime less than the `ST_T_ATTRIB` tree node, then the following procedure should be used, where `str` is the string pointer to the string: ```c - pStructInf inf; - int i; +pStructInf attr_inf = stAddAttr(my_parent_inf, "my_attr"); +if (attr_inf == NULL) goto error_handling; + +char* new_str = (char*)malloc(strlen(str) + 1lu); +if (new_str == NULL) goto error_handling; +strcpy(new_str, str); +stAddValue(attr_inf, new_str, 0); +attr_inf->StrAlloc[0] = 1; +``` - for(i=0;inSubInf;i++) - { - if (inf->SubInf[i]->Type == ST_T_ATTRIB) - { - /** do stuff with attribute... **/ - } - } +With this method (making a copy of the string and then setting the StrAlloc value for that string), the string is automatically freed when the StructInf tree node is freed by the stparse module. + + +### stparse: stFreeInf() +```c +int stFreeInf(pStructInf this); ``` +This function is used to free a `StructInf` tree node. This also recursively frees sub-tree nodes, so these should be disconnected before calling if they are still needed. To do this, remove them from the SubInf array by appropriately adjusting the nSubInf counter and setting the SubInf array position to `NULL`. This function also disconnects the tree node from its parent, if any, so if the parent is already `free()`'d, prevent this behavior by setting the node's Parent pointer to `NULL` before calling this function. Any strings marked allocated with the StrAlloc flags will also be `free()`'d by this function, so update that flag if necessary. + + +### stparse: Using Fields Directly +It is also common practice to bypass the stparse functions entirely and access the elements of the `StructInf` struct directly, which is allowed. (See `stparse.h` for more information about this structure.) + +For example (assuming `inf` is a `pStructInfo` variable in scope): +```c +for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + switch (inf->SubInf[i]->Type) + { + case ST_T_ATTRIB: + /** Do stuff with attribute... **/ + break; + + case ST_T_SUBGROUP: + /** Do stuff with group... **/ + break; + + ... + } + } +``` + + ## IV Memory Management in Centrallix -Centrallix has its own memory manager that caches freshly-deallocated blocks of memory in lists according to size so that they can be quickly reallocated. This memory manager also catches double-freeing of blocks, making debugging of memory problems a little easier. + +Centrallix has its own memory management wrapper that caches deallocated blocks of memory by size to allow for faster reuse. This wrapper also detects double-freeing of blocks (sometimes), making debugging of memory problems just a little bit easier. + +In addition, the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. `malloc()`, and on how many blocks of each size/type are `malloc()`ed and cached. This information can be helpful for tracking down memory leaks. Empirical testing has shown an increase of performance of around 50% or more in programs with the newmalloc module in use. -In addition the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. malloc(), and information on how many blocks of each size/type are allocated out and cached. This information can be invaluable in tracking down memory leaks. +One caveat is that this memory manager does not provide `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or `nmSysMalloc()`, `nmSysFree()`, and `nmSysRealloc()` should be used for blocks of memory that might vary in size. -One caveat is that this memory manager does not provide a realloc() function, so the standard malloc(), free(), and realloc() must be used for blocks of memory that might grow in size. This memory manager is also perhaps not the best to use for blocks of memory of arbitrary sizes, but rather is best for allocating structures quickly that are of a specific size and belong to specific objects, such as the StructInf structure or the SnNode structure, and others. In short, use it for structures, but not for strings. +- 📖 **Note**: This memory manager is usually the wrong choice for blocks of memory of arbitrary sizes. It is intended for allocating structures quickly that are of a specific size. For example, allocated space for a struct that is always the same size. + +- 🥱 **tl;dr**: Use `nmMalloc()` for structs, not for strings. + +- ⚠️ **Warning**: Calling `free()` on a block obtained from `nmMalloc()` or calling `nmFree()` on a block obtained from `malloc()` might not crash the program immediately. Instead, it will result in either inefficient use of the memory manager, or a significant memory leak, respectively. These practices will also lead to incorrect results from the statistics and block count mechanisms. -Empirical testing has shown an increase of performance of around 50% or more in programs with the newmalloc module in use. The following are the functions for the newmalloc module: -### void* nmMalloc(int size) -This function allocates a block of the given 'size'. It returns NULL if the memory could not be allocated. +### nmMalloc() +```c +void* nmMalloc(int size); +``` +This function allocates a block of the given `size`. It returns `NULL` if the memory could not be allocated. + + +### nmFree() +```c +void nmFree(void* ptr, int size); +``` +This function frees the block of memory. + +- ⚠️ **Warning**: The caller **must know the size of the block.** Getting this wrong is very bad!! For structures, this is trivial, simply use `sizeof()`, exactly the same as with `nmMalloc()`. + -### void nmFree(void* ptr, int size) -This function frees the block of memory. NOTE THAT THE CALLING FUNCTION MUST KNOW THE SIZE OF THE BLOCK. Getting this wrong is very bad. For structures, this is trivial, just use sizeof() just like with nmMalloc(). +### nmStats() +```c +void nmStats(void); +``` +Prints statistics about the memory manager, for debugging and optimizing. -### void nmStats() -Prints out statistics on how well the memory manager is doing. +For example: +``` +NewMalloc subsystem statistics: + nmMalloc: 0 calls, 0 hits (-nan%) + nmFree: 0 calls + bigblks: 0 too big, 0 largest size +``` -### void nmRegister(int size, char* name) -Registers a name with a block size. This allows the memory manager to be intelligent when reporting block allocation counts. The first argument is the size of the block, the second, an intelligent name for that size of block. A size can have more than one name. This function is optional and need not be used except when tracking down memory leaks, but can be used freely. + -Typically this function is called in a module's Initialize() function on each of the structures the module uses internally. -### void nmDebug() -Prints out a listing of block allocation counts, giving (by size): 1) number of blocks allocated but not yet freed, 2) number of blocks in the cache, 3) total allocations for this block size, and a list of names (from nmRegister()) for that block size. +### nmRegister() +```c +void nmRegister(int size, char* name); +``` +Registers an inteligent name with a block size. This allows the memory manager to be intelligent when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production code to work, but using it can make tracking down memory leaks easier. -### void nmDeltas() -Prints a listing of all blocks whose allocation count has changed, and by how much, since the last nmDeltas() call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. +This function is usually called in a module's `Initialize()` function on each of the structures the module uses internally. -### void* nmSysMalloc(int size) -Allocates memory without using the block-caching algorithm. This is roughly equivalent to malloc(), but pointers returned by malloc and this function are not compatible with each other - i.e., you cannot free() something that was nmSysMalloc'ed, nor can you nmSysFree() something that was malloc'ed. -This function is much better to use on variable-sized blocks of memory. nmMalloc is better for fixed-size blocks, such as for data structures. +### nmDebug() +```c +void nmDebug(void); +``` +Prints a listing of block allocation counts, giving (by size): +- The number of blocks allocated but not yet freed. +- The number of blocks in the cache. +- The total allocations for this block size. +- A list of names (from `nmRegister()`) for that block size. -### void nmSysFree(void* ptr) -Frees a block of memory allocated by nmSysMalloc, nmSysStrdup, or nmSysRealloc. -### void* nmSysRealloc(void* ptr, int newsize) -Changes the size of an allocated block of memory that was obtained via nmSysMalloc or nmSysRealloc or nmSysStrdup. The new pointer may be different if the block had to be moved. This is the rough equivalent of realloc(). Usage Note: If you are realloc'ing a block of memory, and need to store pointers to data somewhere inside the block, it is often better to store the offset rather than a full pointer, as a pointer would become invalid if a nmSysRealloc caused the block to move. +### nmDeltas() +```c +void nmDeltas(void); +``` +Prints a listing of all blocks whose allocation count has changed, and by how much, since the last `nmDeltas()` call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. + + +### nmSysMalloc() +```c +void* nmSysMalloc(int size); +``` +Allocates memory without using the block-caching algorithm. This is roughly equivalent to `malloc()`, but pointers returned by malloc and this function are not compatible with each other - i.e., you cannot `free()` something that was `nmSysMalloc()`'ed, nor can you `nmSysFree()` something that was `malloc()`'ed. + +- 📖 **Note**: This function is much better to use on variable-sized blocks of memory. `nmMalloc()` is better for fixed-size blocks, such as for data structures. + + +### nmSysRealloc() +```c +void* nmSysRealloc(void* ptr, int newsize); +``` +Changes the size of an allocated block of memory that was obtained from `nmSysMalloc()`, `nmSysRealloc()`, or `nmSysStrdup()`. The new pointer may be different if the block has to be moved. This is the rough equivalent of `realloc()`. + +- 📖 **Note**: If you are `realloc()`'ing a block of memory and need to store pointers to data somewhere inside the block, it is often better to store an offset rather than a full pointer. This is because a full pointer becomes invalid if a `nmSysRealloc()` causes the block to move. + + +### nmSysStrdup() +```c +char* nmSysStrdup(const char* str); +``` +Allocates memory using `nmSysMalloc()` function and copies the string `str` into this memory. It is a rough equivalent of `strdup()`. The resulting pointer can be free'd using `nmSysFree()`. + + +### nmSysFree() +```c +void nmSysFree(void* ptr); +``` +Frees a block of memory allocated by `nmSysMalloc()`, `nmSysRealloc()`, or `nmSysStrdup()`. -### char* nmSysStrdup(const char* str) -Allocates memory for a copy of the string str by using the nmSysMalloc function, and then makes a copy of the string str. It is a rough equivalent of strdup(). The resulting pointer can be free'd using nmSysFree(). -Calling free() on a block obtained from nmMalloc() or calling nmFree() on a block obtained from malloc() will not crash the program. Instead, it will result in either inefficient use of the memory manager, or a huge memory leak, respectively. These practices will also render the statistics and block count mechanisms useless. ## V Other Utility Modules -There are many other utility modules useful in Centrallix. These include the xarray module, used for managing growable arrays; the xhash module, used for managing hash tables with no overflow problems and variable-length keys, the xstring module used for managing growable strings; the expression module used for compiling and evaluating expressions; and the mtsession module, used for managing session-level variables and reporting errors. + + +The Centrallix library (`centralllix-lib`) has a host of useful utility modules. These include `xarray`, used for managing growable arrays; `xstring`, used for managing growable strings; `xhash`, used for managing hash tables with no overflow problems and variable-length keys; `expression`, used for compiling and evaluating expressions; and `mtsession`, used for managing session-level variables and reporting errors. + ### A. XArray (XA) - Arrays The first is the xarray (XA) module. diff --git a/centrallix-sysdoc/string_comparison.md b/centrallix-sysdoc/string_comparison.md deleted file mode 100644 index dac13d544..000000000 --- a/centrallix-sysdoc/string_comparison.md +++ /dev/null @@ -1,101 +0,0 @@ -# String Comparison -The following sections discuss the two approaches to calculating similarity between two strings. Both approaches use a SQL function to calculate a similarity metric (on a scale of 0 to 1) for two string parameters. - -## Table of Contents -- [String Comparison](#string-comparison) - - [Table of Contents](#table-of-contents) - - [Levenshtein Similarity](#levenshtein-similarity) - - [Levenshtein](#levenshtein) - - [Cosine Similarity](#cosine-similarity) - - [CHAR_SET](#char_set) - - [Frequency Table](#frequency-table) - - [Relative Frequency Table](#relative-frequency-table) - - [TF-IDF](#tf-idf) - - [Dot Product](#dot-product) - - [Magnitude](#magnitude) - - [Similarity](#similarity) - - [Future Implementation](#future-implementation) - - [Inverse Document Frequency (IDF)](#inverse-document-frequency-idf) - -## Levenshtein Similarity -The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. - -### Levenshtein -```c -int exp_fn_levenshtein(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) -``` -Returns the levenshtein edit distance between two strings. - -```c -int exp_fn_fuzzy_compare(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) -``` -Returns a value between 0.0 (complete match) and 1.0 (complete difference) between strings a and b, based on the (levenshtein distance) / (max len of input strings). -Some alterations to the calculation are as follows: -- Matching an empty string against anything returns 0.5. -- A string that only required insertions to become the other string has its `(lev_dist)/(strlen)` value halved before returning. -- The parameter `max_field_width` is required, but not used. - -## Cosine Similarity - -The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. We use the relative frequency of the individual characters within each term as the vectors in the calculation. The following functions are used to calculate cosine similarity. - -### CHAR_SET -```c -const char *CHAR_SET ... -``` -`CHAR_SET` represents all of the characters that should be considered during the calculation of similarity. `CHAR_SET` can be extended to include additional characters, as necessary. - -### Frequency Table - -```c -int exp_fn_i_frequency_table(double *table, char *term) -``` -Helper function for similarity(). Creates a frequency table containing indices corresponding to all characters in `CHAR_SET` (all other characters are ignored). The values in the frequency table will contain the number of times each character appers in `term`. - -The `table` parameter must be allocated prior to calling the function with `nmMalloc()` using `sizeof(x * sizeof(double))`, where `x` is the length of `CHAR_SET`. The function will initialize all `table` values to 0, before calculating the frequency values. - -### Relative Frequency Table -```c -int exp_fn_i_relative_frequency_table(double *frequency_table) -``` -Helper function for similarity(). Converts a frequency table into a relative frequency table, where each value in the `frequency_table` is converted to the percent of occurrence (i.e., frequency divided by the sum of total occurrences). - -The `frequency_table` parameter must have been created using the `exp_fn_i_frequency_table` function above. - -### TF-IDF -```c -int exp_fn_i_tf_idf_table(double *frequency_table) -``` -Helper function for similarity(). Creates a TF x IDF vector from a frequency table, where each value in the resulting table is created by multiplying the relative frequency of each letter by the corresponding coefficient in the IDF array. - -The `frequency_table` parameter must have been created using the `exp_fn_i_frequency_table` function above. - -### Dot Product - -```c -int exp_fn_i_dot_product(double *dot_product, double *r_freq_table1, double *r_freq_table2) -``` -Helper function for similarity(). Calculates the dot product of two relative frequency tables (sum of the squared values from each relative frequency table). - -The `dot_product` parameter should be initialized to 0 before calling the function. The table parameters must contain relative frequency tables that are generated from the `exp_fn_i_relative_frequency_table` function. The lengths of both tables must equal the length of `CHAR_SET`. - -### Magnitude - -```c -int exp_fn_i_magnitude(double *magnitude, double *r_freq_table) -``` -Helper function for similarity(). Calculates the magnitude of a relative frequency table (square root of the sum of the squared relative frequencies). - -The `magnitude` parameter should be initialized to 0 before calling the function. The table parameter must contain a relative frequency table that was generated from the `exp_fn_i_relative_frequency_table` function. The length of the frequency table must equal the length of `CHAR_SET`. - -### Similarity - -```c -int exp_fn_similarity(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) -``` -Returns a value between 0.0 (completely different) and 1.0 (complete match) reflecting the similarity between the value passed in to i0 and the value passed in to i1. The first two parameters should contain strings that need to be compared. If the value 1 is passed in the third parameter, then the similarity function will rely on TF x IDF scores to determine similarity. If no third parameter is passed, then the function will rely only on relative frequency scores. - -## Future Implementation - -### Inverse Document Frequency (IDF) -In text mining, the most common metric to use in the cosine similarity function is the [TF x IDF](https://en.wikipedia.org/wiki/Tf%E2%80%93idf) metric. Our approach uses only TF (term frequency). Inverse document frequency calculates a weighting factor for each character. This could increase precision a small amount by weighting characters that appear on many records as less important in distinguishing matches, and weighting characters that appear on only certain records as more important. IDF could be calculated by iterating through the entire partner dataset each time. The current approach uses the relative frequency of each letter used in the English language on [Wikipedia](https://en.wikipedia.org/wiki/Letter_frequency), which may not be consistent with the data in the partner database. diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index f466a057c..b9a3a28b6 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -49,7 +49,7 @@ ---> # String Similarity -The following sections discuss the approaches to calculating similarity between two strings which are implemented in the `clusters.c` library. This library can be incuded using `#include "clusters.h"` in centrallix-lib and `#include "cxlib/clusters.h"` in centrallix. +The following sections discuss the approaches to calculating similarity between two strings which are implemented in the `clusters.c` library. This library can be included using `#include "clusters.h"` in centrallix-lib and `#include "cxlib/clusters.h"` in centrallix. ## Table of Contents @@ -74,10 +74,10 @@ The following sections discuss the approaches to calculating similarity between ## Cosine Similarity -The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. Conceptually, it's like finding the _angle_ between two vectors. To get these vectors, we use the relative frequency of character pairs within each string. To reduce memory cost and speed up computation, we store them in a special sparcely allocated form, described below. +The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. Conceptually, it's like finding the _angle_ between two vectors. To get these vectors, we use the relative frequency of character pairs within each string. To reduce memory cost and speed up computation, we store them in a special sparsely allocated form, described below. ### Character Sets -Cosine compare currnetly uses the following character sets. These can be extended or modified later, if necessary. +Cosine compare currently uses the following character sets. These can be extended or modified later, if necessary. ```c const char ALLOW_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const char CHAR_SET[] = "`abcdefghijklmnopqrstuvwxyz0123456789"; @@ -85,83 +85,86 @@ const char SIGNIFICANT_SET[] = "`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu const char IGNORE_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}"; const char BOUNDARY_CHAR = ('a' - 1); // aka. '`' ``` -- `ALLOW_SET` represents all characters which can be passed to a similarity detection algorithm. Passing other characters may cause warnings and errors, undefined or unintended behavior, and even security concerns. -- `CHAR_SET` represents all of the characters that will be uniquely considered during the calculation of similarity. Currently, this is all lowercase letters and numbers. -- `SIGNIFICANT_SET` represents all of the characters that are significant for the purposes of similarity. For example, the upercase letters are significant because they are considered identical to lowercase letters. Thus, they are included in the `SIGNIFICANT_SET`, but not in the `CHAR_SET`. -- `IGNORE_SET` represents characters which, while allowed to be passed to a similarity algorithm, will be ignored. For example, the strings "Ya!!" and "Ya..." will be considered identical. +- `ALLOW_SET` represents all characters which can be passed to a similarity detection algorithm. Passing other characters may cause warnings and errors, undefined or unintended behavior, and even security concerns. +- `CHAR_SET` represents all of the characters that will be uniquely considered during the calculation of similarity. Currently, this is all lowercase letters and numbers. +- `SIGNIFICANT_SET` represents all of the characters that are significant for the purposes of similarity. For example, the uppercase letters are significant because they are considered identical to lowercase letters. Thus, they are included in the `SIGNIFICANT_SET`, but not in the `CHAR_SET`. +- `IGNORE_SET` represents characters which, while allowed to be passed to a similarity algorithm, will be ignored. For example, the strings "Ya!!" and "Ya..." will be considered identical. - The `BOUNDARY_CHAR` is a special character which is conceptually added to the start and end of any string to be checked. - This allows for pairs that functionally include only the first and last character. - This character appears to have been selected to be one before the first character in `CHAR_SET` (thus convention dictates that it be written `'a' - 1` to indicate this), although it's unknown if that's the main or only reason. - If `clusters.h` is included, it can be accessed using the `CA_BOUNDARY_CHAR` macro. ### Character Pair Hashing -Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296` possible character pairs. If the number of characters in the `CHAR_SET` ever needed to be expanded - for example, to include all UTF-8 characters - this number would quickly explode exponentially to utterly infeasible proportions. Thus, a hashing algorithm is employed to hash each character pair down to a more reasonable number of dimensions (which can be accessed with the `CA_NUM_DIMS` macro). +Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296` possible character pairs. If the number of characters in the `CHAR_SET` ever needed to be expanded - for example, to include all UTF-8 characters - this number would quickly explode exponentially to utterly infeasible proportions. Thus, a hashing algorithm is employed to hash each character pair down to a more reasonable number of dimensions (which can be accessed with the `CA_NUM_DIMS` macro). ### String Vectors -Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicty, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies to based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). +Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies to based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). -Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. +Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. ### Sparse Vectors -As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. +As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. **Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. -**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. +**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. ### Computing Similarity -Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. +Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. ## Levenshtein Similarity -The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using levenshtein edit distance. +The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using levenshtein edit distance. The levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. ## Clustering -When searching for similar strings in a large amount of data (for example, `1,000,000` strings), comparing every string to every other string can be very computationally expensive. To speed up this process, it is helpful to _cluster_ similar strings together, then only compare strings within similar clusters. This sacrifices some accuracy to allow large amounts of data to be searched and compared in a feasable amount of time. +When searching for similar strings in a large amount of data (for example, `1,000,000` strings), comparing every string to every other string can be very computationally expensive. To speed up this process, it is helpful to _cluster_ similar strings together, then only compare strings within similar clusters. This sacrifices some accuracy to allow large amounts of data to be searched and compared in a feasible amount of time. ### K-means Clustering -When clustering data using the [k-means](https://en.wikipedia.org/wiki/K-means_clustering) algorithm, data is divided into a predefined number of clusters with the goal of maximizing the average similarity of datapoints within any given cluster. To quickly summarize the algorithm: -1. Randomly select `k` datapoints to be the initial centroids of each cluster. -2. For each datapoint, find the centroid it is most similar to, and assign it to that clustser. -3. For each cluster, find the new centroid by averaging all datapoints in the cluster. -4. Repeat steps 2 and 3 until the clusters stabilize (i.e. no datapoint changes clusters). +When clustering data using the [k-means](https://en.wikipedia.org/wiki/K-means_clustering) algorithm, data is divided into a predefined number of clusters with the goal of maximizing the average similarity of data points within any given cluster. To quickly summarize the algorithm: +1. Randomly select `k` data points to be the initial centroids of each cluster. +2. For each data point, find the centroid it is most similar to, and assign it to that cluster. +3. For each cluster, find the new centroid by averaging all data points in the cluster. +4. Repeat steps 2 and 3 until the clusters stabilize (i.e. no data point changes clusters). -The implementation used in `clusters.c` also allows the programmer to specify a maximum number of iterations (called `max_iter` in the code) to prevent this process from running forever. Additionally, successive iterations can give diminishing results or even produce clusters that are slightly worse. To improve performance, the programmer can also specify a minimum improvement threshold (called `min_improvement`). Clusters must become more similar by at least this amount each iteration, otherwise the algorithm ends, even if the maximum number of iterations has not yet been reached. +The implementation used in `clusters.c` also allows the programmer to specify a maximum number of iterations (called `max_iter` in the code) to prevent this process from running forever. Additionally, successive iterations can give diminishing results or even produce clusters that are slightly worse. To improve performance, the programmer can also specify a minimum improvement threshold (called `min_improvement`). Clusters must become more similar by at least this amount each iteration, otherwise the algorithm ends, even if the maximum number of iterations has not yet been reached. The `ca_kmeans()` function can be invoked using [the cosine comparison string vectors](#string-vectors) (see above) to cluster them into similar clusters. ### K-means++ Clustering **Not yet implemented** -This method is largely identical to k-means, except that [k-means++](https://en.wikipedia.org/wiki/K-means%2B%2B) assignes the initial centroids using an aproximate algorithm designed to avoid some of the poor clusterings possible with random assignment. +This method is largely identical to k-means, except that [k-means++](https://en.wikipedia.org/wiki/K-means%2B%2B) assigns the initial centroids using an approximate algorithm designed to avoid some of the poor clustering possible with random assignment. ### K-medoids Clustering **Not yet implemented** -This method is also very similar to k-means, except that [k-medoids](https://en.wikipedia.org/wiki/K-medoids) places an aditional requirement that all centroids be points in the data. This would theoretically allow for other similarity measures (such as levenshtein edit distance) to be used for clustering instead of only cosine compare. +This method is also very similar to k-means, except that [k-medoids](https://en.wikipedia.org/wiki/K-medoids) places an additional requirement that all centroids be points in the data. This would theoretically allow for other similarity measures (such as Levenshtein edit distance) to be used for clustering instead of only cosine compare. ### DB-Scan **Proposed, not yet implemented or documented** ### Sliding Clusters -A far more basic method of "clustering" is to simply sort all data alphabetically, then, instead of comparing each string to all other strings, it can be compared to only the next `n` strings. Of course, differences near the start of a string (for example, "fox" vs. "box") will cause those strings to sort far away from each other, leading them to be completely missed. +A far more basic method of "clustering" is to simply sort all data alphabetically, then, instead of comparing each string to all other strings, it can be compared to only the next `n` strings. Of course, differences near the start of a string (for example, "fox" vs. "box") will cause those strings to sort far away from each other, leading them to be completely missed. -Sorting using a similarity measure, such as `ca_cos_compare()` or `ca_lev_compare()` would resolve this issue. However, these comparison functions do not meet the transitivity requirement for sorting, which is that `(A < B) & (B < C) -> (A < C)`. For example, "car" is similar to "boxcar", which is also similar to "box". However, "car" and "box" are not similar at all. +Sorting using a similarity measure, such as `ca_cos_compare()` or `ca_lev_compare()` would resolve this issue. However, these comparison functions do not meet the transitivity requirement for sorting, which is that `(A < B) & (B < C) -> (A < C)`. For example, "car" is similar to "boxcar", which is also similar to "box". However, "car" and "box" are not similar at all. Additionally, sorting by the cosine vectors (similarly to how we cluster by them when using k-means) was proposed, but further investigation showed that this was also not possible. -For problems where a sorting algorithm exists which can mitigate the above issues, this solution may prove very promissing. However, so far we have not found such a problem, so the other clustering algorithms tend to out perform Sliding Clusters. +For problems where a sorting algorithm exists which can mitigate the above issues, this solution may prove very promising. However, so far we have not found such a problem, so the other clustering algorithms tend to outperform Sliding Clusters. ## Future Implementation ### K-means Fuzzy Clustering -One of the biggest downsides with k-means is that it creates very arbitrary boundaries between clusters. Elements on either side of these boundaries may be highly similar, but if comparisons only occur within a cluster, these similar entries will be missed. The problem becomes more extreme as a higher k value (more clusters) is used, creating more arbitrary boundaries. This drawback is probably the main reason that clustering sacrifices some accuracy over searching every element. +One of the biggest downsides with k-means is that it creates very arbitrary boundaries between clusters. Elements on either side of these boundaries may be highly similar, but if comparisons only occur within a cluster, these similar entries will be missed. The problem becomes more extreme as a higher k value (more clusters) is used, creating more arbitrary boundaries. This drawback is probably the main reason that clustering sacrifices some accuracy over searching every element. -Running the entire search multiple types may allow some of these to be found because the initial cluster locations are random. This approach is partially implemented for duplocate searching because the algorithm runs nightly anyway, so a simple up-sert (**UP**date existing entries; in**SERT** new entries) slightly reduces this problem. However, this solution is obviously far from ideal. +Running the entire search multiple types may allow some of these to be found because the initial cluster locations are random. This approach is partially implemented for duplicate searching because the algorithm runs nightly anyway, so a simple upsert (**UP**date existing entries; in**SERT** new entries) slightly reduces this problem. However, this solution is obviously far from ideal. -If the clustering could be expanded with an additional step that makes clusters larger, adding elements from other clusters to them, this might effectively mitigate the issue. It may also allow developers to use larger numbers of clusters, improving performance as well as accuracy. Further research is needed to verify the effectiveness of this approach before an implementation is written. +If the clustering could be expanded with an additional step that makes clusters larger, adding elements from other clusters to them, this might effectively mitigate the issue. It may also allow developers to use larger numbers of clusters, improving performance as well as accuracy. Further research is needed to verify the effectiveness of this approach before an implementation is written. ### Implement Missing Algorithms -Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-medoids-clustering), and [DBScan](#db-scan)) above are proposed but lack an implementation. They may be effective and useful, however, to reduce development time, they have not yet been implemented. +Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-medoids-clustering), and [DBScan](#db-scan)) above are proposed but lack an implementation. They may be effective and useful, however, to reduce development time, they have not yet been implemented. + +### Upgrade Other Duplicate Detection Systems +When a new record is entered, a quick scan is run to check if it might be a duplicate. There is also a button in the UI for a record that lets you run a duplicate check. These systems could also be upgraded using the new algorithms and strategies developed for general duplicate detection. \ No newline at end of file diff --git a/centrallix/expression/exp_double_metaphone.c b/centrallix/expression/exp_double_metaphone.c index f3d76c49b..8b7c4cd6f 100644 --- a/centrallix/expression/exp_double_metaphone.c +++ b/centrallix/expression/exp_double_metaphone.c @@ -18,9 +18,9 @@ /* */ /* A summary of the relevant content from https://dev.perl.org/licenses */ /* has been included below for the convenience of the reader. This */ -/* was collected and saved on September 5th, 2025 and may not reflect */ -/* current information. For the most up to date information, please use */ -/* the link above. */ +/* information was collected and saved on September 5th, 2025 and may */ +/* differ from current information. For the most up to date copy of */ +/* this information, please use the link provided above. */ /* */ /* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ /* */ @@ -64,11 +64,15 @@ /* */ /* Module: exp_double_metaphone.c */ /* Author: Maurice Aubrey */ -/* Description: This module implements a "sounds like" algorithm */ -/* developed by Lawrence Philips which he published */ -/* in the June, 2000 issue of C/C++ Users Journal. */ -/* Double Metaphone is an improved version of Philips' */ -/* original Metaphone algorithm. */ +/* Description: This module implements a "sounds like" algorithm by */ +/* Lawrence Philips which he published in the June, 2000 */ +/* issue of C/C++ Users Journal. Double Metaphone is an */ +/* improved version of the original Metaphone algorithm */ +/* written by Philips'. This implementaton was written by */ +/* Maurice Aubrey for C/C++ with bug fixes provided by */ +/* Kevin Atkinson. It was revised by Israel Fuller to */ +/* better align with the Centrallix coding style and */ +/* standards so that it could be included here. */ /************************************************************************/ /*** Note to future programmers reading this file (by Israel Fuller): @@ -83,7 +87,7 @@ *** might not line up with the original author. *** *** To be honest, though, trying to make this code as readable as possible - *** was very challanging due to all the messy boolean algebra. If there is + *** was very challenging due to all the messy boolean algebra. If there is *** ever a professional linguist reading this, please factor out some of the *** logic into local variables with descriptive names so that the rest of us *** can read this code without our eyes glazing over. @@ -205,7 +209,7 @@ void meta_destroy_string(MetaString* s) /*** Increases a MetaString's buffer size. *** *** @param s The MetaString* being modified. - *** @param chars_needed Minimumn number of characters to increase buffer size. + *** @param chars_needed Minimum number of characters to increase buffer size. ***/ void meta_increase_buffer(MetaString* s, const size_t chars_needed) { @@ -347,7 +351,7 @@ void meta_double_metaphone(const char* str, char** primary_code, char** secondar if (str == NULL || (length = strlen(str)) == 0u) { fprintf(stderr, "Warning: Call to meta_double_metaphone() with invalid string.\n"); - /** Double Metaphone on an invalid string yeilds two empty strings. **/ + /** Double Metaphone on an invalid string yields two empty strings. **/ *primary_code = (char*)SAFE_MALLOC(sizeof(char)); *secondary_code = (char*)SAFE_MALLOC(sizeof(char)); return; @@ -1066,7 +1070,7 @@ void meta_double_metaphone(const char* str, char** primary_code, char** secondar } /** german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' **/ - /** also, -sz- in slavic language altho in hungarian it is pronounced 's' **/ + /** also, -sz- in slavic language although in hungarian it is pronounced 's' **/ if (current == 0 && meta_is_str_at(original, (current + 1), "M", "N", "L", "W", "")) { meta_add_str(primary, "S"); @@ -1269,7 +1273,7 @@ void meta_double_metaphone(const char* str, char** primary_code, char** secondar /*** Built in test cases. *** *** These tests have been integrated into the Centrallix testing environment, - *** where they can be run using `export TONLY=expfn_double_metaphone_00`, + *** where they can be run using `export TONLY=exp_fn_double_metaphone_00`, *** followed by make test, in the Centrallix directory. *** *** The can also be run here by executing the following commands in the diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 71f906e3d..159c292b0 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -86,7 +86,7 @@ static char* ci_TypeToStr(const int type) case DATA_T_STRING: return "String"; case DATA_T_DOUBLE: return "Double"; case DATA_T_DATETIME: return "DateTime"; - case DATA_T_INTVEC: return "IntVecor"; + case DATA_T_INTVEC: return "IntVector"; case DATA_T_STRINGVEC: return "StringVector"; case DATA_T_MONEY: return "Money"; case DATA_T_ARRAY: return "Array"; @@ -2414,17 +2414,37 @@ int exp_fn_truncate(pExpression tree, pParamObjects objlist, pExpression i0, pEx /*** constrain(value, min, max) ***/ int exp_fn_constrain(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { - if (!i0 || !i1 || !i2 || (i0->DataType != i1->DataType) || i0->DataType != i2->DataType || !(i0->DataType == DATA_T_INTEGER || i0->DataType == DATA_T_MONEY || i0->DataType == DATA_T_DOUBLE)) - { - mssError(1,"EXP","constrain() requires three numeric parameters of the same data type"); - return -1; - } + /** Skip null value. **/ tree->DataType = i0->DataType; if ((i0->Flags & EXPR_F_NULL)) { tree->Flags |= EXPR_F_NULL; return 0; } + + /** Verify parameters. **/ + if (i0 == NULL || i1 == NULL || i2 == NULL) + { + mssError(1, "EXP", "constrain() expects three parameters."); + return -1; + } + if (i0->DataType != DATA_T_INTEGER && i0->DataType != DATA_T_DOUBLE && i0->DataType != DATA_T_MONEY) + { + mssError(1, "EXP", + "constrain() expects three numeric parameters: %s is not numeric.", + ci_TypeToStr(i0->DataType) + ); + if (i0->DataType == DATA_T_STRING) printf("Value: '%s'\n", i0->String); + return -1; + } + if (i0->DataType != i1->DataType || i1->DataType != i2->DataType) + { + mssError(1, "EXP", + "constrain() expects three numeric parameters of the same data type but got types %s, %s, and %s.", + ci_TypeToStr(i0->DataType), ci_TypeToStr(i1->DataType), ci_TypeToStr(i2->DataType) + ); + return -1; + } /* check min */ if (!(i1->Flags & EXPR_F_NULL)) @@ -4131,78 +4151,143 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress return 0; } - -/*** Computes cosine or levenshtien similarity between two strings. These two - *** tasks have a large amount of overlapping logic (mostly error checking), - *** so doing them with one function greatly reduces code duplocation. - *** - *** @param tree The tree resulting from this function. - *** @param objlist The evaluation "scope", including available variables. - *** @param maybe_str1 Possibly the first string. - *** @param maybe_str2 Possibly the second string. - *** @param u1 Unused parameter. - *** @param is_cos Whether to compute cosine or levenshtien. - *** @returns 0 for success, -1 for failure. - ***/ -static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1, const char* fn_name) +static int exp_fn_verify_schema( + const char* fn_name, + const int* param_types, + const int num_params, + pExpression tree, + pParamObjects obj_list) { - /** Check number of arguments. **/ - const int num_params = tree->Children.nItems; - if (num_params != 2) + /** Verify object list and session. **/ + if (obj_list == NULL) { - mssErrorf(1, "EXP", "%s(?) expects 2 parameters, got %d parameters.", fn_name, num_params); + mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); return -1; } - if (maybe_str1 == NULL || maybe_str2 == NULL || u1 != NULL) + ASSERTMAGIC(obj_list->Session, MGK_OBJSESSION); + + /** Verify expression tree. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + + /** Verify parameter number. **/ + const int num_params_actual = tree->Children.nItems; + if (num_params != num_params_actual) { - mssErrorf(1, "EXP", "%s(?) expects 2 parameters.", fn_name); + mssErrorf(1, "EXP", + "%s(?) expects %u param%s, got %d param%s.", + fn_name, num_params, (num_params > 1) ? "s" : "", num_params_actual, (num_params_actual > 1) ? "s" : "" + ); return -1; } + + /** Verify parameter datatypes. **/ + for (int i = 0; i < num_params; i++) + { + const pExpression arg = tree->Children.Items[i]; + ASSERTMAGIC(arg, MGK_EXPRESSION); + + /** Skip null values. **/ + if (arg->Flags & EXPR_F_NULL) continue; + + /** Extract datatypes. **/ + const int expected_datatype = param_types[i]; + const int actual_datatype = arg->DataType; + + /** Verify datatypes. **/ + if (expected_datatype != actual_datatype) + { + mssErrorf(1, "EXP", + "%s(...) param #%d/%d expects type %s (%d) but got type %s (%d).", + fn_name, i + 1, num_params, ci_TypeToStr(expected_datatype), expected_datatype, ci_TypeToStr(actual_datatype), actual_datatype + ); + return -1; + } + } - /** Magic checks. **/ - ASSERTMAGIC(tree, MGK_EXPRESSION); - ASSERTMAGIC(maybe_str1, MGK_EXPRESSION); - ASSERTMAGIC(maybe_str2, MGK_EXPRESSION); + /** Pass. **/ + return 0; + } + + +int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) + { + const char fn_name[] = "metaphone"; - /** Check object list. **/ - if (objlist == NULL) + /** Verify function schema. **/ + if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING }, 1, tree, obj_list) != 0) { - mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); + mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); return -1; } - ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - /** Extract str1. **/ - if (maybe_str1->Flags & EXPR_F_NULL) + /** Extract string param. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; - tree->DataType = DATA_T_DOUBLE; + tree->DataType = DATA_T_STRING; + return 0; + } + const char* str = check_ptr(maybe_str->String); + const size_t str_len = strlen(str); + if (str_len == 0u) + { + tree->String = ""; + tree->DataType = DATA_T_STRING; return 0; } - if (maybe_str1->DataType != DATA_T_STRING) + + /** Compute DoubleMetaphone. **/ + char* primary = NULL; + char* secondary = NULL; + meta_double_metaphone(str, &primary, &secondary); + + /** Process result. **/ + const size_t result_length = strlen(primary) + 1u + strlen(secondary) + 1u; + char* result = check_ptr(nmSysMalloc(result_length * sizeof(char*))); + if (result == NULL) return -1; + sprintf(result, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); + + /** Return the result. **/ + tree->String = result; + tree->DataType = DATA_T_STRING; + return 0; + } + + +/*** Computes cosine or Levenshtein similarity between two strings. These two + *** tasks have a large amount of overlapping logic (mostly error checking), + *** so doing them with one function greatly reduces code duplocation. + *** + *** @param tree The tree resulting from this function. + *** @param obj_list The evaluation "scope", including available variables. + *** @param fn_name Either `cos_compare()` or `lev_compare()`. + *** @returns 0 for success, -1 for failure. + ***/ +static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) + { + /** Verify function schema. **/ + if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) { - mssErrorf(1, "EXP", "%s(\?\?\?, ..) str1 should be a string.", fn_name); + mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); return -1; } - char* str1 = check_ptr(maybe_str1->String); - - /** Extract str2. **/ - if (maybe_str2->Flags & EXPR_F_NULL) + + /** Extract strings. **/ + pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); + pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_DOUBLE; return 0; } - if (maybe_str2->DataType != DATA_T_STRING) - { - mssErrorf(1, "EXP", "%s(\"%s\", \?\?\?) str2 should be a string.", fn_name, str1); - return -1; - } + char* str1 = check_ptr(maybe_str1->String); char* str2 = check_ptr(maybe_str2->String); - /** Handle either cos_cmp or lev_cmp. **/ + /** Handle either cos_compare() or lev_compare(). **/ if (fn_name[0] == 'c') - { /* cos_cmp */ + { /* cos_compare() */ int ret; /** Build vectors. **/ @@ -4218,17 +4303,19 @@ static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe } else { + /** Compute the similarity. **/ tree->Types.Double = ca_cos_compare(v1, v2); tree->DataType = DATA_T_DOUBLE; ret = 0; } + /** Clean up. **/ if (v1 != NULL) ca_free_vector(v1); if (v2 != NULL) ca_free_vector(v2); return ret; } else - { /* lev_cmp */ + { /* lev_compare() */ tree->Types.Double = ca_lev_compare(str1, str2); tree->DataType = DATA_T_DOUBLE; return 0; @@ -4237,310 +4324,43 @@ static int exp_fn_cmp(pExpression tree, pParamObjects objlist, pExpression maybe } -int exp_fn_cos_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "cos_cmp"); } -int exp_fn_cos_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "cos_compare"); } -int exp_fn_cosine_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "cosine_compare"); } -int exp_fn_lev_cmp(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "lev_cmp"); } -int exp_fn_lev_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "lev_compare"); } -int exp_fn_levenshtein_compare(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1) - { return exp_fn_cmp(tree, objlist, maybe_str1, maybe_str2, u1, "levenshtein_compare"); } - - -/*** Comparse two strings to see if their sparse vectors are equal. - *** - *** @param tree The tree resulting from this function. - *** @param objlist The evaluation "scope", including available variables. - *** @param maybe_str1 Possibly the first string. - *** @param maybe_str2 Possibly the second string. - *** @param u1 Unused parameter. - *** @returns 0 for success, -1 for failure. - ***/ -static int exp_fn_sparse_eql(pExpression tree, pParamObjects objlist, pExpression maybe_str1, pExpression maybe_str2, pExpression u1, bool is_cos) +int exp_fn_cos_compare(pExpression tree, pParamObjects obj_list) { - const char fn_name[] = "sparse_compare"; - - /** Check number of arguments. **/ - const int num_params = tree->Children.nItems; - if (num_params != 2) - { - mssErrorf(1, "EXP", "%s(?) expects 2 parameters, got %d parameters.", fn_name, num_params); - return -1; - } - if (maybe_str1 == NULL || maybe_str2 == NULL || u1 != NULL) - { - mssErrorf(1, "EXP", "%s(?) expects 2 parameters.", fn_name); - return -1; - } - - /** Magic checks. **/ - ASSERTMAGIC(tree, MGK_EXPRESSION); - ASSERTMAGIC(maybe_str1, MGK_EXPRESSION); - ASSERTMAGIC(maybe_str2, MGK_EXPRESSION); - - /** Check object list. **/ - if (objlist == NULL) - { - mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); - return -1; - } - ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - - /** Extract str1. **/ - if (maybe_str1->Flags & EXPR_F_NULL) - { - mssErrorf(1, "EXP", "%s(NULL, ...) str1 cannot be NULL.", fn_name); - return -1; - } - if (maybe_str1->DataType != DATA_T_STRING) - { - mssErrorf(1, "EXP", "%s(\?\?\?, ..) str1 should be a string.", fn_name); - return -1; - } - char* str1 = maybe_str1->String; - if (str1 == NULL) - { - mssErrorf(1, "EXP", - "%s(nothing?, ...) expected string from str1 (of datatype DataType = " - "DATA_T_STRING), but the String was NULL or did not exist!", - fn_name - ); - return -1; - } - - /** Extract str2. **/ - if (maybe_str2->Flags & EXPR_F_NULL) - { - mssErrorf(1, "EXP", "%s(\"%s\", NULL) str2 cannot be NULL.", fn_name, str1); - return -1; - } - if (maybe_str2->DataType != DATA_T_STRING) - { - mssErrorf(1, "EXP", "%s(\"%s\", \?\?\?) str2 should be a string.", fn_name, str1); - return -1; - } - char* str2 = maybe_str2->String; - if (str2 == NULL) - { - mssErrorf(1, "EXP", - "%s(\"%s\", nothing?) expected string from str2 (of datatype DataType = " - "DATA_T_STRING), but the String was NULL or did not exist!", - fn_name, str1 - ); - return -1; - } - - /** Build vectors. **/ - int ret; - const pVector v1 = check_ptr(ca_build_vector(str1)); - const pVector v2 = check_ptr(ca_build_vector(str2)); - if (v1 == NULL || v2 == NULL) - { - mssErrorf(1, "EXP", - "%s(\"%s\", \"%s\") - Failed to build vectors.", - fn_name, str1, str2 - ); - ret = -1; - } - else - { - tree->Integer = (ca_eql(v1, v2)) ? 1 : 0; - tree->DataType = DATA_T_INTEGER; - ret = 0; - } - - if (v1 != NULL) ca_free_vector(v1); - if (v2 != NULL) ca_free_vector(v2); - return ret; + return exp_fn_compare(tree, obj_list, "cos_compare"); } - -/*** Computes double metaphone. - *** - *** @param tree The tree resulting from this function. - *** @param objlist The evaluation "scope", including available variables. - *** @param maybe_str Possibly the string passed to double metaphone. - *** @param u1 Unused parameter. - *** @param u2 Unused parameter. - ***/ -int exp_fn_double_metaphone(pExpression tree, pParamObjects objlist, pExpression maybe_str, pExpression u1, pExpression u2) +int exp_fn_lev_compare(pExpression tree, pParamObjects obj_list) { - const char fn_name[] = "double_metaphone"; - - /** Check number of arguments. **/ - const int num_params = tree->Children.nItems; - if (num_params != 1) - { - mssErrorf(1, "EXP", "%s(?) expects 1 parameter, got %d parameters.", fn_name, num_params); - return -1; - } - if (maybe_str == NULL || u1 != NULL || u2 != NULL) - { - mssErrorf(1, "EXP", "%s(?) expects 1 parameter.", fn_name); - return -1; - } - - /** Magic checks. **/ - ASSERTMAGIC(tree, MGK_EXPRESSION); - ASSERTMAGIC(maybe_str, MGK_EXPRESSION); - - /** Check object list. **/ - if (objlist == NULL) - { - mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); - return -1; - } - ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - - /** Extract str. **/ - if (maybe_str->Flags & EXPR_F_NULL) - { - mssErrorf(1, "EXP", "%s(NULL) str cannot be NULL.", fn_name); - return -1; - } - if (maybe_str->DataType != DATA_T_STRING) - { - mssErrorf(1, "EXP", "%s(\?\?\?) str should be a string.", fn_name); - return -1; - } - const char* str = maybe_str->String; - if (str == NULL) - { - mssErrorf(1, "EXP", - "%s(nothing?) expected string from str (of datatype DataType = " - "DATA_T_STRING), but the String was NULL or did not exist!", - fn_name - ); - return -1; - } - const size_t str_len = strlen(str); - if (str_len == 0u) - { - mssErrorf(1, "EXP", "%s(\"\") str cannot be an empty string.", fn_name); - return -1; - } - - /** Compute DoubleMetaphone. **/ - char* primary = NULL; - char* secondary = NULL; - meta_double_metaphone(str, &primary, &secondary); - - /** Process result. **/ - const size_t primary_length = strlen(primary); - const size_t secondary_length = strlen(secondary); - char* result = check_ptr(nmSysMalloc(primary_length + 1u + secondary_length + 1u)); - if (result == NULL) return -1; - sprintf(result, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); - - /** Return the result. **/ - tree->String = result; - tree->DataType = DATA_T_STRING; - return 0; + return exp_fn_compare(tree, obj_list, "lev_compare"); } - -int exp_fn_aggregate_similarities(pExpression tree, pParamObjects objlist) - { - const char fn_name[] = "aggregate_similarities"; - - /** Check number of arguments. **/ - const int num_params = tree->Children.nItems; - if (num_params != 6) - { - mssErrorf(1, "EXP", "%s(?) expects 6 parameters, got %d parameters.", fn_name, num_params); - return -1; - } - /** Magic checks. **/ - ASSERTMAGIC(tree, MGK_EXPRESSION); - ASSERTMAGIC(tree->Children.Items[0], MGK_EXPRESSION); - ASSERTMAGIC(tree->Children.Items[1], MGK_EXPRESSION); - ASSERTMAGIC(tree->Children.Items[2], MGK_EXPRESSION); - ASSERTMAGIC(tree->Children.Items[3], MGK_EXPRESSION); +int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) + { + const char fn_name[] = "levenshtein"; - /** Check object list. **/ - if (objlist == NULL) + /** Verify function schema. **/ + if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) { - mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); + mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); return -1; } - ASSERTMAGIC(objlist->Session, MGK_OBJSESSION); - - /** Extract parameters. **/ - double params[4] = {NAN}; - const char names[4][8] = {"name", "email", "phone", "address"}; - for (unsigned int i = 0; i < 4u; i++) - { - pExpression param = (pExpression)tree->Children.Items[i]; - - /** Ignore null values. **/ - if (param->Flags & EXPR_F_NULL) continue; - - /** Only accept doubles. **/ - if (param->DataType != DATA_T_DOUBLE) - { - mssErrorf(1, "EXP", - "%s() param%u (%s) expected type %s but got %s.", - fn_name, i, names[i], ci_TypeToStr(DATA_T_DOUBLE), ci_TypeToStr(param->DataType) - ); - if (param->DataType == DATA_T_INTEGER) fprintf(stderr, "Value: %d\n", param->Integer); - return -1; - } - - /** Do not accept NaN. **/ - params[i] = param->Types.Double; - if (isnan(params[i])) - { - mssErrorf(1, "EXP", "%s() param%u (%s) cannot be NaN", fn_name, names[i], i); - return -1; - } - } - char* dup_names[2] = {NULL}; - for (unsigned int i = 0; i < 2u; i++) + /** Extract strings. **/ + pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); + pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { - pExpression param = (pExpression)tree->Children.Items[i + 4u]; - - /** Ignore null values. **/ - if (param->Flags & EXPR_F_NULL) continue; - - /** Only accept doubles. **/ - if (param->DataType != DATA_T_STRING) - { - mssErrorf(1, "EXP", - "%s() param%u expected type %s but got %s.", - fn_name, i, ci_TypeToStr(DATA_T_STRING), ci_TypeToStr(param->DataType) - ); - return -1; - } - - dup_names[i] = param->String; + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_INTEGER; + return 0; } + char* str1 = check_ptr(maybe_str1->String); + char* str2 = check_ptr(maybe_str2->String); - FILE *f = check_ptr(fopen("/home/israel/exp_log.swift", "a")); - check_neg(fprintf(f, "aggregate_similarities(%g, %g, %g, %g, \"%s\", \"%s\")", params[0], params[1], params[2], params[3], dup_names[0], dup_names[1])); - - /** Compute aggregated similarity. **/ - double name_sim = params[0]; - double email_sim = params[1]; - double phone_sim = params[2]; - double address_sim = params[3]; - - double mean = 0.0, n = 0.0; - if (name_sim > 0.0) { mean += name_sim; n++; } - if (email_sim > 0.0) { mean += email_sim; n++; } - if (phone_sim > 0.0) { mean += phone_sim; n++; } - if (address_sim > 0.0) { mean += address_sim; n++; } - mean /= n; - - /** Success. **/ - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = mean; - fprintf(f, " = %g\n", tree->Types.Double); - check(fclose(f)); + /** Compute edit distance. **/ + /** Length 0 is provided for both strings so that the function will compute it for us. **/ + tree->Integer = edit_dist(str1, str2, 0lu, 0lu); + tree->DataType = DATA_T_INTEGER; return 0; } @@ -4552,7 +4372,7 @@ int exp_fn_aggregate_similarities(pExpression tree, pParamObjects objlist) * Parameters: * pExpression tree: * pParamObjects: - * pExpression passowrd: The password, passed as a pExpression + * pExpression password: The password, passed as a pExpression * pExpression salt: The salt, passed as a pExpression * * returns: @@ -4745,15 +4565,10 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); /** Duplicate detection. **/ - xhAdd(&EXP.Functions, "cos_cmp", (char*)exp_fn_cos_cmp); + xhAdd(&EXP.Functions, "metaphone", (char*)exp_fn_metaphone); xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); - xhAdd(&EXP.Functions, "cosine_compare", (char*)exp_fn_cosine_compare); - xhAdd(&EXP.Functions, "lev_cmp", (char*)exp_fn_lev_cmp); xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); - xhAdd(&EXP.Functions, "levenshtein_compare", (char*)exp_fn_levenshtein_compare); - xhAdd(&EXP.Functions, "sparse_eql", (char*)exp_fn_sparse_eql); - xhAdd(&EXP.Functions, "aggregate_similarities", (char*)exp_fn_aggregate_similarities); - xhAdd(&EXP.Functions, "double_metaphone", (char*)exp_fn_double_metaphone); + xhAdd(&EXP.Functions, "levenshtein", (char*)exp_fn_levenshtein); /** Windowing. **/ xhAdd(&EXP.Functions, "row_number", (char*)exp_fn_row_number); diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index b3d416668..4bf94ebe9 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -102,7 +102,7 @@ void void_func() {} *** not readily available. *** *** @param clr Whether to clear the current error stack. As a rule of thumb, - *** if you are the first one to detec the error, clear the stack so that + *** if you are the first one to detect the error, clear the stack so that *** other unrelated messages are not shown. If you are detecting an error *** from another function that may also call an mssError() function, do *** not clear the stack. @@ -188,7 +188,7 @@ static int ci_TypeFromStr(const char* str) case 'I': case 'i': if (strcmp(str+1, "Integer"+1) == 0) return DATA_T_INTEGER; - if (strcmp(str+1, "IntVecor"+1) == 0) return DATA_T_INTVEC; + if (strcmp(str+1, "IntVector"+1) == 0) return DATA_T_INTVEC; break; case 'M': case 'm': @@ -221,7 +221,7 @@ static char* ci_TypeToStr(const int type) case DATA_T_STRING: return "String"; case DATA_T_DOUBLE: return "Double"; case DATA_T_DATETIME: return "DateTime"; - case DATA_T_INTVEC: return "IntVecor"; + case DATA_T_INTVEC: return "IntVector"; case DATA_T_STRINGVEC: return "StringVector"; case DATA_T_MONEY: return "Money"; case DATA_T_ARRAY: return "Array"; @@ -282,7 +282,7 @@ static void** ci_xaToTrimmedArray(pXArray arr, int array_handling) }) -/** ================ Enum Declairations ================ **/ +/** ================ Enum Declarations ================ **/ /** ANCHOR[id=enums] **/ /** Enum representing a clustering algorithm. **/ @@ -430,7 +430,7 @@ char* const METHOD_NAME[] = *** the SourcePath. *** *** @skip --> Computed data. - *** @param Strings The keys for each data string strings recieved from the + *** @param Strings The keys for each data string strings received from the *** database, allowing them to be lined up again when queried. *** @param Strings The data strings to be clustered and searched, or NULL if *** they have not been fetched from the source. @@ -741,7 +741,7 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData /** Driver Functions. **/ // LINK #driver -void* clusterOpen(pObject obj, int mask, pContentType systype, char* usr_type, pObjTrxTree* oxt); +void* clusterOpen(pObject parent, int mask, pContentType systype, char* usr_type, pObjTrxTree* oxt); int clusterClose(void* inf_v, pObjTrxTree* oxt); void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt); @@ -749,30 +749,30 @@ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt); int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt); -char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt); -char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt); +char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt); +char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt); int clusterInfo(void* inf_v, pObjectInfo info); /** Method Execution Functions. **/ // LINK #method -char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt); -char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt); +char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt); +char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt); static int ci_PrintEntry(pXHashEntry entry, void* arg); static void ci_CacheFreeSourceData(pXHashEntry entry, void* path); static void ci_CacheFreeCluster(pXHashEntry entry, void* path); static void ci_CacheFreeSearch(pXHashEntry entry, void* path); -int clusterExecuteMethod(void* inf_v, char* methodname, pObjData param, pObjTrxTree oxt); +int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); /** Unimplemented DriverFunctions. **/ // LINK #unimplemented int clusterCreate(pObject obj, int mask, pContentType systype, char* usrtype, pObjTrxTree* oxt); -int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt); int clusterDelete(pObject obj, pObjTrxTree* oxt); -int clusterRead(void* inf_v, char* buffer, int maxcnt, int offset, int flags, pObjTrxTree* oxt); +int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt); +int clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt); int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt); -int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree oxt); -int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree oxt); -void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree oxt); +int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt); +void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt); int clusterCommit(void* inf_v, pObjTrxTree *oxt); /** ================ Parsing Functions ================ **/ @@ -1037,7 +1037,7 @@ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, /** Cache hit. **/ tprintf("# source: \"%s\"\n", source_data->Key); - /** Cause an imediate invalid read if cache was incorrectly freed. **/ + /** Cause an immediate invalid read if cache was incorrectly freed. **/ tprintf("--> Name: %s\n", source_maybe->Name); /** Free data we don't need. **/ @@ -1709,7 +1709,7 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) int num_provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf : 0; pStruct* provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf : NULL; - /** Itterate over each param in the structure file. **/ + /** Iterate over each param in the structure file. **/ node_data->nParams = param_infs.nItems; const size_t params_size = node_data->nParams * sizeof(pParam); node_data->Params = check_ptr(nmSysMalloc(params_size)); @@ -1920,7 +1920,7 @@ static void ci_FreeSourceData(pSourceData source_data) /*** Free pClusterData struct with an option to recursively free subclusters. *** *** @param cluster_data The cluster data struct to free. - *** @param recrusive Whether to recursively free subclusters. + *** @param recursive Whether to recursively free subclusters. ***/ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) { @@ -2142,7 +2142,7 @@ static unsigned int ci_SizeOfSourceData(pSourceData source_data) *** caching systems, so it is not technically part of the struct. *** *** @param cluster_data The cluster data struct to be queried. - *** @param recrusive Whether to recursively free subclusters. + *** @param recursive Whether to recursively free subclusters. *** @returns The size in bytes of the struct and all internal allocated data. ***/ static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) @@ -2224,7 +2224,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (!check(objCurrentDate(&source_data->DateComputed))) goto end; /** Open the source path specified by the .cluster file. **/ - tprintf("Openning...\n"); + tprintf("Opening...\n"); pObject obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); if (obj == NULL) { @@ -2239,7 +2239,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Generate a "query" for retrieving data. **/ - tprintf("Openning query...\n"); + tprintf("Opening query...\n"); pObjQuery query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); if (query == NULL) { @@ -2424,7 +2424,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) // ret = ret; // Fall-through: Failure ignored. } } - tprintf("\nData aquired.\n"); + tprintf("\nData acquired.\n"); source_data->nVectors = vector_xarray.nItems; if (source_data->nVectors == 0) { @@ -2508,7 +2508,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) ***/ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { - /** If the clusters are alreadyd computed, we're done. **/ + /** If the clusters are already computed, we're done. **/ if (cluster_data->Clusters != NULL) return 0; /** Make source data available. **/ @@ -2565,7 +2565,7 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) { mssErrorf(1, "Cluster", - "The similarity meausre \"%s\" is not implemented.", + "The similarity measure \"%s\" is not implemented.", ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) ); goto err_free_sims; @@ -2840,8 +2840,8 @@ static int ci_GetParamType(void* inf_v, const char* attr_name) *** *** @attention - Warning: If the retrieved value is `NULL`, the pObjectData *** val is not updated, and the function returns 1, indicating `NULL`. - *** This is intended behavior, for consistancy with other Centrallix - *** functions, so keep it in mind so you're not surpised. + *** This is intended behavior, for consistency with other Centrallix + *** functions, so keep it in mind so you're not surprised. *** *** @param inf_v Node data containing the list of paramenters. *** @param attr_name The name of the requested paramenter. @@ -2853,9 +2853,9 @@ static int ci_GetParamType(void* inf_v, const char* attr_name) *** so that they will have a pointer to the data. *** This buffer will not be modified unless the data is successfully *** found. If a value other than 0 is returned, the buffer is not updated. - *** @returns 0 if successsful, + *** @returns 0 if successful, *** 1 if the variable is null, - *** -1 if an error occures. + *** -1 if an error occurs. *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ @@ -2919,7 +2919,7 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData *** @param oxt The object system tree, similar to a kind of "scope" (unused). *** *** @returns A pDriverData struct representing a driver instance, or - *** NULL if an error occures. + *** NULL if an error occurs. ***/ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { @@ -3352,8 +3352,8 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) *** so that they will have a pointer to the data. *** This buffer will not be modified unless the data is successfully *** found. If a value other than 0 is returned, the buffer is not updated. - *** @returns 0 if successsful, - *** -1 if an error occures. + *** @returns 0 if successful, + *** -1 if an error occurs. *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ @@ -3593,7 +3593,7 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val static StringVec* vec = NULL; if (vec != NULL) nmFree(vec, sizeof(StringVec)); - /** Allocate and initiallize the requested data. **/ + /** Allocate and initialize the requested data. **/ val->StringVec = vec = check_ptr(nmMalloc(sizeof(StringVec))); if (val->StringVec == NULL) return -1; val->StringVec->nStrings = target_cluster->Size; @@ -3653,15 +3653,15 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val *** provided cluster driver instance. *** *** Note: Failures from nmSysStrdup() and several others are ignored because - *** the worst case senario is that the attributes are set to null, which + *** the worst case scenario is that the attributes are set to null, which *** will cause them to be ignored. I consider that to be better than than *** throwing an error that could unnecessarily disrupt normal usage. *** *** @param inf_v The driver instance to be read. *** @param attr_name The name of the requested attribute. *** @param oxt The object system tree, similar to a kind of "scope" (unused). - *** @returns A presentation hints object, if successsful, - *** NULL if an error occures. + *** @returns A presentation hints object, if successful, + *** NULL if an error occurs. ***/ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { @@ -3954,13 +3954,13 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /*** Returns the name of the first attribute that one can get from *** this driver instance (using GetAttrType() and GetAttrValue()). *** Resets the internal variable (TargetAttrIndex) used to maintain - *** itteration state for clusterGetNextAttr(). + *** iteration state for clusterGetNextAttr(). *** *** @param inf_v The driver instance to be read. *** @param oxt Unused. *** @returns The name of the first attribute. ***/ -char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt) +char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { tprintf("Warning: clusterGetFirstAttr() is under active development.\n"); pDriverData driver_data = (pDriverData)inf_v; @@ -3973,13 +3973,13 @@ char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt) /*** Returns the name of the next attribute that one can get from *** this driver instance (using GetAttrType() and GetAttrValue()). *** Uses an internal variable (TargetAttrIndex) used to maintain - *** the state of this itteration over repeated calls. + *** the state of this iteration over repeated calls. *** *** @param inf_v The driver instance to be read. *** @param oxt Unused. *** @returns The name of the next attribute. ***/ -char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt) +char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { tprintf("Warning: clusterGetNextAttr("); pDriverData driver_data = (pDriverData)inf_v; @@ -4004,7 +4004,7 @@ char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt) *** *** @param inf_v The driver instance to be checked. *** @param info The struct to be populated with driver flags. - *** @returns 0 if succesful, + *** @returns 0 if successful, *** -1 if the driver is an unimplemented type (should never happen). ***/ int clusterInfo(void* inf_v, pObjectInfo info) @@ -4088,14 +4088,14 @@ int clusterInfo(void* inf_v, pObjectInfo info) /*** Returns the name of the first method that one can execute from *** this driver instance (using clusterExecuteMethod()). Resets the - *** internal variable (TargetMethodIndex) used to maintain itteration + *** internal variable (TargetMethodIndex) used to maintain iteration *** state for clusterGetNextMethod(). *** *** @param inf_v The driver instance to be read. *** @param oxt Unused. - *** @returns The name of the first methd. + *** @returns The name of the first method. ***/ -char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt) +char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { tprintf("Warning: clusterGetFirstMethod() is under active development.\n"); pDriverData driver_data = (pDriverData)inf_v; @@ -4108,13 +4108,13 @@ char* clusterGetFirstMethod(void* inf_v, pObjTrxTree oxt) /*** Returns the name of the next method that one can get from *** this driver instance (using GetAttrType() and GetAttrValue()). *** Uses an internal variable (TargetMethodIndex) used to maintain - *** the state of this itteration over repeated calls. + *** the state of this iteration over repeated calls. *** *** @param inf_v The driver instance to be read. *** @param oxt Unused. *** @returns The name of the next method. ***/ -char* clusterGetNextMethod(void* inf_v, pObjTrxTree oxt) +char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { tprintf("Warning: clusterGetNextMethod() is under active development."); pDriverData driver_data = (pDriverData)inf_v; @@ -4247,7 +4247,7 @@ static void ci_CacheFreeSearch(pXHashEntry entry, void* path) *** @param param A possibly optional param passed to the method. *** @param oxt The object system tree, similar to a kind of "scope" (unused). ***/ -int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree oxt) +int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) { tprintf("Warning: clusterExecuteMethod(\"%s\") is under active development.\n", method_name); pDriverData driver_data = (pDriverData)inf_v; @@ -4304,7 +4304,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx )); if (failed) { - mssErrorf(0, "Cluster", "Unexpected error occured while showhing caches."); + mssErrorf(0, "Cluster", "Unexpected error occurred while showhing caches."); ret = -1; } @@ -4345,7 +4345,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx ); goto err; } - + if (strcmp(method_name, "stat") == 0) { unsigned long long ExpectedOpenCalls = 10666; @@ -4388,25 +4388,25 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx // LINK #functions /** Not implemented. **/ -int clusterCreate(pObject obj, int mask, pContentType systype, char* usrtype, pObjTrxTree* oxt) +int clusterCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterCreate() is not implemented."); return -ENOSYS; } /** Not implemented. **/ -int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) +int clusterDelete(pObject obj, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterDeleteObj() is not implemented."); + mssErrorf(1, "Cluster", "clusterDelete() is not implemented."); return -1; } /** Not implemented. **/ -int clusterDelete(pObject obj, pObjTrxTree* oxt) +int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterDelete() is not implemented."); + mssErrorf(1, "Cluster", "clusterDeleteObj() is not implemented."); return -1; } /** Not implemented. **/ -int clusterRead(void* inf_v, char* buffer, int maxcnt, int offset, int flags, pObjTrxTree* oxt) +int clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterRead() not implemented."); fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); @@ -4419,25 +4419,25 @@ int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObj return -1; } /** Not implemented. **/ -int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree oxt) +int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are imutable."); return -1; } /** Not implemented. **/ -int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree oxt) +int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are imutable."); return -1; } /** Not implemented. **/ -void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree oxt) +void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterOpenAttr() not implemented."); return NULL; } /** Not implemented. **/ -int clusterCommit(void* inf_v, pObjTrxTree *oxt) +int clusterCommit(void* inf_v, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are imutable."); return 0; @@ -4451,7 +4451,7 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt) *** - Initializing global data needed for the driver. *** *** @returns 0 if successful, or - *** -1 if an error occured. + *** -1 if an error occurs. ***/ int clusterInitialize(void) { @@ -4470,10 +4470,10 @@ int clusterInitialize(void) memset(&ClusterStatistics, 0, sizeof(ClusterStatistics)); /** Setup the structure. **/ - if (check_ptr(strcpy(drv->Name, "clu - Clustering Driver")) == NULL) goto err; + if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err; if (!check(xaInit(&drv->RootContentTypes, 1))) goto err; if (!check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster"))) goto err; - drv->Capabilities = OBJDRV_C_TRANS | OBJDRV_C_FULLQUERY; /* TODO: Greg, are these correct? Should I add any others? */ + drv->Capabilities = 0; /* TODO: Greg - Should I add any of these? */ /** Setup the function references. **/ drv->Open = clusterOpen; @@ -4503,8 +4503,11 @@ int clusterInitialize(void) drv->Commit = clusterCommit; drv->GetQueryCoverageMask = NULL; drv->GetQueryIdentityPath = NULL; - - /** Register some structures. **/ + + /** Register the driver. **/ + if (!check(objRegisterDriver(drv))) goto err; + + /** Register structs used in this project with the newmalloc memory management system. **/ nmRegister(sizeof(SourceData), "ClusterSourceData"); nmRegister(sizeof(Cluster), "Cluster"); nmRegister(sizeof(ClusterData), "ClusterData"); @@ -4514,42 +4517,6 @@ int clusterInitialize(void) nmRegister(sizeof(ClusterQuery), "ClusterQuery"); nmRegister(sizeof(ClusterDriverCaches), "ClusterDriverCaches"); - /** Print debug size info. **/ -// char buf1[16], buf2[16], buf3[16], buf4[16], buf5[16], buf6[16], buf7[16], buf8[16]; -// tprintf( -// "Cluster driver struct sizes:\n" -// " > sizeof(SourceData): %s\n" -// " > sizeof(Cluster): %s\n" -// " > sizeof(ClusterData): %s\n" -// " > sizeof(SearchData): %s\n" -// " > sizeof(NodeData): %s\n" -// " > sizeof(DriverData): %s\n" -// " > sizeof(ClusterQuery): %s\n" -// " > sizeof(ClusterDriverCaches): %s\n", -// snprint_bytes(buf1, sizeof(buf1), sizeof(SourceData)), -// snprint_bytes(buf2, sizeof(buf2), sizeof(Cluster)), -// snprint_bytes(buf3, sizeof(buf3), sizeof(ClusterData)), -// snprint_bytes(buf4, sizeof(buf4), sizeof(SearchData)), -// snprint_bytes(buf5, sizeof(buf5), sizeof(NodeData)), -// snprint_bytes(buf6, sizeof(buf6), sizeof(DriverData)), -// snprint_bytes(buf7, sizeof(buf7), sizeof(ClusterQuery)), -// snprint_bytes(buf8, sizeof(buf8), sizeof(ClusterDriverCaches)) -// ); -// - // 'st' (7: 13) collides with 'an' (7: 11) -// char* str1 = "This is a very long string of text"; -// char* str2 = "This is a very long string of textttttttttttt"; -// pVector v1 = ca_build_vector(str1); -// pVector v2 = ca_build_vector(str2); -// ca_fprint_vector(stdout, v1); printf("\n"); -// ca_fprint_vector(stdout, v2); printf("\n"); -// fprintf(stderr, "'%s' ?= '%s' -> %g\n", str1, str2, ca_cos_compare(v1, v2)); -// ca_free_vector(v1); -// ca_free_vector(v2); - - /** Register the driver. **/ - if (!check(objRegisterDriver(drv))) goto err; - /** Success. **/ return 0; From fa28afa4b0c282b45fe46a87a7d11db904578925 Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 14 Nov 2025 11:48:53 -0700 Subject: [PATCH 009/361] Add ClusterDriverRequirements (forgot to commit them before). Add known issues to string similarity documentation. Clean up and organize todos. Clean up testing code in several files. --- .../ClusterDriverRequirements-old.md | 186 +++++++++++++++ centrallix-sysdoc/OSDriver_Authoring.md | 22 +- centrallix-sysdoc/string_similarity.md | 6 +- centrallix/expression/exp_functions.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 217 ++++++------------ 5 files changed, 271 insertions(+), 162 deletions(-) create mode 100644 centrallix-sysdoc/ClusterDriverRequirements-old.md diff --git a/centrallix-sysdoc/ClusterDriverRequirements-old.md b/centrallix-sysdoc/ClusterDriverRequirements-old.md new file mode 100644 index 000000000..601f41703 --- /dev/null +++ b/centrallix-sysdoc/ClusterDriverRequirements-old.md @@ -0,0 +1,186 @@ + +## Cluster Driver Specifications +### Cluster Open +```c +void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); +``` +`clusterOpen()` shall... +- Create or read a node, as indicated by passed flags. + - Read flags from `obj->Mode`. + - If `O_EXCL` is specified, `O_CREAT` is specified, and there are no other elements in the path, create a new node. + - Otherwise attempt to read the previous object (in `obj->Prev`). + - If this fails and `O_CREAT` is specified, create a new node. + - If there is still no node, fail. +- Parse the provided path. + - Use `obj_internal_PathPart()` with the pathname in `obj->Pathname`. + - Not parse previous parts of the path already parsed by other drivers. + - Start at the `obj->SubPtr`-th path element (skipping `obj->SubPtr - 1` elements). + - Consume elements in the path until `obj_internal_PathPart()` returns `NULL`. + - Store the number of elements consumed in `obj->SubCnt`. +- Determine what data is being targeted from the parsed path. + - If the relevant part of the path contains only the name of the file, the driver shall set the target to root. + - If it contains the name of a valid (sub)cluster or search, the driver shall set the target to that (sub)cluster or search. + - Otherwise, the driver shall produce a descriptive error. +- Parse the provided structure file. + - Follow the spec given in `cluster-schema.cluster`. + - Produce descriptive errors when issues are detected. +- Return a new struct containing necessary information, including: + - The name, source path, and attribute name. + - All parameters (and a param list for scope), clusters, and searches. + - Each parameter shall be represented by a `pParam` object (see `params.h`). + - Each cluster shall be represented by a struct with information including: + - Its name, clustering algorithm, and similarity measure. + - The number of clusters to generate. + - If a k-means algorithm is specified, the improvement threshold. + - The maximum number of iterations to run. + - A list of subclusters with at least this information for each. + - Each search shall be represented by a struct with information including: + - Its name, threshold, and similarity measure. + - Its source, which is a valid cluster name of a cluster in the clusters list. + - Information about targets, derived from the path. + +### Cluster Close +```c +int clusterClose(void* inf_v, pObjTrxTree* oxt); +``` +`clusterClose()` shall... +- Free all allocated data in the driver struct. +- Close any open files or the like in the driver struct. +- Return 0. + +### Cluster Open Query +```c +void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); +``` +`clusterOpenQuery()` shall... +- Return a query struct that can be passed to `clusterQueryFetch()`. + - This struct shall contain an index to the last row accessed (starting at 0). + - This struct shall contain a pointer to the driver data. + +### Cluster Query Fetch +```c +void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) +``` +`clusterQueryFetch()` shall... +- If the driver struct targets the root node, this function shall produce an error. +- If the driver struct targets an entry, this function shall produce a different error. +- If the driver targets a cluster or search, this function shall return a driver struct targetting the cluster or search *entry* (respectively) indicated by the query struct's row pointer, and increment the pointer. + - Exception: If no data remains, this function shall return `NULL` instead. + - This request shall cause clustering / searching to execute, if it has not executed already. + +### Cluster Query Close +```c +int clusterQueryClose(void* qy_v, pObjTrxTree* oxt); +``` +`clusterQueryClose()` shall... +- Free all allocated data in the query struct. +- Close any open files or the like in the query struct. +- Return 0. + +### Cluster Get Attribute Type +```c +int clusterGetAttrType(void* qy_v, pObjTrxTree* oxt); +``` +`clusterGetAttrType()` shall... +- Return the `DATA_T_...` type of the requested attribute, or `DATA_T_UNAVAILABLE` if the attribute does not exist. +- The name, content_type, inner_type, and outer_type attributes shall be of type `DATA_T_STRING`. +- The last_modification attribute shall be of type `DATA_T_DATETIME`. +- If the target is root... + - The source and attr_name attributes shall be of type `DATA_T_STRING`. +- If the target is a cluster... + - The algorithm and similarity_measure attributes shall be of type `DATA_T_STRING`. + - The num_clusters and max_iterations attributes shall be of type `DATA_T_INTEGER`. + - The improvement_threshold and average_similarity attributes shall be of type `DATA_T_DOUBLE`. +- If the target is a search... + - The source and similarity_measure attribute shall be of type `DATA_T_STRING`. + - The threshold attribute shall be of type `DATA_T_DOUBLE`. +- If the target is a cluster entry... + - The val attribute shall be of type `DATA_T_INTEGER`. + - The sim attribute shall be of type `DATA_T_DOUBLE`. +- If the target is a search entry... + - The val1 and val2 attribute shall be of type `DATA_T_INTEGER`. + - The sim attribute shall be of type `DATA_T_DOUBLE`. + +### Cluster Get Attribute Value +```c +int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* _); +``` +`clusterGetAttrValue()` shall... +- If the given datatype does not match that returned from `clusterGetAttrType()`, the function shall produce an error. +- Requesting the name attribute shall produce the following values, depending on the target: + - If the target is root, the name in the driver struct (aka. the one specified in the .cluster file) shall be produced. + - If the target is a cluster or cluster entry, the name of the cluster shall be produced. + - If the target is a search or search entry, the name of the search shall be produced. +- Requesting the annotation shall produce some string describing the driver. +- Requesting the outer_type shall produce "system/row". +- Requesting the inner_type or content_type shall produce "system/void". (All path elements are consumed.) +- If the target is root... + - Requesting source shall produce the source path. + - Requesting attr_name shall produce the attribute name. +- If the target is a cluster... + - Requesting algorithm shall produce the name of the clustering algorithm. + - Requesting similarity_measure shall produce the name of the similarity measure. + - Requesting num_clusters shall produce the number of clusters. + - Requesting max_iterations shall produce the maximum number of iterations. + - Requesting improvement_threshold shall produce the minimum improvement threshold. + - Requesting average_similarity shall produce the average size of clusters, running clustering / searching algorithms, if necessary. +- If the target is a search... + - Requesting source shall produce the name of the source cluster for the search. + - Requesting similarity_measure shall produce the name of the similarity measure. + - Requesting threshold shall produce the filtering threshold. +- If the target is a cluster entry... + - Requesting val shall produce the value of the data point in this cluster. + - Requesting sim shall produce the similarity of the data point to the center of the cluster. +- If the target is a cluster entry... + - Requesting val1 or val2 shall produce the first and second value (respectively)detected in this search. + - Requesting sim shall produce the similarity of these two data points. + + +### Cluster Get First Attribute +```c +char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt); +``` +`clusterGetFirstAttr()` shall... +- Reset the current attribute index on the driver struct to 0. +- Return the value of invoking `clusterGetNextAttr()`. + +### Cluster Get Next Attribute +```c +char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt); +``` +`clusterGetNextAttr()` shall... +- Return the attribute name at the attribute index given by the driver struct in the list of attributes based on the target type. +- Return `NULL` if the end of the list has been reached. +- Increase the attribute index on the driver struct by 1. + +- The attribute name list for a targetting root shall include "source" and "attr_name". +- The attribute name list for a targetting a cluster shall include "algorithm", "similarity_measure", "num_clusters", "improvement_threshold", and "max_iterations". +- The attribute name list for a targetting a search shall include "source", "threshold", and "similarity_measure". +- The attribute name list for a targetting a cluster entry shall include "val" and "sim". +- The attribute name list for a targetting a search entry shall include "val1", "val2", and "sim". + +### Cluster Get Next Attribute +```c +int clusterInfo(void* inf_v, pObjectInfo info); +``` +`clusterInfo()` shall... +- Provide the OBJ_INFO_F_CANT_ADD_ATTR flag. +- Provide the OBJ_INFO_F_CANT_HAVE_CONTENT flag. +- Provide the OBJ_INFO_F_NO_CONTENT flag. +- If the target is a root... + - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. + - Provide the OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag. + - Provide the OBJ_INFO_F_HAS_SUBOBJ flag if there is at least one cluster or search. + - Provide the OBJ_INFO_F_NO_SUBOBJ flag otherwise. + - Provide the total number of clusters and searches as the number of subobjects. +- If the target is a cluster... + - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. + - Provide the OBJ_INFO_F_HAS_SUBOBJ flag. + - If the algorithm has been run, provide OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag and the number of data points clustered as the number of subobjects. +- If the target is a search... + - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. + - If the algorithm has been run... + - Provide OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag and the number of elements found by the search as the number of subobjects. + - Provide the OBJ_INFO_F_HAS_SUBOBJ flag if at least one element was found. +- If the target is a cluster entry or a search entry... + - Provide the OBJ_INFO_F_CANT_HAVE_SUBOBJ flag. \ No newline at end of file diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index d00c192f6..f679dac32 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -202,9 +202,12 @@ Using the example above, we can query from the database using a statement like ` This section describes the standard interface between the OSML and the ObjectSystem driver itself. Every driver should implement certain required functions. (**Note**: Many drivers "implement" some required functions to simply fail with a not implemented or not supported error. For example, most database drivers "implement" `Read()` and `Write()` this way because database content should be queried, not read). Various optional functions are also available, which a driver is not required to implement. The driver should implement an `Initialize()` function, as well as the following (* indicates required functions): | Function Name | Description @@ -332,7 +335,8 @@ The `Open()` function opens a given file to create a new driver instance. This p 5. Return a pointer to the node instance as a void pointer. This pointer will be passed as `void* inf_v` to the driver in subsequent calls involving this object (except the Query functions, discussed below). - 📖 **Note - Transactions**: If the os driver specified the `OBJDRV_C_TRANS` capability, it must respect the current state of the user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). This is handled using the transaction tree parameter (`oxt : pObjTrxTree*`). The transaction later is discussed in depth in the ??? section. - + + #### Accessing the Node Object If `O_CREAT` and `O_EXCL` are both specified in `parent->Mode`, the driver should **only** create a new file and fail if the file already exists (refusing to open and read it). Otherwise, the driver should read an existing file, or create one if it does not exist and `O_CREAT` is specified, failing if no file can be read or created. @@ -540,7 +544,6 @@ The `QueryFetch()` function fetches a driver instance pointer (aka. an `inf_v` p | mode | int | The open mode for the new object, the same as `obj->Mode` in `Open()`. | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. - The driver should add an element to the `obj->Pathname` structure to indicate the path of the returned child object. This will involve a process somewhat like this, where: - `new_name : char*` is the new object's name. - `qy : pMyDriversQueryInf` is the current query structure. @@ -747,7 +750,7 @@ The return value, `hints : ObjPresentationHints`, contains the following useful - `hints->MaxValue : void*`: An expression defining the maximum valid value. - `hints->EnumList : XArray`: If the attribute is a string enum, this XArray lists the valid string values. - `hints->EnumQuery : char*`: A query string which enumerates the valid values a string enum attribute. -- `hints->Format : char*`: presentation format - datetime or money +- `hints->Format : char*`: presentation format - datetime or money - `hints->AllowChars : char*`: An array of all valid characters for a string attribute, NULL to allow all characters. - `hints->BadChars : char*`: An array of all invalid characters for a string attribute. - `hints->Length : int`: The maximum length of data that can be included in a string attribute. @@ -785,7 +788,7 @@ The following macros are provided for setting style flags: - `OBJ_PH_STYLE_SEPWINDOW`: Prefer separate windows for grouped fields. - `OBJ_PH_STYLE_ALWAYSDEF`: Always reset the default value when this attribute is modified. - `OBJ_PH_STYLE_CREATEONLY`: This attribute is writeable only when created, after that it is read only. -- `OBJ_PH_STYLE_MULTISEL`: Multiple select +- `OBJ_PH_STYLE_MULTISEL`: Multiple select - `OBJ_PH_STYLE_KEY`: This attribute is a primary key. - `OBJ_PH_STYLE_APPLYCHG`: Presentation hints should be applied on DataChange instead of on DataModify. @@ -812,10 +815,12 @@ The `pObjectInfo` struct has two fields: `Flags` and `nSubobjects`. This functi - `OBJ_INFO_F_CANT_SEEK`: Seeking is not supported at all. - `OBJ_INFO_F_CAN_ADD_ATTR` / `OBJ_INFO_F_CANT_ADD_ATTR`: Indicates that the object does or does not allow attributes to be added with the [AddAttr()](#function-addattr) function. - `OBJ_INFO_F_SUPPORTS_INHERITANCE`: Indicates that the object supports inheritance through attributes such as `cx__inherit`. See ??? for more information about object inheritance. - + + - `OBJ_INFO_F_FORCED_LEAF`: Indicates that the object is forced to be a 'leaf' unless ls__type used. - `OBJ_INFO_F_TEMPORARY`: Indicates that this is a temporary object without a vaoid pathname. + The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. @@ -852,7 +857,6 @@ Although using the structure file format may be complex, it allows significant f Structure files are accessed via the st_node (SN) and stparse (SP) modules. The st_node module loads and saves the structure file heirarchies as a whole. It also manages caching to reduce disk activity and eliminate repeated parsing of the same file. The stparse module provides access to the individual attributes and groups of attributes within a node structure file. For example, if two sessions open two files, `/test1.rpt` and `/test2.rpt` the st_node module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be re-parsed. The file will be re-parsed if its timestamp changes. - If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then st_node prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the st_node module to re-read the structure file defining the node object. Otherwise, the st_node module keeps track of the timestamp, and if it changes, the node object is re-read and re-parsed. @@ -1157,7 +1161,7 @@ Frees a block of memory allocated by `nmSysMalloc()`, `nmSysRealloc()`, or `nmSy ## V Other Utility Modules - + The Centrallix library (`centralllix-lib`) has a host of useful utility modules. These include `xarray`, used for managing growable arrays; `xstring`, used for managing growable strings; `xhash`, used for managing hash tables with no overflow problems and variable-length keys; `expression`, used for compiling and evaluating expressions; and `mtsession`, used for managing session-level variables and reporting errors. diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index b9a3a28b6..33667a05c 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -167,4 +167,8 @@ If the clustering could be expanded with an additional step that makes clusters Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-medoids-clustering), and [DBScan](#db-scan)) above are proposed but lack an implementation. They may be effective and useful, however, to reduce development time, they have not yet been implemented. ### Upgrade Other Duplicate Detection Systems -When a new record is entered, a quick scan is run to check if it might be a duplicate. There is also a button in the UI for a record that lets you run a duplicate check. These systems could also be upgraded using the new algorithms and strategies developed for general duplicate detection. \ No newline at end of file +When a new record is entered, a quick scan is run to check if it might be a duplicate. There is also a button in the UI for a record that lets you run a duplicate check. These systems could also be upgraded using the new algorithms and strategies developed for general duplicate detection. + +### Known Issues +- The cluster driver often fails to open the structure file if it was modifed since the last time the path was openned. Opening a different path (including the root path, even though it does not support queries) fixes this issue. This is either a bug in the st_node caching or in the cluster driver's usage of stparse. +- The cluster does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to invalidate caches when data is changed, or use a fresh object system instance. diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 159c292b0..dd65a0c52 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -75,7 +75,7 @@ #include "obj.h" -/** TODO: I think this should be moved to datatypes. **/ +/** TODO: Greg - I think this should be moved to datatypes. **/ /** Should maybe replace duplocate functionality elsewhere. **/ static char* ci_TypeToStr(const int type) { diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 4bf94ebe9..462d0625f 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -72,18 +72,6 @@ *** https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors ***/ -/** Pure Laziness. **/ -// #define ENABLE_TPRINTF - -/** Debugging **/ -#ifndef ENABLE_TPRINTF -void void_func() {} -#define tprintf void_func -#endif -#ifdef ENABLE_TPRINTF -#define tprintf printf -#endif - /** Defaults for unspecified optional attributes. **/ #define DEFAULT_MIN_IMPROVEMENT 0.0001 #define DEFAULT_MAX_ITERATIONS 64u @@ -91,7 +79,7 @@ void void_func() {} /** ================ Stuff That Should Be Somewhere Else ================ **/ /** ANCHOR[id=temp] **/ -/** TODO: I think this should be moved to mtsession. **/ +/** TODO: Greg - I think this should be moved to mtsession. **/ /*** I caused at least 10 bugs so far trying to pass format specifiers to *** mssError without realizing that it didn't support them. Eventually, I *** got fed up enough with the whole thing to write the following function. @@ -147,7 +135,7 @@ void mssErrorf(int clr, char* module, const char* format, ...) } -/** TODO: I think this should be moved to datatypes. **/ +/** TODO: Greg - I think this should be moved to datatypes. **/ /** Should maybe replace current type parsing in the presentation hints. **/ /*** Parse the given string into a datatype. The case of the first character *** is ignored, but all other characters must be capitalized correctly. @@ -210,8 +198,8 @@ static int ci_TypeFromStr(const char* str) return -1; } -/** TODO: I think this should be moved to datatypes. **/ -/** Should maybe replace duplocate functionality elsewhere. **/ +/** TODO: Greg - I think this should be moved to datatypes. **/ +/** Should maybe replace this functionality where it appears elsewhere. **/ static char* ci_TypeToStr(const int type) { switch (type) @@ -234,7 +222,7 @@ static char* ci_TypeToStr(const int type) return "Invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ } -/** TODO: I think this should be moved to xarray. **/ +/** TODO: Greg - I think this should be moved to xarray. **/ /*** Trims an xArray, returning a new array (with nmSysMalloc). *** *** @param arr The array to be trimmed. @@ -803,7 +791,6 @@ static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_va /** Issue hint. **/ ci_GiveHint(guess); - tprintf(" > Similarity: %.4g\n", ca_lev_compare(value, guess)); return true; } @@ -816,9 +803,9 @@ static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_va *** *** @attention - Promises that a failure invokes mssError() at least once. *** - *** TODO: Greg - Review Carefully. - *** This function took a lot of debugging to get it to work. Please make sure - *** it works correctly and properly requires runserver() for dynamic attributes. + *** TODO: Greg - Review carefully. I think this code is the reason that runserver() + *** is NOT REQUIRED for dynamic attributes in the cluster driver. I had to debug + *** and rewrite this for ages and it uses several functions I don't 100% understand. ***/ static int ci_ParseAttribute( pStructInf inf, @@ -830,7 +817,6 @@ static int ci_ParseAttribute( bool print_type_error) { int ret; - tprintf("Invoking ci_ParseAttribute('%s').\n", attr_name); /** Get attribute inf. **/ pStructInf attr_info = stLookup(inf, attr_name); @@ -1035,10 +1021,8 @@ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, if (source_maybe != NULL) { /** Cache hit. **/ - tprintf("# source: \"%s\"\n", source_data->Key); /** Cause an immediate invalid read if cache was incorrectly freed. **/ - tprintf("--> Name: %s\n", source_maybe->Name); /** Free data we don't need. **/ nmSysFree(source_data->Key); @@ -1049,7 +1033,6 @@ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, } /** Cache miss: Add the new object to the cache for next time. **/ - tprintf("+ source: \"%s\"\n", source_data->Key); if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->Key, (void*)source_data))) goto err_free; @@ -1087,8 +1070,6 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) { int result; - tprintf("Parsing cluster: %s\n", inf->Name); - /** Extract values. **/ pParamObjects param_list = node_data->ParamList; pSourceData source_data = node_data->SourceData; @@ -1252,7 +1233,7 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) if (group_type == NULL) goto err_free_subclusters; if (strcmp(group_type, "cluster/cluster") != 0) { - fprintf(stderr, + mssErrorf(1, "Cluster", "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", name, group_type, inf->Name ); @@ -1333,13 +1314,7 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) /** Check for a cached version. **/ pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, key); if (cluster_maybe != NULL) - { - /** Cache hit. **/ - tprintf("# cluster: \"%s\"\n", key); - - /** Cause invalid read if cache was incorrectly freed. **/ - tprintf("--> Name: %s\n", cluster_maybe->Name); - + { /* Cache hit. */ /** Free the parsed cluster that we no longer need. */ ci_FreeClusterData(cluster_data, false); nmSysFree(key); @@ -1349,7 +1324,6 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) } /** Cache miss. **/ - tprintf("+ cluster: \"%s\"\n", key); if (!check(xhAdd(&ClusterDriverCaches.ClusterDataCache, key, (void*)cluster_data))) goto err_free_key; return cluster_data; @@ -1386,9 +1360,7 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) *** @returns A new pSearchData struct on success, or NULL on failure. ***/ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) - { - tprintf("Parsing search: %s\n", inf->Name); - + { /** Allocate space for search struct. **/ pSearchData search_data = check_ptr(nmMalloc(sizeof(SearchData))); if (search_data == NULL) goto err; @@ -1525,10 +1497,7 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Check for a cached version. **/ pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); if (search_maybe != NULL) - { - /** Cache hit. **/ - tprintf("# search: \"%s\"\n", key); - tprintf("--> Name: %s\n", search_maybe->Name); /* Cause invalid read if cache was incorrectly freed. */ + { /* Cache hit. */ /** Free the parsed search that we no longer need. **/ ci_FreeSearchData(search_data); @@ -1539,7 +1508,6 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Cache miss. **/ - tprintf("+ search: \"%s\"\n", key); check(xhAdd(search_cache, key, (void*)search_data)); return search_data; @@ -1751,7 +1719,6 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) ); goto err_free_arrs; } - tprintf("Found provided value for %s of type %s\n", param->Name, ci_TypeToStr(param->Type)); /** Provided value successfully handled, we're done. **/ break; @@ -1860,7 +1827,7 @@ static void ci_FreeSourceData(pSourceData source_data) /** Guard segfault. **/ if (source_data == NULL) { - fprintf(stderr, "Call to ci_FreeSourceData(NULL);\n"); + fprintf(stderr, "Warning: Call to ci_FreeSourceData(NULL);\n"); return; } @@ -1927,7 +1894,7 @@ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) /** Guard segfault. **/ if (cluster_data == NULL) { - fprintf(stderr, "Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + fprintf(stderr, "Warning: Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); return; } @@ -1983,7 +1950,7 @@ static void ci_FreeSearchData(pSearchData search_data) /** Guard segfault. **/ if (search_data == NULL) { - fprintf(stderr, "Call to ci_FreeSearchData(NULL);\n"); + fprintf(stderr, "Warning: Call to ci_FreeSearchData(NULL);\n"); return; } @@ -2019,7 +1986,7 @@ static void ci_FreeNodeData(pNodeData node_data) /** Guard segfault. **/ if (node_data == NULL) { - fprintf(stderr, "Call to ci_FreeNodeData(NULL);\n"); + fprintf(stderr, "Warning: Call to ci_FreeNodeData(NULL);\n"); return; } @@ -2111,6 +2078,13 @@ static void ci_ClearCaches(void) ***/ static unsigned int ci_SizeOfSourceData(pSourceData source_data) { + /** Guard segfault. **/ + if (source_data == NULL) + { + fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); + return 0u; + } + unsigned int size = 0u; if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); @@ -2146,20 +2120,22 @@ static unsigned int ci_SizeOfSourceData(pSourceData source_data) *** @returns The size in bytes of the struct and all internal allocated data. ***/ static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) - { + { + /** Guard segfault. **/ + if (cluster_data == NULL) + { + fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + return 0u; + } + unsigned int size = 0u; if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); if (cluster_data->Clusters != NULL) { const unsigned int nVectors = cluster_data->SourceData->nVectors; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - { - const unsigned int cluster_size = cluster_data->Clusters[i].Size; - size += cluster_size * sizeof(char*); - size += cluster_size * sizeof(pVector); - } - size += nVectors * sizeof(Cluster); - size += nVectors * sizeof(double); + size += cluster_data->Clusters[i].Size * (sizeof(char*) + sizeof(pVector)); + size += nVectors * (sizeof(Cluster) + sizeof(double)); } if (cluster_data->SubClusters != NULL) { @@ -2188,6 +2164,13 @@ static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursi ***/ static unsigned int ci_SizeOfSearchData(pSearchData search_data) { + /** Guard segfault. **/ + if (search_data == NULL) + { + fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); + return 0u; + } + unsigned int size = 0u; if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); if (search_data->Dups != NULL) size += search_data->nDups * (sizeof(void*) + sizeof(Dup)); @@ -2224,7 +2207,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (!check(objCurrentDate(&source_data->DateComputed))) goto end; /** Open the source path specified by the .cluster file. **/ - tprintf("Opening...\n"); pObject obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); if (obj == NULL) { @@ -2239,7 +2221,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Generate a "query" for retrieving data. **/ - tprintf("Opening query...\n"); pObjQuery query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); if (query == NULL) { @@ -2265,7 +2246,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (!check(xaInit(&vector_xarray, 64))) goto end_free_data; /** Fetch data and build vectors. **/ - tprintf("Skips: "); while (true) { pObject entry = objQueryFetch(query, O_RDONLY); @@ -2307,7 +2287,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) ret = objGetAttrValue(entry, source_data->NameAttr, DATA_T_STRING, POD(&data)); if (ret != 0) { - tprintf("\n"); mssErrorf(0, "Cluster", "Failed to value for %uth entry:\n" " > Attribute: ['%s':'%s' : String]\n" @@ -2326,7 +2305,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Skip empty strings. **/ if (strlen(data) == 0) { - tprintf("_"); check(fflush(stdout)); /* Failure ignored. */ continue; } @@ -2348,7 +2326,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (ca_has_no_pairs(vector)) { /** Skip pVector with no pairs. **/ - tprintf("."); check(fflush(stdout)); /* Failure ignored. */ ca_free_vector(vector); continue; @@ -2391,7 +2368,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); if (ret != 0) { - tprintf("\n"); mssErrorf(0, "Cluster", "Failed to value for key on %uth entry:\n" " > Attribute: ['%s':'%s' : String]\n" @@ -2424,7 +2400,7 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) // ret = ret; // Fall-through: Failure ignored. } } - tprintf("\nData acquired.\n"); + source_data->nVectors = vector_xarray.nItems; if (source_data->nVectors == 0) { @@ -2449,7 +2425,6 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (source_data->Vectors == NULL) goto end_free_data; /** Success. **/ - fprintf(stderr, "[SourceData: %s] Compute done.\n", source_data->Name); successful = true; end_free_data: @@ -2539,7 +2514,6 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { case ALGORITHM_NONE: { - tprintf("Applying no clustering...\n"); /** Put all the data into one cluster. **/ pCluster first_cluster = &cluster_data->Clusters[0]; first_cluster->Size = source_data->nVectors; @@ -2554,13 +2528,11 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) case ALGORITHM_SLIDING_WINDOW: /** Computed in each search for efficiency. **/ - tprintf("Skipping sliding window clustering...\n"); memset(cluster_data->Clusters, 0, clusters_size); break; case ALGORITHM_KMEANS: { - tprintf("Applying kmeans clustering...\n"); /** Check for unimplemented similarity measures. **/ if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) { @@ -2577,7 +2549,6 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (labels == NULL) goto err_free_sims; /** Run kmeans. **/ - tprintf("Running kmeans\n"); Timer timer_i, *timer = timer_start(timer_init(&timer_i)); const bool successful = check(ca_kmeans( source_data->Vectors, @@ -2589,7 +2560,6 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) cluster_data->Sims )); timer_stop(timer); - tprintf("Clustering done after %.4lfs.\n", timer_get(timer)); if (!successful) goto err_free_sims; /** Convert the labels into clusters. **/ @@ -2636,7 +2606,6 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) } /** Success. **/ - fprintf(stderr, "[ClusterData: %s] Compute done.\n", cluster_data->Name); return 0; err_free_sims: @@ -2692,8 +2661,6 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Record the date and time. **/ if (!check(objCurrentDate(&search_data->DateComputed))) goto err; - tprintf("Invoking search.\n"); - Timer timer_i, *timer = timer_start(timer_init(&timer_i)); /** Execute the search using the specified source and comparison function. **/ pXArray dups = NULL, dups_temp = NULL; switch (search_data->SimilarityMeasure) @@ -2771,10 +2738,8 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) ); goto err_free; } - timer_stop(timer); if (dups_temp == NULL) goto err_free; else dups = dups_temp; - tprintf("Search done after %.4lfs.\n", timer_get(timer)); /** Store dups. **/ search_data->nDups = dups->nItems; @@ -2783,7 +2748,6 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) : ci_xaToTrimmedArray(dups, 2); /** Success. **/ - fprintf(stderr, "[SearchData: %s] Compute done.\n", search_data->Name); return 0; err_free: @@ -2817,7 +2781,6 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) ***/ static int ci_GetParamType(void* inf_v, const char* attr_name) { - tprintf("Call to ci_GetParamType(\"%s\")\n", attr_name); pNodeData node_data = (pNodeData)inf_v; /** Find the parameter. **/ @@ -2861,7 +2824,6 @@ static int ci_GetParamType(void* inf_v, const char* attr_name) ***/ static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - tprintf("Call to ci_GetParamValue(\"%s\", %s)\n", attr_name, ci_TypeToStr(datatype)); pNodeData node_data = (pNodeData)inf_v; /** Find the parameter. **/ @@ -2896,7 +2858,6 @@ static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData /** Not implemented. **/ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - tprintf("Call to ci_SetParamValue(%s, %s)\n", attr_name, ci_TypeToStr(datatype)); mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are imutable."); return -1; } @@ -2923,7 +2884,7 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData ***/ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { - tprintf("Warning: clusterOpen(\"%s\") is under active development.\n", ci_file_name(parent)); + /** Update statistics. **/ ClusterStatistics.OpenCalls++; /** If CREAT and EXCL are specified, exclusively create it, failing if the file already exists. **/ @@ -2973,12 +2934,10 @@ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_typ driver_data->NodeData = node_data; /** Detect target from path. **/ - tprintf("Parsing node path: %d %d\n", parent->SubPtr, parent->SubCnt); parent->SubCnt = 0; char* target_name = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); if (target_name == NULL) { /** Target found: Root **/ - tprintf("Found target: Root.\n"); driver_data->TargetType = TARGET_ROOT; driver_data->TargetData = (void*)driver_data->NodeData->SourceData; return (void*)driver_data; /* Success. */ @@ -2992,7 +2951,6 @@ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_typ /** Target found: Cluster **/ driver_data->TargetType = TARGET_CLUSTER; - tprintf("Found target cluster: %s\n", cluster->Name); /** Check for sub-clusters in the path. **/ while (true) @@ -3014,7 +2972,6 @@ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_typ if (strcmp(sub_cluster->Name, path_part) != 0) continue; /** Target found: Sub-cluster **/ - tprintf("Found target sub-cluster: %s\n", sub_cluster->Name); cluster = sub_cluster; goto continue_descent; } @@ -3045,7 +3002,6 @@ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_typ mssErrorf(1, "Cluster", "Unknown path part %s.", extra_data); goto err_free_node; } - tprintf("Found target search: %s %d %d\n", search->Name, parent->SubPtr, parent->SubCnt); return (void*)driver_data; /* Success. */ } @@ -3088,7 +3044,6 @@ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_typ ***/ int clusterClose(void* inf_v, pObjTrxTree* oxt) { - tprintf("Warning: clusterClose() is under active development.\n"); pDriverData driver_data = (pDriverData)inf_v; ClusterStatistics.CloseCalls++; @@ -3124,7 +3079,6 @@ int clusterClose(void* inf_v, pObjTrxTree* oxt) void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) { ClusterStatistics.OpenQueryCalls++; - tprintf("Warning: clusterOpenQuery() is under active development.\n"); pClusterQuery cluster_query = check_ptr(nmMalloc(sizeof(ClusterQuery))); if (cluster_query == NULL) return NULL; cluster_query->DriverData = (pDriverData)inf_v; @@ -3148,10 +3102,11 @@ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { int ret; - ClusterStatistics.FetchCalls++; -// tprintf("Warning: clusterQueryFetch() is under active development.\n"); pClusterQuery cluster_query = (pClusterQuery)qy_v; + /** Update statistics. **/ + ClusterStatistics.FetchCalls++; + /** Ensure that the data being fetched exists and is computed. **/ TargetType target_type = cluster_query->DriverData->TargetType, new_target_type; unsigned int data_amount = 0u; @@ -3199,10 +3154,6 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); goto err; } - tprintf("Fetch Index: %u/16 (total: %u)\n", cluster_query->RowIndex, data_amount); - - /** Cap results to 16 for faster debugging. TODO: Israel - Remove. **/ -// data_amount = min(data_amount, 16); /** Check that the requested data exists, returning null if we've reached the end of the data. **/ if (cluster_query->RowIndex >= data_amount) return NULL; @@ -3233,9 +3184,7 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) *** @returns 0, success. ***/ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) - { -// tprintf("Warning: clusterQueryClose() is under active development.\n"); - + { nmFree(qy_v, sizeof(ClusterQuery)); return 0; } @@ -3254,6 +3203,8 @@ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; + + /** Update statistics. **/ ClusterStatistics.GetTypeCalls++; /** Guard possible segfault. **/ @@ -3266,10 +3217,6 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ if (attr_name[0] == 'k' || attr_name[0] == 's') goto handle_targets; - /** Debug info. **/ - if (oxt == NULL) tprintf(" > "); - tprintf("Call to clusterGetAttrType(%s)\n", attr_name); - /** Types for general attributes. **/ if (strcmp(attr_name, "name") == 0 || strcmp(attr_name, "annotation") == 0 @@ -3374,9 +3321,6 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ ) goto handle_targets; - /** Debug info. **/ - tprintf("Call to clusterGetAttrValue(%s)\n", attr_name); - /** Type check. **/ const int expected_datatype = clusterGetAttrType(inf_v, attr_name, NULL); if (datatype != expected_datatype) @@ -3665,7 +3609,6 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val ***/ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { - tprintf("Warning: clusterPresentationHints(\"%s\") is under active development.", attr_name); pDriverData driver_data = (pDriverData)inf_v; /** Malloc presentation hints struct. **/ @@ -3795,7 +3738,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - char buf[4u]; + char buf[8]; snprintf(buf, sizeof(buf), "%d", nClusteringAlgorithms); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -3825,7 +3768,7 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - char buf[4u]; + char buf[8]; snprintf(buf, sizeof(buf), "%d", nSimilarityMeasures); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -3962,7 +3905,6 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb ***/ char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { - tprintf("Warning: clusterGetFirstAttr() is under active development.\n"); pDriverData driver_data = (pDriverData)inf_v; driver_data->TargetAttrIndex = 0u; return clusterGetNextAttr(inf_v, oxt); @@ -3981,10 +3923,8 @@ char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) ***/ char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { - tprintf("Warning: clusterGetNextAttr("); pDriverData driver_data = (pDriverData)inf_v; const unsigned int i = driver_data->TargetAttrIndex++; - tprintf("%u) is under active development.\n", i); switch (driver_data->TargetType) { case TARGET_ROOT: return ATTR_ROOT[i]; @@ -4009,7 +3949,6 @@ char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) ***/ int clusterInfo(void* inf_v, pObjectInfo info) { - tprintf("Warning: clusterInfo() is under active development.\n"); pDriverData driver_data = (pDriverData)inf_v; pNodeData node_data = (pNodeData)driver_data->NodeData; @@ -4073,7 +4012,6 @@ int clusterInfo(void* inf_v, pObjectInfo info) goto err; } - tprintf("Info result: "INT_TO_BINARY_PATTERN"\n", INT_TO_BINARY(info->Flags)); return 0; err: @@ -4097,7 +4035,6 @@ int clusterInfo(void* inf_v, pObjectInfo info) ***/ char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { - tprintf("Warning: clusterGetFirstMethod() is under active development.\n"); pDriverData driver_data = (pDriverData)inf_v; driver_data->TargetMethodIndex = 0u; return clusterGetNextMethod(inf_v, oxt); @@ -4116,7 +4053,6 @@ char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) ***/ char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { - tprintf("Warning: clusterGetNextMethod() is under active development."); pDriverData driver_data = (pDriverData)inf_v; return METHOD_NAME[driver_data->TargetMethodIndex++]; } @@ -4197,7 +4133,6 @@ static void ci_CacheFreeSourceData(pXHashEntry entry, void* path) if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; /** Free data. **/ - tprintf("- source: \"%s\"\n", key); ci_FreeSourceData(source_data); nmSysFree(key); } @@ -4215,7 +4150,6 @@ static void ci_CacheFreeCluster(pXHashEntry entry, void* path) if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; /** Free data. **/ - tprintf("- cluster: \"%s\"\n", key); ci_FreeClusterData(cluster_data, false); nmSysFree(key); } @@ -4233,7 +4167,6 @@ static void ci_CacheFreeSearch(pXHashEntry entry, void* path) if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; /** Free data. **/ - tprintf("- search: \"%s\"\n", key); ci_FreeSearchData(search_data); nmSysFree(key); } @@ -4249,7 +4182,6 @@ static void ci_CacheFreeSearch(pXHashEntry entry, void* path) ***/ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) { - tprintf("Warning: clusterExecuteMethod(\"%s\") is under active development.\n", method_name); pDriverData driver_data = (pDriverData)inf_v; /** Cache management method. **/ @@ -4266,7 +4198,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx goto err; } - /** show and show_all. **/ + /** 'show' and 'show_all'. **/ bool show = false; if (strcmp(param->String, "show") == 0) { @@ -4323,18 +4255,11 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx return ret; } - /** drop_all. **/ + /** 'drop_all'. **/ if (strcmp(param->String, "drop_all") == 0) { - /** Print info. **/ - printf("\nDropping cache for "); - if (path != NULL) printf("\"%s\":\n", path); - else printf("all files:\n"); - - /** Free caches. **/ + printf("\nDropping cache for all files:\n"); ci_ClearCaches(); - - tprintf("Cache dropped.\n"); return 0; } @@ -4348,29 +4273,19 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx if (strcmp(method_name, "stat") == 0) { - unsigned long long ExpectedOpenCalls = 10666; - unsigned long long ExpectedOpenQueryCalls = 10665; - unsigned long long ExpectedFetchCalls = 3368007; - unsigned long long ExpectedCloseCalls = 3368007; - unsigned long long ExpectedGetTypeCalls = 26664164; - unsigned long long ExpectedGetValCalls = 15021419; - unsigned long long ExpectedGetValCalls_name = 3368008; - unsigned long long ExpectedGetValCalls_key1 = 3357342; - unsigned long long ExpectedGetValCalls_key2 = 1574; - unsigned long long ExpectedGetValCalls_sim = 8283829; char buf[12]; printf("Cluster Driver Statistics:\n"); - printf(" Stat Name Value\n"); - printf(" OpenCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls), snprint_llu(buf, sizeof(buf), ExpectedOpenCalls), ClusterStatistics.OpenCalls / ExpectedOpenCalls * 100.0); - printf(" OpenQueryCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls), snprint_llu(buf, sizeof(buf), ExpectedOpenQueryCalls), ClusterStatistics.OpenQueryCalls / ExpectedOpenQueryCalls * 100.0); - printf(" FetchCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls), snprint_llu(buf, sizeof(buf), ExpectedFetchCalls), ClusterStatistics.FetchCalls / ExpectedFetchCalls * 100.0); - printf(" CloseCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls), snprint_llu(buf, sizeof(buf), ExpectedCloseCalls), ClusterStatistics.CloseCalls / ExpectedCloseCalls * 100.0); - printf(" GetTypeCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls), snprint_llu(buf, sizeof(buf), ExpectedGetTypeCalls), ClusterStatistics.GetTypeCalls / ExpectedGetTypeCalls * 100.0); - printf(" GetValCalls %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls), ClusterStatistics.GetValCalls / ExpectedGetValCalls * 100.0); - printf(" GetValCalls_name %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_name), ClusterStatistics.GetValCalls_name / ExpectedGetValCalls_name * 100.0); - printf(" GetValCalls_key1 %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_key1), ClusterStatistics.GetValCalls_key1 / ExpectedGetValCalls_key1 * 100.0); - printf(" GetValCalls_key2 %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_key2), ClusterStatistics.GetValCalls_key2 / ExpectedGetValCalls_key2 * 100.0); - printf(" GetValCalls_sim %10s / %10s (%.4g%%)\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim), snprint_llu(buf, sizeof(buf), ExpectedGetValCalls_sim), ClusterStatistics.GetValCalls_sim / ExpectedGetValCalls_sim * 100.0); + printf(" Stat Name Value\n"); + printf(" OpenCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); + printf(" OpenQueryCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); + printf(" FetchCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); + printf(" CloseCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); + printf(" GetTypeCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); + printf(" GetValCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); + printf(" GetValCalls_name %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); + printf(" GetValCalls_key1 %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); + printf(" GetValCalls_key2 %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); + printf(" GetValCalls_sim %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); return 0; } @@ -4473,7 +4388,7 @@ int clusterInitialize(void) if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err; if (!check(xaInit(&drv->RootContentTypes, 1))) goto err; if (!check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster"))) goto err; - drv->Capabilities = 0; /* TODO: Greg - Should I add any of these? */ + drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ /** Setup the function references. **/ drv->Open = clusterOpen; @@ -4523,6 +4438,6 @@ int clusterInitialize(void) /** Error cleanup. **/ err: if (drv != NULL) nmFree(drv, sizeof(ObjDriver)); - fprintf(stderr, "Error: Failed to initialize cluster driver.\n"); + mssErrorf(1, "Cluster", "Failed to initialize cluster driver.\n"); return -1; } From 81a1d2fd3c46edd7b511f877afb60071f73497fe Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 14 Nov 2025 12:05:39 -0700 Subject: [PATCH 010/361] Clean up unintended usage of glyph.h --- centrallix-lib/src/clusters.c | 124 ---------------------------------- 1 file changed, 124 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 4a96b6ca1..ef2336269 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -42,7 +42,6 @@ #include #include "clusters.h" -#include "glyph.h" #include "newmalloc.h" #include "util.h" #include "xarray.h" @@ -199,108 +198,6 @@ pVector ca_build_vector(const char* str) return trimmed_sparse_vector; } -// Build vector by converting a dense vector to a sparse one. -//pVector ca_build_vector_old(const char* str) -// { -// /** Allocate space for a dense vector. **/ -// unsigned int dense_vector[CA_NUM_DIMS] = {0u}; -// -// /** j is the former character, i is the latter. **/ -// const unsigned int num_chars = (unsigned int)strlen(str); -// for (unsigned int j = 65535u, i = 0u; i <= num_chars; i++) -// { -// if (isspace(str[i])) continue; -// if (ispunct(str[i]) && str[i] != CA_BOUNDARY_CHAR) continue; -// -// /** First and last character should fall one before 'a' in the ASCII table. **/ -// unsigned int temp1 = (j == 65535u) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[j]); -// unsigned int temp2 = (i == num_chars) ? CA_BOUNDARY_CHAR : (unsigned int)tolower(str[i]); -// -// /** Shift numbers to the end of the lowercase letters. **/ -// if ('0' <= temp1 && temp1 <= '9') temp1 += 75u; -// if ('0' <= temp2 && temp2 <= '9') temp2 += 75u; -// -// /** Hash the character pair into an index (dimension). **/ -// /** Note that temp will be between 97 ('a') and 132 ('9'). **/ -// unsigned int dim = hash_char_pair(temp1, temp2); -// -// /** Increment the dimension of the dense vector by a number from 1 to 13. **/ -// dense_vector[dim] += (temp1 + temp2) % 13u + 1u; -// -// j = i; -// } -// -// /** Count how much space is needed for a sparse vector. **/ -// bool zero_prev = false; -// size_t size = 0u; -// for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) -// { -// if (dense_vector[dim] == 0u) -// { -// size += (zero_prev) ? 0u : 1u; -// zero_prev = true; -// } -// else -// { -// size++; -// zero_prev = false; -// } -// } -// -// /*** Check compression size. -// *** If this check fails, I doubt anything will break. However, the longest -// *** word I know (supercalifragilisticexpialidocious) has only 35 character -// *** pairs, so it shouldn't reach half this size (and it'd be even shorter -// *** if the hash generates at least one collision). -// *** -// *** Bad vector compression will result in degraded performace and increased -// *** memory usage. This indicates a likely bug in the code. Thus, if this -// *** warning is ever generated, it is definitely worth investigating. -// ***/ -// const size_t expected_max_size = 256u; -// if (size > expected_max_size) -// { -// fprintf(stderr, -// "cli_build_vector(\"%s\") - Warning: Sparse vector larger than expected.\n" -// " > Size: %lu\n" -// " > #Dims: %u\n", -// str, -// size, -// CA_NUM_DIMS -// ); -// } -// -// /** Allocate space for sparse vector. **/ -// const size_t sparse_vector_size = size * sizeof(int); -// pVector sparse_vector = (pVector)check_ptr(nmSysMalloc(sparse_vector_size)); -// if (sparse_vector == NULL) return NULL; -// -// /** Convert the dense vector above to a sparse vector. **/ -// unsigned int dim = 0u, sparse_idx = 0u; -// while (dim < CA_NUM_DIMS) -// { -// if (dense_vector[dim] == 0u) -// { -// /** Count and store consecutive zeros, skipping the first one. **/ -// unsigned int zero_count = 1u; -// dim++; -// while (dim < CA_NUM_DIMS && dense_vector[dim] == 0u) -// { -// zero_count++; -// dim++; -// } -// sparse_vector[sparse_idx++] = (int)-zero_count; -// } -// else -// { -// /** Store the value. **/ -// sparse_vector[sparse_idx++] = (int)dense_vector[dim++]; -// } -// } -// -// return sparse_vector; -// } - /*** Free memory allocated to store a sparse vector. *** *** @param sparse_vector The sparse vector being freed. @@ -837,17 +734,10 @@ int ca_kmeans( } } - /** Setup debug visualizations. **/ - glyph_init(iter, "\n", 1, false); - glyph_init(find, ".", 64, false); - glyph_init(update_label, "!", 16, false); - glyph_init(update_centroid, ":", 8, false); - /** Main kmeans loop. **/ double old_average_cluster_size = 1.0; for (unsigned int iter = 0u; iter < max_iter; iter++) { - glyph(iter); bool changed = false; /** Reset new centroids. **/ @@ -861,7 +751,6 @@ int ca_kmeans( /** Assign each point to the nearest centroid. **/ for (unsigned int i = 0u; i < num_vectors; i++) { - glyph(find); const pVector vector = vectors[i]; double min_dist = DBL_MAX; unsigned int best_centroid_label = 0u; @@ -880,7 +769,6 @@ int ca_kmeans( /** Update label to new centroid, if necessary. **/ if (labels[i] != best_centroid_label) { - glyph(update_label); labels[i] = best_centroid_label; changed = true; } @@ -902,7 +790,6 @@ int ca_kmeans( /** Update centroids. **/ for (unsigned int i = 0u; i < num_clusters; i++) { - glyph(update_centroid); if (cluster_counts[i] == 0u) continue; pCentroid centroid = centroids[i]; const pCentroid new_centroid = new_centroids[i]; @@ -926,8 +813,6 @@ int ca_kmeans( vector_sims[i] = sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); } - glyph_print("\n"); - /** Success. **/ successful = true; @@ -1028,25 +913,17 @@ pXArray ca_sliding_search( if (dups == NULL) goto err; } const int num_starting_dups = dups->nItems; - - /** Setup debug visualizations. **/ - glyph_init(outer, " ", 4, true); - glyph_init(inner, ".", 128, false); - glyph_init(find, "!", 32, false); /** Search for dups. **/ for (unsigned int i = 0u; i < num_data; i++) { - glyph(outer); const unsigned int window_start = i + 1u; const unsigned int window_end = min(i + window_size, num_data); for (unsigned int j = window_start; j < window_end; j++) { - glyph(inner); const double sim = similarity(data[i], data[j]); if (sim > threshold) /* Dup found! */ { - glyph(find); Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); if (dup == NULL) goto err_free_dups; if (maybe_keys != NULL) @@ -1059,7 +936,6 @@ pXArray ca_sliding_search( } } } - glyph_print("\n"); /** Success. **/ return dups; From e624d40b242d8cd5644c05f243688eb6da13f53e Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 14 Nov 2025 16:10:00 -0700 Subject: [PATCH 011/361] Attempt to reduce issues from ambiguously signed chars. --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/src/clusters.c | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 05480e742..ffa1223fb 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -49,7 +49,7 @@ /// LINK ../../centrallix-sysdoc/string_comparison.md#cosine_charsets /** The character used to create a pair with the first and last characters of a string. **/ -#define CA_BOUNDARY_CHAR ('a' - 1) +#define CA_BOUNDARY_CHAR (unsigned char)('a' - 1) /** Types. **/ typedef int* pVector; /* Sparse vector. */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index ef2336269..ba126e5f1 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -53,7 +53,7 @@ *** @param c2 The second character in the pair. *** @returns The resulting hash. ***/ -static unsigned int hash_char_pair(const char c1, const char c2) +static unsigned int hash_char_pair(const unsigned char c1, const unsigned char c2) { const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); @@ -121,12 +121,17 @@ static int charpair_cmp(const void *p1, const void *p2) ***/ pVector ca_build_vector(const char* str) { - char chars[strlen(str) + 2u]; + unsigned char chars[strlen(str) + 2u]; unsigned int num_chars = 0u; chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) { - unsigned char c = *char_ptr; + char maybe_char = *char_ptr; + if (maybe_char < 0) + { + fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); + } + unsigned char c = (unsigned char)maybe_char; /** Always consider boundary character in string. **/ if (c == CA_BOUNDARY_CHAR) goto skip_checks; @@ -175,7 +180,10 @@ pVector ca_build_vector(const char* str) /** Dividing value by 2 each time reduces the impact of repeated pairs. **/ int value = 0; for (; i < num_pairs && char_pairs[i].hash == hash; i++) - value = (value / 2) + ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; + { + value /= 2; /* Reduce impact of repeated pairs. */ + value += ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; + } /** Skip zeros to reach the dimension index specified by the hash. **/ unsigned int num_zeros = hash - dim; From b0e000bfa58535b3a73b79e4f2dcec0e60f3553b Mon Sep 17 00:00:00 2001 From: Israel Date: Mon, 17 Nov 2025 10:43:37 -0700 Subject: [PATCH 012/361] All tests now pass. --- centrallix-lib/src/clusters.c | 7 +- centrallix/tests/test_cos_compare_00.cmp | 18 +- centrallix/tests/test_cos_compare_00.to | 23 ++- .../tests/test_expfn_double_metaphone_00.to | 161 ------------------ ...one_00.cmp => test_expfn_metaphone_00.cmp} | 0 centrallix/tests/test_expfn_metaphone_00.to | 161 ++++++++++++++++++ centrallix/tests/test_fuzzycompare_00.cmp | 13 -- centrallix/tests/test_fuzzycompare_00.to | 15 -- centrallix/tests/test_lev_compare_00.cmp | 23 +++ centrallix/tests/test_lev_compare_00.to | 28 +++ centrallix/tests/test_levenshtein_00.cmp | 24 ++- centrallix/tests/test_levenshtein_00.to | 29 +++- centrallix/tests/test_similarity_00.cmp | 5 - centrallix/tests/test_similarity_00.to | 7 - 14 files changed, 281 insertions(+), 233 deletions(-) delete mode 100644 centrallix/tests/test_expfn_double_metaphone_00.to rename centrallix/tests/{test_expfn_double_metaphone_00.cmp => test_expfn_metaphone_00.cmp} (100%) create mode 100644 centrallix/tests/test_expfn_metaphone_00.to delete mode 100644 centrallix/tests/test_fuzzycompare_00.cmp delete mode 100644 centrallix/tests/test_fuzzycompare_00.to create mode 100644 centrallix/tests/test_lev_compare_00.cmp create mode 100644 centrallix/tests/test_lev_compare_00.to delete mode 100644 centrallix/tests/test_similarity_00.cmp delete mode 100644 centrallix/tests/test_similarity_00.to diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index ba126e5f1..84f01c535 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -127,10 +127,7 @@ pVector ca_build_vector(const char* str) for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) { char maybe_char = *char_ptr; - if (maybe_char < 0) - { - fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); - } + if (maybe_char < 0) fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); unsigned char c = (unsigned char)maybe_char; /** Always consider boundary character in string. **/ @@ -181,7 +178,7 @@ pVector ca_build_vector(const char* str) int value = 0; for (; i < num_pairs && char_pairs[i].hash == hash; i++) { - value /= 2; /* Reduce impact of repeated pairs. */ + // value /= 2; /* Reduce impact of repeated pairs. */ value += ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; } diff --git a/centrallix/tests/test_cos_compare_00.cmp b/centrallix/tests/test_cos_compare_00.cmp index d586365f7..2061443ac 100644 --- a/centrallix/tests/test_cos_compare_00.cmp +++ b/centrallix/tests/test_cos_compare_00.cmp @@ -1,7 +1,11 @@ -Attribute [case1]: integer 1 -Attribute [case2]: integer 1 -Attribute [case3]: integer 1 -Attribute [case4]: integer 1 -Attribute [case5]: integer 1 -Attribute [case6]: integer 1 -Attribute [case7]: integer 1 +Attribute [case1]: string "pass" +Attribute [case2]: string "pass" +Attribute [case3]: string "pass" +Attribute [case4]: string "pass" +Attribute [cynthia]: string "pass" +Attribute [timothy]: string "pass" +Attribute [lance]: string "pass" +Attribute [gregory]: string "pass" +Attribute [nathan]: string "pass" +Attribute [identical]: string "pass" +Attribute [name]: string "pass" diff --git a/centrallix/tests/test_cos_compare_00.to b/centrallix/tests/test_cos_compare_00.to index 5bf950514..f45dec13a 100644 --- a/centrallix/tests/test_cos_compare_00.to +++ b/centrallix/tests/test_cos_compare_00.to @@ -1,17 +1,24 @@ ##NAME Text Mining String Similarity with Cosine Compare -# All email addresses and phone numbers are imaginary and were fabricated for the purposes of this test +# Basic tests of cosine similarity. +query select case1 = condition((cos_compare('hello', 'hello') >= 0.999) and (cos_compare('hello', 'hello') <= 1.0), "pass", "fail") +query select case2 = condition((cos_compare('hello', 'zephora') <= 0.001) and (cos_compare('hello', 'zephora') >= 0.0), "pass", "fail") +query select case3 = condition((cos_compare('hello', 'hello world') <= 0.7) and (cos_compare('hello', 'hello world') >= 0.6), "pass", "fail") +query select case4 = condition((cos_compare('hello there', 'hellow there') >= 0.9) and (cos_compare('hello', 'hellow') <= 1.0), "pass", "fail") + -query select case1 = (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") >= 0.49) and (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") <= 0.54) +# Tests on fabricated contact information. +# All email addresses and phone numbers are imaginary and were fabricated for the purposes of this test +query select cynthia = condition((cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") >= 0.49) and (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") <= 0.54), "pass", "fail") -query select case2 = (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.425) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.475) +query select timothy = condition((cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.425) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.475), "pass", "fail") -query select case3 = (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.35) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.40) +query select lance = condition((cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.35) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.40), "pass", "fail") -query select case4 = (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") >= 0.94) and (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") <= 0.99) +query select gregory = condition((cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") >= 0.94) and (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") <= 0.99), "pass", "fail") -query select case5 = (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >=0.66) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.71) +query select nathan = condition((cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >= 0.66) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.71), "pass", "fail") -query select case6 = (cos_compare("This is an identical case", "This is an identical case") >=0.975) and (cos_compare("This is an identical case", "This is an identical case") <=1.00) +query select identical = condition((cos_compare("This is an identical case", "This is an identical case") >= 0.975) and (cos_compare("This is an identical case", "This is an identical case") <= 1.00), "pass", "fail") -query select case7 = (cos_compare("Samuel", "Alex") >= 0.00) and (cos_compare("Samuel", "Alex") <= 0.025) +query select name = condition((cos_compare("Samuel", "Alex") >= 0.00) and (cos_compare("Samuel", "Alex") <= 0.025), "pass", "fail") diff --git a/centrallix/tests/test_expfn_double_metaphone_00.to b/centrallix/tests/test_expfn_double_metaphone_00.to deleted file mode 100644 index efd7548cc..000000000 --- a/centrallix/tests/test_expfn_double_metaphone_00.to +++ /dev/null @@ -1,161 +0,0 @@ -##NAME double_metaphone() function - -# Special thanks to the following websites for double checking the correct results: -# 1: https://words.github.io/double-metaphone -# 2: https://mainegenealogy.net/metaphone_converter.asp -# 3: https://en.toolpage.org/tool/metaphone - -# These tests were collected from the following sources: -# - Example comments in the source code of exp_double_metaphone.c -# - Maurice Aubrey's Tests* -# - Tests manually written by Israel Fuller -# - Tests written by prompting ChatGPT-5 (preview)** -# -# *Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt -# **GPT-5 mini (Preview) was run in GitHub Copilot to suggest the words -# for some tests after analizing a generated coverage report. I (Israel) -# used the suggestions to write some "AI generated" test cases. -# -# For more information, see the manual test suite implementation at the -# end of the exp_double_metaphone.c file. - -query select result = double_metaphone("Test") -query select result = double_metaphone("Basic") -query select result = double_metaphone("Centrallix") -query select result = double_metaphone("Lawrence") -query select result = double_metaphone("Philips") -query select result = double_metaphone("Acceptingness") -query select result = double_metaphone("Supercalifragilisticexpialidocious") -query select result = double_metaphone("Suoicodilaipxecitsiligarfilacrepus") -query select result = double_metaphone("Smith") -query select result = double_metaphone("Schmidt") -query select result = double_metaphone("Snider") -query select result = double_metaphone("Schneider") -query select result = double_metaphone("Arnow") -query select result = double_metaphone("Arnoff") -query select result = double_metaphone("Accede") -query select result = double_metaphone("Accident") -query select result = double_metaphone("Actually") -query select result = double_metaphone("Arch") -query select result = double_metaphone("Artois") -query select result = double_metaphone("Bacchus") -query select result = double_metaphone("Bacci") -query select result = double_metaphone("Bajador") -query select result = double_metaphone("Bellocchio") -query select result = double_metaphone("Bertucci") -query select result = double_metaphone("Biaggi") -query select result = double_metaphone("Bough") -query select result = double_metaphone("Breaux") -query select result = double_metaphone("Broughton") -query select result = double_metaphone("Cabrillo") -query select result = double_metaphone("Caesar") -query select result = double_metaphone("Cagney") -query select result = double_metaphone("Campbell") -query select result = double_metaphone("Carlisle") -query select result = double_metaphone("Carlysle") -query select result = double_metaphone("Chemistry") -query select result = double_metaphone("Chianti") -query select result = double_metaphone("Chorus") -query select result = double_metaphone("Cough") -query select result = double_metaphone("Czerny") -query select result = double_metaphone("Dumb") -query select result = double_metaphone("Edgar") -query select result = double_metaphone("Edge") -query select result = double_metaphone("Filipowicz") -query select result = double_metaphone("Focaccia") -query select result = double_metaphone("Gallegos") -query select result = double_metaphone("Germanic") -query select result = double_metaphone("Ghiradelli") -query select result = double_metaphone("Ghislane") -query select result = double_metaphone("Gospel") -query select result = double_metaphone("Gough") -query select result = double_metaphone("Greek") -query select result = double_metaphone("Hochmeier") -query select result = double_metaphone("Hugh") -query select result = double_metaphone("Island") -query select result = double_metaphone("Isle") -query select result = double_metaphone("Italian") -query select result = double_metaphone("Jankelowicz") -query select result = double_metaphone("Jose") -query select result = double_metaphone("Laugh") -query select result = double_metaphone("Mac Caffrey") -query select result = double_metaphone("Mac Gregor") -query select result = double_metaphone("Manager") -query select result = double_metaphone("McHugh") -query select result = double_metaphone("McLaughlin") -query select result = double_metaphone("Michael") -query select result = double_metaphone("Middle") -query select result = double_metaphone("Orchestra") -query select result = double_metaphone("Orchid") -query select result = double_metaphone("Pinyin") -query select result = double_metaphone("Raspberry") -query select result = double_metaphone("Resnais") -query select result = double_metaphone("Rogier") -query select result = double_metaphone("Rough") -query select result = double_metaphone("Salvador") -query select result = double_metaphone("San jacinto") -query select result = double_metaphone("Schenker") -query select result = double_metaphone("Schermerhorn") -query select result = double_metaphone("Schlesinger") -query select result = double_metaphone("School") -query select result = double_metaphone("Schooner") -query select result = double_metaphone("Succeed") -query select result = double_metaphone("Sugar") -query select result = double_metaphone("Sugary") -query select result = double_metaphone("Tagliaro") -query select result = double_metaphone("Thames") -query select result = double_metaphone("Thomas") -query select result = double_metaphone("Thumb") -query select result = double_metaphone("Tichner") -query select result = double_metaphone("Tough") -query select result = double_metaphone("Vghee") -query select result = double_metaphone("Wachtler") -query select result = double_metaphone("Wechsler") -query select result = double_metaphone("Word") -query select result = double_metaphone("Xavier") -query select result = double_metaphone("Yankelovich") -query select result = double_metaphone("Zhao") -query select result = double_metaphone("McClellan") -query select result = double_metaphone("maurice") -query select result = double_metaphone("aubrey") -query select result = double_metaphone("cambrillo") -query select result = double_metaphone("heidi") -query select result = double_metaphone("katherine") -query select result = double_metaphone("catherine") -query select result = double_metaphone("richard") -query select result = double_metaphone("bob") -query select result = double_metaphone("eric") -query select result = double_metaphone("geoff") -query select result = double_metaphone("dave") -query select result = double_metaphone("ray") -query select result = double_metaphone("steven") -query select result = double_metaphone("bryce") -query select result = double_metaphone("randy") -query select result = double_metaphone("bryan") -query select result = double_metaphone("brian") -query select result = double_metaphone("otto") -query select result = double_metaphone("auto") -query select result = double_metaphone("Abbott") -query select result = double_metaphone("Back") -query select result = double_metaphone("Bacher") -query select result = double_metaphone("Charles") -query select result = double_metaphone("Ghana") -query select result = double_metaphone("Gnome") -query select result = double_metaphone("Raj") -query select result = double_metaphone("Quentin") -query select result = double_metaphone("Who") -query select result = double_metaphone("Shoemaker") -query select result = double_metaphone("Sian") -query select result = double_metaphone("Scold") -query select result = double_metaphone("Station") -query select result = double_metaphone("Match") -query select result = double_metaphone("Pizza") -query select result = double_metaphone("Agnes") -query select result = double_metaphone("Science") -query select result = double_metaphone("Van Gogh") -query select result = double_metaphone("Josef") -query select result = double_metaphone("Object") -query select result = double_metaphone("Sholz") -query select result = double_metaphone("Scharf") -query select result = double_metaphone("Kasia") -query select result = double_metaphone("Van Geller") diff --git a/centrallix/tests/test_expfn_double_metaphone_00.cmp b/centrallix/tests/test_expfn_metaphone_00.cmp similarity index 100% rename from centrallix/tests/test_expfn_double_metaphone_00.cmp rename to centrallix/tests/test_expfn_metaphone_00.cmp diff --git a/centrallix/tests/test_expfn_metaphone_00.to b/centrallix/tests/test_expfn_metaphone_00.to new file mode 100644 index 000000000..de1897c3e --- /dev/null +++ b/centrallix/tests/test_expfn_metaphone_00.to @@ -0,0 +1,161 @@ +##NAME metaphone() function + +# Special thanks to the following websites for double checking the correct results: +# 1: https://words.github.io/double-metaphone +# 2: https://mainegenealogy.net/metaphone_converter.asp +# 3: https://en.toolpage.org/tool/metaphone + +# These tests were collected from the following sources: +# - Example comments in the source code of exp_double_metaphone.c +# - Maurice Aubrey's Tests* +# - Tests manually written by Israel Fuller +# - Tests written by prompting ChatGPT-5 (preview)** +# +# *Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt +# **GPT-5 mini (Preview) was run in GitHub Copilot to suggest the words +# for some tests after analizing a generated coverage report. I (Israel) +# used the suggestions to write some "AI generated" test cases. +# +# For more information, see the manual test suite implementation at the +# end of the exp_double_metaphone.c file. + +query select result = metaphone("Test") +query select result = metaphone("Basic") +query select result = metaphone("Centrallix") +query select result = metaphone("Lawrence") +query select result = metaphone("Philips") +query select result = metaphone("Acceptingness") +query select result = metaphone("Supercalifragilisticexpialidocious") +query select result = metaphone("Suoicodilaipxecitsiligarfilacrepus") +query select result = metaphone("Smith") +query select result = metaphone("Schmidt") +query select result = metaphone("Snider") +query select result = metaphone("Schneider") +query select result = metaphone("Arnow") +query select result = metaphone("Arnoff") +query select result = metaphone("Accede") +query select result = metaphone("Accident") +query select result = metaphone("Actually") +query select result = metaphone("Arch") +query select result = metaphone("Artois") +query select result = metaphone("Bacchus") +query select result = metaphone("Bacci") +query select result = metaphone("Bajador") +query select result = metaphone("Bellocchio") +query select result = metaphone("Bertucci") +query select result = metaphone("Biaggi") +query select result = metaphone("Bough") +query select result = metaphone("Breaux") +query select result = metaphone("Broughton") +query select result = metaphone("Cabrillo") +query select result = metaphone("Caesar") +query select result = metaphone("Cagney") +query select result = metaphone("Campbell") +query select result = metaphone("Carlisle") +query select result = metaphone("Carlysle") +query select result = metaphone("Chemistry") +query select result = metaphone("Chianti") +query select result = metaphone("Chorus") +query select result = metaphone("Cough") +query select result = metaphone("Czerny") +query select result = metaphone("Dumb") +query select result = metaphone("Edgar") +query select result = metaphone("Edge") +query select result = metaphone("Filipowicz") +query select result = metaphone("Focaccia") +query select result = metaphone("Gallegos") +query select result = metaphone("Germanic") +query select result = metaphone("Ghiradelli") +query select result = metaphone("Ghislane") +query select result = metaphone("Gospel") +query select result = metaphone("Gough") +query select result = metaphone("Greek") +query select result = metaphone("Hochmeier") +query select result = metaphone("Hugh") +query select result = metaphone("Island") +query select result = metaphone("Isle") +query select result = metaphone("Italian") +query select result = metaphone("Jankelowicz") +query select result = metaphone("Jose") +query select result = metaphone("Laugh") +query select result = metaphone("Mac Caffrey") +query select result = metaphone("Mac Gregor") +query select result = metaphone("Manager") +query select result = metaphone("McHugh") +query select result = metaphone("McLaughlin") +query select result = metaphone("Michael") +query select result = metaphone("Middle") +query select result = metaphone("Orchestra") +query select result = metaphone("Orchid") +query select result = metaphone("Pinyin") +query select result = metaphone("Raspberry") +query select result = metaphone("Resnais") +query select result = metaphone("Rogier") +query select result = metaphone("Rough") +query select result = metaphone("Salvador") +query select result = metaphone("San jacinto") +query select result = metaphone("Schenker") +query select result = metaphone("Schermerhorn") +query select result = metaphone("Schlesinger") +query select result = metaphone("School") +query select result = metaphone("Schooner") +query select result = metaphone("Succeed") +query select result = metaphone("Sugar") +query select result = metaphone("Sugary") +query select result = metaphone("Tagliaro") +query select result = metaphone("Thames") +query select result = metaphone("Thomas") +query select result = metaphone("Thumb") +query select result = metaphone("Tichner") +query select result = metaphone("Tough") +query select result = metaphone("Vghee") +query select result = metaphone("Wachtler") +query select result = metaphone("Wechsler") +query select result = metaphone("Word") +query select result = metaphone("Xavier") +query select result = metaphone("Yankelovich") +query select result = metaphone("Zhao") +query select result = metaphone("McClellan") +query select result = metaphone("maurice") +query select result = metaphone("aubrey") +query select result = metaphone("cambrillo") +query select result = metaphone("heidi") +query select result = metaphone("katherine") +query select result = metaphone("catherine") +query select result = metaphone("richard") +query select result = metaphone("bob") +query select result = metaphone("eric") +query select result = metaphone("geoff") +query select result = metaphone("dave") +query select result = metaphone("ray") +query select result = metaphone("steven") +query select result = metaphone("bryce") +query select result = metaphone("randy") +query select result = metaphone("bryan") +query select result = metaphone("brian") +query select result = metaphone("otto") +query select result = metaphone("auto") +query select result = metaphone("Abbott") +query select result = metaphone("Back") +query select result = metaphone("Bacher") +query select result = metaphone("Charles") +query select result = metaphone("Ghana") +query select result = metaphone("Gnome") +query select result = metaphone("Raj") +query select result = metaphone("Quentin") +query select result = metaphone("Who") +query select result = metaphone("Shoemaker") +query select result = metaphone("Sian") +query select result = metaphone("Scold") +query select result = metaphone("Station") +query select result = metaphone("Match") +query select result = metaphone("Pizza") +query select result = metaphone("Agnes") +query select result = metaphone("Science") +query select result = metaphone("Van Gogh") +query select result = metaphone("Josef") +query select result = metaphone("Object") +query select result = metaphone("Sholz") +query select result = metaphone("Scharf") +query select result = metaphone("Kasia") +query select result = metaphone("Van Geller") diff --git a/centrallix/tests/test_fuzzycompare_00.cmp b/centrallix/tests/test_fuzzycompare_00.cmp deleted file mode 100644 index baa6db1e9..000000000 --- a/centrallix/tests/test_fuzzycompare_00.cmp +++ /dev/null @@ -1,13 +0,0 @@ -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 diff --git a/centrallix/tests/test_fuzzycompare_00.to b/centrallix/tests/test_fuzzycompare_00.to deleted file mode 100644 index 78141a473..000000000 --- a/centrallix/tests/test_fuzzycompare_00.to +++ /dev/null @@ -1,15 +0,0 @@ -##NAME Levenshtein String Comparison - -query select sw1 = 1 where fuzzy_compare('hello', 'hello!', 20) >= 0 and fuzzy_compare("hello","hello!", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'asdfkh', 20) >= 0 and fuzzy_compare("hello","asdfkh", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'aaaaaaaaaaaaaaaaa', 20) >= 0 and fuzzy_compare("hello","aaaaaaaaaaaaaaaaa", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'nope', 20) >= 0 and fuzzy_compare("hello","nope", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('below', 'hello!', 20) >= 0 and fuzzy_compare("below","hello!", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('kitten', 'smitten', 20) >= 0 and fuzzy_compare("kitten","smitten", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'bobbobbobbob', 20) >= 0 and fuzzy_compare("hello","bobbobbobbob", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', '', 20) >= 0 and fuzzy_compare("hello","", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('', '', 20) >= 0 and fuzzy_compare("","", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('blooooop', 'blob', 20) >= 0 and fuzzy_compare("blooooop","blob", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('', '!', 20) >= 0 and fuzzy_compare("","!", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('h', 'h', 20) >= 0 and fuzzy_compare("h","h", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hi', 'hi', 20) >= 0 and fuzzy_compare("hi","hi", 20) <= 1 diff --git a/centrallix/tests/test_lev_compare_00.cmp b/centrallix/tests/test_lev_compare_00.cmp new file mode 100644 index 000000000..1c295a360 --- /dev/null +++ b/centrallix/tests/test_lev_compare_00.cmp @@ -0,0 +1,23 @@ +Attribute [case1]: string "pass" +Attribute [case2]: string "pass" +Attribute [case3]: string "pass" +Attribute [case4]: string "pass" +Attribute [case5]: string "pass" +Attribute [case6]: string "pass" +Attribute [case7]: string "pass" +Attribute [case8]: string "pass" +Attribute [case9]: string "pass" +Attribute [case10]: string "pass" +Attribute [case11]: string "pass" +Attribute [case12]: string "pass" +Attribute [case13]: string "pass" +Attribute [case14]: string "pass" +Attribute [case15]: string "pass" +Attribute [case16]: string "pass" +Attribute [case17]: string "pass" +Attribute [case18]: string "pass" +Attribute [case19]: string "pass" +Attribute [case20]: string "pass" +Attribute [case21]: string "pass" +Attribute [case22]: string "pass" +Attribute [case23]: string "pass" diff --git a/centrallix/tests/test_lev_compare_00.to b/centrallix/tests/test_lev_compare_00.to new file mode 100644 index 000000000..5d9cec0f7 --- /dev/null +++ b/centrallix/tests/test_lev_compare_00.to @@ -0,0 +1,28 @@ +##NAME Levenshtein String Comparison + +# Legacy tests. +query select case1 = condition(lev_compare('hello', 'hello!') >= 0 and lev_compare('hello','hello!') <= 1, 'pass', 'fail') +query select case2 = condition(lev_compare('hello', 'asdfkh') >= 0 and lev_compare('hello','asdfkh') <= 1, 'pass', 'fail') +query select case3 = condition(lev_compare('hello', 'aaaaaaaaaaaaaaaaa') >= 0 and lev_compare('hello','aaaaaaaaaaaaaaaaa') <= 1, 'pass', 'fail') +query select case4 = condition(lev_compare('hello', 'nope') >= 0 and lev_compare('hello', 'nope') <= 1, 'pass', 'fail') +query select case5 = condition(lev_compare('below', 'hello!') >= 0 and lev_compare('below', 'hello!') <= 1, 'pass', 'fail') +query select case6 = condition(lev_compare('kitten', 'smitten') >= 0 and lev_compare('kitten', 'smitten') <= 1, 'pass', 'fail') +query select case7 = condition(lev_compare('hello', 'bobbobbobbob') >= 0 and lev_compare('hello', 'bobbobbobbob') <= 1, 'pass', 'fail') +query select case8 = condition(lev_compare('hello', '') >= 0 and lev_compare('hello', '') <= 1, 'pass', 'fail') +query select case9 = condition(lev_compare('', '') >= 0 and lev_compare('', '') <= 1, 'pass', 'fail') +query select case10 = condition(lev_compare('blooooop', 'blob') >= 0 and lev_compare('blooooop', 'blob') <= 1, 'pass', 'fail') +query select case11 = condition(lev_compare('', '!') >= 0 and lev_compare('','!') <= 1, 'pass', 'fail') +query select case12 = condition(lev_compare('h', 'h') >= 0 and lev_compare('h','h') <= 1, 'pass', 'fail') +query select case13 = condition(lev_compare('hi', 'hi') >= 0 and lev_compare('hi','hi') <= 1, 'pass', 'fail') + +# Kitten tests. +query select case14 = condition(lev_compare('kitten', 'kitten') >= 0.99 and lev_compare('kitten', 'kitten') <= 1.0, 'pass', 'fail') -- 0 edits +query select case15 = condition(lev_compare('kitten', 'skitten') >= 0.8 and lev_compare('kitten', 'skitten') <= 0.9, 'pass', 'fail') -- 1 insert +query select case16 = condition(lev_compare('kitten', 'itten') >= 0.8 and lev_compare('kitten', 'itten') <= 0.9, 'pass', 'fail') -- 1 delete +query select case17 = condition(lev_compare('kitten', 'mitten') >= 0.8 and lev_compare('kitten', 'mitten') <= 0.9, 'pass', 'fail') -- 1 replace +query select case18 = condition(lev_compare('kitten', 'smitten') >= 0.7 and lev_compare('kitten', 'smitten') <= 0.8, 'pass', 'fail') -- 1 insert and one replace +query select case19 = condition(lev_compare('kitten', 'iktten') >= 0.8 and lev_compare('kitten', 'iktten') <= 0.9, 'pass', 'fail') -- 1 transpose +query select case20 = condition(lev_compare('kitten', 'kittens') >= 0.8 and lev_compare('kitten', 'kittens') <= 0.9, 'pass', 'fail') -- 1 insert (end) +query select case21 = condition(lev_compare('kitten', 'kitte') >= 0.8 and lev_compare('kitten', 'kitte') <= 0.9, 'pass', 'fail') -- 1 delete (end) +query select case22 = condition(lev_compare('kitten', 'kittem') >= 0.8 and lev_compare('kitten', 'kittem') <= 0.9, 'pass', 'fail') -- 1 replace (end) +query select case23 = condition(lev_compare('kitten', 'kittne') >= 0.8 and lev_compare('kitten', 'kittne') <= 0.9, 'pass', 'fail') -- 1 transpose (end) diff --git a/centrallix/tests/test_levenshtein_00.cmp b/centrallix/tests/test_levenshtein_00.cmp index 0bc319c9d..2a084162d 100644 --- a/centrallix/tests/test_levenshtein_00.cmp +++ b/centrallix/tests/test_levenshtein_00.cmp @@ -1,6 +1,18 @@ -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 2 -Attribute [sw1]: integer 2 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 +Attribute [case1]: string "pass" +Attribute [case2]: string "pass" +Attribute [case3]: string "pass" +Attribute [case4]: string "pass" +Attribute [case5]: string "pass" +Attribute [case6]: string "pass" +Attribute [case7]: string "pass" +Attribute [case8]: string "pass" +Attribute [case9]: string "pass" +Attribute [case10]: string "pass" +Attribute [case11]: string "pass" +Attribute [case12]: string "pass" +Attribute [case13]: string "pass" +Attribute [case14]: string "pass" +Attribute [case15]: string "pass" +Attribute [case16]: string "pass" +Attribute [case17]: string "pass" +Attribute [case18]: string "pass" diff --git a/centrallix/tests/test_levenshtein_00.to b/centrallix/tests/test_levenshtein_00.to index a666c3a4b..33f78e5f8 100644 --- a/centrallix/tests/test_levenshtein_00.to +++ b/centrallix/tests/test_levenshtein_00.to @@ -1,8 +1,25 @@ ##NAME Levenshtein String Comparison -query select sw1 = levenshtein('hello', 'hello!') -query select sw1 = levenshtein('kitten', 'mitten') -query select sw1 = levenshtein('kitten', 'smitten') -query select sw1 = levenshtein('lawn', 'flown') -query select sw1 = levenshtein('kitten', 'itten') -query select sw1 = levenshtein('kitten', 'skitten') +# Kitten tests. +query select case1 = condition(levenshtein('kitten', 'kitten') == 0, 'pass', 'fail') -- 0 edits +query select case2 = condition(levenshtein('kitten', 'skitten') == 1, 'pass', 'fail') -- 1 insert +query select case3 = condition(levenshtein('kitten', 'itten') == 1, 'pass', 'fail') -- 1 delete +query select case4 = condition(levenshtein('kitten', 'mitten') == 1, 'pass', 'fail') -- 1 replace +query select case5 = condition(levenshtein('kitten', 'smitten') == 2, 'pass', 'fail') -- 1 insert and one replace +query select case6 = condition(levenshtein('kitten', 'iktten') == 1, 'pass', 'fail') -- 1 transpose +query select case7 = condition(levenshtein('kitten', 'kittens') == 1, 'pass', 'fail') -- 1 insert (end) +query select case8 = condition(levenshtein('kitten', 'kitte') == 1, 'pass', 'fail') -- 1 delete (end) +query select case9 = condition(levenshtein('kitten', 'kittem') == 1, 'pass', 'fail') -- 1 replace (end) +query select case10 = condition(levenshtein('kitten', 'kittne') == 1, 'pass', 'fail') -- 1 transpose (end) + +# Alternate words. +query select case11 = condition(levenshtein('lawn', 'flown') == 2, 'pass', 'fail') -- 1 insert and one replace +query select case12 = condition(levenshtein('hello', 'hello!') == 1, 'pass', 'fail') -- 1 insert (end) +query select case13 = condition(levenshtein('zert', 'zerf') == 1, 'pass', 'fail') -- 1 replace (end) +query select case14 = condition(levenshtein('llearr', 'lear') == 2, 'pass', 'fail') -- 2 deletes (start & end) + +# Edge cases. +query select case15 = condition(levenshtein('', '') == 0, 'pass', 'fail') -- 0 edits +query select case16 = condition(levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...') == 0, 'pass', 'fail') -- 0 edits. +query select case17 = condition(levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is quite a lengthy string. I do not expect the function to compute any longer string since this one is a full 254 characters. That is plenty, even if someone adds many contact details to their record!! Thus, this test should cover most cases we see.') == 133, 'pass', 'fail') -- 133 edits. +query select case18 = condition(levenshtein('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB') == 254, 'pass', 'fail') -- 254 replaces. diff --git a/centrallix/tests/test_similarity_00.cmp b/centrallix/tests/test_similarity_00.cmp deleted file mode 100644 index a0d292206..000000000 --- a/centrallix/tests/test_similarity_00.cmp +++ /dev/null @@ -1,5 +0,0 @@ -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 diff --git a/centrallix/tests/test_similarity_00.to b/centrallix/tests/test_similarity_00.to deleted file mode 100644 index a0942ab76..000000000 --- a/centrallix/tests/test_similarity_00.to +++ /dev/null @@ -1,7 +0,0 @@ -##NAME Text Mining String Similarity - -query select sw1 = (cos_compare('hello', 'hello') >= 0.999) and (cos_compare('hello', 'hello') <= 1) -query select sw1 = (cos_compare('hello', 'nancy') <= 0.001) and (cos_compare('hello', 'nancy') >= 0) -query select sw1 = (cos_compare('hello', 'hello world') <= 0.891) and (cos_compare('hello', 'hello world') >= 0.890) -query select sw1 = (cos_compare('hello', 'hellow') >= 0.935) and (cos_compare('hello', 'hellow') <= 0.936) -query select sw1 = (cos_compare('hello', 'hellow', 1) >= 0.935) and (cos_compare('hello', 'hellow', 1) <= 0.936) From 08743657d5d4aa922f85ac5c2f7f0e1ff117da79 Mon Sep 17 00:00:00 2001 From: Israel Date: Mon, 17 Nov 2025 10:50:42 -0700 Subject: [PATCH 013/361] Re-apply reduced weight for duplicate pairs (temporarily turned off last commit). Update tests to pass with this modification. --- centrallix-lib/src/clusters.c | 2 +- centrallix/tests/test_cos_compare_00.to | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 84f01c535..7d8a225ca 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -178,7 +178,7 @@ pVector ca_build_vector(const char* str) int value = 0; for (; i < num_pairs && char_pairs[i].hash == hash; i++) { - // value /= 2; /* Reduce impact of repeated pairs. */ + value /= 2; /* Reduce impact of repeated pairs. */ value += ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; } diff --git a/centrallix/tests/test_cos_compare_00.to b/centrallix/tests/test_cos_compare_00.to index f45dec13a..c5b0b1a5b 100644 --- a/centrallix/tests/test_cos_compare_00.to +++ b/centrallix/tests/test_cos_compare_00.to @@ -11,13 +11,13 @@ query select case4 = condition((cos_compare('hello there', 'hellow there') >= 0. # All email addresses and phone numbers are imaginary and were fabricated for the purposes of this test query select cynthia = condition((cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") >= 0.49) and (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") <= 0.54), "pass", "fail") -query select timothy = condition((cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.425) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.475), "pass", "fail") +query select timothy = condition((cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.45) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.50), "pass", "fail") -query select lance = condition((cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.35) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.40), "pass", "fail") +query select lance = condition((cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.425) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.475), "pass", "fail") query select gregory = condition((cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") >= 0.94) and (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") <= 0.99), "pass", "fail") -query select nathan = condition((cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >= 0.66) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.71), "pass", "fail") +query select nathan = condition((cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >= 0.575) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.625), "pass", "fail") query select identical = condition((cos_compare("This is an identical case", "This is an identical case") >= 0.975) and (cos_compare("This is an identical case", "This is an identical case") <= 1.00), "pass", "fail") From 01d918aa2d4f3f3cb03fbf95d682c6da5388fca2 Mon Sep 17 00:00:00 2001 From: Israel Date: Mon, 17 Nov 2025 11:09:19 -0700 Subject: [PATCH 014/361] Clean up. --- centrallix-lib/src/clusters.c | 20 +++------ centrallix-os/cluster-schema.cluster | 6 +-- centrallix-os/testdir/file.cluster | 64 ---------------------------- 3 files changed, 8 insertions(+), 82 deletions(-) delete mode 100644 centrallix-os/testdir/file.cluster diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 7d8a225ca..e0b71efaa 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -234,18 +234,17 @@ unsigned int ca_sparse_len(const pVector vector) } /*** Print the underlying implementation values sparsely allocated - *** vector (intended for debugging). + *** vector (for debugging). *** - *** @param out File to print to. *** @param vector The vector. ***/ -void ca_fprint_vector(FILE* out, const pVector vector) +void ca_print_vector(const pVector vector) { const unsigned int len = ca_sparse_len(vector); - fprintf(out, "Vector: [%d", vector[0]); + printf("Vector: [%d", vector[0]); for (unsigned int i = 1u; i < len; i++) - fprintf(out, ", %d", vector[i]); - fprintf(out, "]"); + printf(", %d", vector[i]); + printf("]"); } /*** Compute the magnitude of a sparsely allocated vector. @@ -409,9 +408,6 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 *** *** @attention - `Complexity`: O(nm), where n and m are the lengths of str1 *** and str2 (respectively). - *** - *** @skip - *** LINK ../../centrallix-sysdoc/string_comparison.md#levenshtein ***/ unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) { @@ -500,9 +496,6 @@ unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_len *** @param v1 A `pVector` to the first string to compare. *** @param v2 A `pVector` to the second string to compare. *** @returns The cosine similarity between the two strings. - *** - *** @skip - *** LINK ../../centrallix-sysdoc/string_comparison.md#cosine ***/ double ca_cos_compare(void* v1, void* v2) { @@ -532,9 +525,6 @@ double ca_cos_compare(void* v1, void* v2) *** @param str1 A `char*` to the first string to compare. *** @param str2 A `char*` to the second string to compare. *** @returns The levenshtein similarity between the two strings. - *** - *** @skip - *** LINK ../../centrallix-sysdoc/string_comparison.md#levenshtein ***/ double ca_lev_compare(void* str1, void* str2) { diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index 4113a339a..5e11cd7c2 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -4,9 +4,9 @@ file_name "system/cluster" { name "cluster/parameter" { - type : DATA_T // See datatypes.h - ?default : type - ?name : String // Overrides the name above. + type : DATA_T // See datatypes.h + ?default : type // Default value for the variable. + ?name : String // Overrides the name above. ?style : StyleObj // idk where to find docs for this. } // Access with :parameters:name. Accessing dynamic data (e.g. parameters) diff --git a/centrallix-os/testdir/file.cluster b/centrallix-os/testdir/file.cluster deleted file mode 100644 index 929efdd03..000000000 --- a/centrallix-os/testdir/file.cluster +++ /dev/null @@ -1,64 +0,0 @@ -$Version=2$ -file_name "system/cluster" - { - // Developer can specify parameters to improve file reuseability. - // TIP: Improve performance by declairing frequently used parameters first. - k "cluster/parameter" { type = integer; style=notnull; } - str "cluster/parameter" { type = string; } - int "cluster/parameter" { type = integer; default = runserver(:parameters:k); } - dbl "cluster/parameter" { type = double; default=4.2; } - // conversion "cluster/parameter" { type=double; default=4; } - - null_str "cluster/parameter" { type = string; default = null; } - null_int "cluster/parameter" { type = integer; default = null; } - null_dbl "cluster/parameter" { type = double; default = null; } - - // We calculate k in a centrallix script using: - // k = max(2, pow(log(n) / log(36), 3.2) - 8) - // where n is the number of records passed. - - // Specify the data source at the top of the file. - // How do we pass distinct data? Should the driver - // handle that for us? - source = "/apps/kardia/data/Kardia_DB/p_partner/rows"; - attr_name = p_given_name; // runserver(:parameters:str) - - // Clustering object specifies properties for clustering. - kmeans_cluster "cluster/cluster" - { - algorithm = "k-means"; - similarity_measure = "cosine"; - num_clusters = runserver(:parameters:k); - min_improvement = 0.0001; - max_iterations = 48; - - // Create subclusters. (Not implemented) - sub_cluster "cluster/cluster" - { - algorithm = "none"; - similarity_measure = "cosine"; - num_clusters = 7; - min_improvement = "max"; - } - } - - // Complete search. - no_clustering "cluster/cluster" - { - algorithm = "none"; - } - - dups "cluster/search" - { - source = kmeans_cluster; - threshold = 0.75; - similarity_measure = "cosine"; - } - - dups2 "cluster/search" - { - source = no_clustering; - threshold = 0.75; - similarity_measure = "cosine"; - } - } From 42a65f17a15c96fe2792268e7af6638cd285d4c3 Mon Sep 17 00:00:00 2001 From: Israel Date: Mon, 17 Nov 2025 13:34:41 -0700 Subject: [PATCH 015/361] Update licences. --- centrallix-lib/include/clusters.h | 7 ++++-- centrallix-lib/include/util.h | 33 +++++++++++++++---------- centrallix-lib/src/clusters.c | 7 ++++-- centrallix-lib/src/util.c | 33 +++++++++++++++---------- centrallix-sysdoc/OSDriver_Authoring.md | 31 +++++++++++++++++++++++ centrallix-sysdoc/string_similarity.md | 5 +++- 6 files changed, 85 insertions(+), 31 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index ffa1223fb..c0718cea9 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -28,8 +28,11 @@ /* Module: lib_cluster.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ -/* Description: Internal algorithms for the cluster object driver. */ -/* See centrallix-sysdoc/EAV_Pivot.md for more information. */ +/* Description: Clustering library used to cluster and search data with */ +/* cosine similarity and Levenshtein similarity (aka. edit */ +/* distance). Used by the "clustering driver". */ +/* For more information on how to use this library, see */ +/* string-similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ #include diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 0f2685039..853954409 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -2,19 +2,26 @@ #define UTILITY_H /************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Base Library */ -/* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ -/* */ -/* You may use these files and this library under the terms of the */ -/* GNU Lesser General Public License, Version 2.1, contained in the */ -/* included file "COPYING". */ -/* */ -/* Module: (util.c,.h) */ -/* Author: Micah Shennum */ -/* Date: May 26, 2011 */ -/* Description: Collection of utilities */ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: util.c, util.h */ +/* Author: Micah Shennum and Israel Fuller */ +/* Date: May 26, 2011 */ +/* Description: Collection of utilities including: */ +/* - Utilities for parsing numbers. */ +/* - The timer utility for benchmarking code. */ +/* - snprint_bytes() for formatting a byte count. */ +/* - snprint_llu() for formatting large numbers. */ +/* - fprint_mem() for printing memory stats. */ +/* - min() and max() for handling numbers. */ +/* - The check functions for reliably printing debug data. */ /************************************************************************/ #ifdef __cplusplus diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index e0b71efaa..4bfce8ee6 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -1,4 +1,3 @@ - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ @@ -26,7 +25,11 @@ /* Module: lib_cluster.c */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ -/* Description: Internal algorithms for the cluster object driver. */ +/* Description: Clustering library used to cluster and search data with */ +/* cosine similarity and Levenshtein similarity (aka. edit */ +/* distance). Used by the "clustering driver". */ +/* For more information on how to use this library, see */ +/* string-similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ /** This file has additional documentation in string_similarity.md. **/ diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index f60349a74..7c234a341 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -1,17 +1,24 @@ /************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Base Library */ -/* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ -/* */ -/* You may use these files and this library under the terms of the */ -/* GNU Lesser General Public License, Version 2.1, contained in the */ -/* included file "COPYING". */ -/* */ -/* Module: (util.c,.h) */ -/* Author: Micah Shennum */ -/* Date: May 26, 2011 */ -/* Description: Collection of utilities */ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: util.c, util.h */ +/* Author: Micah Shennum and Israel Fuller */ +/* Date: May 26, 2011 */ +/* Description: Collection of utilities including: */ +/* - Utilities for parsing numbers. */ +/* - The timer utility for benchmarking code. */ +/* - snprint_bytes() for formatting a byte count. */ +/* - snprint_llu() for formatting large numbers. */ +/* - fprint_mem() for printing memory stats. */ +/* - min() and max() for handling numbers. */ +/* - The check functions for reliably printing debug data. */ /************************************************************************/ #include diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index f679dac32..8e58f7cee 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -1,3 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # ObjectSystem Driver Interface **Author**: Greg Beeley diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 33667a05c..526cbe307 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -25,7 +25,10 @@ - + + + + - - # String Similarity -The following sections discuss the approaches to calculating similarity between two strings which are implemented in the `clusters.c` library. This library can be included using `#include "clusters.h"` in centrallix-lib and `#include "cxlib/clusters.h"` in centrallix. +The following sections discuss the approaches to calculating similarity between two strings using the `clusters.c` library. This library can be included using `#include "cxlib/clusters.h"` in the centrallix codebase (use `#include "clusters.h"` in other libaries in centrallix-lib). -## Table of Contents +## Table of Contents - [String Comparison](#string-comparison) - [Table of Contents](#table-of-contents) - [Cosine Similarity](#cosine-similarity) @@ -76,10 +56,10 @@ The following sections discuss the approaches to calculating similarity between - [Implement Missing Algorithms](#implement-missing-algorithms) -## Cosine Similarity +## Cosine Similarity The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. Conceptually, it's like finding the _angle_ between two vectors. To get these vectors, we use the relative frequency of character pairs within each string. To reduce memory cost and speed up computation, we store them in a special sparsely allocated form, described below. -### Character Sets +### Character Sets Cosine compare currently uses the following character sets. These can be extended or modified later, if necessary. ```c const char ALLOW_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -97,26 +77,26 @@ const char BOUNDARY_CHAR = ('a' - 1); // aka. '`' - This character appears to have been selected to be one before the first character in `CHAR_SET` (thus convention dictates that it be written `'a' - 1` to indicate this), although it's unknown if that's the main or only reason. - If `clusters.h` is included, it can be accessed using the `CA_BOUNDARY_CHAR` macro. -### Character Pair Hashing +### Character Pair Hashing Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296` possible character pairs. If the number of characters in the `CHAR_SET` ever needed to be expanded - for example, to include all UTF-8 characters - this number would quickly explode exponentially to utterly infeasible proportions. Thus, a hashing algorithm is employed to hash each character pair down to a more reasonable number of dimensions (which can be accessed with the `CA_NUM_DIMS` macro). -### String Vectors +### String Vectors Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies to based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. -### Sparse Vectors +### Sparse Vectors As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. **Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. **Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. -### Computing Similarity +### Computing Similarity Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. -## Levenshtein Similarity +## Levenshtein Similarity The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using levenshtein edit distance. The levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 462d0625f..7ee0bdbba 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1,4 +1,3 @@ - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ @@ -42,14 +41,12 @@ #include #include "cxlib/clusters.h" -#include "cxlib/mtask.h" #include "cxlib/mtsession.h" #include "cxlib/newmalloc.h" #include "cxlib/util.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "expression.h" -#include "hints.h" #include "obj.h" #include "param.h" #include "st_node.h" From ee0bca7351115d3ea5c6cdbd46db7b1225ad4424 Mon Sep 17 00:00:00 2001 From: Israel Date: Wed, 19 Nov 2025 11:58:18 -0700 Subject: [PATCH 017/361] Add "show_less" option to the cache method (skips printing uncomputed caches). Fix a formatting issue with the stat method. Fix a missing include in the util.c library. --- centrallix-lib/src/util.c | 1 + centrallix/osdrivers/objdrv_cluster.c | 93 +++++++++++++++++++-------- 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index cd8a3b49a..e39572f95 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 7ee0bdbba..8fb97b184 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4067,7 +4067,8 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) void** args = (void**)arg; unsigned int* type_id_ptr = (unsigned int*)args[0]; unsigned int* total_bytes_ptr = (unsigned int*)args[1]; - char* path = (char*)args[2]; + unsigned long long* less_ptr = (unsigned long long*)args[2]; + char* path = (char*)args[3]; /** If a path is provided, check that it matches the start of the key. **/ if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return 0; @@ -4081,25 +4082,46 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) case 1u: { pSourceData source_data = (pSourceData)data; + + /** Compute size. **/ + bytes = ci_SizeOfSourceData(source_data); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && source_data->Vectors == NULL) goto no_print; + + /** Compute printing information. **/ type = "Source"; name = source_data->Name; - bytes = ci_SizeOfSourceData(source_data); break; } case 2u: { pClusterData cluster_data = (pClusterData)data; + + /** Compute size. **/ + bytes = ci_SizeOfClusterData(cluster_data, false); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && cluster_data->Clusters == NULL) goto no_print; + + /** Compute printing information. **/ type = "Cluster"; name = cluster_data->Name; - bytes = ci_SizeOfClusterData(cluster_data, false); break; } case 3u: { pSearchData search_data = (pSearchData)data; + + /** Compute size. **/ + bytes = ci_SizeOfSearchData(search_data); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && search_data->Dups == NULL) goto no_print; + + /** Compute printing information. **/ type = "Search"; name = search_data->Name; - bytes = ci_SizeOfSearchData(search_data); break; } default: @@ -4107,14 +4129,20 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) return -1; } - /** Increment total bytes. **/ - *total_bytes_ptr += bytes; + /** Print the cache entry data. **/ char buf[12]; snprint_bytes(buf, sizeof(buf), bytes); printf("%-8s %-16s %-12s \"%s\"\n", type, name, buf, key); + increment_total: + *total_bytes_ptr += bytes; + return 0; + + no_print: + (*less_ptr)++; + goto increment_total; } @@ -4190,14 +4218,18 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx if (param->String == NULL) { mssErrorf(1, "Cluster", - "[param : \"show\" | \"show_all\" | \"drop_all\"] is required for the cache method." + "[param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] is required for the cache method." ); goto err; } /** 'show' and 'show_all'. **/ bool show = false; - if (strcmp(param->String, "show") == 0) + unsigned long long skip_uncomputed = 0llu; + if (strcmp(param->String, "show_less") == 0) + /** Specify show_less to skip uncomputed caches. **/ + skip_uncomputed = 1ull; + if (skip_uncomputed == 1ull || strcmp(param->String, "show") == 0) { show = true; path = ci_file_path(driver_data->NodeData->Parent); @@ -4217,25 +4249,32 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx failed |= !check(xhForEach( &ClusterDriverCaches.SourceDataCache, ci_PrintEntry, - (void*[]){&i, &source_bytes, path} + (void*[]){&i, &source_bytes, (void*)&skip_uncomputed, path} )); i++; failed |= !check(xhForEach( &ClusterDriverCaches.ClusterDataCache, ci_PrintEntry, - (void*[]){&i, &cluster_bytes, path} + (void*[]){&i, &cluster_bytes, (void*)&skip_uncomputed, path} )); i++; failed |= !check(xhForEach( &ClusterDriverCaches.SearchDataCache, ci_PrintEntry, - (void*[]){&i, &search_bytes, path} + (void*[]){&i, &search_bytes, (void*)&skip_uncomputed, path} )); if (failed) { mssErrorf(0, "Cluster", "Unexpected error occurred while showhing caches."); ret = -1; } + + /** Precomputations. **/ + unsigned int total_caches = 0u + + (unsigned int)ClusterDriverCaches.SourceDataCache.nItems + + (unsigned int)ClusterDriverCaches.ClusterDataCache.nItems + + (unsigned int)ClusterDriverCaches.SearchDataCache.nItems; + if (total_caches <= skip_uncomputed) printf("All caches skipped, nothing to show...\n"); /** Print stats. **/ char buf[16]; @@ -4244,10 +4283,10 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprint_bytes(buf, sizeof(buf), source_bytes)); printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprint_bytes(buf, sizeof(buf), cluster_bytes)); printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprint_bytes(buf, sizeof(buf), search_bytes)); - printf("%-8s %-4d %-12s\n\n", "Total", - ClusterDriverCaches.SourceDataCache.nItems + ClusterDriverCaches.ClusterDataCache.nItems + ClusterDriverCaches.SearchDataCache.nItems, - snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes) - ); + printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); + + /** Print skip stats (if anything was skipped.) **/ + if (skip_uncomputed > 0llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); return ret; } @@ -4262,7 +4301,7 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx /** Unknown parameter. **/ mssErrorf(1, "Cluster", - "Expected [param : \"show\" | \"show_all\" | \"drop_all\"] for the cache method, but got: \"%s\"", + "Expected [param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] for the cache method, but got: \"%s\"", param->String ); goto err; @@ -4272,17 +4311,17 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx { char buf[12]; printf("Cluster Driver Statistics:\n"); - printf(" Stat Name Value\n"); - printf(" OpenCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); - printf(" OpenQueryCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); - printf(" FetchCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); - printf(" CloseCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); - printf(" GetTypeCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); - printf(" GetValCalls %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); - printf(" GetValCalls_name %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); - printf(" GetValCalls_key1 %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); - printf(" GetValCalls_key2 %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); - printf(" GetValCalls_sim %8s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); + printf(" Stat Name %12s\n", "Value"); + printf(" OpenCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); + printf(" OpenQueryCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); + printf(" FetchCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); + printf(" CloseCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); + printf(" GetTypeCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); + printf(" GetValCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); + printf(" GetValCalls_name %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); + printf(" GetValCalls_key1 %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); + printf(" GetValCalls_key2 %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); + printf(" GetValCalls_sim %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); return 0; } From 0c9eb2cf957b109b981b8f318652e0cff8effa0d Mon Sep 17 00:00:00 2001 From: Israel Date: Wed, 19 Nov 2025 15:30:35 -0700 Subject: [PATCH 018/361] Update cluster library to use dynamic memory for any data over a couple hundred bytes. Add check_double() to handle functions that return NAN on failure. Clean up. --- centrallix-lib/include/clusters.h | 4 +- centrallix-lib/include/util.h | 14 ++ centrallix-lib/src/clusters.c | 191 +++++++++++++++----------- centrallix/expression/exp_functions.c | 16 ++- centrallix/osdrivers/objdrv_cluster.c | 24 +++- 5 files changed, 160 insertions(+), 89 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index c0718cea9..218422253 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -76,7 +76,7 @@ typedef struct nmRegister(sizeof(Dup), "Dup") /** Edit distance function. **/ -unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); +int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); /** Vector functions. **/ pVector ca_build_vector(const char* str); @@ -102,7 +102,7 @@ int ca_kmeans( _v[0] == -172 && _v[1] == 11 && _v[2] == -78; \ }) -/** Comparison functions, for ca_search(). **/ +/** Comparison functions (see ca_search()). **/ double ca_cos_compare(void* v1, void* v2); double ca_lev_compare(void* str1, void* str2); bool ca_eql(pVector v1, pVector v2); diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 853954409..2c8537327 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -147,6 +147,20 @@ void print_diagnostics(int code, const char* function_name, const char* file_nam success; \ }) +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is a NAN double. Not intended for user errors. + *** + *** @param result The result of the function we're checking. + *** @returns result + ***/ +#define check_double(result) \ + ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ + __typeof__ (result) _r = (result); \ + if (isnan(_r)) print_diagnostics(0, #result, __FILE__, __LINE__); \ + _r; \ + }) + /*** Ensures that developer diagnostics are printed if the result of the *** passed function call is a NULL pointer. Not intended for user errors. *** diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 4bfce8ee6..6487e28e7 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -124,8 +124,11 @@ static int charpair_cmp(const void *p1, const void *p2) ***/ pVector ca_build_vector(const char* str) { - unsigned char chars[strlen(str) + 2u]; unsigned int num_chars = 0u; + unsigned char* chars = check_ptr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); + if (chars == NULL) goto err; + + /** Begin adding char pairs (in order). **/ chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) { @@ -149,8 +152,9 @@ pVector ca_build_vector(const char* str) } chars[num_chars++] = CA_BOUNDARY_CHAR; /* Ending boundary character. */ - /** Compute char pairs. **/ - CharPair char_pairs[num_chars]; + /** Compute hash values for char pairs. **/ + CharPair* char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); + if (char_pairs == NULL) goto err; const unsigned int num_pairs = num_chars - 1u; for (unsigned int i = 0u; i < num_pairs; i++) { @@ -163,12 +167,16 @@ pVector ca_build_vector(const char* str) char_pairs[i].hash = hash_char_pair(chars[i], chars[i + 1]); } + /** Free unused memory. **/ + nmSysFree(chars); + chars = NULL; + /** Sort char_pairs by hash value. **/ qsort(char_pairs, num_pairs, sizeof(CharPair), charpair_cmp); /** Allocate space for the sparse vector. **/ - pVector sparse_vector = (pVector)check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); - if (sparse_vector == NULL) return NULL; + pVector sparse_vector = check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); + if (sparse_vector == NULL) goto err; /** Build the sparse vector. **/ unsigned int cur = 0u, dim = 0u; @@ -199,11 +207,23 @@ pVector ca_build_vector(const char* str) } if (dim != CA_NUM_DIMS) sparse_vector[cur++] = -(CA_NUM_DIMS - dim); + /** Free unused memory. **/ + nmSysFree(char_pairs); + char_pairs = NULL; + /** Trim extra space wasted by identical hashes. **/ - pVector trimmed_sparse_vector = (pVector)check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); - if (trimmed_sparse_vector == NULL) return NULL; + pVector trimmed_sparse_vector = check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); + if (trimmed_sparse_vector == NULL) goto err; + sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ + /** Return the result. **/ return trimmed_sparse_vector; + + err: + if (sparse_vector != NULL) nmSysFree(sparse_vector); + if (char_pairs != NULL) nmSysFree(char_pairs); + if (chars != NULL) nmSysFree(chars); + return NULL; } /*** Free memory allocated to store a sparse vector. @@ -404,6 +424,7 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 *** @param str2 The second string. *** @param length1 The length of the first string. *** @param length1 The length of the first string. + *** @returns The edit distance between the two strings, or a negative value on error. *** *** @attention - `Tip`: Pass 0 for the length of either string to infer it *** using the null terminating character. Conversely, character arrays @@ -412,8 +433,10 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 *** @attention - `Complexity`: O(nm), where n and m are the lengths of str1 *** and str2 (respectively). ***/ -unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) +int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) { + int result = -1; + /*** lev_matrix: *** For all i and j, d[i][j] will hold the Levenshtein distance between *** the first i characters of s and the first j characters of t. @@ -423,9 +446,13 @@ unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_len ***/ const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; - unsigned int* lev_matrix[str1_len + 1]; + unsigned int** lev_matrix = check_ptr(nmSysMalloc((str1_len + 1) * sizeof(unsigned int*))); + if (lev_matrix == NULL) goto end; for (unsigned int i = 0u; i < str1_len + 1u; i++) - lev_matrix[i] = nmMalloc((str2_len + 1) * sizeof(unsigned int)); + { + lev_matrix[i] = check_ptr(nmSysMalloc((str2_len + 1) * sizeof(unsigned int))); + if (lev_matrix[i] == NULL) goto end; + } /*** Base case #0: *** Transforming an empty string into an empty string has 0 cost. @@ -472,19 +499,36 @@ unsigned int edit_dist(const char* str1, const char* str2, const size_t str1_len ); unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; - // Find the best operation. + /** Assign the best operation. **/ lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); } } } /** Store result. **/ - unsigned int result = lev_matrix[str1_len][str2_len]; + unsigned int unsigned_result = lev_matrix[str1_len][str2_len]; + if (unsigned_result > INT_MAX) + { + fprintf(stderr, + "Warning: Integer overflow detected in edit_dist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", + str1, str2, str1_length, str2_length, unsigned_result, INT_MAX + ); + } + result = (int)unsigned_result; /** Cleanup. **/ - for (unsigned int i = 0u; i < str1_len + 1u; i++) - nmFree(lev_matrix[i], (str2_len + 1) * sizeof(unsigned int)); + end: + if (lev_matrix != NULL) + { + for (unsigned int i = 0u; i < str1_len + 1u; i++) + { + if (lev_matrix[i] == NULL) break; + else nmSysFree(lev_matrix[i]); + } + nmSysFree(lev_matrix); + } + /** Done. **/ return result; } @@ -527,7 +571,7 @@ double ca_cos_compare(void* v1, void* v2) *** *** @param str1 A `char*` to the first string to compare. *** @param str2 A `char*` to the second string to compare. - *** @returns The levenshtein similarity between the two strings. + *** @returns The levenshtein similarity between the two strings, or NAN on failure. ***/ double ca_lev_compare(void* str1, void* str2) { @@ -543,7 +587,8 @@ double ca_lev_compare(void* str1, void* str2) if (len1 == 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ - const unsigned int dist = edit_dist((const char*)str1, (const char*)str2, len1, len2); + const int dist = check_neg(edit_dist((const char*)str1, (const char*)str2, len1, len2)); + if (dist < 0) return NAN; /** Normalize edit distance into a similarity measure. **/ const double normalized_similarity = 1.0 - (double)dist / (double)max(len1, len2); @@ -583,11 +628,14 @@ static double get_cluster_size( pCentroid* centroids, const unsigned int num_clusters) { + double result = NAN; /** Could be up to around 1KB on the stack, but I think that's fine. **/ - double cluster_sums[num_clusters]; - unsigned int cluster_counts[num_clusters]; - memset(cluster_sums, 0, sizeof(cluster_sums)); - memset(cluster_counts, 0, sizeof(cluster_counts)); + double* cluster_sums = check_ptr(nmSysMalloc(num_clusters * sizeof(double))); + unsigned int* cluster_counts = check_ptr(nmSysMalloc(num_clusters * sizeof(unsigned int))); + if (cluster_sums == NULL) goto end; + if (cluster_counts == NULL) goto end; + memset(cluster_sums, 0, sizeof(num_clusters * sizeof(double))); + memset(cluster_counts, 0, sizeof(num_clusters * sizeof(unsigned int))); /** Sum the difference from each vector to its cluster centroid. **/ for (unsigned int i = 0u; i < num_vectors; i++) @@ -609,37 +657,15 @@ static double get_cluster_size( num_valid_clusters++; } - /** Return average sizes. **/ - return cluster_total / num_valid_clusters; - } - -/*** Compute the param_value for `k` (number of clusters), given a dataset of with - *** a size of `n`. - *** - *** The following table shows data sizes vs.selected cluster size. In testing, - *** these numbers tended to give a good balance of accuracy and duplicates detected. - *** - *** ```csv - *** Data Size, Actual - *** 10k, 12 - *** 100k, 33 - *** 1M, 67 - *** 4M, 93 - *** ``` - *** - *** This function is not intended for datasets smaller than (`n < ~2000`). - *** These should be handled using complete search. - *** - *** LaTeX Notation: \log_{36}\left(n\right)^{3.1}-8 - *** - *** @param n The size of the dataset. - *** @returns k, the number of clusters to use. - *** - *** Complexity: `O(1)` - ***/ -unsigned int compute_k(const unsigned int n) - { - return (unsigned)max(2, pow(log(n) / log(36), 3.2) - 8); + /** Calculate average sizes. **/ + result = cluster_total / num_valid_clusters; + + end: + /** Clean up. **/ + if (cluster_sums != NULL) nmSysFree(cluster_sums); + if (cluster_counts != NULL) nmSysFree(cluster_counts); + + return result; } /*** Executes the k-means clustering algorithm. Selects NUM_CLUSTERS random @@ -696,22 +722,19 @@ int ca_kmeans( /** Allocate space to store centroids and new_centroids. **/ /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ const size_t centroids_size = num_clusters * sizeof(pCentroid); - pCentroid* centroids = (pCentroid*)check_ptr(nmMalloc(centroids_size)); + pCentroid* centroids = check_ptr(nmMalloc(centroids_size)); + pCentroid* new_centroids = check_ptr(nmMalloc(centroids_size)); if (centroids == NULL) goto end; + if (new_centroids == NULL) goto end; memset(centroids, 0, centroids_size); - pCentroid* new_centroids = (pCentroid*)check_ptr(nmMalloc(centroids_size)); - if (new_centroids == NULL) goto end_free_centroids; memset(new_centroids, 0, centroids_size); for (unsigned int i = 0u; i < num_clusters; i++) { - /** Malloc each centroid. **/ - centroids[i] = (pCentroid)check_ptr(nmMalloc(pCentroidSize)); - if (centroids[i] == NULL) goto end_deep_free_centroids; + centroids[i] = check_ptr(nmMalloc(pCentroidSize)); + new_centroids[i] = check_ptr(nmMalloc(pCentroidSize)); + if (centroids[i] == NULL) goto end; + if (new_centroids[i] == NULL) goto end; memset(centroids[i], 0, pCentroidSize); - - /** Malloc each new centroid. **/ - new_centroids[i] = (pCentroid)check_ptr(nmMalloc(pCentroidSize)); - if (new_centroids[i] == NULL) goto end_deep_free_centroids; memset(new_centroids[i], 0, pCentroidSize); } @@ -797,8 +820,9 @@ int ca_kmeans( } /** Is there enough improvement? **/ - if (min_improvement < -1) continue; /** Skip check if it will always fail. **/ - const double average_cluster_size = get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters); + if (min_improvement < -1) continue; /** Skip check if it will never end the loop. **/ + const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); + if (isnan(average_cluster_size)) goto end; const double improvement = old_average_cluster_size - average_cluster_size; if (improvement < min_improvement) break; old_average_cluster_size = average_cluster_size; @@ -815,22 +839,25 @@ int ca_kmeans( successful = true; /** Clean up. **/ - end_deep_free_centroids: - for (unsigned int i = 0u; i < num_clusters; i++) + end: + if (centroids != NULL) + { + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (centroids[i] != NULL) nmFree(centroids[i], pCentroidSize); + else break; + } + nmFree(centroids, num_clusters * sizeof(pCentroid)); + } + if (new_centroids != NULL) { - if (centroids[i] != NULL) nmFree(centroids[i], pCentroidSize); - else break; - if (new_centroids[i] != NULL) nmFree(new_centroids[i], pCentroidSize); - else break; + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (new_centroids[i] != NULL) nmFree(new_centroids[i], pCentroidSize); + else break; + } + nmFree(new_centroids, num_clusters * sizeof(pCentroid)); } - - // end_free_new_centroids: - nmFree(new_centroids, num_clusters * sizeof(pCentroid)); - - end_free_centroids: - nmFree(centroids, num_clusters * sizeof(pCentroid)); - - end: return (successful) ? 0 : -1; } @@ -860,7 +887,8 @@ void* ca_most_similar( double best_sim = -INFINITY; for (unsigned int i = 0u; (num_data == 0u) ? (data[i] != NULL) : (i < num_data); i++) { - const double sim = similarity(target, data[i]); + const double sim = check_double(similarity(target, data[i])); + if (isnan(sim)) continue; /* Skip this comparison. */ if (sim > best_sim && sim > threshold) { most_similar = data[i]; @@ -889,8 +917,8 @@ void* ca_most_similar( *** struct. If this variable is null, these values are also left null. *** @param maybe_dups A pointer to an xArray in which dups should be found. *** Pass NULL to allocate a new one. - *** @returns An xArray holding all of the duplocates found. If maybe_dups is - *** not NULL, this will be that xArray, to allow for chaining. + *** @returns An xArray holding all of the duplocates found, or NULL if an + *** error occurs. ***/ pXArray ca_sliding_search( void** data, @@ -919,7 +947,8 @@ pXArray ca_sliding_search( const unsigned int window_end = min(i + window_size, num_data); for (unsigned int j = window_start; j < window_end; j++) { - const double sim = similarity(data[i], data[j]); + const double sim = check_double(similarity(data[i], data[j])); + if (isnan(sim)) goto err_free_dups; if (sim > threshold) /* Dup found! */ { Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index dd65a0c52..4932bcf1a 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4316,7 +4316,15 @@ static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* } else { /* lev_compare() */ - tree->Types.Double = ca_lev_compare(str1, str2); + double lev_sim = check_double(ca_lev_compare(str1, str2)); + if (isnan(lev_sim)) + { + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute levenstein edit distance."); + return -1; + } + + /** Return the computed result. **/ + tree->Types.Double = lev_sim; tree->DataType = DATA_T_DOUBLE; return 0; } @@ -4359,7 +4367,11 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ - tree->Integer = edit_dist(str1, str2, 0lu, 0lu); + int dist = check_neg(edit_dist(str1, str2, 0lu, 0lu)); + if (dist < 0) return -1; + + /** Return the computed distance. **/ + tree->Integer = dist; tree->DataType = DATA_T_INTEGER; return 0; } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 8fb97b184..e38fb3e68 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2546,7 +2546,6 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (labels == NULL) goto err_free_sims; /** Run kmeans. **/ - Timer timer_i, *timer = timer_start(timer_init(&timer_i)); const bool successful = check(ca_kmeans( source_data->Vectors, source_data->nVectors, @@ -2556,7 +2555,6 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) labels, cluster_data->Sims )); - timer_stop(timer); if (!successful) goto err_free_sims; /** Convert the labels into clusters. **/ @@ -2675,6 +2673,11 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) (void**)cluster_data->SourceData->Keys, dups )); + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", "Failed to compute sliding search with cosine similarity measure."); + goto err_free; + } } else { @@ -2688,7 +2691,11 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) (void**)cluster_data->SourceData->Keys, dups )); - if (dups_temp == NULL) goto err_free; + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", "Failed to compute complete search with cosine similarity measure."); + goto err_free; + } else dups = dups_temp; } } @@ -2708,6 +2715,11 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) (void**)cluster_data->SourceData->Keys, dups )); + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", "Failed to compute sliding search with Levenstein similarity measure."); + goto err_free; + } } else { @@ -2721,7 +2733,11 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) (void**)cluster_data->SourceData->Keys, dups )); - if (dups_temp == NULL) goto err_free; + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", "Failed to compute complete search with Levenstein similarity measure."); + goto err_free; + } else dups = dups_temp; } } From 394764efdbc6548162b1b5b7a9401428ec8e6aaa Mon Sep 17 00:00:00 2001 From: Israel Date: Wed, 19 Nov 2025 15:37:31 -0700 Subject: [PATCH 019/361] Remove necessary requests for the driver name in objQueryFetch(). --- centrallix/objectsystem/obj_query.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/centrallix/objectsystem/obj_query.c b/centrallix/objectsystem/obj_query.c index 9b64f241f..c4dff40ac 100644 --- a/centrallix/objectsystem/obj_query.c +++ b/centrallix/objectsystem/obj_query.c @@ -414,7 +414,6 @@ objQueryFetch(pObjQuery this, int mode) { pObject obj = NULL; void* obj_data; - char* name; char buf[OBJSYS_MAX_PATH + 32]; pObjQuerySortItem sort_item; int rval; @@ -529,14 +528,6 @@ objQueryFetch(pObjQuery this, int mode) goto error; } obj->Data = obj_data; - - this->Obj->Driver->GetAttrValue(obj_data, "name", DATA_T_STRING, &name, NULL); - if (strlen(name) + strlen(this->Obj->Pathname->Pathbuf) + 2 > OBJSYS_MAX_PATH) - { - mssError(1,"OSML","Filename in query result exceeded internal limits"); - OSMLDEBUG(OBJ_DEBUG_F_APITRACE, " null\n"); - goto error; - } /** If we need to check it, do so now. **/ if (!(this->Flags & OBJ_QY_F_FULLQUERY) && this->Tree) @@ -778,4 +769,3 @@ objGetQueryIdentityPath(pObjQuery this, char* pathbuf, int maxlen) return 0; } - From 9b8cc19754e2109004de9bac1cb944ce378fbdf9 Mon Sep 17 00:00:00 2001 From: Israel Date: Thu, 20 Nov 2025 09:52:50 -0700 Subject: [PATCH 020/361] Fix bugs that caused regressions after the updates to the cluster library. Round similarity results to avoid floating point errors. Enable caching for memory allocated in get_cluster_size(). Rename edit_dist() to ca_edit_dist() to match format for public functions. Rename print_diagnostics() to print_err(). --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/include/util.h | 12 +++---- centrallix-lib/src/clusters.c | 51 ++++++++++++++++----------- centrallix-lib/src/util.c | 2 +- centrallix/expression/exp_functions.c | 10 ++++-- centrallix/osdrivers/objdrv_cluster.c | 5 +-- 6 files changed, 48 insertions(+), 34 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 218422253..879ac652a 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -76,7 +76,7 @@ typedef struct nmRegister(sizeof(Dup), "Dup") /** Edit distance function. **/ -int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); +int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); /** Vector functions. **/ pVector ca_build_vector(const char* str); diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 2c8537327..03b63abaf 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -100,7 +100,7 @@ extern "C" { }) /** Error Handling. **/ -void print_diagnostics(int code, const char* function_name, const char* file_name, const int line_number); +void print_err(int code, const char* function_name, const char* file_name, const int line_number); /*** Ensures that developer diagnostics are printed if the result of the *** passed function call is not zero. Not intended for user errors. @@ -113,7 +113,7 @@ void print_diagnostics(int code, const char* function_name, const char* file_nam errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ const bool success = (_r == 0); \ - if (!success) print_diagnostics(_r, #result, __FILE__, __LINE__); \ + if (!success) print_err(_r, #result, __FILE__, __LINE__); \ success; \ }) @@ -128,7 +128,7 @@ void print_diagnostics(int code, const char* function_name, const char* file_nam errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ const bool success = (_r >= 0); \ - if (!success) print_diagnostics(_r, #result, __FILE__, __LINE__); \ + if (!success) print_err(_r, #result, __FILE__, __LINE__); \ success; \ }) @@ -143,7 +143,7 @@ void print_diagnostics(int code, const char* function_name, const char* file_nam errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ const bool success = (_r != -1); \ - if (!success) print_diagnostics(_r, #result, __FILE__, __LINE__); \ + if (!success) print_err(_r, #result, __FILE__, __LINE__); \ success; \ }) @@ -157,7 +157,7 @@ void print_diagnostics(int code, const char* function_name, const char* file_nam ({ \ errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ - if (isnan(_r)) print_diagnostics(0, #result, __FILE__, __LINE__); \ + if (isnan(_r)) print_err(0, #result, __FILE__, __LINE__); \ _r; \ }) @@ -171,7 +171,7 @@ void print_diagnostics(int code, const char* function_name, const char* file_nam ({ \ errno = 0; /* Reset errno to prevent confusion. */ \ __typeof__ (result) _r = (result); \ - if (_r == NULL) print_diagnostics(0, #result, __FILE__, __LINE__); \ + if (_r == NULL) print_err(0, #result, __FILE__, __LINE__); \ _r; \ }) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 6487e28e7..92125ce03 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -433,7 +433,7 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 *** @attention - `Complexity`: O(nm), where n and m are the lengths of str1 *** and str2 (respectively). ***/ -int edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) +int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) { int result = -1; @@ -473,12 +473,12 @@ int edit_dist(const char* str1, const char* str2, const size_t str1_length, cons for (unsigned int j = 1u; j <= str2_len; j++) lev_matrix[0][j] = j; - /** General Case **/ + /** General Case. **/ for (unsigned int i = 1u; i <= str1_len; i++) { for (unsigned int j = 1u; j <= str2_len; j++) { - /** Equal characters need no changes. **/ + /** If the characters are equal, no change is needed. **/ if (str1[i - 1] == str2[j - 1]) lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; @@ -510,7 +510,7 @@ int edit_dist(const char* str1, const char* str2, const size_t str1_length, cons if (unsigned_result > INT_MAX) { fprintf(stderr, - "Warning: Integer overflow detected in edit_dist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", + "Warning: Integer overflow detected in ca_edit_dist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", str1, str2, str1_length, str2_length, unsigned_result, INT_MAX ); } @@ -556,8 +556,8 @@ double ca_cos_compare(void* v1, void* v2) if (v1_empty && !v2_empty) return 0.0; if (!v1_empty && v2_empty) return 0.0; - /** Return the sparse similarity. **/ - return sparse_similarity(vec1, vec2); + /** Apply rounding to avoid annoying floating point issues before returning. **/ + return round(sparse_similarity(vec1, vec2) * 1000000) / 1000000; } /*** Compares two strings using their Levenshtein edit distance to compute a @@ -587,14 +587,14 @@ double ca_lev_compare(void* str1, void* str2) if (len1 == 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ - const int dist = check_neg(edit_dist((const char*)str1, (const char*)str2, len1, len2)); - if (dist < 0) return NAN; + const int edit_dist = ca_edit_dist((const char*)str1, (const char*)str2, len1, len2); + if (!check_neg(edit_dist)) return NAN; /** Normalize edit distance into a similarity measure. **/ - const double normalized_similarity = 1.0 - (double)dist / (double)max(len1, len2); + const double normalized_similarity = 1.0 - (double)edit_dist / (double)max(len1, len2); - /** Done. **/ - return normalized_similarity; + /** Apply rounding to avoid annoying floating point issues before returning. **/ + return round(normalized_similarity * 1000000) / 1000000; } /*** Check if two sparse vectors are identical. @@ -629,9 +629,15 @@ static double get_cluster_size( const unsigned int num_clusters) { double result = NAN; - /** Could be up to around 1KB on the stack, but I think that's fine. **/ - double* cluster_sums = check_ptr(nmSysMalloc(num_clusters * sizeof(double))); - unsigned int* cluster_counts = check_ptr(nmSysMalloc(num_clusters * sizeof(unsigned int))); + + /** Allocate space to store clusters as averages are computed. **/ + /*** We use nmMalloc() here because this function is usually called + *** repeatedly with the same number of clusters in the k-means loop. + *** Also, it is likely that k-means may be invoked multiple times with + *** the same k value, leading to additional caching benefits. + ***/ + double* cluster_sums = check_ptr(nmMalloc(num_clusters * sizeof(double))); + unsigned int* cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); if (cluster_sums == NULL) goto end; if (cluster_counts == NULL) goto end; memset(cluster_sums, 0, sizeof(num_clusters * sizeof(double))); @@ -662,8 +668,8 @@ static double get_cluster_size( end: /** Clean up. **/ - if (cluster_sums != NULL) nmSysFree(cluster_sums); - if (cluster_counts != NULL) nmSysFree(cluster_counts); + if (cluster_sums != NULL) nmFree(cluster_sums, num_clusters * sizeof(double)); + if (cluster_counts != NULL) nmFree(cluster_counts, num_clusters * sizeof(unsigned int)); return result; } @@ -939,7 +945,7 @@ pXArray ca_sliding_search( if (dups == NULL) goto err; } const int num_starting_dups = dups->nItems; - + /** Search for dups. **/ for (unsigned int i = 0u; i < num_data; i++) { @@ -948,7 +954,11 @@ pXArray ca_sliding_search( for (unsigned int j = window_start; j < window_end; j++) { const double sim = check_double(similarity(data[i], data[j])); - if (isnan(sim)) goto err_free_dups; + if (isnan(sim) || sim < 0.0 || 1.0 < sim) + { + fprintf(stderr, "Invalid similarity %g %lf.\n", sim, sim); + goto err_free_dups; + } if (sim > threshold) /* Dup found! */ { Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); @@ -968,11 +978,10 @@ pXArray ca_sliding_search( return dups; /** Error cleanup. **/ - err_free_dups: - /** Free the dups we added to the XArray. */ + /** Free the dups that we added to the XArray. **/ while (dups->nItems > num_starting_dups) - nmFree(dups->Items[dups->nItems--], sizeof(Dup)); + nmFree(dups->Items[--dups->nItems], sizeof(Dup)); if (maybe_dups == NULL) check(xaDeInit(dups)); /* Failure ignored. */ err: diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index e39572f95..d326944d1 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -268,7 +268,7 @@ void timer_free(pTimer timer) /*** Function for failing on error, assuming the error came from a library or *** system function call, so that the error buffer is set to a valid value. ***/ -void print_diagnostics(int code, const char* function_name, const char* file_name, const int line_number) +void print_err(int code, const char* function_name, const char* file_name, const int line_number) { /** Create a descriptive error message. **/ char error_buf[BUFSIZ]; diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 4932bcf1a..b2e3e84a8 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4367,11 +4367,15 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ - int dist = check_neg(edit_dist(str1, str2, 0lu, 0lu)); - if (dist < 0) return -1; + int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); + if (!check_neg(edit_dist)) + { + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute edit distance.\n", fn_name, str1, str2); + return -1; + } /** Return the computed distance. **/ - tree->Integer = dist; + tree->Integer = edit_dist; tree->DataType = DATA_T_INTEGER; return 0; } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index e38fb3e68..a264d886b 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2691,7 +2691,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) (void**)cluster_data->SourceData->Keys, dups )); - if (dups_temp == NULL) + if (dups_temp == NULL) { mssErrorf(1, "Cluster", "Failed to compute complete search with cosine similarity measure."); goto err_free; @@ -2733,7 +2733,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) (void**)cluster_data->SourceData->Keys, dups )); - if (dups_temp == NULL) + if (dups_temp == NULL) { mssErrorf(1, "Cluster", "Failed to compute complete search with Levenstein similarity measure."); goto err_free; @@ -2753,6 +2753,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } if (dups_temp == NULL) goto err_free; else dups = dups_temp; + // fprintf(stderr, "Done searching, found %d dups.\n", dups->nItems); /** Store dups. **/ search_data->nDups = dups->nItems; From 17156b7344d1aa2c32a839c66960b810b67b4d60 Mon Sep 17 00:00:00 2001 From: Israel Date: Thu, 20 Nov 2025 13:53:39 -0700 Subject: [PATCH 021/361] Fix an invalid free (nmFree used instead of nmSysFree()). Fix a possible uninitialized read. Fix memset() not initializing data. --- centrallix-lib/src/clusters.c | 23 ++++++++++++++++------- centrallix/osdrivers/objdrv_cluster.c | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 92125ce03..4504f53d7 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -154,7 +154,7 @@ pVector ca_build_vector(const char* str) /** Compute hash values for char pairs. **/ CharPair* char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); - if (char_pairs == NULL) goto err; + if (char_pairs == NULL) goto err_free_chars; const unsigned int num_pairs = num_chars - 1u; for (unsigned int i = 0u; i < num_pairs; i++) { @@ -176,7 +176,7 @@ pVector ca_build_vector(const char* str) /** Allocate space for the sparse vector. **/ pVector sparse_vector = check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); - if (sparse_vector == NULL) goto err; + if (sparse_vector == NULL) goto err_free_char_pairs; /** Build the sparse vector. **/ unsigned int cur = 0u, dim = 0u; @@ -213,16 +213,22 @@ pVector ca_build_vector(const char* str) /** Trim extra space wasted by identical hashes. **/ pVector trimmed_sparse_vector = check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); - if (trimmed_sparse_vector == NULL) goto err; + if (trimmed_sparse_vector == NULL) goto err_free_sparse_vector; sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ /** Return the result. **/ return trimmed_sparse_vector; - err: + err_free_sparse_vector: if (sparse_vector != NULL) nmSysFree(sparse_vector); + + err_free_char_pairs: if (char_pairs != NULL) nmSysFree(char_pairs); + + err_free_chars: if (chars != NULL) nmSysFree(chars); + + err: return NULL; } @@ -640,8 +646,11 @@ static double get_cluster_size( unsigned int* cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); if (cluster_sums == NULL) goto end; if (cluster_counts == NULL) goto end; - memset(cluster_sums, 0, sizeof(num_clusters * sizeof(double))); - memset(cluster_counts, 0, sizeof(num_clusters * sizeof(unsigned int))); + for (unsigned int i = 0u; i < num_clusters; i++) + { + cluster_sums[i] = 0.0; + cluster_counts[i] = 0u; + } /** Sum the difference from each vector to its cluster centroid. **/ for (unsigned int i = 0u; i < num_vectors; i++) @@ -757,7 +766,7 @@ int ca_kmeans( { const int token = vector[i++]; if (token > 0) centroid[dim++] = (double)token; - else for (unsigned int j = 0u; j < -token; j++) centroid[dim++] = 0.0; + else for (unsigned int j = 0u; j < (unsigned)-token; j++) centroid[dim++] = 0.0; } } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index a264d886b..8561eaba1 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2567,7 +2567,7 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Iterate through each label and add the index of the specified cluster to the xArray. **/ for (unsigned long long i = 0llu; i < source_data->nVectors; i++) if (!check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i))) goto err_free_sims; - nmFree(labels, lables_size); /* Free unused data. */ + nmSysFree(labels); /* Free unused data. */ /** Iterate through each cluster, store it, and free the xArray. **/ for (unsigned int i = 0u; i < cluster_data->nClusters; i++) From 29640a165ae7041158bcca22113a08941006f466 Mon Sep 17 00:00:00 2001 From: Israel Date: Thu, 20 Nov 2025 16:21:45 -0700 Subject: [PATCH 022/361] Minor improvements and clean up. --- centrallix/osdrivers/objdrv_cluster.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 8561eaba1..c09939b66 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -392,6 +392,7 @@ char* const ATTR_SEARCH_ENTRY[] = char* const METHOD_NAME[] = { "cache", + "stat", END_OF_ARRAY, }; @@ -549,7 +550,7 @@ typedef struct _SEARCH { char* Name; char* Key; - pClusterData Source; + pClusterData SourceCluster; double Threshold; pDup* Dups; unsigned int nDups; @@ -1376,8 +1377,8 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) pClusterData cluster_data = node_data->ClusterDatas[i]; if (strcmp(source_cluster_name, cluster_data->Name) == 0) { - /** Source found. **/ - search_data->Source = cluster_data; + /** SourceCluster found. **/ + search_data->SourceCluster = cluster_data; break; } @@ -1385,7 +1386,7 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Did we find the requested source? **/ - if (search_data->Source == NULL) + if (search_data->SourceCluster == NULL) { /** Print error. **/ mssErrorf(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); @@ -1479,7 +1480,7 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Create cache entry key. **/ - char* source_key = search_data->Source->Key; + char* source_key = search_data->SourceCluster->Key; const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; char* key = check_ptr(nmSysMalloc(len * sizeof(char))); if (key == NULL) goto err_free_search; @@ -2645,7 +2646,7 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (search_data->Dups != NULL) return 0; /** We need the cluster data to be computed before we search it. **/ - pClusterData cluster_data = search_data->Source; + pClusterData cluster_data = search_data->SourceCluster; ret = ci_ComputeClusterData(cluster_data, node_data); if (ret != 0) { @@ -3525,7 +3526,7 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val if (strcmp(attr_name, "source") == 0) { - val->String = target->Source->Name; + val->String = target->SourceCluster->Name; return 0; } if (strcmp(attr_name, "similarity_measure") == 0) From 0fa62d3e487bae818a5e23a06ef99b006c09bcd4 Mon Sep 17 00:00:00 2001 From: Israel Date: Thu, 20 Nov 2025 16:38:48 -0700 Subject: [PATCH 023/361] Correct minor mistakes. Improve documentation. --- centrallix-lib/include/clusters.h | 4 ++++ centrallix-lib/src/clusters.c | 17 ++++++++++++++ centrallix/osdrivers/objdrv_cluster.c | 33 ++++++++++++++++----------- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 879ac652a..8f916210e 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -47,6 +47,10 @@ /*** 2147483629 is the signed int max, and is also a prime number. *** Using this value ensures that the longest run of 0s will not *** cause an int underflow with the current encoding scheme. + *** + *** Unfortunately, we can't use a number this large yet because + *** kmeans algorithm creates densely allocated centroids with + *** `CA_NUM_DIMS` dimensions, so a large number causes it to fail. ***/ #define CA_NUM_DIMS 251 //2147483629 /* aka. The vector table size. */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 4504f53d7..48119176a 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -64,6 +64,14 @@ static unsigned int hash_char_pair(const unsigned char c1, const unsigned char c return hash % CA_NUM_DIMS; } +/*** An internal struct for temporarily storing character pairs while building + *** sparse vectors. + *** + *** @param c1 The first character in the character pair. + *** @param c2 The second character in the character pair. + *** @param hash The hash for the two characters, calculated by calling the + *** hash_char_pair() function (above). + **/ typedef struct { unsigned char c1, c2; @@ -71,6 +79,15 @@ typedef struct } CharPair, *pCharPair; +/*** Internal function to compare two character pairs to allow us to sort them + *** by hash (ascending). + *** + *** @param p1 The first pCharPair. + *** @param p2 The second pCharPair. + *** @returns An int > 0 if p1's hash is larger. + *** An int < 0 if p2's hash is larger. + *** 0 if p1 and p2 have identical hashes. + ***/ static int charpair_cmp(const void *p1, const void *p2) { const CharPair *a = p1, *b = p2; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index c09939b66..a41ffbd21 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -350,11 +350,14 @@ typedef unsigned char TargetType; /** Attribute name lists by TargetType. **/ #define END_OF_ARRAY NULL -char* const ATTR_ROOT[] = { +char* const ATTR_ROOT[] = + { "source", "attr_name", + "date_created", + "date_computed", END_OF_ARRAY, -}; + }; char* const ATTR_CLUSTER[] = { "algorithm", @@ -378,6 +381,8 @@ char* const ATTR_SEARCH[] = char* const ATTR_CLUSTER_ENTRY[] = { "items", + "date_created", + "date_computed", END_OF_ARRAY, }; char* const ATTR_SEARCH_ENTRY[] = @@ -385,6 +390,8 @@ char* const ATTR_SEARCH_ENTRY[] = "key1", "key2", "sim", + "date_created", + "date_computed", END_OF_ARRAY, }; @@ -432,17 +439,17 @@ char* const METHOD_NAME[] = ***/ typedef struct _SOURCE { - char* Name; - char* Key; - char* SourcePath; - char* KeyAttr; - char* NameAttr; - char** Keys; - char** Strings; - pVector* Vectors; - unsigned int nVectors; - DateTime DateCreated; - DateTime DateComputed; + char* Name; + char* Key; + char* SourcePath; + char* KeyAttr; + char* NameAttr; + char** Keys; + char** Strings; + pVector* Vectors; + unsigned int nVectors; + DateTime DateCreated; + DateTime DateComputed; } SourceData, *pSourceData; From 06bae81769b36244c3fb555c4041c7d1965d334e Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 21 Nov 2025 14:36:37 -0700 Subject: [PATCH 024/361] Implement a more extendable schema verification system. --- centrallix/expression/exp_functions.c | 193 ++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index b2e3e84a8..2e98cfd7c 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -99,6 +99,199 @@ static char* ci_TypeToStr(const int type) return "Invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ } +/*** Specifies expectations about an argument. + *** + *** @param Datatypes An array of datatypes (terminated with a -1). Set to NULL + *** to accept any datatype as valid for this argument. + *** @param Flags Flags to require other properties about an argument. If the + *** flag a required behavior for specific types, the requirement will be + *** skipped for other types. + *** + *** Valid Flags: + *** - `EXP_ARG_NOT_NULL`: Expect the arg to not be null. + *** - `EXP_ARG_FORCE_TYPE`: Run type check on null args (not recommended). + *** + *** @attention - Checks like `EXP_ARG_NON_EMPTY`, `EXP_ARG_NON_NAN`, etc. also + *** succeed for `NULL` values. To avoid this, specify `EXP_ARG_NOT_NULL`. + ***/ +typedef struct + { + int* Datatypes; + int Flags; + } + ArgExpect, *pArgExpect; + +#define EXP_ARG_NO_FLAGS (0) +#define EXP_ARG_NOT_NULL (1 << 0) +#define EXP_ARG_FORCE_TYPE (1 << 1) + +/*** An internal function used by the schema verifier (below) to verify each + *** argument of the schema. + ***/ +static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) + { + /** The expectation struct cannot be NULL. **/ + if (arg_expect == NULL) + { + mssErrorf(1, "EXP", + "%s(...): Expectation struct cannot be NULL", + fn_name + ); + return -1; + } + + /** Extract values. **/ + ASSERTMAGIC(arg, MGK_EXPRESSION); + int actual_datatype = arg->DataType; + + /** Check for a provided NULL value. **/ + if (arg->Flags & EXPR_F_NULL) + { + if (arg_expect->Flags & EXP_ARG_NOT_NULL) + { + mssErrorf(1, "EXP", + "%s(...): Expects a non-null value, but got NULL : %s (%d).", + fn_name, ci_TypeToStr(actual_datatype), actual_datatype + ); + return -1; + } + + /** Skip type checks unless forced. **/ + if (!(arg_expect->Flags & EXP_ARG_FORCE_TYPE)) goto skip_type_checks; + } + + /** No type checking required. **/ + if (arg_expect->Datatypes == NULL) goto skip_type_checks; + + /** No types given: Probably a mistake. **/ + if (arg_expect->Datatypes[0] == -1) + { + mssErrorf(1, "EXP", + "%s(...): Array of allowed Datatypes is empty.", + fn_name + ); + return -1; + } + + /** Verify Datatypes. **/ + bool found = false; + for (int j = 0; arg_expect->Datatypes[j] != -1; j++) + { + const int expected_datatype = arg_expect->Datatypes[j]; + if (expected_datatype == actual_datatype) + { + found = true; + break; + } + } + + /** Handle failure. **/ + if (!found) + { + /** Accumulate additional valid types. **/ + char buf[256] = {'\0'}; + int cur = 0, j = 1; + while (true) + { + int datatype = arg_expect->Datatypes[j++]; + if (datatype == -1) break; + + cur += snprintf( + buf + cur, 256 - cur, + " or %s (%d)", + ci_TypeToStr(datatype), datatype + ); + } + + /** Print error. **/ + int first_datatype = arg_expect->Datatypes[0]; + mssErrorf(1, "EXP", + "%s(...): Expects type %s (%d)%s but got type %s (%d).", + fn_name, ci_TypeToStr(first_datatype), first_datatype, buf, ci_TypeToStr(actual_datatype), actual_datatype + ); + return -1; + } + + skip_type_checks: + return 0; + } + +/*** Verify that arguments passed to a function match some expected values. + *** + *** @param fn_name The name of the function (for error messages). + *** @param arg_expects A pointer to an array of ArgExpect structs, each + *** representing expectations for a single argument, in the order they + *** are passed to the function. + *** @param num_args The number of arguments to expect to be passed to the + *** function (and the length of arg_expects). + *** @param tree The tree containing the actual arguments passed. + *** @param obj_list The object list scope which was passed to the function. + *** @returns 0 if all arguments are successfully verified, or + *** -1 if an error occurs or arguments are incorrect. + *** + *** @attention - Promises that an error message will be printed with a call + *** to mssError() if an error occurs. + *** + *** Example: + *** ```c + *** char fn_name[] = "example"; + *** if (verify_schema(fn_name, + *** (ArgExpect[]){ + *** {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, -1}, EXP_PARAM_NOT_NULL}, + *** {(int[]){DATA_T_STRING, -1}, 0} + *** }, 2, + *** tree, obj_list + *** ) != 0) + *** { + *** mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + *** return -1; + *** } + *** ``` + ***/ +static int verify_schema( + const char* fn_name, + const ArgExpect* arg_expects, + const int num_args, + pExpression tree, + pParamObjects obj_list) + { + /** Verify object list and session. **/ + if (obj_list == NULL) + { + mssErrorf(1, "EXP", "%s(\?\?\?): No object list?", fn_name); + return -1; + } + ASSERTMAGIC(obj_list->Session, MGK_OBJSESSION); + + /** Verify expression tree. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + + /** Verify argument count. **/ + const int num_args_actual = tree->Children.nItems; + if (num_args != num_args_actual) + { + mssErrorf(1, "EXP", + "%s(?): Expects %u argument%s, got %d argument%s.", + fn_name, num_args, (num_args == 1) ? "" : "s", num_args_actual, (num_args_actual == 1) ? "" : "s" + ); + return -1; + } + + /** Verify argument datatypes. **/ + for (int i = 0; i < num_args; i++) + { + if (verify_arg(fn_name, tree->Children.Items[i], &arg_expects[i]) != 0) + { + mssErrorf(0, "EXP", "%s(...): Error while reading arg #%d/%d.", fn_name, i + 1, num_args); + return -1; + } + } + + /** Pass. **/ + return 0; + } + + /****** Evaluator functions follow for expEvalFunction ******/ int exp_fn_getdate(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) From 13fd4b7017bcf3da971129d3d8e1a6b3f86d5fe3 Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 21 Nov 2025 14:38:24 -0700 Subject: [PATCH 025/361] Replace old schema verification with the new system. --- centrallix/expression/exp_functions.c | 94 ++++++++------------------- 1 file changed, 26 insertions(+), 68 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 2e98cfd7c..50e3ec745 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4344,72 +4344,18 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress return 0; } -static int exp_fn_verify_schema( - const char* fn_name, - const int* param_types, - const int num_params, - pExpression tree, - pParamObjects obj_list) - { - /** Verify object list and session. **/ - if (obj_list == NULL) - { - mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); - return -1; - } - ASSERTMAGIC(obj_list->Session, MGK_OBJSESSION); - - /** Verify expression tree. **/ - ASSERTMAGIC(tree, MGK_EXPRESSION); - - /** Verify parameter number. **/ - const int num_params_actual = tree->Children.nItems; - if (num_params != num_params_actual) - { - mssErrorf(1, "EXP", - "%s(?) expects %u param%s, got %d param%s.", - fn_name, num_params, (num_params > 1) ? "s" : "", num_params_actual, (num_params_actual > 1) ? "s" : "" - ); - return -1; - } - - /** Verify parameter datatypes. **/ - for (int i = 0; i < num_params; i++) - { - const pExpression arg = tree->Children.Items[i]; - ASSERTMAGIC(arg, MGK_EXPRESSION); - - /** Skip null values. **/ - if (arg->Flags & EXPR_F_NULL) continue; - - /** Extract datatypes. **/ - const int expected_datatype = param_types[i]; - const int actual_datatype = arg->DataType; - - /** Verify datatypes. **/ - if (expected_datatype != actual_datatype) - { - mssErrorf(1, "EXP", - "%s(...) param #%d/%d expects type %s (%d) but got type %s (%d).", - fn_name, i + 1, num_params, ci_TypeToStr(expected_datatype), expected_datatype, ci_TypeToStr(actual_datatype), actual_datatype - ); - return -1; - } - } - - /** Pass. **/ - return 0; - } - int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) { const char fn_name[] = "metaphone"; /** Verify function schema. **/ - if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING }, 1, tree, obj_list) != 0) + if (verify_schema(fn_name, + (ArgExpect[]){{(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}}, 1, + tree, obj_list + ) != 0) { - mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); return -1; } @@ -4460,9 +4406,15 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) { /** Verify function schema. **/ - if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) - { - mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + if (verify_schema(fn_name, + (ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS} + }, 2, + tree, obj_list + ) != 0) + { + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); return -1; } @@ -4489,7 +4441,7 @@ static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* if (v1 == NULL || v2 == NULL) { mssErrorf(1, "EXP", - "%s(\"%s\", \"%s\") - Failed to build vectors.", + "%s(\"%s\", \"%s\"): Failed to build vectors.", fn_name, str1, str2 ); ret = -1; @@ -4512,7 +4464,7 @@ static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* double lev_sim = check_double(ca_lev_compare(str1, str2)); if (isnan(lev_sim)) { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute levenstein edit distance."); + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute levenstein edit distance."); return -1; } @@ -4540,9 +4492,15 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) const char fn_name[] = "levenshtein"; /** Verify function schema. **/ - if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) - { - mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + if (verify_schema(fn_name, + (ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS} + }, 2, + tree, obj_list + ) != 0) + { + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); return -1; } @@ -4563,7 +4521,7 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); if (!check_neg(edit_dist)) { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute edit distance.\n", fn_name, str1, str2); + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", fn_name, str1, str2); return -1; } From e83c15f8255ea451e3310019400031576225ad12 Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 21 Nov 2025 14:41:00 -0700 Subject: [PATCH 026/361] Expand the new schema verification system with extra data validation features. --- centrallix/expression/exp_functions.c | 181 ++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 50e3ec745..7f8ad875d 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -110,6 +110,13 @@ static char* ci_TypeToStr(const int type) *** Valid Flags: *** - `EXP_ARG_NOT_NULL`: Expect the arg to not be null. *** - `EXP_ARG_FORCE_TYPE`: Run type check on null args (not recommended). + *** - `EXP_ARG_NON_EMPTY`: Expect string to be non-empty. Expect a + *** stringvec or intvec to have elements (does not check them). + *** - `EXP_ARG_POSITIVE`: Expect a positive or zero value for int, double, + *** money, or datetime. (Includes NON_NAN: NAN is not positive). + *** - `EXP_ARG_NEGATIVE`: Expect a negative or zero value for int, double, + *** money, or datetime. (Includes NON_NAN: NAN is not negative). + *** - `EXP_ARG_NON_NAN`: Expect a double to be a number, not NAN. *** *** @attention - Checks like `EXP_ARG_NON_EMPTY`, `EXP_ARG_NON_NAN`, etc. also *** succeed for `NULL` values. To avoid this, specify `EXP_ARG_NOT_NULL`. @@ -124,6 +131,10 @@ typedef struct #define EXP_ARG_NO_FLAGS (0) #define EXP_ARG_NOT_NULL (1 << 0) #define EXP_ARG_FORCE_TYPE (1 << 1) +#define EXP_ARG_NON_EMPTY (1 << 2) +#define EXP_ARG_NEGATIVE (1 << 3) +#define EXP_ARG_POSITIVE (1 << 4) +#define EXP_ARG_NON_NAN (1 << 5) /*** An internal function used by the schema verifier (below) to verify each *** argument of the schema. @@ -213,6 +224,150 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg } skip_type_checks: + /** All flag checks not implemented above should pass on NULL values. **/ + if (arg->Flags & EXPR_F_NULL) return 0; + + /** Verify other Flags by type, if specified. **/ + switch (actual_datatype) + { + case DATA_T_INTEGER: + { + int value = arg->Integer; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive int but got %d.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative int but got %d.", + fn_name, value + ); + return -1; + } + break; + } + + case DATA_T_DOUBLE: + { + double value = arg->Types.Double; + if (arg_expect->Flags & EXP_ARG_NON_NAN && isnan(value)) + { + mssErrorf(1, "EXP", + "%s(...): Expects non-nan double but got %g.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive double but got %g.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative double but got %g.", + fn_name, value + ); + return -1; + } + break; + } + + case DATA_T_STRING: + { + char* str = arg->String; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str[0] == '\0') + { + mssErrorf(1, "EXP", + "%s(...): Expects string to contain characters, but got \"\".", + fn_name + ); + return -1; + } + break; + } + + case DATA_T_DATETIME: + { + pDateTime value = &arg->Types.Date; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value->Value < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive date offset but got %llu.", + fn_name, value->Value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->Value > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative date offset but got %llu.", + fn_name, value->Value + ); + return -1; + } + break; + } + + case DATA_T_MONEY: + { + pMoneyType value = &arg->Types.Money; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value->WholePart < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive money value but got $%d.%d.", + fn_name, value->WholePart, value->FractionPart + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->WholePart > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative money value but got $%d.%d.", + fn_name, value->WholePart, value->FractionPart + ); + return -1; + } + } + + case DATA_T_STRINGVEC: + { + pStringVec str_vec = &arg->Types.StrVec; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str_vec->nStrings == 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects StringVec to contain strings, but got [].", + fn_name + ); + return -1; + } + break; + } + + case DATA_T_INTVEC: + { + pIntVec int_vec = &arg->Types.IntVec; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && int_vec->nIntegers == 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects IntVec to contain strings, but got [].", + fn_name + ); + return -1; + } + break; + } + } + return 0; } @@ -291,6 +446,31 @@ static int verify_schema( return 0; } +int exp_fn_test(pExpression tree, pParamObjects obj_list) + { + char fn_name[] = "test"; + if (verify_schema(fn_name, + (ArgExpect[]){ + {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, -1}, EXP_ARG_NOT_NULL | EXP_ARG_NON_NAN}, + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NOT_NULL | EXP_ARG_NON_EMPTY} + }, 2, + tree, obj_list + ) != 0) + { + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); + return -1; + } + + pExpression arg1 = tree->Children.Items[0]; + pExpression arg2 = tree->Children.Items[1]; + if (arg1->DataType == DATA_T_INTEGER) printf("Success: %d, '%s'.\n", arg1->Integer, arg2->String); + else printf("Success: %g, '%s'.\n", arg1->Types.Double, arg2->String); + + tree->DataType = DATA_T_INTEGER; + tree->Flags |= EXPR_F_NULL; + return 0; + } + /****** Evaluator functions follow for expEvalFunction ******/ @@ -4678,6 +4858,7 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); + xhAdd(&EXP.Functions, "test", (char*)exp_fn_test); /** Dates. **/ xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); From 070cfe3191265ee9c5546066799cfdcedf3572d0 Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 21 Nov 2025 16:04:14 -0700 Subject: [PATCH 027/361] Clean up, bug fixes, and naming convention updates. Remove test function. --- centrallix/expression/exp_functions.c | 54 +++++++-------------------- 1 file changed, 14 insertions(+), 40 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 7f8ad875d..08867f8af 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -139,7 +139,7 @@ typedef struct /*** An internal function used by the schema verifier (below) to verify each *** argument of the schema. ***/ -static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) +static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) { /** The expectation struct cannot be NULL. **/ if (arg_expect == NULL) @@ -251,7 +251,7 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg } break; } - + case DATA_T_DOUBLE: { double value = arg->Types.Double; @@ -281,7 +281,7 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg } break; } - + case DATA_T_STRING: { char* str = arg->String; @@ -295,7 +295,7 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg } break; } - + case DATA_T_DATETIME: { pDateTime value = &arg->Types.Date; @@ -317,15 +317,15 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg } break; } - + case DATA_T_MONEY: { pMoneyType value = &arg->Types.Money; if (arg_expect->Flags & EXP_ARG_POSITIVE && value->WholePart < 0) { mssErrorf(1, "EXP", - "%s(...): Expects positive money value but got $%d.%d.", - fn_name, value->WholePart, value->FractionPart + "%s(...): Expects positive money value but got $%d.%g.", + fn_name, value->WholePart, (double)value->FractionPart / 100.0 ); return -1; } @@ -333,7 +333,7 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg { mssErrorf(1, "EXP", "%s(...): Expects negative money value but got $%d.%d.", - fn_name, value->WholePart, value->FractionPart + fn_name, value->WholePart, (double)value->FractionPart / 100.0 ); return -1; } @@ -390,7 +390,7 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg *** Example: *** ```c *** char fn_name[] = "example"; - *** if (verify_schema(fn_name, + *** if (exp_fn_i_verify_schema(fn_name, *** (ArgExpect[]){ *** {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, -1}, EXP_PARAM_NOT_NULL}, *** {(int[]){DATA_T_STRING, -1}, 0} @@ -403,7 +403,7 @@ static int verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg *** } *** ``` ***/ -static int verify_schema( +static int exp_fn_i_verify_schema( const char* fn_name, const ArgExpect* arg_expects, const int num_args, @@ -435,7 +435,7 @@ static int verify_schema( /** Verify argument datatypes. **/ for (int i = 0; i < num_args; i++) { - if (verify_arg(fn_name, tree->Children.Items[i], &arg_expects[i]) != 0) + if (exp_fn_i_verify_arg(fn_name, tree->Children.Items[i], &arg_expects[i]) != 0) { mssErrorf(0, "EXP", "%s(...): Error while reading arg #%d/%d.", fn_name, i + 1, num_args); return -1; @@ -446,31 +446,6 @@ static int verify_schema( return 0; } -int exp_fn_test(pExpression tree, pParamObjects obj_list) - { - char fn_name[] = "test"; - if (verify_schema(fn_name, - (ArgExpect[]){ - {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, -1}, EXP_ARG_NOT_NULL | EXP_ARG_NON_NAN}, - {(int[]){DATA_T_STRING, -1}, EXP_ARG_NOT_NULL | EXP_ARG_NON_EMPTY} - }, 2, - tree, obj_list - ) != 0) - { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); - return -1; - } - - pExpression arg1 = tree->Children.Items[0]; - pExpression arg2 = tree->Children.Items[1]; - if (arg1->DataType == DATA_T_INTEGER) printf("Success: %d, '%s'.\n", arg1->Integer, arg2->String); - else printf("Success: %g, '%s'.\n", arg1->Types.Double, arg2->String); - - tree->DataType = DATA_T_INTEGER; - tree->Flags |= EXPR_F_NULL; - return 0; - } - /****** Evaluator functions follow for expEvalFunction ******/ @@ -4530,7 +4505,7 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) const char fn_name[] = "metaphone"; /** Verify function schema. **/ - if (verify_schema(fn_name, + if (exp_fn_i_verify_schema(fn_name, (ArgExpect[]){{(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}}, 1, tree, obj_list ) != 0) @@ -4586,7 +4561,7 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) { /** Verify function schema. **/ - if (verify_schema(fn_name, + if (exp_fn_i_verify_schema(fn_name, (ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS} @@ -4672,7 +4647,7 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) const char fn_name[] = "levenshtein"; /** Verify function schema. **/ - if (verify_schema(fn_name, + if (exp_fn_i_verify_schema(fn_name, (ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS} @@ -4858,7 +4833,6 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); - xhAdd(&EXP.Functions, "test", (char*)exp_fn_test); /** Dates. **/ xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); From 8795aaf1b89c934ca6ec05797e559077b7551e50 Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 21 Nov 2025 17:44:12 -0700 Subject: [PATCH 028/361] Add tests for log and power functions. --- centrallix/tests/test_expfn_log_00.cmp | 34 ++++++++++++++++++++ centrallix/tests/test_expfn_log_00.to | 44 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 centrallix/tests/test_expfn_log_00.cmp create mode 100644 centrallix/tests/test_expfn_log_00.to diff --git a/centrallix/tests/test_expfn_log_00.cmp b/centrallix/tests/test_expfn_log_00.cmp new file mode 100644 index 000000000..c072b68f2 --- /dev/null +++ b/centrallix/tests/test_expfn_log_00.cmp @@ -0,0 +1,34 @@ +Attribute [ln(1)]: double 0.0 +Attribute [ln(e)]: double 1.0 +Attribute [ln(0)]: double -inf.0 +Attribute [ln(-1)]: double nan.0 +Attribute [ln(10)]: double 2.30258509 +Attribute [ln(1.5)]: double 0.40546511 +Attribute [ln(1e-10)]: integer 1 +Attribute [ln(1e10)+]: integer 1 +Attribute [ln(1e10)-]: integer 1 +Attribute [log10(1)]: double 0.0 +Attribute [log10(10)]: double 1.0 +Attribute [log10(0)]: double -inf.0 +Attribute [log10(-10)]: double nan.0 +Attribute [log10(100)]: double 2.0 +Attribute [log10(0.01)]: double -2.0 +Attribute [log10(1.234)]: double 0.09131516 +Attribute [log10(1e-10)]: double -10.0 +Attribute [log10(1e10)]: double 10.0 +Attribute [logn(8, 2)]: double 3.0 +Attribute [logn(1000, 10)]: double 3.0 +Attribute [logn(10, 0)]: double -0.0 +Attribute [logn(10, 1)]: double inf.0 +Attribute [logn(8, -2)]: double nan.0 +Attribute [logn(0, 2)]: double -inf.0 +Attribute [logn(-8, 2)]: double nan.0 +Attribute [logn(1, 2)]: double 0.0 +Attribute [logn(1e10, 10)]: double 10.0 +Attribute [logn(8, 0.5)]: double -3.0 +Attribute [ln(2.718281828)]: double 1.0 +Attribute [log10(3.14159)]: double 0.49714951 +Attribute [logn(10, 1.1)]: double 0.04139269 +Attribute [logn(1.1, 10)]: double 24.15885793 +Attribute [logn(10, 0.001)]: double -3.0 +Attribute [logn(0.1, 1000)]: double -3.0 diff --git a/centrallix/tests/test_expfn_log_00.to b/centrallix/tests/test_expfn_log_00.to new file mode 100644 index 000000000..e454e4003 --- /dev/null +++ b/centrallix/tests/test_expfn_log_00.to @@ -0,0 +1,44 @@ +##NAME log() functions + +# Natural Log: ln(x) +query select 'ln(1)' = ln(1) -- Expect 0. +query select 'ln(e)' = ln(2.718281828459045) -- Expect 1. +query select 'ln(0)' = ln(0) -- Expect -inf (log approaches infinity). +query select 'ln(-1)' = ln(-1) -- Expect NaN (log undefined for negative). +query select 'ln(10)' = round(ln(10), 8) -- Expect ~2.30258509. +query select 'ln(1.5)' = round(ln(1.5), 8) -- Expect ~0.40546511. +query select 'ln(1e-10)' = ln(0.0000000001) < 0.0000000001 -- Expect true (value is very small). +query select 'ln(1e10)+' = ln(10000000000.0) > 23.0 -- Expect true (value is ~23.02585). +query select 'ln(1e10)-' = ln(10000000000.0) < 23.1 -- Expect true (value is ~23.02585). + +# Log base 10: log10(x) +query select 'log10(1)' = log10(1) -- Expect 0. +query select 'log10(10)' = log10(10) -- Expect 1. +query select 'log10(0)' = log10(0) -- Expect -inf. +query select 'log10(-10)' = log10(-10) -- Expect NaN. +query select 'log10(100)' = log10(100) -- Expect 2. +query select 'log10(0.01)' = log10(0.01) -- Expect -2. +query select 'log10(1.234)' = round(log10(1.234), 8) -- Expect ~0.091315. +query select 'log10(1e-10)' = log10(0.0000000001) -- Expect ~-10. +query select 'log10(1e10)' = log10(10000000000.0) -- Expect ~10. + +# General base n of x: logn(x, n) +# Edge cases: base <= 0 or base == 1 (invalid), x <= 0 (invalid) +query select 'logn(8, 2)' = logn(8, 2) -- Expect 3. +query select 'logn(1000, 10)' = logn(1000, 10) -- Expect 3. +query select 'logn(10, 0)' = logn(10, 0) -- Expect -0.0 (base 0 is undefined). +query select 'logn(10, 1)' = logn(10, 1) -- Expect inf (base 1 is undefined). +query select 'logn(8, -2)' = logn(8, -2) -- Expect NaN (negative base). +query select 'logn(0, 2)' = logn(0, 2) -- Expect -inf (x=0). +query select 'logn(-8, 2)' = logn(-8, 2) -- Expect NaN or error (x negative). +query select 'logn(1, 2)' = logn(1, 2) -- Expect 0. +query select 'logn(1e10, 10)' = logn(10000000000.0, 10) -- Expect 10. +query select 'logn(8, 0.5)' = logn(8, 0.5) -- Expect negative value. + +-- Additional double/int mixed cases +query select 'ln(2.718281828)' = round(ln(2.718281828), 8) -- Expect ~1 (close to e). +query select 'log10(3.14159)' = round(log10(3.14159), 8) -- Expect ~0.49715. +query select 'logn(10, 1.1)' = round(logn(1.1, 10), 8) -- Expect 0.04139289. +query select 'logn(1.1, 10)' = round(logn(10, 1.1), 8) -- Expect 24.15885793. +query select 'logn(10, 0.001)' = round(logn(0.001, 10), 8) -- Expect ~-0.33333333... +query select 'logn(0.1, 1000)' = round(logn(1000, 0.1), 8) -- Expect ~-0.33333333... From 2e948d8028f8c88aa933d17c172a4b089c5795b3 Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 21 Nov 2025 17:44:53 -0700 Subject: [PATCH 029/361] Add exp_fn_i_get_number(). --- centrallix/expression/exp_functions.c | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 08867f8af..e8d67976d 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -446,6 +446,47 @@ static int exp_fn_i_verify_schema( return 0; } +/*** Extract a number from a numeric expression. + *** + *** @param numeric_expr The numeric expression to be extracted. + *** @param result_ptr A pointer to a double where the result is stored. + *** @returns 0 on success, + *** -1 on failure, + *** 1 if the expression is NULL. + ***/ +static int exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) + { + /** Check for null values. **/ + if (numeric_expr == NULL || numeric_expr->Flags & EXPR_F_NULL) return 1; + + /** Check for null destination. **/ + if (result_ptr == NULL) + { + mssError(1, "EXP", "Null location provided to store numeric result."); + return -1; + } + + /** Get the numeric value. **/ + double n; + switch(numeric_expr->DataType) + { + case DATA_T_INTEGER: n = numeric_expr->Integer; break; + case DATA_T_DOUBLE: n = numeric_expr->Types.Double; break; + case DATA_T_MONEY: n = objDataToDouble(DATA_T_MONEY, &(numeric_expr->Types.Money)); break; + default: + mssError(1, "EXP", + "%s (%d) is not a numeric type.", + ci_TypeToStr(numeric_expr->DataType), numeric_expr->DataType + ); + return -1; + } + + /** Store the result. **/ + *result_ptr = n; + + return 0; + } + /****** Evaluator functions follow for expEvalFunction ******/ From 4c347be808bf95a80fa7ee8e5346cf95900ab36a Mon Sep 17 00:00:00 2001 From: Israel Date: Fri, 21 Nov 2025 17:45:44 -0700 Subject: [PATCH 030/361] Add exp_fn_i_do_math() to bring the power of schema verification to logarithm and power functions. --- centrallix/expression/exp_functions.c | 223 ++++++-------------------- 1 file changed, 52 insertions(+), 171 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index e8d67976d..208bd48f8 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -3691,194 +3691,75 @@ int exp_fn_from_base64(pExpression tree, pParamObjects objlist, pExpression i0, return -1; } - -int exp_fn_log10(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +static int exp_fn_i_do_math(pExpression tree, pParamObjects obj_list, const char* fn_name, double (*math)(), int arg_num) { - double n; - - if (!i0) - { - mssError(1, "EXP", "log10() requires a number as its first parameter"); - goto error; - } - if (i0->Flags & EXPR_F_NULL) - { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; - } - switch(i0->DataType) - { - case DATA_T_INTEGER: - n = i0->Integer; - break; - case DATA_T_DOUBLE: - n = i0->Types.Double; - break; - case DATA_T_MONEY: - n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); - break; - default: - mssError(1, "EXP", "log10() requires a number as its first parameter"); - goto error; - } - if (n < 0) - { - mssError(1, "EXP", "log10(): cannot compute the logarithm of a negative number"); - goto error; - } - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = log10(n); - return 0; - - error: + /** Verify function schema: expect arg_num numeric values. **/ + ArgExpect expects[arg_num]; + for (int i = 0; i < arg_num; i++) + expects[i] = (ArgExpect){(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}; + if (exp_fn_i_verify_schema(fn_name, expects, arg_num, tree, obj_list) != 0) + { + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); return -1; - } - - -int exp_fn_log_natural(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - double n; - - if (!i0) - { - mssError(1, "EXP", "ln() requires a number as its first parameter"); - goto error; - } - if (i0->Flags & EXPR_F_NULL) + } + + /** Null checks. **/ + for (int i = 0; i < arg_num; i++) + { + pExpression arg = tree->Children.Items[i]; + if (arg->Flags & EXPR_F_NULL) { tree->DataType = DATA_T_DOUBLE; tree->Flags |= EXPR_F_NULL; return 0; } - switch(i0->DataType) - { - case DATA_T_INTEGER: - n = i0->Integer; - break; - case DATA_T_DOUBLE: - n = i0->Types.Double; - break; - case DATA_T_MONEY: - n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); - break; - default: - mssError(1, "EXP", "ln() requires a number as its first parameter"); - goto error; - } - if (n < 0) - { - mssError(1, "EXP", "ln(): cannot compute the logarithm of a negative number"); - goto error; - } - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = log(n); - return 0; + } - error: + /** Maximum supported args. **/ + if (arg_num > 4) + { + mssErrorf(1, "EXP", "%s(...): exp_fn_i_do_math() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", fn_name, __FILE__, __LINE__); return -1; - } - - -int exp_fn_log_base_n(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - double n, p; - - if (!i0 || !i1) - { - mssError(1, "EXP", "logn() requires numbers as its first and second parameters"); - goto error; - } - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) - { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; - } - switch(i0->DataType) - { - case DATA_T_INTEGER: - n = i0->Integer; - break; - case DATA_T_DOUBLE: - n = i0->Types.Double; - break; - case DATA_T_MONEY: - n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); - break; - default: - mssError(1, "EXP", "logn() requires a number as its first parameter"); - goto error; - } - switch(i1->DataType) + } + + /** Get the numbers for the args. **/ + double n[4]; + for (int i = 0; i < arg_num; i++) + { + if (!check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i])))) { - case DATA_T_INTEGER: - p = i1->Integer; - break; - case DATA_T_DOUBLE: - p = i1->Types.Double; - break; - default: - mssError(1, "EXP", "logn() requires an integer or double as its second parameter"); - goto error; + mssErrorf(0, "EXP", "%s(...): Failed to get arg%d.", fn_name, i); + return -1; } - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = log(n) / log(p); - return 0; + } - error: - return -1; + tree->DataType = DATA_T_DOUBLE; + tree->Types.Double = math(n[0], n[1], n[2], n[3]); /* Call function with max supported args. */ + return 0; } +int exp_fn_log_natural(pExpression tree, pParamObjects obj_list) + { + return exp_fn_i_do_math(tree, obj_list, "ln", log, 1); + } +int exp_fn_log10(pExpression tree, pParamObjects obj_list) + { + return exp_fn_i_do_math(tree, obj_list, "log10", log10, 1); + } -int exp_fn_power(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +/** This is why we need lambdas in C. **/ +double exp_fn_i_log_base_n(double x, double base) { - double n, p; + return log(x) / log(base); + } - if (!i0 || !i1) - { - mssError(1, "EXP", "power() requires numbers as its first and second parameters"); - goto error; - } - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) - { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; - } - switch(i0->DataType) - { - case DATA_T_INTEGER: - n = i0->Integer; - break; - case DATA_T_DOUBLE: - n = i0->Types.Double; - break; - case DATA_T_MONEY: - n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); - break; - default: - mssError(1, "EXP", "power() requires a number as its first parameter"); - goto error; - } - switch(i1->DataType) - { - case DATA_T_INTEGER: - p = i1->Integer; - break; - case DATA_T_DOUBLE: - p = i1->Types.Double; - break; - default: - mssError(1, "EXP", "power() requires an integer or double as its second parameter"); - goto error; - } - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = pow(n, p); - return 0; - - error: - return -1; +int exp_fn_log_base_n(pExpression tree, pParamObjects obj_list) + { + return exp_fn_i_do_math(tree, obj_list, "logn", exp_fn_i_log_base_n, 2); + } +int exp_fn_power(pExpression tree, pParamObjects obj_list) + { + return exp_fn_i_do_math(tree, obj_list, "power", pow, 2); } From d1775228b7b911ad6e5a73aef5ae8e18f3949788 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 25 Nov 2025 09:45:26 -0700 Subject: [PATCH 031/361] Minor clean up. --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/src/clusters.c | 2 +- centrallix-lib/src/util.c | 20 -------------------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 8f916210e..06c5075fe 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -25,7 +25,7 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: lib_cluster.h */ +/* Module: lib_cluster.c, lib_cluster.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description: Clustering library used to cluster and search data with */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 48119176a..edc37920e 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -22,7 +22,7 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: lib_cluster.c */ +/* Module: lib_cluster.c, lib_cluster.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description: Clustering library used to cluster and search data with */ diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index d326944d1..edb65dbfa 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -90,26 +90,6 @@ unsigned int strtoui(const char *nptr, char **endptr, int base){ return (unsigned int)tmp; } -/*** Detects the optimal number of threads to use on this system. - *** Note: Multithreading is not currently supported, so this function - *** will always return 1, for now. - *** - *** @returns The number of threads that should be used on this system. - ***/ -int util_detect_num_threads(void) - { - /** Centrallix does not support multithreading. **/ - return 1; - - long num_procs = sysconf(_SC_NPROCESSORS_ONLN); - if (num_procs < 1 || INT_MAX < num_procs) - { - fprintf(stderr, "Warning: Detected strange number of processors (assuming 1): %ld\n", num_procs); - return 1; - } - else return (int)num_procs; - } - /*** snprint_bytes() allows one to pick between CS units, where the kibibyte *** (KiB) is 1024 bytes, and metric units where the kilobyte (KB) is 1000 bytes. *** Fun Fact: Windows uses kibibytes, but displays them as KB. From 7b49a5bd07acf1b2a43f02339bf29c6b71b682b9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 11 Dec 2025 10:30:04 -0700 Subject: [PATCH 032/361] Address Greg's comments Fix styling mistakes. Finish docs in OSDriver_Authoring.md. Add support for querying the driver node object. Fix clusterOpenQuery() succeeding on objects that could not be queried, resulting in fetch failures. Remove "date_created" and "date_computed" from the list of * attributes on cluster and search entries. Rename TARGET_ROOT to TARGET_NODE. Rename snprint_llu() to snprint_commas_llu(). Move double_metaphone.c into centrallix util. Move TypeToStr() to obj_datatypes.c. Move TypeFromStr() to obj_datatypes.c. Remove exp_fn_trim() (temporarily). Revert reorder of exp_function registrations to avoid merge conflicts. Update tests to give clearer feedback. Add GCC_Dependencies.md to document a list of dependencies on GCC features. Add .cluster to Prefixes.md. --- centrallix-lib/include/clusters.h | 87 +- centrallix-lib/include/glyph.h | 18 +- centrallix-lib/include/util.h | 93 +- centrallix-lib/src/clusters.c | 1072 ++-- centrallix-lib/src/mtask.c | 3 +- centrallix-lib/src/util.c | 223 +- centrallix-lib/src/xhash.c | 103 +- centrallix-sysdoc/GCC_Dependencies.md | 20 + centrallix-sysdoc/OSDriver_Authoring.md | 1614 +++-- centrallix-sysdoc/Prefixes.md | 1 + centrallix/Makefile.in | 2 +- centrallix/centrallix.c | 2 +- centrallix/expression/exp_compiler.c | 9 +- centrallix/expression/exp_double_metaphone.c | 1521 ----- centrallix/expression/exp_functions.c | 460 +- centrallix/include/double_metaphone.h | 83 + centrallix/include/expression.h | 1 - centrallix/include/obj.h | 4 +- centrallix/multiquery/multiquery.c | 4 +- centrallix/objectsystem/obj_datatypes.c | 85 + centrallix/osdrivers/objdrv_cluster.c | 5910 +++++++++--------- centrallix/test_obj.c | 8 +- centrallix/tests/test_levenshtein_00.cmp | 36 +- centrallix/tests/test_levenshtein_00.to | 38 +- centrallix/utility/double_metaphone.c | 1545 +++++ 25 files changed, 6962 insertions(+), 5980 deletions(-) create mode 100644 centrallix-sysdoc/GCC_Dependencies.md delete mode 100644 centrallix/expression/exp_double_metaphone.c create mode 100644 centrallix/include/double_metaphone.h create mode 100644 centrallix/utility/double_metaphone.c diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 06c5075fe..5aa3123e4 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -2,37 +2,37 @@ #define CLUSTERS_H /************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Core */ -/* */ -/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ -/* */ -/* This program is free software; you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation; either version 2 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program; if not, write to the Free Software */ -/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ -/* 02111-1307 USA */ -/* */ -/* A copy of the GNU General Public License has been included in this */ -/* distribution in the file "COPYING". */ -/* */ -/* Module: lib_cluster.c, lib_cluster.h */ -/* Author: Israel Fuller */ -/* Creation: September 29, 2025 */ -/* Description: Clustering library used to cluster and search data with */ -/* cosine similarity and Levenshtein similarity (aka. edit */ -/* distance). Used by the "clustering driver". */ -/* For more information on how to use this library, see */ -/* string-similarity.md in the centrallix-sysdoc folder. */ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: lib_cluster.c, lib_cluster.h */ +/* Author: Israel Fuller */ +/* Creation: September 29, 2025 */ +/* Description Clustering library used to cluster and search data with */ +/* cosine similarity and Levenshtein similarity (aka. edit */ +/* distance). Used by the "clustering driver". */ +/* For more information on how to use this library, see */ +/* string-similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ #include @@ -44,15 +44,24 @@ #include "cxlib/xarray.h" #endif -/*** 2147483629 is the signed int max, and is also a prime number. +/*** This value defines the number of dimensions used for a sparse + *** vector. The higher the number, the fewer collisions will be + *** encountered when using these vectors for cosine comparisons. + *** This is also called the vector table size, if viewing the + *** vector as a hash table of character pairs. + *** + *** 2147483629 is the signed int max, and is also a prime number. *** Using this value ensures that the longest run of 0s will not *** cause an int underflow with the current encoding scheme. *** *** Unfortunately, we can't use a number this large yet because *** kmeans algorithm creates densely allocated centroids with *** `CA_NUM_DIMS` dimensions, so a large number causes it to fail. + *** This, we use 251 as the largest prime number less than 256, + *** giving us a decent balance between collision reduction and + *** kmeans centroid performance/memory overhead. ***/ -#define CA_NUM_DIMS 251 //2147483629 /* aka. The vector table size. */ +#define CA_NUM_DIMS 251 /// LINK ../../centrallix-sysdoc/string_comparison.md#cosine_charsets /** The character used to create a pair with the first and last characters of a string. **/ @@ -74,10 +83,10 @@ typedef struct /** Registering all defined types for debugging. **/ #define ca_init() \ - nmRegister(sizeof(pVector), "pVector"); \ - nmRegister(sizeof(pCentroid), "pCentroid"); \ - nmRegister(pCentroidSize, "Centroid"); \ - nmRegister(sizeof(Dup), "Dup") + nmRegister(sizeof(pVector), "pVector"); \ + nmRegister(sizeof(pCentroid), "pCentroid"); \ + nmRegister(pCentroidSize, "Centroid"); \ + nmRegister(sizeof(Dup), "Dup") /** Edit distance function. **/ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); @@ -102,8 +111,8 @@ int ca_kmeans( #define ca_is_empty(vector) (vector[0] == -CA_NUM_DIMS) #define ca_has_no_pairs(vector) \ ({ \ - __typeof__ (vector) _v = (vector); \ - _v[0] == -172 && _v[1] == 11 && _v[2] == -78; \ + __typeof__ (vector) _v = (vector); \ + _v[0] == -172 && _v[1] == 11 && _v[2] == -78; \ }) /** Comparison functions (see ca_search()). **/ diff --git a/centrallix-lib/include/glyph.h b/centrallix-lib/include/glyph.h index cfafd3946..649437fac 100644 --- a/centrallix-lib/include/glyph.h +++ b/centrallix-lib/include/glyph.h @@ -54,21 +54,21 @@ *** @param flush Whether to flush on output. ***/ #define glyph_init(name, str, interval, flush) \ - const char* vis_##name##_str = str; \ - const unsigned int vis_##name##_interval = interval; \ - const bool vis_##name##_flush = flush; \ - unsigned int vis_##name##_i = 0u; + const char* vis_##name##_str = str; \ + const unsigned int vis_##name##_interval = interval; \ + const bool vis_##name##_flush = flush; \ + unsigned int vis_##name##_i = 0u; /*** Invoke a visualizer. *** *** @param name The name of the visualizer to invoke. ***/ #define glyph(name) \ - if (++vis_##name##_i % vis_##name##_interval == 0) \ - { \ - glyph_print(vis_##name##_str); \ - if (vis_##name##_flush) fflush(stdout); \ - } + if (++vis_##name##_i % vis_##name##_interval == 0) \ + { \ + glyph_print(vis_##name##_str); \ + if (vis_##name##_flush) fflush(stdout); \ + } #else #define glyph_print(str) #define glyph_init(name, str, interval, flush) diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 03b63abaf..efe914d8a 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -2,26 +2,26 @@ #define UTILITY_H /************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Base Library */ -/* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ -/* */ -/* You may use these files and this library under the terms of the */ -/* GNU Lesser General Public License, Version 2.1, contained in the */ -/* included file "COPYING". */ -/* */ -/* Module: util.c, util.h */ -/* Author: Micah Shennum and Israel Fuller */ -/* Date: May 26, 2011 */ -/* Description: Collection of utilities including: */ -/* - Utilities for parsing numbers. */ -/* - The timer utility for benchmarking code. */ -/* - snprint_bytes() for formatting a byte count. */ -/* - snprint_llu() for formatting large numbers. */ -/* - fprint_mem() for printing memory stats. */ -/* - min() and max() for handling numbers. */ -/* - The check functions for reliably printing debug data. */ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: util.c, util.h */ +/* Author: Micah Shennum and Israel Fuller */ +/* Date: May 26, 2011 */ +/* Description: Collection of utilities including: */ +/* - Utilities for parsing numbers. */ +/* - The timer utility for benchmarking code. */ +/* - snprint_bytes() for formatting a byte count. */ +/* - snprint_commas_llu() for formatting large numbers. */ +/* - fprint_mem() for printing memory stats. */ +/* - min() and max() for handling numbers. */ +/* - The check functions for reliably printing debug data. */ /************************************************************************/ #ifdef __cplusplus @@ -32,7 +32,7 @@ extern "C" { unsigned int strtoui(const char *nptr, char **endptr, int base); char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes); - char* snprint_llu(char* buf, size_t buflen, unsigned long long value); + char* snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value); void fprint_mem(FILE* out); typedef struct @@ -57,11 +57,6 @@ extern "C" { #ifndef __cplusplus #include -/*** TODO: Greg - Can we assume this code will always be compiled with GCC? - *** If not, then the __typeof__, __LINE__, and __FILE__ syntaxes might be a - *** portability concern. - ***/ - /*** @brief Returns the smaller of two values. *** *** @param a The first value. @@ -110,11 +105,11 @@ void print_err(int code, const char* function_name, const char* file_name, const ***/ #define check(result) \ ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - const bool success = (_r == 0); \ - if (!success) print_err(_r, #result, __FILE__, __LINE__); \ - success; \ + errno = 0; /* Reset errno to prevent confusion. */ \ + __typeof__ (result) _r = (result); \ + const bool success = (_r == 0); \ + if (!success) print_err(_r, #result, __FILE__, __LINE__); \ + success; \ }) /*** Ensures that developer diagnostics are printed if the result of the @@ -125,11 +120,11 @@ void print_err(int code, const char* function_name, const char* file_name, const ***/ #define check_neg(result) \ ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - const bool success = (_r >= 0); \ - if (!success) print_err(_r, #result, __FILE__, __LINE__); \ - success; \ + errno = 0; /* Reset errno to prevent confusion. */ \ + __typeof__ (result) _r = (result); \ + const bool success = (_r >= 0); \ + if (!success) print_err(_r, #result, __FILE__, __LINE__); \ + success; \ }) /*** Ensures that developer diagnostics are printed if the result of the @@ -140,11 +135,11 @@ void print_err(int code, const char* function_name, const char* file_name, const ***/ #define check_weak(result) \ ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - const bool success = (_r != -1); \ - if (!success) print_err(_r, #result, __FILE__, __LINE__); \ - success; \ + errno = 0; /* Reset errno to prevent confusion. */ \ + __typeof__ (result) _r = (result); \ + const bool success = (_r != -1); \ + if (!success) print_err(_r, #result, __FILE__, __LINE__); \ + success; \ }) /*** Ensures that developer diagnostics are printed if the result of the @@ -155,10 +150,10 @@ void print_err(int code, const char* function_name, const char* file_name, const ***/ #define check_double(result) \ ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - if (isnan(_r)) print_err(0, #result, __FILE__, __LINE__); \ - _r; \ + errno = 0; /* Reset errno to prevent confusion. */ \ + __typeof__ (result) _r = (result); \ + if (isnan(_r)) print_err(0, #result, __FILE__, __LINE__); \ + _r; \ }) /*** Ensures that developer diagnostics are printed if the result of the @@ -169,10 +164,10 @@ void print_err(int code, const char* function_name, const char* file_name, const ***/ #define check_ptr(result) \ ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - if (_r == NULL) print_err(0, #result, __FILE__, __LINE__); \ - _r; \ + errno = 0; /* Reset errno to prevent confusion. */ \ + __typeof__ (result) _r = (result); \ + if (_r == NULL) print_err(0, #result, __FILE__, __LINE__); \ + _r; \ }) #endif /* __cplusplus */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index edc37920e..d404e863e 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -1,35 +1,35 @@ /************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Core */ -/* */ -/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ -/* */ -/* This program is free software; you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation; either version 2 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program; if not, write to the Free Software */ -/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ -/* 02111-1307 USA */ -/* */ -/* A copy of the GNU General Public License has been included in this */ -/* distribution in the file "COPYING". */ -/* */ -/* Module: lib_cluster.c, lib_cluster.h */ -/* Author: Israel Fuller */ -/* Creation: September 29, 2025 */ -/* Description: Clustering library used to cluster and search data with */ -/* cosine similarity and Levenshtein similarity (aka. edit */ -/* distance). Used by the "clustering driver". */ -/* For more information on how to use this library, see */ -/* string-similarity.md in the centrallix-sysdoc folder. */ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: lib_cluster.c, lib_cluster.h */ +/* Author: Israel Fuller */ +/* Creation: September 29, 2025 */ +/* Description Clustering library used to cluster and search data with */ +/* cosine similarity and Levenshtein similarity (aka. edit */ +/* distance). Used by the "clustering driver". */ +/* For more information on how to use this library, see */ +/* string-similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ /** This file has additional documentation in string_similarity.md. **/ @@ -58,10 +58,10 @@ ***/ static unsigned int hash_char_pair(const unsigned char c1, const unsigned char c2) { - const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); - const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); - const unsigned int hash = (unsigned int)round(sum * scale) - 1u; - return hash % CA_NUM_DIMS; + const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); + const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); + const unsigned int hash = (unsigned int)round(sum * scale) - 1u; + return hash % CA_NUM_DIMS; } /*** An internal struct for temporarily storing character pairs while building @@ -90,8 +90,8 @@ typedef struct ***/ static int charpair_cmp(const void *p1, const void *p2) { - const CharPair *a = p1, *b = p2; - return a->hash - b->hash; + const CharPair *a = p1, *b = p2; + return a->hash - b->hash; } /*** Builds a vector using a string. @@ -141,112 +141,112 @@ static int charpair_cmp(const void *p1, const void *p2) ***/ pVector ca_build_vector(const char* str) { - unsigned int num_chars = 0u; - unsigned char* chars = check_ptr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); - if (chars == NULL) goto err; + unsigned char* chars = NULL; + CharPair* char_pairs = NULL; + pVector sparse_vector = NULL; + pVector trimmed_sparse_vector = NULL; - /** Begin adding char pairs (in order). **/ - chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ - for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) - { - char maybe_char = *char_ptr; - if (maybe_char < 0) fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); - unsigned char c = (unsigned char)maybe_char; + unsigned int num_chars = 0u; + chars = check_ptr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); + if (chars == NULL) goto err_free; - /** Always consider boundary character in string. **/ - if (c == CA_BOUNDARY_CHAR) goto skip_checks; + /** Begin adding char pairs (in order). **/ + chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ + for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) + { + char maybe_char = *char_ptr; + if (maybe_char < 0) fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); + unsigned char c = (unsigned char)maybe_char; + + /** Always consider boundary character in string. **/ + if (c != CA_BOUNDARY_CHAR) goto skip_checks; + + /** Ignore insignificant characters: spaces and punctuation. **/ + if (isspace(c)) continue; /* space, \n, \v, \f, \r */ + if (ispunct(c)) continue; /* !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ */ + + skip_checks: + /** Shift numbers to the end of the lowercase letters. **/ + if ('0' <= c && c <= '9') c += 75u; + + /** Store the character. **/ + chars[num_chars++] = tolower(c); + } + chars[num_chars++] = CA_BOUNDARY_CHAR; /* Ending boundary character. */ - /** Ignore insignificant characters: spaces and punctuation. **/ - if (isspace(c)) continue; /* space, \n, \v, \f, \r */ - if (ispunct(c)) continue; /* !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ */ + /** Compute hash values for char pairs. **/ + char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); + if (char_pairs == NULL) goto err_free; + const unsigned int num_pairs = num_chars - 1u; + for (unsigned int i = 0u; i < num_pairs; i++) + { + /** Store characters. **/ + char_pairs[i].c1 = chars[i]; + char_pairs[i].c2 = chars[i + 1]; + + /** Hash the character pair into an index (dimension). **/ + /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ + char_pairs[i].hash = hash_char_pair(chars[i], chars[i + 1]); + } - skip_checks: - /** Shift numbers to the end of the lowercase letters. **/ - if ('0' <= c && c <= '9') c += 75u; + /** Free unused memory. **/ + nmSysFree(chars); + chars = NULL; - /** Store the character. **/ - chars[num_chars++] = tolower(c); - } - chars[num_chars++] = CA_BOUNDARY_CHAR; /* Ending boundary character. */ - - /** Compute hash values for char pairs. **/ - CharPair* char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); - if (char_pairs == NULL) goto err_free_chars; - const unsigned int num_pairs = num_chars - 1u; - for (unsigned int i = 0u; i < num_pairs; i++) - { - /** Store characters. **/ - char_pairs[i].c1 = chars[i]; - char_pairs[i].c2 = chars[i + 1]; + /** Sort char_pairs by hash value. **/ + qsort(char_pairs, num_pairs, sizeof(CharPair), charpair_cmp); - /** Hash the character pair into an index (dimension). **/ - /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ - char_pairs[i].hash = hash_char_pair(chars[i], chars[i + 1]); - } - - /** Free unused memory. **/ - nmSysFree(chars); - chars = NULL; - - /** Sort char_pairs by hash value. **/ - qsort(char_pairs, num_pairs, sizeof(CharPair), charpair_cmp); - - /** Allocate space for the sparse vector. **/ - pVector sparse_vector = check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); - if (sparse_vector == NULL) goto err_free_char_pairs; - - /** Build the sparse vector. **/ - unsigned int cur = 0u, dim = 0u; - for (unsigned int i = 0u; i < num_pairs;) - { - unsigned int hash = char_pairs[i].hash; + /** Allocate space for the sparse vector. **/ + sparse_vector = check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); + if (sparse_vector == NULL) goto err_free; - /** Proceed through the pairs until we find a unique hash. **/ - /** Dividing value by 2 each time reduces the impact of repeated pairs. **/ - int value = 0; - for (; i < num_pairs && char_pairs[i].hash == hash; i++) + /** Build the sparse vector. **/ + unsigned int cur = 0u, dim = 0u; + for (unsigned int i = 0u; i < num_pairs;) { - value /= 2; /* Reduce impact of repeated pairs. */ - value += ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; + unsigned int hash = char_pairs[i].hash; + + /** Proceed through the pairs until we find a unique hash. **/ + /** Dividing value by 2 each time reduces the impact of repeated pairs. **/ + int value = 0; + for (; i < num_pairs && char_pairs[i].hash == hash; i++) + { + value /= 2; /* Reduce impact of repeated pairs. */ + value += ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; + } + + /** Skip zeros to reach the dimension index specified by the hash. **/ + unsigned int num_zeros = hash - dim; + if (num_zeros > 0u) + { + sparse_vector[cur++] = (int)-num_zeros; + dim = hash; + } + + /** Add the value to the sparse vector. **/ + sparse_vector[cur++] = value; + dim++; } + if (dim != CA_NUM_DIMS) sparse_vector[cur++] = -(CA_NUM_DIMS - dim); - /** Skip zeros to reach the dimension index specified by the hash. **/ - unsigned int num_zeros = hash - dim; - if (num_zeros > 0u) - { - sparse_vector[cur++] = (int)-num_zeros; - dim = hash; - } + /** Free unused memory. **/ + nmSysFree(char_pairs); + char_pairs = NULL; - /** Add the value to the sparse vector. **/ - sparse_vector[cur++] = value; - dim++; - } - if (dim != CA_NUM_DIMS) sparse_vector[cur++] = -(CA_NUM_DIMS - dim); - - /** Free unused memory. **/ - nmSysFree(char_pairs); - char_pairs = NULL; - - /** Trim extra space wasted by identical hashes. **/ - pVector trimmed_sparse_vector = check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); - if (trimmed_sparse_vector == NULL) goto err_free_sparse_vector; - sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ - - /** Return the result. **/ - return trimmed_sparse_vector; - - err_free_sparse_vector: - if (sparse_vector != NULL) nmSysFree(sparse_vector); - - err_free_char_pairs: - if (char_pairs != NULL) nmSysFree(char_pairs); - - err_free_chars: - if (chars != NULL) nmSysFree(chars); - - err: - return NULL; + /** Trim extra space wasted by identical hashes. **/ + trimmed_sparse_vector = check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); + if (trimmed_sparse_vector == NULL) goto err_free; + sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ + + /** Return the result. **/ + return trimmed_sparse_vector; + + err_free: + if (trimmed_sparse_vector != NULL) nmSysFree(trimmed_sparse_vector); + if (sparse_vector != NULL) nmSysFree(sparse_vector); + if (char_pairs != NULL) nmSysFree(char_pairs); + if (chars != NULL) nmSysFree(chars); + return NULL; } /*** Free memory allocated to store a sparse vector. @@ -255,7 +255,7 @@ pVector ca_build_vector(const char* str) ***/ void ca_free_vector(pVector sparse_vector) { - nmSysFree(sparse_vector); + nmSysFree(sparse_vector); } /*** Compute the length of a sparsely allocated vector. @@ -266,16 +266,18 @@ void ca_free_vector(pVector sparse_vector) unsigned int ca_sparse_len(const pVector vector) { unsigned int i = 0u; - for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) - { - const int val = vector[i++]; - - /** Negative val represents -val 0s in the array, so skip that many values. **/ - if (val < 0) dim += (unsigned)(-val); - - /** We have a param_value, but we don't need to do anything with it. **/ - else dim++; - } + + for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = vector[i++]; + + /** Negative val represents -val 0s in the array, so skip that many values. **/ + if (val < 0) dim += (unsigned)(-val); + + /** We have a param_value, but we don't need to do anything with it. **/ + else dim++; + } + return i; } @@ -286,11 +288,11 @@ unsigned int ca_sparse_len(const pVector vector) ***/ void ca_print_vector(const pVector vector) { - const unsigned int len = ca_sparse_len(vector); - printf("Vector: [%d", vector[0]); - for (unsigned int i = 1u; i < len; i++) - printf(", %d", vector[i]); - printf("]"); + const unsigned int len = ca_sparse_len(vector); + printf("Vector: [%d", vector[0]); + for (unsigned int i = 1u; i < len; i++) + printf(", %d", vector[i]); + printf("]"); } /*** Compute the magnitude of a sparsely allocated vector. @@ -301,16 +303,18 @@ void ca_print_vector(const pVector vector) static double magnitude_sparse(const pVector vector) { unsigned int magnitude = 0u; - for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) - { - const int val = vector[i++]; - - /** Negative val represents -val 0s in the array, so skip that many values. **/ - if (val < 0) dim += (unsigned)(-val); - - /** We have a param_value, so square it and add it to the magnitude. **/ - else { magnitude += (unsigned)(val * val); dim++; } - } + + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = vector[i++]; + + /** Negative val represents -val 0s in the array, so skip that many values. **/ + if (val < 0) dim += (unsigned)(-val); + + /** We have a param_value, so square it and add it to the magnitude. **/ + else { magnitude += (unsigned)(val * val); dim++; } + } + return sqrt((double)magnitude); } @@ -322,8 +326,10 @@ static double magnitude_sparse(const pVector vector) static double magnitude_dense(const pCentroid centroid) { double magnitude = 0.0; - for (int i = 0; i < CA_NUM_DIMS; i++) - magnitude += centroid[i] * centroid[i]; + + for (int i = 0; i < CA_NUM_DIMS; i++) + magnitude += centroid[i] * centroid[i]; + return sqrt(magnitude); } @@ -336,18 +342,18 @@ static double magnitude_dense(const pCentroid centroid) ***/ static void parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) { - if (token < 0) - { - /** This run contains -token zeros. **/ - *remaining = (unsigned)(-token); - *param_value = 0u; - } - else - { - /** This run contains one param_value. **/ - *remaining = 1u; - *param_value = (unsigned)(token); - } + if (token < 0) + { + /** This run contains -token zeros. **/ + *remaining = (unsigned)(-token); + *param_value = 0u; + } + else + { + /** This run contains one param_value. **/ + *remaining = 1u; + *param_value = (unsigned)(token); + } } /*** Calculate the similarity on sparsely allocated vectors. Comparing @@ -361,30 +367,30 @@ static void parse_vector_token(const int token, unsigned int* remaining, unsigne ***/ static double sparse_similarity(const pVector v1, const pVector v2) { - /** Calculate dot product. **/ - unsigned int vec1_remaining = 0u, vec2_remaining = 0u; - unsigned int dim = 0u, i1 = 0u, i2 = 0u, dot_product = 0u; - while (dim < CA_NUM_DIMS) - { - unsigned int val1 = 0u, val2 = 0u; - if (vec1_remaining == 0u) parse_vector_token(v1[i1++], &vec1_remaining, &val1); - if (vec2_remaining == 0u) parse_vector_token(v2[i2++], &vec2_remaining, &val2); + /** Calculate dot product. **/ + unsigned int vec1_remaining = 0u, vec2_remaining = 0u; + unsigned int dim = 0u, i1 = 0u, i2 = 0u, dot_product = 0u; + while (dim < CA_NUM_DIMS) + { + unsigned int val1 = 0u, val2 = 0u; + if (vec1_remaining == 0u) parse_vector_token(v1[i1++], &vec1_remaining, &val1); + if (vec2_remaining == 0u) parse_vector_token(v2[i2++], &vec2_remaining, &val2); + + /*** Accumulate the dot_product. If either vector is 0 here, + *** the total is 0 and this statement does nothing. + ***/ + dot_product += val1 * val2; + + /** Consume overlap from both runs. **/ + unsigned int overlap = min(vec1_remaining, vec2_remaining); + vec1_remaining -= overlap; + vec2_remaining -= overlap; + dim += overlap; + } - /*** Accumulate the dot_product. If either vector is 0 here, - *** the total is 0 and this statement does nothing. - ***/ - dot_product += val1 * val2; + /** Optional optimization to speed up nonsimilar vectors. **/ + if (dot_product == 0u) return 0.0; - /** Consume overlap from both runs. **/ - unsigned int overlap = min(vec1_remaining, vec2_remaining); - vec1_remaining -= overlap; - vec2_remaining -= overlap; - dim += overlap; - } - - /** Optional optimization to speed up nonsimilar vectors. **/ - if (dot_product == 0u) return 0.0; - /** Return the difference score. **/ return (double)dot_product / (magnitude_sparse(v1) * magnitude_sparse(v2)); } @@ -400,9 +406,9 @@ static double sparse_similarity(const pVector v1, const pVector v2) ***/ #define sparse_dif(v1, v2) (1.0 - sparse_similarity(v1, v2)) -/*** Calculate the similarity between a sparsely allocated vector - *** and a densely allocated centroid. Comparing any string to an - *** empty string should always return 0.5 (untested). +/*** Calculate the similarity between a sparsely allocated vector and a densely + *** allocated centroid using a dot product. Comparing any string to an empty + *** string should always return 0.5 (untested). *** *** @param v1 Sparse vector #1. *** @param c1 Dense centroid #2. @@ -412,26 +418,26 @@ static double sparse_similarity(const pVector v1, const pVector v2) ***/ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) { - /** Calculate dot product. **/ double dot_product = 0.0; - for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) - { - const int val = v1[i++]; - - /** Negative val represents -val 0s in the array, so skip that many values. **/ - if (val < 0) dim += (unsigned)(-val); - - /** We have a param_value, so square it and add it to the magnitude. **/ - else dot_product += (double)val * c2[dim++]; - } + + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = v1[i++]; + + /** Negative val represents -val 0s in the array, so skip that many values. **/ + if (val < 0) dim += (unsigned)(-val); + + /** We have a param_value, so square it and add it to the magnitude. **/ + else dot_product += (double)val * c2[dim++]; + } /** Return the difference score. **/ return dot_product / (magnitude_sparse(v1) * magnitude_dense(c2)); } -/*** Calculate the difference between a sparsely allocated vector - *** and a densely allocated centroid. Comparing any string to an - *** empty string should always return 0.5 (untested). +/*** Calculate the difference between a sparsely allocated vector and a densely + *** allocated centroid. Comparing any string to an empty string should always + *** return 0.5 (untested). *** *** @param v1 Sparse vector #1. *** @param c1 Dense centroid #2. @@ -459,97 +465,98 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) { int result = -1; + unsigned int** lev_matrix = NULL; - /*** lev_matrix: - *** For all i and j, d[i][j] will hold the Levenshtein distance between - *** the first i characters of s and the first j characters of t. - *** - *** As they say, no dynamic programming algorithm is complete without a - *** matrix that you fill out and it has the answer in the final location. - ***/ - const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; - const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; - unsigned int** lev_matrix = check_ptr(nmSysMalloc((str1_len + 1) * sizeof(unsigned int*))); - if (lev_matrix == NULL) goto end; - for (unsigned int i = 0u; i < str1_len + 1u; i++) - { - lev_matrix[i] = check_ptr(nmSysMalloc((str2_len + 1) * sizeof(unsigned int))); - if (lev_matrix[i] == NULL) goto end; - } - - /*** Base case #0: - *** Transforming an empty string into an empty string has 0 cost. - ***/ - lev_matrix[0][0] = 0u; - - /*** Base case #1: - *** Any source prefixe can be transformed into an empty string by - *** dropping each character. - ***/ - for (unsigned int i = 1u; i <= str1_len; i++) - lev_matrix[i][0] = i; - - /*** Base case #2: - *** Any target prefixes can be transformed into an empty string by - *** inserting each character. - ***/ - for (unsigned int j = 1u; j <= str2_len; j++) - lev_matrix[0][j] = j; - - /** General Case. **/ - for (unsigned int i = 1u; i <= str1_len; i++) - { + /*** lev_matrix: + *** For all i and j, d[i][j] will hold the Levenshtein distance between + *** the first i characters of s and the first j characters of t. + *** + *** As they say, no dynamic programming algorithm is complete without a + *** matrix that you fill out and it has the answer in the final location. + ***/ + const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; + const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; + lev_matrix = check_ptr(nmSysMalloc((str1_len + 1) * sizeof(unsigned int*))); + if (lev_matrix == NULL) goto end; + for (unsigned int i = 0u; i < str1_len + 1u; i++) + { + lev_matrix[i] = check_ptr(nmSysMalloc((str2_len + 1) * sizeof(unsigned int))); + if (lev_matrix[i] == NULL) goto end; + } + + /*** Base case #0: + *** Transforming an empty string into an empty string has 0 cost. + ***/ + lev_matrix[0][0] = 0u; + + /*** Base case #1: + *** Any source prefixe can be transformed into an empty string by + *** dropping each character. + ***/ + for (unsigned int i = 1u; i <= str1_len; i++) + lev_matrix[i][0] = i; + + /*** Base case #2: + *** Any target prefixes can be transformed into an empty string by + *** inserting each character. + ***/ for (unsigned int j = 1u; j <= str2_len; j++) + lev_matrix[0][j] = j; + + /** General Case. **/ + for (unsigned int i = 1u; i <= str1_len; i++) { - /** If the characters are equal, no change is needed. **/ - if (str1[i - 1] == str2[j - 1]) - lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; - - /*** We need to make a change, so use the oppereration with the - *** lowest cost out of delete, insert, replace, or swap. - ***/ - else + for (unsigned int j = 1u; j <= str2_len; j++) { - unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; - unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; - unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; + /** If the characters are equal, no change is needed. **/ + if (str1[i - 1] == str2[j - 1]) + lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; - /** If a swap is possible, calculate the cost. **/ - bool can_swap = ( - i > 1 && j > 1 && - str1[i - 1] == str2[j - 2] && - str1[i - 2] == str2[j - 1] - ); - unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; - - /** Assign the best operation. **/ - lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); + /*** We need to make a change, so use the oppereration with the + *** lowest cost out of delete, insert, replace, or swap. + ***/ + else + { + unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; + unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; + unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; + + /** If a swap is possible, calculate the cost. **/ + bool can_swap = ( + i > 1 && j > 1 && + str1[i - 1] == str2[j - 2] && + str1[i - 2] == str2[j - 1] + ); + unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; + + /** Assign the best operation. **/ + lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); + } } } - } - - /** Store result. **/ - unsigned int unsigned_result = lev_matrix[str1_len][str2_len]; - if (unsigned_result > INT_MAX) - { - fprintf(stderr, - "Warning: Integer overflow detected in ca_edit_dist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", - str1, str2, str1_length, str2_length, unsigned_result, INT_MAX - ); - } - result = (int)unsigned_result; - - /** Cleanup. **/ + + /** Store result. **/ + unsigned int unsigned_result = lev_matrix[str1_len][str2_len]; + if (unsigned_result > INT_MAX) + { + fprintf(stderr, + "Warning: Integer overflow detected in ca_edit_dist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", + str1, str2, str1_length, str2_length, unsigned_result, INT_MAX + ); + } + result = (int)unsigned_result; + + /** Cleanup. **/ end: - if (lev_matrix != NULL) - { - for (unsigned int i = 0u; i < str1_len + 1u; i++) + if (lev_matrix != NULL) { - if (lev_matrix[i] == NULL) break; - else nmSysFree(lev_matrix[i]); + for (unsigned int i = 0u; i < str1_len + 1u; i++) + { + if (lev_matrix[i] == NULL) break; + else nmSysFree(lev_matrix[i]); + } + nmSysFree(lev_matrix); } - nmSysFree(lev_matrix); - } /** Done. **/ return result; @@ -569,15 +576,15 @@ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, c ***/ double ca_cos_compare(void* v1, void* v2) { - if (v1 == v2) return 1.0; - - /** Input validation checks. **/ - const pVector vec1 = v1, vec2 = v2; - const bool v1_empty = (vec1 == NULL || ca_is_empty(vec1) || ca_has_no_pairs(vec1)); - const bool v2_empty = (vec2 == NULL || ca_is_empty(vec2) || ca_has_no_pairs(vec2)); - if (v1_empty && v2_empty) return 1.0; - if (v1_empty && !v2_empty) return 0.0; - if (!v1_empty && v2_empty) return 0.0; + if (v1 == v2) return 1.0; + + /** Input validation checks. **/ + const pVector vec1 = v1, vec2 = v2; + const bool v1_empty = (vec1 == NULL || ca_is_empty(vec1) || ca_has_no_pairs(vec1)); + const bool v2_empty = (vec2 == NULL || ca_is_empty(vec2) || ca_has_no_pairs(vec2)); + if (v1_empty && v2_empty) return 1.0; + if (v1_empty && !v2_empty) return 0.0; + if (!v1_empty && v2_empty) return 0.0; /** Apply rounding to avoid annoying floating point issues before returning. **/ return round(sparse_similarity(vec1, vec2) * 1000000) / 1000000; @@ -598,23 +605,23 @@ double ca_cos_compare(void* v1, void* v2) ***/ double ca_lev_compare(void* str1, void* str2) { - /** Input validation checks. **/ - if (str1 == NULL || str2 == NULL) return 0.0; - if (str1 == str2) return 1.0; - - /** Handle string length. **/ - const size_t len1 = strlen(str1); - const size_t len2 = strlen(str2); - if (len1 == 0lu && len2 == 0lu) return 1.0; - if (len1 != 0lu && len2 == 0lu) return 0.0; - if (len1 == 0lu && len2 != 0lu) return 0.0; - - /** Compute levenshtein edit distance. **/ - const int edit_dist = ca_edit_dist((const char*)str1, (const char*)str2, len1, len2); - if (!check_neg(edit_dist)) return NAN; - - /** Normalize edit distance into a similarity measure. **/ - const double normalized_similarity = 1.0 - (double)edit_dist / (double)max(len1, len2); + /** Input validation checks. **/ + if (str1 == NULL || str2 == NULL) return 0.0; + if (str1 == str2) return 1.0; + + /** Handle string length. **/ + const size_t len1 = strlen(str1); + const size_t len2 = strlen(str2); + if (len1 == 0lu && len2 == 0lu) return 1.0; + if (len1 != 0lu && len2 == 0lu) return 0.0; + if (len1 == 0lu && len2 != 0lu) return 0.0; + + /** Compute levenshtein edit distance. **/ + const int edit_dist = ca_edit_dist((const char*)str1, (const char*)str2, len1, len2); + if (!check_neg(edit_dist)) return NAN; + + /** Normalize edit distance into a similarity measure. **/ + const double normalized_similarity = 1.0 - (double)edit_dist / (double)max(len1, len2); /** Apply rounding to avoid annoying floating point issues before returning. **/ return round(normalized_similarity * 1000000) / 1000000; @@ -630,8 +637,10 @@ double ca_lev_compare(void* str1, void* str2) bool ca_eql(pVector v1, pVector v2) { const unsigned int len = ca_sparse_len(v1); - for (unsigned int i = 0u; i < len; i++) - if (v1[i] != v2[i]) return false; + + for (unsigned int i = 0u; i < len; i++) + if (v1[i] != v2[i]) return false; + return true; } @@ -652,50 +661,52 @@ static double get_cluster_size( const unsigned int num_clusters) { double result = NAN; + double* cluster_sums = NULL; + unsigned int* cluster_counts = NULL; - /** Allocate space to store clusters as averages are computed. **/ - /*** We use nmMalloc() here because this function is usually called - *** repeatedly with the same number of clusters in the k-means loop. - *** Also, it is likely that k-means may be invoked multiple times with - *** the same k value, leading to additional caching benefits. - ***/ - double* cluster_sums = check_ptr(nmMalloc(num_clusters * sizeof(double))); - unsigned int* cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); - if (cluster_sums == NULL) goto end; - if (cluster_counts == NULL) goto end; - for (unsigned int i = 0u; i < num_clusters; i++) - { - cluster_sums[i] = 0.0; - cluster_counts[i] = 0u; - } - - /** Sum the difference from each vector to its cluster centroid. **/ - for (unsigned int i = 0u; i < num_vectors; i++) - { - const unsigned int label = labels[i]; - cluster_sums[label] += sparse_dif_to_centroid(vectors[i], centroids[label]); - cluster_counts[label]++; - } - - /** Add up the average cluster size. **/ - double cluster_total = 0.0; - unsigned int num_valid_clusters = 0u; - for (unsigned int label = 0u; label < num_clusters; label++) - { - const unsigned int cluster_count = cluster_counts[label]; - if (cluster_count == 0u) continue; + /** Allocate space to store clusters as averages are computed. **/ + /*** We use nmMalloc() here because this function is usually called + *** repeatedly with the same number of clusters in the k-means loop. + *** Also, it is likely that k-means may be invoked multiple times with + *** the same k value, leading to additional caching benefits. + ***/ + cluster_sums = check_ptr(nmMalloc(num_clusters * sizeof(double))); + cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); + if (cluster_sums == NULL) goto end; + if (cluster_counts == NULL) goto end; + for (unsigned int i = 0u; i < num_clusters; i++) + { + cluster_sums[i] = 0.0; + cluster_counts[i] = 0u; + } + + /** Sum the difference from each vector to its cluster centroid. **/ + for (unsigned int i = 0u; i < num_vectors; i++) + { + const unsigned int label = labels[i]; + cluster_sums[label] += sparse_dif_to_centroid(vectors[i], centroids[label]); + cluster_counts[label]++; + } + + /** Add up the average cluster size. **/ + double cluster_total = 0.0; + unsigned int num_valid_clusters = 0u; + for (unsigned int label = 0u; label < num_clusters; label++) + { + const unsigned int cluster_count = cluster_counts[label]; + if (cluster_count == 0u) continue; + + cluster_total += cluster_sums[label] / cluster_count; + num_valid_clusters++; + } + + /** Calculate average sizes. **/ + result = cluster_total / num_valid_clusters; - cluster_total += cluster_sums[label] / cluster_count; - num_valid_clusters++; - } - - /** Calculate average sizes. **/ - result = cluster_total / num_valid_clusters; - end: - /** Clean up. **/ - if (cluster_sums != NULL) nmFree(cluster_sums, num_clusters * sizeof(double)); - if (cluster_counts != NULL) nmFree(cluster_counts, num_clusters * sizeof(unsigned int)); + /** Clean up. **/ + if (cluster_sums != NULL) nmFree(cluster_sums, num_clusters * sizeof(double)); + if (cluster_counts != NULL) nmFree(cluster_counts, num_clusters * sizeof(unsigned int)); return result; } @@ -746,151 +757,156 @@ int ca_kmeans( unsigned int* labels, double* vector_sims) { - /** Setup stuff. **/ - bool successful = false; - unsigned int cluster_counts[num_clusters]; - memset(labels, 0u, num_vectors * sizeof(unsigned int)); - - /** Allocate space to store centroids and new_centroids. **/ - /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ - const size_t centroids_size = num_clusters * sizeof(pCentroid); - pCentroid* centroids = check_ptr(nmMalloc(centroids_size)); - pCentroid* new_centroids = check_ptr(nmMalloc(centroids_size)); - if (centroids == NULL) goto end; - if (new_centroids == NULL) goto end; - memset(centroids, 0, centroids_size); - memset(new_centroids, 0, centroids_size); - for (unsigned int i = 0u; i < num_clusters; i++) - { - centroids[i] = check_ptr(nmMalloc(pCentroidSize)); - new_centroids[i] = check_ptr(nmMalloc(pCentroidSize)); - if (centroids[i] == NULL) goto end; - if (new_centroids[i] == NULL) goto end; - memset(centroids[i], 0, pCentroidSize); - memset(new_centroids[i], 0, pCentroidSize); - } - - /** Select random vectors to use as the initial centroids. **/ - srand(time(NULL)); - for (unsigned int i = 0u; i < num_clusters; i++) - { - /** Pick a random vector. **/ - const pVector vector = vectors[rand() % num_vectors]; + pCentroid* centroids = NULL; + pCentroid* new_centroids = NULL; - /** Sparse copy the vector to expand it into a densely allocated centroid. **/ - pCentroid centroid = centroids[i]; - for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + /** Setup variables. **/ + bool successful = false; + unsigned int cluster_counts[num_clusters]; + memset(labels, 0u, num_vectors * sizeof(unsigned int)); + + /** Allocate space to store centroids and new_centroids. **/ + /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ + const size_t centroids_size = num_clusters * sizeof(pCentroid); + centroids = check_ptr(nmMalloc(centroids_size)); + new_centroids = check_ptr(nmMalloc(centroids_size)); + if (centroids == NULL) goto end; + if (new_centroids == NULL) goto end; + memset(centroids, 0, centroids_size); + memset(new_centroids, 0, centroids_size); + for (unsigned int i = 0u; i < num_clusters; i++) { - const int token = vector[i++]; - if (token > 0) centroid[dim++] = (double)token; - else for (unsigned int j = 0u; j < (unsigned)-token; j++) centroid[dim++] = 0.0; + centroids[i] = check_ptr(nmMalloc(pCentroidSize)); + new_centroids[i] = check_ptr(nmMalloc(pCentroidSize)); + if (centroids[i] == NULL) goto end; + if (new_centroids[i] == NULL) goto end; + memset(centroids[i], 0, pCentroidSize); + memset(new_centroids[i], 0, pCentroidSize); } - } - - /** Main kmeans loop. **/ - double old_average_cluster_size = 1.0; - for (unsigned int iter = 0u; iter < max_iter; iter++) - { - bool changed = false; - /** Reset new centroids. **/ + /** Select random vectors to use as the initial centroids. **/ + srand(time(NULL)); for (unsigned int i = 0u; i < num_clusters; i++) { - cluster_counts[i] = 0u; - for (unsigned int dim = 0; dim < CA_NUM_DIMS; dim++) - new_centroids[i][dim] = 0.0; + /** Pick a random vector. **/ + const pVector vector = vectors[rand() % num_vectors]; + + /** Sparse copy the vector to expand it into a densely allocated centroid. **/ + pCentroid centroid = centroids[i]; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int token = vector[i++]; + if (token > 0) centroid[dim++] = (double)token; + else for (unsigned int j = 0u; j < (unsigned)-token; j++) centroid[dim++] = 0.0; + } } - /** Assign each point to the nearest centroid. **/ - for (unsigned int i = 0u; i < num_vectors; i++) + /** Main kmeans loop. **/ + double old_average_cluster_size = 1.0; + for (unsigned int iter = 0u; iter < max_iter; iter++) { - const pVector vector = vectors[i]; - double min_dist = DBL_MAX; - unsigned int best_centroid_label = 0u; + bool changed = false; - // Find nearest centroid. - for (unsigned int j = 0u; j < num_clusters; j++) + /** Reset new centroids. **/ + for (unsigned int i = 0u; i < num_clusters; i++) + { + cluster_counts[i] = 0u; + for (unsigned int dim = 0; dim < CA_NUM_DIMS; dim++) + new_centroids[i][dim] = 0.0; + } + + /** Assign each point to the nearest centroid. **/ + for (unsigned int i = 0u; i < num_vectors; i++) { - const double dist = sparse_dif_to_centroid(vector, centroids[j]); - if (dist < min_dist) + const pVector vector = vectors[i]; + double min_dist = DBL_MAX; + unsigned int best_centroid_label = 0u; + + /** Find nearest centroid. **/ + for (unsigned int j = 0u; j < num_clusters; j++) { - min_dist = dist; - best_centroid_label = j; + const double dist = sparse_dif_to_centroid(vector, centroids[j]); + if (dist < min_dist) + { + min_dist = dist; + best_centroid_label = j; + } } - } - /** Update label to new centroid, if necessary. **/ - if (labels[i] != best_centroid_label) - { - labels[i] = best_centroid_label; - changed = true; + /** Update label to new centroid, if necessary. **/ + if (labels[i] != best_centroid_label) + { + labels[i] = best_centroid_label; + changed = true; + } + + /** Accumulate values for new centroid calculation. **/ + pCentroid best_centroid = new_centroids[best_centroid_label]; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = vector[i++]; + if (val < 0) dim += (unsigned)(-val); + else best_centroid[dim++] += (double)val; + } + cluster_counts[best_centroid_label]++; } - /** Accumulate values for new centroid calculation. **/ - pCentroid best_centroid = new_centroids[best_centroid_label]; - for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + /** Stop if centroids didn't change. **/ + if (!changed) break; + + /** Update centroids. **/ + for (unsigned int i = 0u; i < num_clusters; i++) { - const int val = vector[i++]; - if (val < 0) dim += (unsigned)(-val); - else best_centroid[dim++] += (double)val; + if (cluster_counts[i] == 0u) continue; + pCentroid centroid = centroids[i]; + const pCentroid new_centroid = new_centroids[i]; + const unsigned int cluster_count = cluster_counts[i]; + for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) + centroid[dim] = new_centroid[dim] / cluster_count; } - cluster_counts[best_centroid_label]++; + + /** Is there enough improvement? **/ + if (min_improvement < -1) continue; /** Skip check if it will never end the loop. **/ + const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); + if (isnan(average_cluster_size)) goto end; + const double improvement = old_average_cluster_size - average_cluster_size; + if (improvement < min_improvement) break; + old_average_cluster_size = average_cluster_size; } - - /** Stop if centroids didn't change. **/ - if (!changed) break; - - /** Update centroids. **/ - for (unsigned int i = 0u; i < num_clusters; i++) + + /** Compute vector similarities, if requested. **/ + if (vector_sims != NULL) { - if (cluster_counts[i] == 0u) continue; - pCentroid centroid = centroids[i]; - const pCentroid new_centroid = new_centroids[i]; - const unsigned int cluster_count = cluster_counts[i]; - for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) - centroid[dim] = new_centroid[dim] / cluster_count; + for (unsigned int i = 0u; i < num_vectors; i++) + vector_sims[i] = sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); } - /** Is there enough improvement? **/ - if (min_improvement < -1) continue; /** Skip check if it will never end the loop. **/ - const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); - if (isnan(average_cluster_size)) goto end; - const double improvement = old_average_cluster_size - average_cluster_size; - if (improvement < min_improvement) break; - old_average_cluster_size = average_cluster_size; - } - - /** Compute vector similarities, if requested. **/ - if (vector_sims != NULL) - { - for (unsigned int i = 0u; i < num_vectors; i++) - vector_sims[i] = sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); - } - - /** Success. **/ - successful = true; - - /** Clean up. **/ + /** Success. **/ + successful = true; + end: - if (centroids != NULL) - { - for (unsigned int i = 0u; i < num_clusters; i++) + /** Clean up. **/ + if (centroids != NULL) { - if (centroids[i] != NULL) nmFree(centroids[i], pCentroidSize); - else break; + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (centroids[i] != NULL) nmFree(centroids[i], pCentroidSize); + else break; + } + nmFree(centroids, num_clusters * sizeof(pCentroid)); } - nmFree(centroids, num_clusters * sizeof(pCentroid)); - } - if (new_centroids != NULL) - { - for (unsigned int i = 0u; i < num_clusters; i++) + if (new_centroids != NULL) { - if (new_centroids[i] != NULL) nmFree(new_centroids[i], pCentroidSize); - else break; + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (new_centroids[i] != NULL) nmFree(new_centroids[i], pCentroidSize); + else break; + } + nmFree(new_centroids, num_clusters * sizeof(pCentroid)); } - nmFree(new_centroids, num_clusters * sizeof(pCentroid)); - } - return (successful) ? 0 : -1; + + /** Return the function result code. **/ + return (successful) ? 0 : -1; } /*** Finds the data that is the most similar to the target and returns @@ -917,16 +933,18 @@ void* ca_most_similar( { void* most_similar = NULL; double best_sim = -INFINITY; - for (unsigned int i = 0u; (num_data == 0u) ? (data[i] != NULL) : (i < num_data); i++) - { - const double sim = check_double(similarity(target, data[i])); - if (isnan(sim)) continue; /* Skip this comparison. */ - if (sim > best_sim && sim > threshold) + + for (unsigned int i = 0u; (num_data == 0u) ? (data[i] != NULL) : (i < num_data); i++) { - most_similar = data[i]; - best_sim = sim; + const double sim = check_double(similarity(target, data[i])); + if (isnan(sim)) continue; /* Skip this comparison. */ + if (sim > best_sim && sim > threshold) + { + most_similar = data[i]; + best_sim = sim; + } } - } + return most_similar; } @@ -961,57 +979,57 @@ pXArray ca_sliding_search( void** maybe_keys, pXArray maybe_dups) { - /** Allocate space for dups (if necessary). **/ pXArray dups = maybe_dups; - if (dups == NULL) - { - /** Guess that we will need space for num_data * 2 dups. **/ - const int guess_size = num_data * 2; - dups = check_ptr(xaNew(guess_size)); - if (dups == NULL) goto err; - } - const int num_starting_dups = dups->nItems; - /** Search for dups. **/ - for (unsigned int i = 0u; i < num_data; i++) - { - const unsigned int window_start = i + 1u; - const unsigned int window_end = min(i + window_size, num_data); - for (unsigned int j = window_start; j < window_end; j++) + /** Allocate space for dups (if necessary). **/ + if (dups == NULL) { - const double sim = check_double(similarity(data[i], data[j])); - if (isnan(sim) || sim < 0.0 || 1.0 < sim) - { - fprintf(stderr, "Invalid similarity %g %lf.\n", sim, sim); - goto err_free_dups; - } - if (sim > threshold) /* Dup found! */ + /** Guess that we will need space for num_data * 2 dups. **/ + const int guess_size = num_data * 2; + dups = check_ptr(xaNew(guess_size)); + if (dups == NULL) goto err; + } + const int num_starting_dups = dups->nItems; + + /** Search for dups. **/ + for (unsigned int i = 0u; i < num_data; i++) + { + const unsigned int window_start = i + 1u; + const unsigned int window_end = min(i + window_size, num_data); + for (unsigned int j = window_start; j < window_end; j++) { - Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); - if (dup == NULL) goto err_free_dups; - if (maybe_keys != NULL) + const double sim = check_double(similarity(data[i], data[j])); + if (isnan(sim) || sim < 0.0 || 1.0 < sim) { - dup->key1 = maybe_keys[i]; - dup->key2 = maybe_keys[j]; + fprintf(stderr, "Invalid similarity %g %lf.\n", sim, sim); + goto err_free_dups; + } + if (sim > threshold) /* Dup found! */ + { + Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); + if (dup == NULL) goto err_free_dups; + if (maybe_keys != NULL) + { + dup->key1 = maybe_keys[i]; + dup->key2 = maybe_keys[j]; + } + dup->similarity = sim; + if (!check_neg(xaAddItem(dups, (void*)dup))) goto err_free_dups; } - dup->similarity = sim; - if (!check_neg(xaAddItem(dups, (void*)dup))) goto err_free_dups; } } - } - - /** Success. **/ - return dups; - - /** Error cleanup. **/ + + /** Success. **/ + return dups; + err_free_dups: - /** Free the dups that we added to the XArray. **/ - while (dups->nItems > num_starting_dups) - nmFree(dups->Items[--dups->nItems], sizeof(Dup)); - if (maybe_dups == NULL) check(xaDeInit(dups)); /* Failure ignored. */ + /** Error cleanup: Free the dups that we added to the XArray. **/ + while (dups->nItems > num_starting_dups) + nmFree(dups->Items[--dups->nItems], sizeof(Dup)); + if (maybe_dups == NULL) check(xaDeInit(dups)); /* Failure ignored. */ err: - return NULL; + return NULL; } /*** Runs a complete search over the provided data, comparing each element to diff --git a/centrallix-lib/src/mtask.c b/centrallix-lib/src/mtask.c index 9a167d724..401fbf15b 100644 --- a/centrallix-lib/src/mtask.c +++ b/centrallix-lib/src/mtask.c @@ -3407,7 +3407,7 @@ netGetRemotePort(pFile net_filedesc) } -/*** NETCONNECTTCP creats a client socket and connects it to a +/*** NETCONNECTTCP creates a client socket and connects it to a *** server on a given TCP service/port and host name. The flag *** NET_U_NOBLOCK causes the request to return immediately even *** if the connection is still trying to establish. Further @@ -4265,4 +4265,3 @@ syGetSem(pSemaphore sem, int cnt, int flags) return code; } - diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index edb65dbfa..6dbc8bd22 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -1,24 +1,24 @@ /************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Base Library */ -/* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ -/* */ -/* You may use these files and this library under the terms of the */ -/* GNU Lesser General Public License, Version 2.1, contained in the */ -/* included file "COPYING". */ -/* */ -/* Module: util.c, util.h */ -/* Author: Micah Shennum and Israel Fuller */ -/* Date: May 26, 2011 */ -/* Description: Collection of utilities including: */ -/* - Utilities for parsing numbers. */ -/* - The timer utility for benchmarking code. */ -/* - snprint_bytes() for formatting a byte count. */ -/* - snprint_llu() for formatting large numbers. */ -/* - fprint_mem() for printing memory stats. */ -/* - min() and max() for handling numbers. */ -/* - The check functions for reliably printing debug data. */ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: util.c, util.h */ +/* Author: Micah Shennum and Israel Fuller */ +/* Date: May 26, 2011 */ +/* Description: Collection of utilities including: */ +/* - Utilities for parsing numbers. */ +/* - The timer utility for benchmarking code. */ +/* - snprint_bytes() for formatting a byte count. */ +/* - snprint_commas_llu() for formatting large numbers. */ +/* - fprint_mem() for printing memory stats. */ +/* - min() and max() for handling numbers. */ +/* - The check functions for reliably printing debug data. */ /************************************************************************/ #include @@ -95,9 +95,9 @@ unsigned int strtoui(const char *nptr, char **endptr, int base){ *** Fun Fact: Windows uses kibibytes, but displays them as KB. ***/ #define USE_METRIC false -#define nUnits 6u -static char* units_cs[nUnits] = {"bytes", "KiB", "MiB", "GiB"}; -static char* units_metric[nUnits] = {"bytes", "KB", "MB", "GB"}; +#define N_UNITS 6u +static char* units_cs[N_UNITS] = {"bytes", "KiB", "MiB", "GiB"}; +static char* units_metric[N_UNITS] = {"bytes", "KB", "MB", "GB"}; /*** Displays a size in bytes using the largest unit where the result would be *** at least 1.0. Note that units larger than GB and GiB are not supported @@ -113,98 +113,109 @@ static char* units_metric[nUnits] = {"bytes", "KB", "MB", "GB"}; ***/ char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) { - char** units = (USE_METRIC) ? units_metric : units_cs; - const double unit_size = (USE_METRIC) ? 1000.0 : 1024.0; - - /** Search for the largest unit where the value would be at least 1. **/ - const double size = (double)bytes; - for (unsigned char i = nUnits; i >= 1u; i--) - { - const double denominator = pow(unit_size, i); - if (size >= denominator) + char** units = (USE_METRIC) ? units_metric : units_cs; + const double unit_size = (USE_METRIC) ? 1000.0 : 1024.0; + + /** Search for the largest unit where the value would be at least 1. **/ + const double size = (double)bytes; + for (unsigned char i = N_UNITS; i >= 1u; i--) { - const double converted_size = size / denominator; - if (converted_size >= 100.0) - snprintf(buf, buf_size, "%.5g %s", converted_size, units[i]); - else if (converted_size >= 10.0) - snprintf(buf, buf_size, "%.4g %s", converted_size, units[i]); - else /* if (converted_size >= 1.0) - Always true. */ - snprintf(buf, buf_size, "%.3g %s", converted_size, units[i]); - return buf; + const double denominator = pow(unit_size, i); + if (size >= denominator) + { + const double converted_size = size / denominator; + if (converted_size >= 100.0) + snprintf(buf, buf_size, "%.5g %s", converted_size, units[i]); + else if (converted_size >= 10.0) + snprintf(buf, buf_size, "%.4g %s", converted_size, units[i]); + else /* if (converted_size >= 1.0) - Always true. */ + snprintf(buf, buf_size, "%.3g %s", converted_size, units[i]); + return buf; + } } - } - - /** None of the larger units work, so we just use bytes. **/ - snprintf(buf, buf_size, "%u %s", bytes, units[0]); + + /** None of the larger units work, so we just use bytes. **/ + snprintf(buf, buf_size, "%u %s", bytes, units[0]); return buf; } #undef nUints -char* snprint_llu(char* buf, size_t buflen, unsigned long long value) +/*** Print a large number formatted with comas to a buffer. + *** + *** @param buf The buffer to print the number into. + *** @param buf_size The maximum number of characters to add to the buffer. + *** @param value The value to write into the buffer. + *** @returns `buf`, or `NULL` if `buf_size` is 0. + */ +char* snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) { - if (buflen == 0) return NULL; - if (value == 0) - { - if (buflen > 1) { buf[0] = '0'; buf[1] = '\0'; } - else buf[0] = '\0'; - return buf; - } - - char tmp[32]; - unsigned int ti = 0; - while (value > 0 && ti < sizeof(tmp) - 1) - { - if (ti % 4 == 3) tmp[ti++] = ','; - tmp[ti++] = '0' + (value % 10); - value /= 10; - } - tmp[ti] = '\0'; - - unsigned int outlen = min(ti, buflen - 1u); - for (unsigned int i = 0u; i < outlen; i++) buf[i] = tmp[ti - i - 1]; - buf[outlen] = '\0'; + if (buf_size == 0) return NULL; + if (value == 0) + { + if (buf_size > 1) { buf[0] = '0'; buf[1] = '\0'; } + else buf[0] = '\0'; + return buf; + } + + char tmp[32]; + unsigned int ti = 0; + while (value > 0 && ti < sizeof(tmp) - 1) + { + if (ti % 4 == 3) tmp[ti++] = ','; + tmp[ti++] = '0' + (value % 10); + value /= 10; + } + tmp[ti] = '\0'; + + unsigned int outlen = min(ti, buf_size - 1u); + for (unsigned int i = 0u; i < outlen; i++) buf[i] = tmp[ti - i - 1]; + buf[outlen] = '\0'; + return buf; } void fprint_mem(FILE* out) { - FILE* fp = fopen("/proc/self/statm", "r"); - if (fp == NULL) { perror("fopen()"); return; } - - long size, resident, share, text, lib, data, dt; - if (fscanf(fp, "%ld %ld %ld %ld %ld %ld %ld", - &size, &resident, &share, &text, &lib, &data, &dt) != 7) - { - fprintf(stderr, "Failed to read memory info\n"); - fclose(fp); - return; - } - fclose(fp); - - long page_size = sysconf(_SC_PAGESIZE); // in bytes - long resident_bytes = resident * page_size; - - const size_t buf_siz = 16u; - char buf[buf_siz]; - snprint_bytes(buf, buf_siz, (unsigned int)resident_bytes); + FILE* fp = fopen("/proc/self/statm", "r"); + if (fp == NULL) { perror("fopen()"); return; } + + long size, resident, share, text, lib, data, dt; + if (fscanf(fp, "%ld %ld %ld %ld %ld %ld %ld", + &size, &resident, &share, &text, &lib, &data, &dt) != 7) + { + fprintf(stderr, "Failed to read memory info\n"); + check(fclose(fp)); /* Failure ignored. */ + return; + } + check(fclose(fp)); /* Failure ignored. */ + + long page_size = sysconf(_SC_PAGESIZE); // in bytes + long resident_bytes = resident * page_size; - fprintf(out, "Memory used: %ld bytes (%s)\n", resident_bytes, buf); - fprintf(out, "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", share, text, lib, data); + const size_t buf_siz = 16u; + char buf[buf_siz]; + snprint_bytes(buf, buf_siz, (unsigned int)resident_bytes); + + fprintf(out, "Memory used: %ld bytes (%s)\n", resident_bytes, buf); + fprintf(out, "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", share, text, lib, data); } static double get_time(void) { struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9f; } pTimer timer_init(pTimer timer) { - if (timer == NULL) return NULL; - timer->start = NAN; - timer->total = 0.0; + if (timer == NULL) return NULL; + timer->start = NAN; + timer->total = 0.0; + return timer; } @@ -215,15 +226,17 @@ pTimer timer_new(void) pTimer timer_start(pTimer timer) { - if (!timer) return timer; - timer->start = get_time(); + if (!timer) return timer; + timer->start = get_time(); + return timer; } pTimer timer_stop(pTimer timer) { - if (!timer) return timer; - timer->total += get_time() - timer->start; + if (!timer) return timer; + timer->total += get_time() - timer->start; + return timer; } @@ -241,8 +254,8 @@ void timer_de_init(pTimer timer) {} void timer_free(pTimer timer) { - timer_de_init(timer); - nmFree(timer, sizeof(Timer)); + timer_de_init(timer); + nmFree(timer, sizeof(Timer)); } /*** Function for failing on error, assuming the error came from a library or @@ -250,12 +263,12 @@ void timer_free(pTimer timer) ***/ void print_err(int code, const char* function_name, const char* file_name, const int line_number) { - /** Create a descriptive error message. **/ - char error_buf[BUFSIZ]; - snprintf(error_buf, sizeof(error_buf), "%s:%d: %s failed", file_name, line_number, function_name); - - /** Print it with as much info as we can reasonably find. **/ - if (errno != 0) perror(error_buf); - else if (code != 0) fprintf(stderr, "%s (error code %d).\n", error_buf, code); - else fprintf(stderr, "%s.\n", error_buf); + /** Create a descriptive error message. **/ + char error_buf[BUFSIZ]; + snprintf(error_buf, sizeof(error_buf), "%s:%d: %s failed", file_name, line_number, function_name); + + /** Print it with as much info as we can reasonably find. **/ + if (errno != 0) perror(error_buf); + else if (code != 0) fprintf(stderr, "%s (error code %d).\n", error_buf, code); + else fprintf(stderr, "%s.\n", error_buf); } diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index 46ef3a6fb..7bf4242cc 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -292,7 +292,8 @@ xhClear(pXHashTable this, int (*free_fn)(), void* free_arg) /*** Executes an operation on each entry of the hash table entry. *** - *** @param this The affected hash table. + *** @param this The affected hash table (passing NULL causes undefined + *** behavior). *** @param callback_fn A callback function to be called on each hash table *** entry. It takes 2 parameters: the current hash table entry and a void* *** argument specified using each_arg. If any invocation of the callback @@ -307,57 +308,85 @@ xhClear(pXHashTable this, int (*free_fn)(), void* free_arg) int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg) { - if (callback_fn == NULL) return 1; - - for (int row = 0; row < this->nRows; row++) - { - pXHashEntry entry = (pXHashEntry)(this->Rows.Items[row]); - while (entry != NULL) + if (callback_fn == NULL) return 1; + + for (int row = 0; row < this->nRows; row++) { - pXHashEntry next = entry->Next; - const int ret = callback_fn(entry, each_arg); - if (ret != 0) return ret; - entry = next; + pXHashEntry entry = (pXHashEntry)(this->Rows.Items[row]); + while (entry != NULL) + { + pXHashEntry next = entry->Next; + const int ret = callback_fn(entry, each_arg); + if (ret != 0) return ret; + entry = next; + } } - } return 0; } +/*** A helper function for `xhClearKeySafe()`. Deallocates a hash table entry + *** after calling the appropriate free function with the provided free arg. + *** + *** @param entry A pointer to the hash table entry to be freed (passing NULL + *** causes undefined behavior). + *** @param arg A pointer to a void* array with 2 elements: The first element + *** is a function pointer to the free function, which we invoke using the + *** provided entry and the free_arg, specified as the second element of + *** this array. + *** @returns 0, success. + ****/ static int -xhiFreeEntry(pXHashEntry entry, void* arg) +xh_i_FreeEntry(pXHashEntry entry, void* arg) { - /*** The passed void* actually points to a void* array with 2 elements. - *** The first element is a function pointer to the free function, which - *** we invoke using the provided entry and the free_arg, specified as the - *** second element of the array. - *** - *** Interestingly, you can write this code in one line like this: - *** ((void (*)(pXHashEntry, void*))((void**)arg)[0])(entry, ((void**)arg)[1]); - *** But I value code readability, so fortunately, I can't be THAT cleaver... - ***/ - void** args = (void**)arg; - void (*free_fn)(pXHashEntry, void*) = args[0]; - free_fn(entry, args[1]); - - /** Free the entry. **/ - nmFree(entry, sizeof(XHashEntry)); + /*** The passed void* actually points to a void* array with 2 elements. + *** + *** The first element is a function pointer to the free function, which + *** we invoke using the provided entry and the free_arg, specified as the + *** second element of the array. + *** + *** Interestingly, you can write this code in one line like this: + *** ((void (*)(pXHashEntry, void*))((void**)arg)[0])(entry, ((void**)arg)[1]); + *** But I value code readability, so fortunately, I can't be THAT cleaver... + ***/ + void** args = (void**)arg; + void (*free_fn)(pXHashEntry, void*) = args[0]; + free_fn(entry, args[1]); + + /** Free the entry. **/ + nmFree(entry, sizeof(XHashEntry)); return 0; } +/*** Clears all contents from a hash table. The free function is passed each + *** hash entry struct, allowing it to free both the value and key, if needed. + *** + *** @param this The affected hash table (passing NULL causes undefined + *** behavior). + *** @param free_fn A pointer to a free function which will be called with a + *** pointer to each `XHashEntry` before they are deallocated. It is also + *** passed a `void*`, which will be `free_arg` (the third argument). + *** @param free_arg The void pointer value passed to the free function. + *** @returns 0 if successful, or + *** -1 if `free_fn()` is `NULL`. + ***/ int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg) { - /** Free each row. **/ - void* args[2] = {free_fn, free_arg}; - const int ret = xhForEach(this, xhiFreeEntry, args); - - /** Mark all rows as empty. **/ - for (int i = 0; i < this->nRows; i++) - this->Rows.Items[i] = NULL; - this->nItems = 0; + if (free_fn == NULL) return -1; + + /** Free each row. **/ + void* args[2] = {free_fn, free_arg}; + const int ret = xhForEach(this, xh_i_FreeEntry, args); + + /** Mark all rows as empty. **/ + for (int i = 0; i < this->nRows; i++) + this->Rows.Items[i] = NULL; + this->nItems = 0; - /** We are successful only if the free function didn't fail. **/ + /*** We are successful only if the free function didn't fail (and it should + *** not be able to fail). + ***/ return ret; } diff --git a/centrallix-sysdoc/GCC_Dependencies.md b/centrallix-sysdoc/GCC_Dependencies.md new file mode 100644 index 000000000..1327ea090 --- /dev/null +++ b/centrallix-sysdoc/GCC_Dependencies.md @@ -0,0 +1,20 @@ +# GCC Dependencies + +Author: Israel Fuller + +Date: Descember 4, 2025 + +## Table of Contents +- [GCC Dependencies](#gcc-dependencies) + - [Table of Contents](#table-of-contents) + - [Introduction](#intoduction) + - [List of Dependencies](#list-of-dependencies) + +## Intoduction +This document tracks dependencies on the GCC toolchain in the centrallix codebase. As code is added which relies on GCC specific behavior, such additions should be noted here to make possible use of a different toolchain (e.g. LLVM) in the future less painful. + +## List of Dependencies +- `util.h` Uses the `__typeof__` to avoid double-computation in macros. + +## Notes +`__FILE__` and `__LINE__` are not dependencies as they were added in C90. See [this page](https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html) for information about predefined macros. diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index c48288860..6283a8855 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -27,6 +27,8 @@ + + # ObjectSystem Driver Interface @@ -35,16 +37,21 @@ **Date**: January 13, 1999 -**Updated**: November 17, 2025 +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + -**License**: Copyright (C) 2001-2011 LightSys Technology Services. See LICENSE.txt for more information. ## Table of Contents - [ObjectSystem Driver Interface](#objectsystem-driver-interface) - [Table of Contents](#table-of-contents) - [I Introduction](#i-introduction) - [II Interface](#ii-interface) - - [Function: Open](#function-open) + - [Abbreviation Prefix](#abbreviation-prefix) + - [Internal Functions](#internal-functions) + - [Function: Initialize()](#function-initialize) + - [Function: Open()](#function-open) - [Function: OpenChild()](#function-openchild) - [Function: Close()](#function-close) - [Function: Create()](#function-create) @@ -102,84 +109,124 @@ - [nmSysRealloc()](#nmsysrealloc) - [nmSysStrdup()](#nmsysstrdup) - [nmSysFree()](#nmsysfree) - - [V Other Utility Modules](#v-other-utility-modules) - - [A. XArray (XA) - Arrays](#axarray-xa---arrays) - - [xaInit(pXArray this, int init_size)](#xainitpxarray-this-int-init_size) - - [xaDeInit(pXArray this)](#xadeinitpxarray-this) - - [xaAddItem(pXArray this, void* item)](#xaadditempxarray-this-void-item) - - [xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen)](#xaadditemsortedpxarray-this-void-item-int-keyoffset-int-keylen) - - [xaFindItem(pXArray this, void* item)](#xafinditempxarray-this-void-item) - - [xaRemoveItem(pXArray this, int index)](#xaremoveitempxarray-this-int-index) - - [B. XHash (XH) - Hash Tables](#bxhash-xh---hash-tables) - - [int xhInit(pXHashTable this, int rows, int keylen)](#int-xhinitpxhashtable-this-int-rows-int-keylen) - - [int xhDeInit(pXHashTable this)](#int-xhdeinitpxhashtable-this) - - [int xhAdd(pXHashTable this, char* key, char* data)](#int-xhaddpxhashtable-this-char-key-char-data) - - [int xhRemove(pXHashTable this, char* key)](#int-xhremovepxhashtable-this-char-key) - - [char* xhLookup(pXHashTable this, char* key)](#char-xhlookuppxhashtable-this-char-key) - - [int xhClear(pXHashTable this, int free_blk)](#int-xhclearpxhashtable-this-int-free_blk) - - [C. XString (XS) - Strings](#cxstring-xs---strings) - - [int xsInit(pXString this)](#int-xsinitpxstring-this) - - [int xsDeInit(pXString this)](#int-xsdeinitpxstring-this) - - [int xsConcatenate(pXString this, char* text, int len)](#int-xsconcatenatepxstring-this-char-text-int-len) - - [int xsCopy(pXString this, char* text, int len)](#int-xscopypxstring-this-char-text-int-len) - - [char* xsStringEnd(pXString this)](#char-xsstringendpxstring-this) - - [D. Expression (EXP) - Expression Trees](#dexpression-exp---expression-trees) - - [pExpression expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflags)](#pexpression-expcompileexpressionchar-text-pparamobjects-objlist-int-lxflags-int-cmpflags) - - [expFreeExpression(pExpression this)](#expfreeexpressionpexpression-this) - - [int expEvalTree(pExpression this, pParamObjects objlist)](#int-expevaltreepexpression-this-pparamobjects-objlist) - - [pParamObjects expCreateParamList()](#pparamobjects-expcreateparamlist) - - [int expFreeParamList(pParamObjects this)](#int-expfreeparamlistpparamobjects-this) - - [int expAddParamToList(pParamObjects this, char* name, pObject obj, int flags)](#int-expaddparamtolistpparamobjects-this-char-name-pobject-obj-int-flags) - - [int expModifyParam(pParamObjects this, char* name, pObject replace_obj)](#int-expmodifyparampparamobjects-this-char-name-pobject-replace_obj) - - [int expRemoveParamFromList(pParamObjects this, char* name)](#int-expremoveparamfromlistpparamobjects-this-char-name) - - [int expReverseEvalTree(pExpression tree, pParamObjects objlist)](#int-expreverseevaltreepexpression-tree-pparamobjects-objlist) - - [E. MTSession (MSS) - Basic Session Management](#emtsession-mss---basic-session-management) - - [char* mssUserName()](#char-mssusername) - - [char* mssPassword()](#char-msspassword) - - [int mssSetParam(char* paramname, char* param)](#int-msssetparamchar-paramname-char-param) - - [char* mssGetParam(char* paramname)](#char-mssgetparamchar-paramname) - - [int mssError(int clr, char* module, char* message, ...)](#int-msserrorint-clr-char-module-char-message-) - - [int mssErrorErrno(int clr, char* module, char* message, ...)](#int-msserrorerrnoint-clr-char-module-char-message-) - - [F. OSML Utility Functions](#fosml-utility-functions) - - [char* obj_internal_PathPart(pPathname path, int start, int length)](#char-obj_internal_pathpartppathname-path-int-start-int-length) - - [int obj_internal_AddToPath(pPathname path, char* new_element)](#int-obj_internal_addtopathppathname-path-char-new_element) - - [int obj_internal_CopyPath(pPathname dest, pPathname src)](#int-obj_internal_copypathppathname-dest-ppathname-src) - - [void obj_internal_FreePathStruct(pPathname path)](#void-obj_internal_freepathstructppathname-path) - - [VI Network Connection Functionality](#vi-network-connection-functionality) - - [pFile netConnectTCP(char* host_name, char* service_name, int flags)](#pfile-netconnecttcpchar-host_name-char-service_name-int-flags) - - [int netCloseTCP(pFile net_filedesc, int linger_msec, int flags)](#int-netclosetcppfile-net_filedesc-int-linger_msec-int-flags) - - [int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags)](#int-fdwritepfile-filedesc-char-buffer-int-length-int-offset-int-flags) - - [int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags)](#int-fdreadpfile-filedesc-char-buffer-int-maxlen-int-offset-int-flags) - - [VII Parsing Data](#vii-parsing-data) - - [pLxSession mlxOpenSession(pFile fd, int flags)](#plxsession-mlxopensessionpfile-fd-int-flags) - - [pLxSession mlxStringSession(char* str, int flags)](#plxsession-mlxstringsessionchar-str-int-flags) - - [int mlxCloseSession(pLxSession this)](#int-mlxclosesessionplxsession-this) - - [int mlxNextToken(pLxSession this)](#int-mlxnexttokenplxsession-this) - - [char* mlxStringVal(pLxSession this, int* alloc)](#char-mlxstringvalplxsession-this-int-alloc) - - [int mlxIntVal(pLxSession this)](#int-mlxintvalplxsession-this) - - [double mlxDoubleVal(pLxSession this)](#double-mlxdoublevalplxsession-this) - - [int mlxCopyToken(pLxSession this, char* buffer, int maxlen)](#int-mlxcopytokenplxsession-this-char-buffer-int-maxlen) - - [int mlxHoldToken(pLxSession this)](#int-mlxholdtokenplxsession-this) - - [int mlxSetOptions(pLxSession this, int options)](#int-mlxsetoptionsplxsession-this-int-options) - - [int mlxUnsetOptions(pLxSession this, int options)](#int-mlxunsetoptionsplxsession-this-int-options) - - [int mlxSetReservedWords(pLxSession this, char** res_words)](#int-mlxsetreservedwordsplxsession-this-char-res_words) - - [int mlxNoteError(pLxSession this)](#int-mlxnoteerrorplxsession-this) - - [int mlxNotePosition(pLxSession this)](#int-mlxnotepositionplxsession-this) - - [VIII Objectsystem Driver Testing](#viii-objectsystem-driver-testing) - - [A. Object opening, closing, creation, and deletion](#aobject-opening-closing-creation-and-deletion) - - [B. Object attribute enumeration, getting, and setting.](#bobject-attribute-enumeration-getting-and-setting) - - [C. Object querying (for subobjects)](#cobject-querying-for-subobjects) + - [V Module: XArray](#v-module-xarray) + - [xaNew()](#xanew) + - [xaFree()](#xafree) + - [xaInit()](#xainit) + - [xaDeInit()](#xadeinit) + - [xaAddItem()](#xaadditem) + - [xaAddItemSorted()](#xaadditemsorted) + - [xaAddItemSortedInt32()](#xaadditemsortedint32) + - [xaGetItem()](#xagetitem) + - [xaFindItem()](#xafinditem) + - [xaFindItemR()](#xafinditemr) + - [xaRemoveItem()](#xaremoveitem) + - [xaClear()](#xaclear) + - [xaClearR()](#xaclearr) + - [xaCount()](#xacount) + - [xaInsertBefore()](#xainsertbefore) + - [xaInsertAfter()](#xainsertafter) + - [VI Module: XHash](#vi-module-xhash) + - [xhInitialize()](#xhinitialize) + - [xhInit()](#xhinit) + - [xhDeInit()](#xhdeinit) + - [xhAdd()](#xhadd) + - [xhRemove()](#xhremove) + - [xhLookup()](#xhlookup) + - [xhClear()](#xhclear) + - [xhForEach()](#xhforeach) + - [xhClearKeySafe()](#xhclearkeysafe) + - [VII Module: XString](#vii-module-xstring) + - [xsNew()](#xsnew) + - [xsFree()](#xsfree) + - [xsInit()](#xsinit) + - [xsDeInit()](#xsdeinit) + - [xsCheckAlloc()](#xscheckalloc) + - [xsConcatenate()](#xsconcatenate) + - [xsCopy()](#xscopy) + - [xsStringEnd()](#xsstringend) + - [xsConcatPrintf()](#xsconcatprintf) + - [xsPrintf()](#xsprintf) + - [xsWrite()](#xswrite) + - [xsRTrim()](#xsrtrim) + - [xsLTrim()](#xsltrim) + - [xsTrim()](#xstrim) + - [xsFind()](#xsfind) + - [xsFindRev()](#xsfindrev) + - [xsSubst()](#xssubst) + - [xsReplace()](#xsreplace) + - [xsInsertAfter()](#xsinsertafter) + - [xsGenPrintf_va()](#xsgenprintf_va) + - [xsGenPrintf()](#xsgenprintf) + - [xsString()](#xsstring) + - [xsLength()](#xslength) + - [xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf()](#xsqprintf_va-xsqprintf--xsconcatqprintf) + - [VIII Module: Expression](#viii-module-expression) + - [expCompileExpression())](#expallocexpression) + - [expFreeExpression()](#expfreeexpression) + - [expCompileExpression()](#expcompileexpression) + - [expCompileExpressionFromLxs()](#expcompileexpressionfromlxs) + - [expPodToExpression()](#exppodtoexpression) + - [expExpressionToPod()](#expexpressiontopod) + - [expDuplicateExpression()](#expduplicateexpression) + - [expIsConstant()](#expisconstant) + - [expEvalTree()](#expevaltree) + - [expCreateParamList()](#expcreateparamlist) + - [expFreeParamList()](#expfreeparamlist) + - [expAddParamToList()](#expaddparamtolist) + - [expModifyParam()](#expmodifyparam) + - [expRemoveParamFromList()](#expremoveparamfromlist) + - [expSetParamFunctions()](#expsetparamfunctions) + - [expReverseEvalTree()](#expreverseevaltree) + - [IX MTSession](#ix-module-mtsession) + - [mssUserName()](#mssusername) + - [mssPassword()](#msspassword) + - [mssSetParam()](#msssetparam) + - [mssGetParam()](#mssgetparam) + - [mssError()](#msserror) + - [mssErrorErrno()](#msserrorerrno) + - [X Path Handling Functions](#x-path-handling-functions) + - [obj_internal_PathPart()](#obj_internal_pathpart) + - [obj_internal_AddToPath()](#obj_internal_addtopath) + - [obj_internal_CopyPath](#obj_internal_copypath) + - [obj_internal_FreePathStruct()](#obj_internal_freepathstruct) + - [XI Network Connection Functionality](#vi-network-connection-functionality) + - [netConnectTCP()](#netconnecttcp) + - [netCloseTCP()](#netclosetcp) + - [fdWrite()](#fdwrite) + - [fdRead()](#fdread) + - [XII Parsing Data](#xii-parsing-data) + - [mlxOpenSession()](#mlxopensession) + - [mlxStringSession()](#mlxstringsession) + - [mlxCloseSession()](#mlxclosesession) + - [mlxNextToken()](#mlxnexttoken) + - [mlxStringVal()](#mlxstringval) + - [mlxIntVal()](#mlxintval) + - [mlxDoubleVal()](#mlxdoubleval) + - [mlxCopyToken()](#mlxcopytoken) + - [mlxHoldToken()](#mlxholdtoken) + - [mlxSetOptions()](#mlxsetoptions) + - [mlxUnsetOptions()](#mlxunsetoptions) + - [mlxSetReservedWords()](#mlxsetreservedwords) + - [mlxNoteError()](#mlxnoteerror) + - [mlxNotePosition()](#mlxnoteposition) + - [XIII Driver Testing](#xiii-driver-testing) + - [Object opening, closing, creation, and deletion](#aobject-opening-closing-creation-and-deletion) + - [Object attribute enumeration, getting, and setting.](#bobject-attribute-enumeration-getting-and-setting) + - [Object querying (for subobjects)](#cobject-querying-for-subobjects) ## I Introduction -An objectsystem driver's purpose is to provide access to a particular type of local or network data/resource. Specific information about the resource to be accessed (such as credentials for a database, queries for selecting data, the auth token for an API, etc.) is stored in a file that is openned by the relevant driver. For example, the query driver (defined in `objdrv_query.c`) opens `.qy` files, which store one or more ObjectSQL queries used to fetch data. +An objectsystem driver's purpose is to provide access to a particular type of local or network data/resource. Specific information about the resource to be accessed (such as credentials for a database, queries for selecting data, the auth token for an API, etc.) is stored in a file that is opened by the relevant driver. For example, the query driver (defined in `objdrv_query.c`) opens `.qy` files, which store one or more ObjectSQL queries used to fetch data. + +When the object system starts up, each driver registers one or more type names that it supports (e.g. `"system/query"` for the query driver). When a file is opened, the object system uses the file's type name to select which driver to use. It finds this type name with one of two strategies. If the file has an extension (e.g. `example.qy`), that extension can be mapped to a type name using `types.cfg` (e.g. `.qy` maps to `"system/query"`). Althernatively, the file may reside in a directory containing a `.type` file which explicitly specifies the type name for all files in that directory without recognizable extensions. -When the object system starts up, each driver registers one or more type names that it supports (e.g. `"system/query"` for the query driver). When a file is openned, the object system uses the file's type name to select which driver to use. It finds this type name with one of two strategies. If the file has an extension (e.g. `example.qy`), that extension can be mapped to a type name using `types.cfg` (e.g. `.qy` maps to `"system/query"`). Althernatively, the file may reside in a directory containing a `.type` file which explicitly specifies the type name for all files in that directory without recognizable extensions. +Once a file is opened, the driver should organize provided data into a tree-structured hierarchy, which becomes part of the path used by Centrallix's ObjectSystem. For example, when opening `example.qy` in the ObjectSystem, the driver makes `/rows` and `/columns` available, allowing for paths such as `/apps/data/example.qy/rows`. The root of a driver's tree (`example.qy`) is called the driver's "node" object, and most paths traverse the node objects of multiple drivers. The root of the entire tree is a special driver called the root node which is used to begin traversal. Within its tree, a driver author is free to define any manner of hierarchical structures for representing available data. However, the structure should fit the basic ObjectSystem model of a hierarchy of objects, each having attributes, and optionally some methods and/or content. -Once a file is openned, the driver should organize provided data into a tree-structured hierarchy, which becomes part of the path used by Centrallix's ObjectSystem. For example, when opening `example.qy` in the ObjectSystem, the driver makes `/rows` and `/columns` available, allowing for paths such as `/apps/data/example.qy/rows`. The root of a driver's tree (`example.qy`) is called the driver's "node" object, and most paths traverse the root nodes of multiple drivers. A driver author is free to define any manner of tree structures for representing data available within their driver. However, the structure should fit the basic ObjectSystem model of a hierarchy of objects, each having attributes, and optionally some methods and/or content. +A driver can be opened multiple times, leading one driver to have multiple "node" objects, also called instances. Typically, each "node" object relates to a particular instance of a resource. For example, say you are designing a driver to access MySQL databases. You could design the driver file to describe a MySQL instance. Thus, the node object for this driver could have children for each database in that instance (e.g. `Kardia_DB`, `mysql`, and even the system databases used by MySQL to manage the database internals). Another design would be for each driver file to describe one MySQL database. Thus, you could make a `Kardia_DB` file to access that database, and the children of that node object would be each table in the database. A third design option would be for each driver file to describe a MySQL table. Thus, you make a `p_partner` file to access members of the partner table, a `p_contact_info` file to access contact info for parterners, etc. with each node object having children for the rows in the table. This last option would require the developer to create a _lot_ of files (and would probably also make joins hard to implement), so in this case, it's probably not the best. Ultimately, though, these design choices are up to the driver author. -A driver can be openned multiple times, leading one driver to have multiple "node" objects, also called instances. Typically, each "node" object relates to a particular instance of a network resource. For example, an instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. +an instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. The structure of the subtree beneath the node object is entirely up to the drivers' author to determine; the OSML does not impose any structural restrictions on such subtrees. Each object within this structure (e.g. `/example.qy`) can have three types of readable data: - Child objects (e.g. `/rows`) which can have their own data. @@ -191,27 +238,34 @@ Thus, parent objects with child objects behave similarly to a directory, althoug Below is an example of the Sybase driver's node object and its subtrees of child objects (defined in `objdrv_sybase.c`): ```sh -OMSS_DB (type = "application/sybase") +Kardia_DB (type = "application/mysql") | - +----- JNetHelp (type = "system/table") + +----- p_partner (type = "system/table") | | | +----- columns (type = "system/table-columns") | | | - | | +----- document_id (type = "system/column") + | | +----- p_partner_key (type = "system/column") | | | - | | +----- parent_id (type = "system/column") + | | +----- p_given_name (type = "system/column") | | | - | | +----- title (type = "system/column") + | | +----- p_surname (type = "system/column") | | | - | | +----- content (type = "system/column") + | | ... | | | +----- rows (type = "system/table-rows") + | | | + | | +----- 1 (type = "system/row") + | | | + | | +----- 2 (type = "system/row") + | | | + | | ... | | - | +----- 1 (type = "system/row") - | | - | +----- 2 (type = "system/row") + | ... | - +----- Partner (type = "system/table") + +----- p_contact_info (type = "system/table") + | | + | ... + ... ``` (... and so forth) @@ -229,6 +283,7 @@ OS Drivers support several primary areas of functionality: Using the example above, we can query from the database using a statement like `select :title from /OMSS_DB/JNetHelp/rows`, which will open a sybase driver instance, then open a query and repeatedly fetch rows, getting the `title` attribute from each row. + ## II Interface This section describes the standard interface between the OSML and the ObjectSystem driver itself. Every driver should implement certain required functions. (**Note**: Many drivers "implement" some required functions to simply fail with a not implemented or not supported error. For example, most database drivers "implement" `Read()` and `Write()` this way because database content should be queried, not read). Various optional functions are also available, which a driver is not required to implement. @@ -243,16 +298,16 @@ This section describes the standard interface between the OSML and the ObjectSys The driver should implement an `Initialize()` function, as well as the following (* indicates required functions): | Function Name | Description | --------------------------------------------------------- | ------------ -| [Open](#function-open)* | Opens a new driver instance object on a given root node. -| [OpenChild](#function-openchild) | ??? +| [Open](#function-open)* | Opens a new driver instance object on a given node object. +| [OpenChild](#function-openchild) | Opens a single child object of the provided object by name. | [Close](#function-close)* | Close an open object created by either `Open()` or `QueryFetch()`. -| [Create](#function-create) | Create a new driver root node object. -| [Delete](#function-delete) | Delete an existing driver root node object. -| [DeleteObj](#function-deleteobj)* | ??? +| [Create](#function-create) | Create a new driver node object. (Not currently used because the OSML calls the driver Open with the `O_WRONLY \| O_CREAT \| O_EXCL` options instead. See [Open()](#function-open) below for more info.) +| [Delete](#function-delete) | Used for general object deletion. Drivers can implement `DeleteObj()` instead. +| [DeleteObj](#function-deleteobj)* | Replacement for `Delete()` which operates on an already-open object. | [OpenQuery](#function-openquery)** | Start a new query for child objects of a given object. | [QueryDelete](#function-querydelete) | Delete specific objects from a query's result set. | [QueryFetch](#function-queryfetch)** | Open the next child object in the query's result set. -| [QueryCreate](#function-querycreate) | ??? +| [QueryCreate](#function-querycreate) | Currently just a stub function that is not fully implemented. | [QueryClose](#function-queryclose)** | Close an open query. | [Read](#function-read)* | Read content from the object. | [Write](#function-write)* | Write content to the object. @@ -260,7 +315,7 @@ The driver should implement an `Initialize()` function, as well as the following | [GetAttrValue](#function-getattrvalue)* | Get the value of a given object's attribute. | [GetFirstAttr](#function-getfirstattr--getnextattr)* | Get the name of the object's first attribute. | [GetNextAttr](#function-getfirstattr--getnextattr)* | Get the name of the object's next attribute. -| [SetAttrValue](#function-setattrvalue) | Set the value of an object's attribute. +| [SetAttrValue](#function-setattrvalue)* | Set the value of an object's attribute. | [AddAttr](#function-addattr) | Add a new attribute to an object. | [OpenAttr](#function-openattr) | Open an attribute as if it were an object with content. | [GetFirstMethod](#function-getfirstmethod--getnextmethod) | Get the name of an object's first method. @@ -268,35 +323,37 @@ The driver should implement an `Initialize()` function, as well as the following | [ExecuteMethod](#function-executemethod) | Execute a method with a given name and optional parameter string. | [PresentationHints](#function-presentationhints) | Get info about an object's attributes. | [Info](#function-info)* | Get info about an object instance. -| [Commit](#function-commit) | Commit changes made to an object. -| [GetQueryCoverageMask](#function-getquerycoveragemask) | ??? -| [GetQueryIdentityPath](#function-getqueryidentitypath) | ??? +| [Commit](#function-commit) | Commit changes made to an object, ensuring that all modifications in the current transaction are completed and the transaction is closed before returning. +| [GetQueryCoverageMask](#function-getquerycoveragemask) | Should be left `NULL` outside the MultiQuery module. +| [GetQueryIdentityPath](#function-getqueryidentitypath) | Should be left `NULL` outside the MultiQuery module. _*Function is always required._ -_**Function is required to support queries._ +_**Function is always required, but can always return NULL if queries are not supported._ --- -### Abbreviative Prefix +### Abbreviation Prefix Each OS Driver will have an abbreviation prefix, such as `qy` for the query driver or `sydb` for the sybase database driver. This prefix should be prepended to the start of every public function name within the OS driver for consistency and scope management (e.g. `qyInitialize()`, `sydbQueryFetch()`, etc.). Normally, a driver's abbreviation prefix is two to four characters, all lowercase and may be the same as a file extension the driver supports. However, this is not an absolute requirement (see the cluster driver in `objdrv_cluster.c` which supports `.cluster` files using an abbreviation prefix of `cluster`). -This document uses `xxx` to refer to an unspecified abbreviative prefix. +This document uses `xxx` to refer to an unspecified abbreviation prefix. + +- 📖 **Note**: Once an abbreviation prefix has been selected, the driver author should add it to the [Prefixes.md](Prefixes.md) file. + ---- ### Internal Functions It is highly likely that driver authors will find shared functionality in the following functions, or wish to abstract out functionality from any of them for a variety of reasons. When creating additional internal functions in this way, they should be named using the convention of `xxx_internal_FunctionName()`, or possibly `xxxi_FunctionName()` for short. --- -### Function: Initialize +### Function: Initialize() ```c /*** @returns 0 if successful, or *** -1 if an error occurred. ***/ int xxxInitialize(void) ``` -- ⚠️ **Warning**: Currently, the success/failure of this function is ignored by the caller. -- 📖 **Note**: Unlike other functions defined in the driver, each driver author must manually add this call to the start up code, found in the `cxDriverInit()` function in `centrallix.c`. +- ⚠️ **Warning**: For compiled drivers, the success/failure of this function is ignored by the caller. However, for drivers loaded as modules, the return value is checked in order to determine whether to keep the module loaded. In either case, `mssError()` should be called for any failure (other than memory allocation failures). +- 📖 **Note**: Unlike other functions defined in the driver, each driver author must manually add this call to the start up code, found in the `cxDriverInit()` function in `centrallix.c`. The initialization function is called when the Centrallix starts up, and should register the driver with the OSML and initialize necessary global variables. It is recommended to place global variables in a single global 'struct' that is named with the driver's prefix in all uppercase. Global variables should **NOT** be accessed from outside the driver. Instead, the driver should define functions to access them, allowing it to abstract details away from other drivers. @@ -311,7 +368,7 @@ memset(drv, 0, sizeof(ObjDriver)); To initialize this struct, the driver must: - Provide a name (in `drv->Name`). -- Provide an array of supported root node types (in `drv->RootContentTypes`). +- Provide an array of supported root types (in `drv->RootContentTypes`). - Provide capability flags (in `drv->Capabilities`). - Provide function pointers to implemented functions (see [II Interface](#ii-interface) for a list). @@ -324,7 +381,7 @@ if (strcpy(drv->Name, "SYBD - Sybase Database Driver") == NULL) goto error_handl ``` #### RootContentTypes -The `RootContentTypes` field is an XArray containing a list of strings, representing the type names that the driver can open. This should only include types the driver will handle as root nodes, not other objects created by the driver. Thus, the sybase driver would include `"application/sybase"`, but not `"system/table"`. +The `RootContentTypes` field is an XArray containing a list of strings, representing the type names that the driver can open. This should only include types the driver will open as node objects at the root of its tree, not other objects created by the driver within that tree. Thus, the sybase driver would include `"application/sybase"`, but not `"system/table"`. For example: ```c @@ -338,8 +395,8 @@ if (xaAddItem(&(drv->RootContentTypes), ""system/query"") < 0) goto error_handli #### Capabilities The capabilities field is a bitmask which can contain zero or more of the following flags: -- `OBJDRV_C_FULLQUERY`: Indicates that this objectsystem driver will intelligently process the query's expression tree specified in the OpenQuery call, and will only return objects that match that expression. If this flag is missing, the OSML will filter objects returned by QueryFetch so that the calling user does not get objects that do not match the query. Typically this is set by database server drivers. - - > **THE ABOVE IS OUT-OF-DATE** (May 16th, 2022): A driver can now determine whether to handle the Where and OrderBy on a per-query basis, by setting values in the ObjQuery structure used when opening a new query. This allows a because a driver to handle Where and OrderBy for some object listings but not others. +- `OBJDRV_C_FULLQUERY`: Indicates that this objectsystem driver will intelligently process the query's expression tree specified in the `OpenQuery()` call, and will only return objects that match that expression. If this flag is missing, the OSML will filter objects returned by `QueryFetch()` so that the calling user does not get objects that do not match the query. Typically this is set by database server drivers. + - > **THE ABOVE IS OUT-OF-DATE** (May 16th, 2022): A driver can now determine whether to handle the `Where` and `OrderBy` on a per-query basis, by setting values in the ObjQuery structure used when opening a new query. This allows a driver to handle `Where` and `OrderBy` selectively for some object listings but not others. - `OBJDRV_C_TRANS`: Indicates that this objectsystem driver requires transaction management by the OSML's transaction layer (the OXT layer). OS drivers that require this normally are those that for some reason cannot complete operations in independence from one another. For example, with a database driver, the creation of a new row object and the setting of its attributes must be done as one operation, although the operation requires several calls from the end user's process. The OXT allows for the grouping of objectsystem calls so that the os driver does not have to complete them independently, but instead can wait until several calls have been made before actually completing the operation. @@ -360,64 +417,63 @@ void* xxxOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, p The `Open()` function opens a given file to create a new driver instance. This procedure normally includes the following steps: 1. Access or create the node object, depending on specified flags and whether or not it already exists. -2. Parse additional contents of the path after the root node. +2. Parse additional contents of the path after the driver node object. 3. Allocate a structure that will represent the open object, including a pointer to the node object. 4. Perform other opening operations (such as reading database table information, etc., when a db table's row is being accessed). 5. Return a pointer to the node instance as a void pointer. This pointer will be passed as `void* inf_v` to the driver in subsequent calls involving this object (except the Query functions, discussed below). -- 📖 **Note - Transactions**: If the os driver specified the `OBJDRV_C_TRANS` capability, it must respect the current state of the user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). This is handled using the transaction tree parameter (`oxt : pObjTrxTree*`). The transaction later is discussed in depth in the ??? section. - - +- 📖 **Note - Transactions**: If the os driver specified the `OBJDRV_C_TRANS` capability, it must respect the current state of the user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). This is handled using the transaction tree parameter (`oxt : pObjTrxTree*`). #### Accessing the Node Object If `O_CREAT` and `O_EXCL` are both specified in `parent->Mode`, the driver should **only** create a new file and fail if the file already exists (refusing to open and read it). Otherwise, the driver should read an existing file, or create one if it does not exist and `O_CREAT` is specified, failing if no file can be read or created. #### Parsing Path Contents -The task of parsing the provided path into the subtree beneath its root node is one of the more complex operations for a driver. For example, the path to a driver's root node might be `/datasources/OMSS_DB` and the user opens an object called `/datasources/OMSS_DB/JNetHelp/rows/1`. In this case, the OS driver must parse the meaning of the subtree path `JNetHelp/rows/1`, storing the data targetted by the user into the driver instance to allow later method calls to access the correct data. +The task of parsing the provided path into the subtree beneath its node object is one of the more complex operations for a driver. For example, the path to a driver's node object might be `/datasources/Kardia_DB` and the user opens an object called `/datasources/Kardia_DB/p_partner/rows/1`. In this case, the OS driver must parse the meaning of the subtree path `p_partner/rows/1`, storing the data targetted by the user into the driver instance to allow later method calls to access the correct data. #### Parameters The `Open()` routine is called with five parameters: -- `obj : pObject`: A pointer to the Object structure maintained by the OSML. This structure includes some useful fields: +- `parent : pObject`: A pointer to the Object structure maintained by the OSML. This structure includes some useful fields: - - `obj->Mode : int`: A bitmask of the O_* flags, which include: `O_RDONLY` (read only), `O_WRONLY` (write only), `O_RDWR` (read/write), `O_CREAT` (create), `O_TRUNC` (truncate), and `O_EXCL` (exclusive, see above). + - `parent->Mode : int`: A bitmask of the OBJ_O_* flags, which include: `OBJ_O_RDONLY` (read only), `OBJ_O_WRONLY` (write only), `OBJ_O_RDWR` (read/write), `OBJ_O_CREAT` (create), `OBJ_O_TRUNC` (truncate), and `OBJ_O_EXCL` (exclusive, see above). - - `obj->Pathname : pPathname`: A pointer to a Pathname struct (defined in `include/obj.h`) which contains the complete parsed pathname for the object. This provides a buffer for the pathname as well as an array of pointers to the pathname's components. The function `obj_internal_PathPart()` can be used to obtain at will any component or series of components of the pathname. + - `parent->Pathname : pPathname`: A pointer to a Pathname struct (defined in `include/obj.h`) which contains the complete parsed pathname for the object. This provides a buffer for the pathname as well as an array of pointers to the pathname's components. The function `obj_internal_PathPart()` can be used to obtain at will any component or series of components of the pathname. - - `obj->Pathname->OpenCtl : pStruct[]`: Parameters for the open() operation, as defined by the driver author. These are specified in the path in a similar way to URLs (`example.qy?param1=value¶m2=other_value`). Drivers typically only use `obj->Pathname->OpenCtl[obj->SubPtr]` (see SubPtr below) to retrieve their own parameters, ignoring parameters passed to other drivers in the path. + - `parent->Pathname->OpenCtl : pStruct[]`: Parameters for the open() operation, as defined by the driver author. These are specified in the path in a similar way to URLs (`example.qy?param1=value¶m2=other_value`). Drivers typically only use `parent->Pathname->OpenCtl[parent->SubPtr]` (see SubPtr below) to retrieve their own parameters, ignoring parameters passed to other drivers in the path. - - `obj->SubPtr : short`: The number of components in the path that are a part of the path to the root node object, including the `.` for the top level directory. For example, in the above path of `/data/file.csv`, the path would be internally represented as `./ data/ file.csv`, so SubPtr is 3. + - `parent->SubPtr : short`: The number of components in the path that are a part of the path to the driver's node object, including the `.` for the top level directory and the driver's node object. For example, in the above path of `/data/file.csv`, the path would be internally represented as `./ data/ file.csv`, so SubPtr is 3. + + - For example, use `obj_internal_PathPart(parent->Pathname, parent->SubPtr - 1, 1)` to get the name of the file being openned, and use `obj_internal_PathPart(parent->Pathname, 0, parent->SubPtr)` to get the path. - - `obj->SubCnt : short`: _The driver should set this value_ to show the number of components it controls. This includes the root node object, so `SubCnt` will always be at least 1. For example, when opening `/data/file.csv/rows/1`, the CSV driver will read the `SubPtr` of 3 (see above), representing `./ data/ file.csv`. It will then set a `SubCnt` of 3, representing that it will control `file.csv /rows /1`. (The driver only sets `SubCnt`, `SubPtr` is provided.) + - `parent->SubCnt : short`: _The driver should set this value_ to show the number of components it controls. This includes the driver's node object, so `SubCnt` will always be at least 1. For example, when opening `/data/file.csv/rows/1`, the CSV driver will read the `SubPtr` of 3 (see above), representing `./ data/ file.csv`. It will then set a `SubCnt` of 3, representing that it controls `file.csv /rows /1`. (The driver only sets `SubCnt`; `SubPtr` is provided.) - - `obj->Prev : pObject`: The underlying object as opened by the next-lower-level driver. The file can be accessed and parsed by calling functions and passing this pointer to them (such as the st_parse functions, see below). **DO NOT attempt to open the file directly with a call like `fopen()`,** as this would require hard coding the path to the root directory of the object system, which *will* break if the code runs on another machine. + - `parent->Prev : pObject`: The underlying object as opened by the next-lower-level driver. The file can be accessed and parsed by calling functions and passing this pointer to them (such as the st_parse functions, see below). **DO NOT attempt to open the file directly with a call like `fopen()`,** as this would require hard coding the path to the root directory of the object system, which *will* break if the code runs on another machine. - - `obj->Prev->Flags : short`: Contains some useful flags about the underlying object, such as: - - `OBJ_F_CREATED`: The underlying object was just created by this open() operation. In that case, this driver is expected to create the node with `snNewNode()` (see later in this document) as long as `obj->Mode` contains `O_CREAT`. - + - `parent->Prev->Flags : short`: Contains some useful flags about the underlying object, such as: + - `OBJ_F_CREATED`: The underlying object was just created by this open() operation. In that case, this driver is expected to create the node with `snNewNode()` (see later in this document) as long as `parent->Mode` contains `O_CREAT`. - `mask : int`: The permission mask to be given to the object, if it is being created. Typically, this will only apply to files and directories, so most drivers can ignore it. The values are the same as the UNIX [octal digit permissions](https://en.wikipedia.org/wiki/Chmod#:~:text=Octal%20digit%20permission) used for the `chmod()` command. -- `sys_type : pContentType`: Indicates the content type of the node object as determined by the OSML. The ContentType structure is defined in `include/obj.h`. `sys_type->Name` lists the name of the content type (e.g. `"system/query"` for the query driver). - +- `sys_type : pContentType`: Indicates the content type of the node object as determined by the OSML. The ContentType structure is defined in `include/obj.h`. `sys_type->Name` lists the name of the content type (e.g. `"system/query"` for the query driver). This is also the type used to select which driver should open the node object, so it will be one of the types registered in the `Initialize()` function. - `usr_type : char*`: The object type requested by the user. This is normally used when creating a new object, though some drivers also use it when opening an existing object. For example, the reporting driver generates HTML report text or plaintext reports if `usr_type` is `"text/html"` or `"text/plain"` (respectively). - `oxt : pObjTrxTree*`: The transaction tree, used when the driver specifies the `OBJDRV_C_TRANS` capability. More on this field later. Non-transaction-aware drivers can safely ignore this field. - - 📖 **Note**: Yes, this param *is* a pointer to a pointer. Essentially, a pointer passed by reference. + + - 📖 **Note**: Yes, this param *is* a pointer to a pointer. Essentially, a pointer passed by reference. This allows the driver to create a new transaction tree even if none is in progress. The `Open()` routine should return a pointer to an internal driver structure on success, or `NULL` on failure. It is normal to allocate one such structure per `Open()` call, and for one of the structure fields to point to shared data describing the node object. Accessing the node object is described later in this document. While driver instance structures may vary, some fields are common in most drivers (`inf` is the pointer to the structure here): -| Field | Type | Description -| ---------- | --------- | ------------ -| inf->Obj | pObject | A copy of the `obj` pointer passed to `Open()`. -| inf->Mask | int | The `mask` argument passed to `Open()`. -| inf->Node | pSnNode | A pointer to the node object. This can come from `snNewNode()` or `snReadNode()` (for structure files), or other node struct information. +| Field | Type | Description | +|-----------|---------|-------------------------------------------------| +| inf->Obj | pObject | A copy of the `obj` pointer passed to `Open()`. | +| inf->Mask | int | The `mask` argument passed to `Open()`. | +| inf->Node | pSnNode | A pointer to the node object. | +The driver's node pointer typically comes from `snNewNode()` or `snReadNode()` (for structure files), but it can also be other node struct information. --- ### Function: OpenChild() @@ -425,7 +481,25 @@ While driver instance structures may vary, some fields are common in most driver ```c void* xxxOpenChild(void* inf_v, pObject obj, char* child_name, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); ``` -**No documentation provided.** +Opens a single child object of the provided object by name. Conceptually, this is similar to querying the object for all children where the name attribute equals the passed `child_name` parameter and fetching only the first result. This function is used to open children of a driver that do not map well into the driver's node object tree. For example, the query file driver uses this function to allow the caller to open a temporary collection declared in that query file. + +The `OpenChild()` function is called with two parameters: + +| Param | Type | Description | +|------------|--------------|---------------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| obj | pObject | An object? | +| child_name | char* | The value for the name attribute of the child object to be openned. | +| mask | int | The permission mask to be given to the object (if created).* | +| sys_type | pContentType | Indicates the content type of the node object as determined by the OSML.* | +| usr_type | char* | The object type requested by the user.* | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | + + + +*See [`Open()`](#function-open) above for more info. + +The `OpenChild()` function should a pointer to the node object for the newly openned child on success or `NULL` on failure. --- ### Function: Close() @@ -440,14 +514,14 @@ The close function closes a driver instance, freeing all allocated data and rele - 📖 **Note**: Information may be left unfreed if it is stored in a cache for later use. -The `Close()` routine is called with two parameters: +The `Close()` function is called with two parameters: -| Param | Type | Description -| ------ | ------------ | ------------ -| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). -| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. +| Param | Type | Description | +|-------|--------------|-----------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | -The Close routine should return 0 on success or -1 on failure. +The `Close()` function should return 0 on success or -1 on failure. ### Function: Create() @@ -465,12 +539,12 @@ int clusterDelete(pObject obj, pObjTrxTree* oxt); ``` The `Delete()` function is used to delete an object, which often means removing a file from the file system. The Delete routine is passed the following parameters: -| Param | Type | Description -| ------ | ------------- | ------------ -| obj | pObject | The Object structure pointer, used in the same way as in Open and Delete. -| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. +| Param | Type | Description | +|-------|--------------|---------------------------------------------------------------------------| +| obj | pObject | The Object structure pointer, used in the same way as in Open and Delete. | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | -Delete should return 0 on success and -1 on failure. +`Delete()` should return 0 on success and -1 on failure. ### Function: DeleteObj() @@ -490,19 +564,21 @@ The `Read()` function reads content from objects that have content, similar to r The parameters passed are intentionally similar to the `fdRead()` function in `mtask.c`: -| Parameter | Type | Description -| --------- | ------------- | ------------ -| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). -| buffer | char* | The buffer where read data should be stored. -| max_cnt | int | The maximum number of bytes to read into the buffer. -| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 5th argument (`arg`). -| arg | int | Extra argument, currently only used to specify the optional seek offset. -| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. +| Parameter | Type | Description | +|-----------|--------------|------------------------------------------------------------------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| buffer | char* | The buffer where read data should be stored. | +| max_cnt | int | The maximum number of bytes to read into the buffer. | +| offset | int | An optional seek offset. | +| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 5th argument (`arg`). | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | - 📖 **Note**: Not all objects can be seekable and some of the objects handled by the driver may have limited seek functionality, even if others do not. Each of these routines should return -1 on failure and return the number of bytes read/written on success. At end of file or on device hangup, 0 should be returned once, and then subsequent calls should return -1. +- 📖 **Note**: There is no separate seek command to help mitigate [Time-of-check to time-of-use attacks](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use). To seek without reading data, specify a buffer size of zero. + ### Function: Write() ```c @@ -519,11 +595,12 @@ void* xxxOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); The `OpenQuery()` function opens a new query instance struct for fetching query results from a specific driver instance. Queries are often used to enumerate an object's child objects, although this is not a requirement. Queries may include specific criteria, and the driver may decide to intelligently handle them (either manually or, more often, by passing them on to a lower level driver or database) or simply to enumerating all results with its query functions. In the latter case, the OSML layer will filter results and only return objects that match the criteria to the user. `OpenQuery()` is passed three parameters: -| Parameter | Type | Description -| --------- | ------------- | ------------ -| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). -| query | pObjQuery | A query structure created by the object system. -| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +| Parameter | Type | Description | +|-----------|--------------|-----------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| query | pObjQuery | A query structure created by the object system. | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | The `query : pObjQuery` parameter contains several useful fields: | Parameter | Type | Description @@ -533,7 +610,7 @@ The `query : pObjQuery` parameter contains several useful fields: | query->SortBy[] | void*[] (pExpression[]) | An array of expressions giving the various components of the sorting criteria. | query->Flags | int | The driver should set and/or clear the `OBJ_QY_F_FULLQUERY` and `OBJ_QY_F_FULLSORT` flags, if needed. -The `OBJ_QY_F_FULLQUERY` flag indicates that the driver will handle the full WHERE clause specified in `query->Tree`. +The `OBJ_QY_F_FULLQUERY` flag indicates that the driver will handle the full `where` clause specified in `query->Tree`. Even if this flag is not specified, the driver is still free to use the provided `where` clause to pre-filter data, which improves performance when the Object System does its final filtering. However, setting this flag disables the Object System filtering because it promises that the driver will _always_ handle _all_ filtering for _every_ valid queries. The `OBJ_QY_F_FULLSORT` flag indicates that the driver will handle all sorting for the data specified in `query->SortBy[]`. @@ -547,7 +624,6 @@ The `OpenQuery()` function returns a `void*` for the query instance struct, whic ```c int xxxQueryDelete(void* qy_v, pObjTrxTree* oxt); ``` - Deletes results in the query result set, optionally matching a certain criteria. `QueryDelete()` is passed two parameters: | Parameter | Type | Description @@ -581,30 +657,29 @@ The driver should add an element to the `obj->Pathname` structure to indicate th - `qy->Parent->Obj->Pathname : pPathname` points to the affected Pathname struct. ```c - int count; - pObject obj; - char* new_name; - pMyDriversQueryInf qy; +int count; +pObject obj; +char* new_name; +pMyDriversQueryInf qy; - /** Build the new filename. **/ - count = snprintf(obj->Pathname->Pathbuf, 256, "%s/%s", qy->Parent->Obj->Pathname->Pathbuf, new_name); - if (count < 0 || 256 <= count) return NULL; - obj->Pathname->Elements[obj->Pathname->nElements++] = strrchr(obj->Pathname->Pathbuf, '/') + 1; +/** Build the new filename. **/ +count = snprintf(obj->Pathname->Pathbuf, 256, "%s/%s", qy->Parent->Obj->Pathname->Pathbuf, new_name); +if (count < 0 || 256 <= count) goto error_handling; +obj->Pathname->Elements[obj->Pathname->nElements++] = strrchr(obj->Pathname->Pathbuf, '/') + 1; ``` ### Function: QueryCreate() ```c void* xxxQueryCreate(void* qy_v, pObject new_obj, char* name, int mode, int permission_mask, pObjTrxTree *oxt); ``` - -**No documentation provided.** +The `QueryCreate()` function is just a stub function that is not fully implemented yet. Simply not providing it (aka. setting the location in the driver initialization struct to `NULL`) is fine. ### Function: QueryClose() ```c int xxxQueryClose(void* qy_v, pObjTrxTree* oxt); ``` -The close function closes a query instance, freeing all allocated data and releasing all shared memory such as open connections, files, or other driver instances. This function operates very similarly to `Close()`, documented in detail above. The query should be closed, whether or not `QueryFetch()` has been called enough times to enumerate all of the query results. +The `QueryClose()` function closes a query instance, freeing all allocated data and releasing all shared memory such as open connections, files, or other driver instances. This function operates very similarly to `Close()`, documented in detail above. The query should be closed, whether or not `QueryFetch()` has been called enough times to enumerate all of the query results. ### Object Attributes @@ -628,11 +703,11 @@ The following five attributes are required (all are of type `DATA_T_STRING`): | Attribute | Description | ------------ | ------------ -| name | The name of the object, just as it appears in any directory listing. The name of the object must always be unique for its directory. -| annotation | A short description of the object. While users may not assign annotations to all objects, each object should be able to have an annotation. For example, in the Sybase driver, annotations for rows are created by assigning an 'expression' to the table in question, such as `first_name + last_name` for a people table. -| content_type | The type of the object's content, given as a MIME-type. Specify `"system/void"` if the object does not have content. +| name | The name of the object, just as it appears in any directory listing. The name of the object must always be unique for its level in the tree (e.g. a unique file name in a directory, the primary key of a database row, etc.). +| annotation | A short description of the object. While users may not assign annotations to all objects, each object should be able to have an annotation. For example, in the Sybase driver, annotations for rows are created by assigning an 'expression' to the table in question, such as `first_name + last_name` for a people table. This attribute should _never_ be null, however, it can be an empty string (`""`) if the driver has no meaningful way to provide an annotation. +| content_type | The type of the object's content, given as a MIME-type. Specify `"system/void"` if the object does not have content. | inner_type | An alias for 'content_type'. Both should be supported. -| outer_type | This is the type of the object itself (the container). Specify `"system/row"` for objects that can be queried. +| outer_type | This is the type of the object itself (the container). The `last_modification : DATA_T_DATETIME` attribute is a sixth, optional attribute that may be useful in some situations. This attribute should indicate the last time that the object's content was modified or updated. @@ -669,22 +744,35 @@ The `GetAttrValue()` function takes four parameters: | --------- | ------------- | ------------ | inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | attr_name | char* | The name of the attribute to be queried. +| datatype | int | The expected datatype for the requested value. | val | pObjData | A pointer to a location where the value of the attribute should be stored. | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -The value pointer should be handled in different ways, depending on the type: -- For `DATA_T_INTEGER` types, it is assumed to point to a 32-bit integer where the value should be written. -- For `DATA_T_STRING` types, it is assumed to point to an empty `char*` location where a pointer to a string should be written. -- For `DATA_T_DOUBLE` types, it is assumed to point to a double value where the double should be written. -- For `DATA_T_DATETIME` types, it is assumed to point to an empty `pDateTime` where a pointer to a date time struct (see `obj.h`) should be written. +The value pointer points to a union struct which can hold one of several types of data in the same memory location. Which type of data is expected depends on the value of the `datatype` parameter. +| Field | Datatype | Description +| ----------- | ------------------ | ----------- +| `Integer` | `DATA_T_INTEGER` | An int where the value should be written. +| `String` | `DATA_T_STRING` | A `char*` where a pointer to the string should be written. +| `Double` | `DATA_T_DOUBLE` | A double where the double should be written. +| `DateTime` | `DATA_T_DATETIME` | A `pDateTime` where a pointer to the `DateTime` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. +| `IntVec` | `DATA_T_INTVEC` | A `pIntVec` where a pointer to the `IntVec` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. +| `StringVec` | `DATA_T_STRINGVEC` | A `pStringVec` where a pointer to the `StringVec` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. +| `Money` | `DATA_T_MONEY` | A `pMoneyType` where a pointer to the `MoneyType` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. +| `Generic` | ? | A `void*` to somewhere where something should be written should be written (usually implementation dependant). -In this way, integer and double values are returned by value, and string or datetime values are returned by reference. Items returned by reference are guaranteed to be valid until either the object is closed, or another call to `GetAttrValue()` or `SetAttrValue()` call is made on the same driver (which ever happens first). +In this way, `int`s and `double`s can be returned by value while other types are returned by reference. Items returned by reference must be guaranteed to be valid until either the object is closed, or another `GetAttrValue()` or `SetAttrValue()` call is made on the same driver (which ever happens first). -This function should return -1 on a non-existent attribute, 0 on success, and 1 if the value is `NULL` or undefined / unset. +This function should return 0 on success, 1 if the value is `NULL` or undefined / unset, or -1 on a non-existent attribute or other error. -- 📖 **Note**: The caller of this function can use the POD(x) macro to typecast appropriate pointers to the pObjData pointer, passed to this function. The ObjData structure is a UNION type of structure, allowing easy manipulation of data of various types. See `datatypes.h` for more information. +- 📖 **Note**: The caller can use the `POD(x)` macro to typecast appropriate pointers to the `pObjData` pointer. For example: + ```c + char* name; + if (xxxGetAttrValue(obj, "name", DATA_T_STRING, POD(&name)) != 0) + goto error_handling; + printf("Object name: \"%s\"\n", name); + ``` -- 📖 **Note**: In legacy code, a typecasted void* was used instead of a pObjData pointer used today. This method was binary compatible the current solution because the pObjData is a pointer to a struct union. See `datatypes.h` for more information. +- 📖 **Note**: In legacy code, a typecasted `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix/include/datatypes.h) for more information). ### Function: SetAttrValue() @@ -781,22 +869,21 @@ The return value, `hints : ObjPresentationHints`, contains the following useful - `hints->MaxValue : void*`: An expression defining the maximum valid value. - `hints->EnumList : XArray`: If the attribute is a string enum, this XArray lists the valid string values. - `hints->EnumQuery : char*`: A query string which enumerates the valid values a string enum attribute. -- `hints->Format : char*`: presentation format - datetime or money +- `hints->Format : char*`: A presentation format for datetime or money types, such as `"dd MMM yyyy HH:mm"` or `"$0.00"`. See `obj_datatypes.c` (near line 100) for more information creating a presentation format. - `hints->AllowChars : char*`: An array of all valid characters for a string attribute, NULL to allow all characters. -- `hints->BadChars : char*`: An array of all invalid characters for a string attribute. +- `hints->BadChars : char*`: An array of all invalid characters for a string attribute. If a character appears in both `hints->BadChars` and `hints->AllowChars`, the character should be rejected. - `hints->Length : int`: The maximum length of data that can be included in a string attribute. - `hints->VisualLength : int`: The length that the attribute should be displayed if it is show to the user. - `hints->VisualLength2 : int`: The number of lines to use in a multi-line edit box for the attribute. -- `hints->BitmaskRO : unsigned int`: which bits, if any, in bitmask are read-only +- `hints->BitmaskRO : unsigned int`: If the value is an integer that represents a bit mask, _this_ bit mask shows which bits of that bitmask are read-only. - `hints->Style : int`: Style flags, documented below. - `hints->StyleMask : int`: A mask for which style flags were set and which were left unset / undefined. - `hints->GroupID : int`: Used to assign attributes to groups. Use -1 if the attribute is not in a group. - `hints->GroupName : char*`: The name of the group to which this attribute belongs, or NULL if it is ungrouped or if the group is named elsewhere. - `hints->OrderID : int`: Used to specify an attribute order. -- `hints->FriendlyName : char*`: Used to specify a "display name" for an attribute (e.g. `n_rows` might have a friendly name of `"Number of Rows"`). Should be `nmSysMalloc()`ed, often using `nmSysStrdup()`. +- `hints->FriendlyName : char*`: Used to specify a "display name" for an attribute (e.g. `n_rows` might have a friendly name of `"Number of Rows"`). Should be [`nmSysMalloc()`](#nmsysmalloc)ed, often using [`nmSysStrdup()`](#nmsysstrdup). - ⚠️ **Warning**: Behavior is undefined if: - - If a character is included in both `hints->AllowChars` and `hints->BadChars`. - The data is longer than length. The `hints->Style` field can be set with several useful flags. To specify that a flag is not set (e.g. to specify explicitly that a field does allow `NULL`s), set the coresponding bit in the `hints->StyleMask` field while leaving the the bit in the `hints->Style` field set to 0. @@ -819,7 +906,7 @@ The following macros are provided for setting style flags: - `OBJ_PH_STYLE_SEPWINDOW`: Prefer separate windows for grouped fields. - `OBJ_PH_STYLE_ALWAYSDEF`: Always reset the default value when this attribute is modified. - `OBJ_PH_STYLE_CREATEONLY`: This attribute is writeable only when created, after that it is read only. -- `OBJ_PH_STYLE_MULTISEL`: Multiple select +- `OBJ_PH_STYLE_MULTISEL`: This enum attribute can accept more than one value from the list of valid values. Think of using checkboxes instead of radio buttons (although the flag does requirement this UI decision). - `OBJ_PH_STYLE_KEY`: This attribute is a primary key. - `OBJ_PH_STYLE_APPLYCHG`: Presentation hints should be applied on DataChange instead of on DataModify. @@ -845,13 +932,9 @@ The `pObjectInfo` struct has two fields: `Flags` and `nSubobjects`. This functi - `OBJ_INFO_F_CAN_SEEK_REWIND`: Seeking is only supported with an offset of `0`. - `OBJ_INFO_F_CANT_SEEK`: Seeking is not supported at all. - `OBJ_INFO_F_CAN_ADD_ATTR` / `OBJ_INFO_F_CANT_ADD_ATTR`: Indicates that the object does or does not allow attributes to be added with the [AddAttr()](#function-addattr) function. -- `OBJ_INFO_F_SUPPORTS_INHERITANCE`: Indicates that the object supports inheritance through attributes such as `cx__inherit`. See ??? for more information about object inheritance. - - +- `OBJ_INFO_F_SUPPORTS_INHERITANCE`: Indicates that the object supports inheritance through attributes such as `cx__inherit`. - `OBJ_INFO_F_FORCED_LEAF`: Indicates that the object is forced to be a 'leaf' unless ls__type used. -- `OBJ_INFO_F_TEMPORARY`: Indicates that this is a temporary object without a vaoid pathname. - - +- `OBJ_INFO_F_TEMPORARY`: Indicates that this is a temporary object without a valid pathname. The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. @@ -860,21 +943,30 @@ The function returns 0 on success, and -1 to indicate an error, in which case `m ```c int xxxCommit(void* inf_v, pObjTrxTree *oxt); ``` -**No documentation provided.** +The `Commit()` function immediately completes the current transaction, ensuring that all writes are applied to the affected data before returning. For example, if the current transaction involves creating a database row, this call will ensure that the row is created and the transaction is closed before returning. This allows the caller to ensure that actions in a transaction have been completed without needing to close the object, which they may wish to continue using. + +The `Commit()` function takes two parameters: + +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. ### Function: GetQueryCoverageMask() ```c int xxxGetQueryCoverageMask(pObjQuery this); ``` -**No documentation provided.** +This function is only intended to be used by the MultiQuery module. Any other driver should not provide this function by setting the appropriate struct field to `NULL`. ### Function: GetQueryIdentityPath() ```c int xxxGetQueryIdentityPath(pObjQuery this, char* pathbuf, int maxlen); ``` -**No documentation provided.** +This function is only intended to be used by the MultiQuery module. Any other driver should not provide this function by setting the appropriate struct field to `NULL`. @@ -883,7 +975,7 @@ A driver will commonly configure itself by reading text content from its node ob - 📖 **Note**: The node object will **already be open** as an object in the ObjectSystem: The OSML does this for each driver. If a driver does not use the SN/ST modules, then it should read and write the node object directly with `objRead()` and `objWrite()`. A driver should **NEVER** `objClose()` the node object! The OSML handles that. -Although using the structure file format may be complex, it allows significant flexibility. Data is structured in hierarchies where each sub-object can have named attributes as well as sub-objects. Centrallix is filled with examples of this, including any `.qy`, `.app`, `.cmp`, or `.cluster` file. +Although using the structure file format may be complex, it allows significant flexibility, as well as greater consistency across drivers. The use of this shared syntax across different drivers makes learning to use a new driver far easier than it would be if they all used unique, custom syntax for specifying properties. In the structure file syntax, data is structured in hierarchies where each sub-object can have named attributes as well as sub-objects. Centrallix has many examples of this, including any `.qy`, `.app`, `.cmp`, or `.cluster` file. Structure files are accessed via the st_node (SN) and stparse (SP) modules. The st_node module loads and saves the structure file heirarchies as a whole. It also manages caching to reduce disk activity and eliminate repeated parsing of the same file. The stparse module provides access to the individual attributes and groups of attributes within a node structure file. @@ -899,7 +991,7 @@ To obtain node object data, the driver should first open the node object with th ```c pSnNode snReadNode(pObject obj); ``` -The `snReadNode()` function reads a Structure File from the `obj` parameter, which should be a previously openned object. In a driver's `Open()` function, this is `obj->Prev` (the node object as opened by the previous driver in the OSML's chain of drivers). +The `snReadNode()` function reads a Structure File from the `obj` parameter, which should be a previously opened object. In a driver's `Open()` function, this is `obj->Prev` (the node object as opened by the previous driver in the OSML's chain of drivers). **Usage:** ```c @@ -974,7 +1066,7 @@ int stStructType(pStructInf this); ``` The `stStructType()` function returns the struct type of the past `pStructInf` parameter, which is either `ST_T_ATTRIB` or `ST_T_SUBGROUP` (see above). -- ⚠️ **Warning**: The root node of type `ST_T_STRUCT` will return `ST_T_SUBGROUP` from this function. If you wish to avoid this, read `inf->Type` (see [stparse: Using Fields Directly](#stparse-using-fields-directly) for more info). It is unclear whether this behavior is a bug or a feature. I've decided to call it a feature! ;) +- ⚠️ **Warning**: The node object root of type `ST_T_STRUCT` will return `ST_T_SUBGROUP` from this function. In most cases, treating this node as ust another subgroup simplifies logic for the caller. However, if you wish to avoid this behavior, read `inf->Type` (see [stparse: Using Fields Directly](#stparse-using-fields-directly) for more info). ### stparse: stLookup() @@ -1034,13 +1126,13 @@ This function adds a node of type `ST_T_SUBGROUP` to either an `ST_T_SUBGROUP` o ```c int stAddValue(pStructInf inf, char* strval, int intval); ``` -This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If `strval` is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. If the string is non-static, and has a lifetime less than the `ST_T_ATTRIB` tree node, then the following procedure should be used, where `str` is the string pointer to the string: +This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If `strval` is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. If the string is non-static, and has a lifetime less than the `ST_T_ATTRIB` tree node, then the following procedure should be used to allocate a new string which will have the correct lifetime: (In this example, `str` is the string pointer to the string.) ```c pStructInf attr_inf = stAddAttr(my_parent_inf, "my_attr"); if (attr_inf == NULL) goto error_handling; -char* new_str = (char*)malloc(strlen(str) + 1lu); +char* new_str = (char*)nmSysMalloc(strlen(str) + 1lu); if (new_str == NULL) goto error_handling; strcpy(new_str, str); stAddValue(attr_inf, new_str, 0); @@ -1049,6 +1141,7 @@ attr_inf->StrAlloc[0] = 1; With this method (making a copy of the string and then setting the StrAlloc value for that string), the string is automatically freed when the StructInf tree node is freed by the stparse module. + ### stparse: stFreeInf() ```c @@ -1082,18 +1175,17 @@ for (unsigned int i = 0u; i < inf->nSubInf; i++) ## IV Memory Management in Centrallix - -Centrallix has its own memory management wrapper that caches deallocated blocks of memory by size to allow for faster reuse. This wrapper also detects double-freeing of blocks (sometimes), making debugging of memory problems just a little bit easier. +Centrallix has its own memory management wrapper that caches deallocated blocks of memory by size for faster reuse. This wrapper also detects double-freeing of blocks (sometimes), making debugging of memory problems just a little bit easier. -In addition, the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. `malloc()`, and on how many blocks of each size/type are `malloc()`ed and cached. This information can be helpful for tracking down memory leaks. Empirical testing has shown an increase of performance of around 50% or more in programs with the newmalloc module in use. +In addition, the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. `malloc()`, and on how many blocks of each size/type are `malloc()`ed and cached. This information can be helpful for tracking down memory leaks. Empirical testing has shown an increase of performance of around 50% or more in programs that use newmalloc. -One caveat is that this memory manager does not provide `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or `nmSysMalloc()`, `nmSysFree()`, and `nmSysRealloc()` should be used for blocks of memory that might vary in size. +One caveat is that this memory manager does not provide `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or [`nmSysMalloc()`](#nmsysmalloc), [`nmSysFree()`](#nmsysfree), and [`nmSysRealloc()`](#nmsysrealloc) should be used for blocks of memory that might vary in size. -- 📖 **Note**: This memory manager is usually the wrong choice for blocks of memory of arbitrary sizes. It is intended for allocating structures quickly that are of a specific size. For example, allocated space for a struct that is always the same size. +- 📖 **Note**: This memory manager is usually the wrong choice for blocks of memory of arbitrary, inconsistent sizes. It is intended for allocating structures quickly that are of a specific size. For example, allocated space for a struct that is always the same size. - 🥱 **tl;dr**: Use `nmMalloc()` for structs, not for strings. -- ⚠️ **Warning**: Calling `free()` on a block obtained from `nmMalloc()` or calling `nmFree()` on a block obtained from `malloc()` might not crash the program immediately. Instead, it will result in either inefficient use of the memory manager, or a significant memory leak, respectively. These practices will also lead to incorrect results from the statistics and block count mechanisms. +- ⚠️ **Warning**: Do not mix and match, even though calling `free()` on a block obtained from `nmMalloc()` or calling `nmFree()` on a block obtained from `malloc()` might not crash the program immediately. However, it may result in either inefficient use of the memory manager, or a significant memory leak, respectively. These practices will also lead to incorrect results from the statistics and block count mechanisms. The following are the functions for the newmalloc module: @@ -1123,19 +1215,19 @@ Prints statistics about the memory manager, for debugging and optimizing. For example: ``` NewMalloc subsystem statistics: - nmMalloc: 0 calls, 0 hits (-nan%) - nmFree: 0 calls - bigblks: 0 too big, 0 largest size + nmMalloc: 20244967 calls, 19908369 hits (98.337%) + nmFree: 20233966 calls + bigblks: 49370 too big, 32768 largest size ``` - +- ⚠️ **Warning**: Centrallix-lib must be built with the configure option `--enable-debugging` for this function to work. Otherwise, all the stats will be zeros. ### nmRegister() ```c void nmRegister(int size, char* name); ``` -Registers an inteligent name with a block size. This allows the memory manager to be intelligent when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production code to work, but using it can make tracking down memory leaks easier. +Registers an inteligent name for block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production usecases, but using it can make tracking down memory leaks easier. This function is usually called in a module's `Initialize()` function on each of the structures the module uses internally. @@ -1148,7 +1240,7 @@ Prints a listing of block allocation counts, giving (by size): - The number of blocks allocated but not yet freed. - The number of blocks in the cache. - The total allocations for this block size. -- A list of names (from `nmRegister()`) for that block size. +- A list of names (from [`nmRegister()`](#nmregister)) for that block size. ### nmDeltas() @@ -1162,237 +1254,601 @@ Prints a listing of all blocks whose allocation count has changed, and by how mu ```c void* nmSysMalloc(int size); ``` -Allocates memory without using the block-caching algorithm. This is roughly equivalent to `malloc()`, but pointers returned by malloc and this function are not compatible with each other - i.e., you cannot `free()` something that was `nmSysMalloc()`'ed, nor can you `nmSysFree()` something that was `malloc()`'ed. +Allocates memory without using the block-caching algorithm. This is roughly equivalent to `malloc()`, but pointers returned by malloc and this function are not compatible - i.e., you cannot `free()` something that was [`nmSysMalloc()`](#nmsysmalloc)'ed, nor can you [`nmSysFree()`](#nmsysfree) something that was `malloc()`'ed. -- 📖 **Note**: This function is much better to use on variable-sized blocks of memory. `nmMalloc()` is better for fixed-size blocks, such as for data structures. +- 📖 **Note**: This function is much better to use on variable-sized blocks of memory. `nmMalloc()` is better for fixed-size blocks, such as for structs. ### nmSysRealloc() ```c void* nmSysRealloc(void* ptr, int newsize); ``` -Changes the size of an allocated block of memory that was obtained from `nmSysMalloc()`, `nmSysRealloc()`, or `nmSysStrdup()`. The new pointer may be different if the block has to be moved. This is the rough equivalent of `realloc()`. +Changes the size of an allocated block of memory that was obtained from [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). The new pointer may be different if the block needs to be moved. This is the rough equivalent of `realloc()`. -- 📖 **Note**: If you are `realloc()`'ing a block of memory and need to store pointers to data somewhere inside the block, it is often better to store an offset rather than a full pointer. This is because a full pointer becomes invalid if a `nmSysRealloc()` causes the block to move. +- 📖 **Note**: If you are `realloc()`'ing a block of memory and need to store pointers to data somewhere inside the block, it is often better to store an offset rather than a full pointer. This is because a full pointer becomes invalid if a [`nmSysRealloc()`](#nmsysrealloc) causes the block to move. ### nmSysStrdup() ```c char* nmSysStrdup(const char* str); ``` -Allocates memory using `nmSysMalloc()` function and copies the string `str` into this memory. It is a rough equivalent of `strdup()`. The resulting pointer can be free'd using `nmSysFree()`. +Allocates memory using the [`nmSysMalloc()`](#nmsysmalloc) function and copies the string `str` into this memory. It is a rough equivalent of `strdup()`. The resulting pointer can be free'd using [`nmSysFree()`](#nmsysfree). ### nmSysFree() ```c void nmSysFree(void* ptr); ``` -Frees a block of memory allocated by `nmSysMalloc()`, `nmSysRealloc()`, or `nmSysStrdup()`. +Frees a block of memory allocated by [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). + + + +## V Module: XArray +The xarray (xa) module is intended to manage sized growable arrays, similar to a light-weight arraylist implementation. It includes the `XArray`, which has the following fields: +- `nItems : int`: The number of items in the array. +- `nAlloc : int`: Internal variable to store the size of the allocated memory. +- `Items : void**`: The allocated array of items. + +- 📖 **Note**: Some code occasionally sets `nAlloc` to 0 after an XArray struct has been deinitialized to indicate that the relevant data is no longer allocated. Other than this, it is only used internally by the library. + +- ⚠️ **Warning**: Do not mix calls to [`xaNew()`](#xanew)/[`xaFree()`](#xafree) with calls to [`xaInit()`](#xainit)/[`xaDeInit()`](#xadeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. + + +### xaNew() +```c +pXArray xaNew(int init_size); +``` +Allocates a new `XArray` struct on the heap (using [`nmMalloc()`](#nmmalloc) for caching) and returns a pointer to it, or returns `NULL` if an error occurs. + +### xaFree() +```c +int xaFree(pXArray this); +``` +Frees a `pXArray` allocated using [`xaNew`](#xanew), returning 0 if successful or -1 if an error occurs. + +### xaInit() +```c +int xaInit(pXArray this, int init_size); +``` +This function initializes an allocated (but uninitialized) xarray. It makes room for `init_size` items initially, but this is only an optimization. A typical value for `init_size` is 16. Remember to [`xaDeInit`](#xadeinit) this xarray, do **not** [`xaFree`](#xafree) it. + +This function returns 0 on success, or -1 if an error occurs. + +### xaDeInit() +```c +int xaDeInit(pXArray this); +``` +This function de-initializes an xarray, but does not free the XArray structure itself. This is useful if the structure is a local variable allocated using [`xaInit()`](#xainit). +This function returns 0 on success, or -1 if an error occurs. +For example: +```c +XArray arr; +if (xaInit(&arr, 16) != 0) goto handle_error; -## V Other Utility Modules - - -The Centrallix library (`centralllix-lib`) has a host of useful utility modules. These include `xarray`, used for managing growable arrays; `xstring`, used for managing growable strings; `xhash`, used for managing hash tables with no overflow problems and variable-length keys; `expression`, used for compiling and evaluating expressions; and `mtsession`, used for managing session-level variables and reporting errors. +/** Use the xarray. **/ +if (arr.nAlloc != 0 && xaDeInit(&arr) != 0) goto handle_error; +arr.nAlloc = 0; +``` -### A. XArray (XA) - Arrays -The first is the xarray (XA) module. +### xaAddItem() +```c +int xaAddItem(pXArray this, void* item); +``` +This function adds an item to the end of the xarray. The item is assumed to be a `void*`, but this function will _not_ follow pointeres stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). -#### xaInit(pXArray this, int init_size) -This function initializes an allocated-but-uninitialized xarray. It makes room for 'init_size' items initially, but this is only an optimization. A typical value for init_size is 16. +This function returns 0 on success, or -1 if an error occurs. -#### xaDeInit(pXArray this) -This de-initializes an xarray, but does not free the XArray structure itself. +### xaAddItemSorted() +```c +int xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen); +``` +This function adds an item to a sorted xarray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers are stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). -#### xaAddItem(pXArray this, void* item) -This adds an item to the array. The item can be a pointer or an integer (but ints will need a typecast on the function call). +### xaAddItemSortedInt32() +```c +int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset) +``` + -#### xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen) -This adds an item to the xarray, and keeps the array sorted. The value for sorting is expected to begin at offset 'keyoffset' and continue for 'keylen' bytes. This only works when pointers are stored in the array, not integers. +### xaGetItem() +```c +void* xaGetItem(pXArray this, int index) +``` +This function returns an item given a specific index into the xarray, or `NULL` if the index is out of bounds. If the bounds check needs to be omitted for performance and the caller can otherwise verify that no out of bounds read is possible (e.g. because they are iterating from 0 to `xarray->nItems`), the caller should access `xarray->Items` directly. Either way, the result may need to be typecasted or stored in a variable of a specific type for it to be useable, and error checking for `NULL` values should be used. -#### xaFindItem(pXArray this, void* item) -This returns the offset into the array's items of the given value. An exact match is required. The array's items are given below: +### xaFindItem() +```c +int xaFindItem(pXArray this, void* item); +``` +This function returns array index for the provided item in the array, or -1 if the item could not be found. Requires an exact match, so two `void*` pointing to different memory with identical contents are not considered equal by this function. If the data is actually another datatype typecasted as a `void*`, all 8 bytes must be identical for a match. +For example: ```c - XArray xa; - pStructInf inf; - int item_id; +void* data = &some_data; + +XArray xa; +xaInit(&xa, 16); + +... - xaInit(&xa, 16); +xaAddItem(&xa, data); - [...] +... - xaAddItem(&xa, inf); +int item_id = xaFindItem(&xa, data); +assert(data == xa.Items[item_id]); +``` - [...] +### xaFindItemR() +```c +int xaFindItemR(pXArray this, void* item); +``` +This function works the same as [`xaFindItem()`](#xafinditem), however it iterates in reverse, giving a slight performance boost, especially for finding items near the end of the array. - item_id = xaFindItem(&xa, inf); - inf == xa.Items[item_id]; +### xaRemoveItem(pXArray this, int index) +```c +int xaRemoveItem(pXArray this, int index) ``` +This function removes an item from the xarray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. -#### xaRemoveItem(pXArray this, int index) -This function removes an item from the xarray at the given index. +### xaClear() +```c +int xaClear(pXArray this, int (*free_fn)(), void* free_arg); +``` +This function removes all elements from the xarray, leaving it empty. `free_fn()` is invoked on each element with a `void*` to the element to be freed as the first argument and `free_arg` as the second argument (the return value of `free_fn()` is always ignored). This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. -### B. XHash (XH) - Hash Tables -The xhash module provides an extensible hashing table interface. The hash table is a table of linked lists of items, so collisions and overflows are not a problem as in conventional hash tables. +### xaClearR() +```c +int xaClearR(pXArray this, int (*free_fn)(), void* free_arg); +``` +This function works the same as [`xaClear()`](#xaclear), except that it is slightly faster because the free function is evaluated on items in reverse order. -### int xhInit(pXHashTable this, int rows, int keylen) -This initializes a hash table, giving it the given number of rows, and setting the key length. For variable length keys (null- terminated strings), use a key length of 0 (zero). The 'rows' should be an odd number, preferably prime, but does not need to be. It SHOULD NOT be a power of 2. It's value is an optimization depending on how much data you expect to be in the hash table. If its value is set to 1, the hash search degenerates to a linear array search. The value should be large enough to comfortably accomodate the elements. Typical values might be 31 or 255 (though 255 is not prime). +### xaCount() +```c +int xaCount(pXArray this); +``` +This function returns the number of items in the xarray, or -1 on error. It is equivalent to accessing `xarray->nItems` (although the latter expression will not return an error). -#### int xhDeInit(pXHashTable this) -De-initializes a hash table. +### xaInsertBefore() +```c +int xaInsertBefore(pXArray this, int index, void* item) +``` +This function inserts an item before the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. -#### int xhAdd(pXHashTable this, char* key, char* data) -Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that they item is hashed. +### xaInsertAfter() +```c +int xaInsertAfter(pXArray this, int index, void* item) +``` +This function inserts an item after the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. -#### int xhRemove(pXHashTable this, char* key) -Removes an item with the given key value from the hash table. -#### char* xhLookup(pXHashTable this, char* key) -Returns the data pointer for a given key, or NULL if the item is not found. -#### int xhClear(pXHashTable this, int free_blk) -Clears all items from a hash table. If free_blk is set to 1, the items are free()'d as they are removed. +## VI Module: XHash +The xhash (xh) module provides an extensible hash table interface. The hash table is a table of linked lists of items, so collisions and overflows are handled by this data structure (although excessive collisions still cause a performance loss). This implementation also supports variable-length keys for more flexible usecases. -### C. XString (XS) - Strings -The xstring (XS) module is used for managing growable strings. It is based on a structure containing a small initial string buffer to avoid string allocations for small strings, but with the capability of performing realloc() operations to extend the string space for storing incrementally larger strings. The interface to this module allows for strings to contain arbitrary data, even null '\0' characters mid-string. Thus it is useful as an extensible buffer module as well. +- ⚠️ **Warning**: All `xhXYZ()` function calls assume that the `pXHashTable this` arg points to a valid hashtable struct. All non-init functions assume that this struct has been validly initialized and has not yet been freed. If these conditions are not met, the resulting behavior is undefined. -#### int xsInit(pXString this) -Initializes an XString structure, to an empty string. +### xhInitialize() +```c +int xhInitialize(); +``` +Initialize the random number table for hash computation, returning 0 on success or -1 if an error occurs. Normally, you can assume someone else has already called this during program startup. -#### int xsDeInit(pXString this) -Deinitializes an XString structure. +### xhInit() +```c +int xhInit(pXHashTable this, int rows, int keylen); +``` +This function initializes a hash table, setting the number of rows and the key length. Specify a `keylen` of 0 for for variable length keys (aka. null-terminated strings). The `rows` should be an odd number, preferably prime (although that isn't required). `rows` **SHOULD NOT** be a power of 2. Providing this value allows the caller to optimize it based on how much data they expect to be stored in the hash table. If this value is set to 1, the hash search degenerates to a linear array search with extra overhead. Thus, the value should be large enough to comfortably accommodate the elements with minimal collisions. Typical values include 31, 251, or 255 (though 255 is not prime). -#### int xsConcatenate(pXString this, char* text, int len) -Concatenates the string 'text' onto the end of the XString's value. If len is -1, all data up to the null terminater is copied. If len is set, all data up to length 'len' is copied, including possible '\0' characters. +### xhDeInit() +```c +int xhDeInit(pXHashTable this); +``` +This function deinitializes a hash table struct, freeing all rows. Note that the stored data is not freed and neither are the keys as this data is assumed to be the responsibility of the caller. Returns 0 on success, or -1 if an error occurs. -#### int xsCopy(pXString this, char* text, int len) -Copies the string 'text' into the XString. Like xsConcatenate, except that the previous string contents are overwritten. +### xhAdd() +```c +int xhAdd(pXHashTable this, char* key, char* data); +``` +Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that they item is hashed, as they are assumed to be the responsibility of the caller. This function returns 0 on success, or -1 if an error occurs. -#### char* xsStringEnd(pXString this) -Returns a pointer to the end of the string. Useful for finding the end of the string without performing: +### xhRemove() +```c +int xhRemove(pXHashTable this, char* key); +``` +This function removes an item with the given key value from the hash table. It returns 0 if the item was successfully removed, or -1 if an error occurs (including failing to find the item). +### xhLookup() ```c - pXString xs; +char* xhLookup(pXHashTable this, char* key); +``` +This function returns a pointer to the data associated with the given key, or `NULL` if an error occurs (including failing to find the key). - xs->String + strlen(xs->String) +### xhClear() +```c +int xhClear(pXHashTable this, int (*free_fn)(), void* free_arg); ``` +Clears all items from a hash table. If a `free_fn()` is provided, it will be invoked with each data pointer as the first argument and `free_arg` as the second argument as items are removed. The return value of the `free_fn()` is ignored. This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. -since the xs module already knows the string length and does not have to search for the null terminator. Furthermore, since the string can contain nulls, the above statement could produce incorrect results in those situations. +### xhForEach() +```c +int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); +``` +This function executes an operation on each entry of the hash table entry. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `void*` argument specified using `each_arg`. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. -The contents of the XString can be easily referenced via: +This function returns 0 if the function executes successfully, 1 if the callback function is `NULL`, or n (where n != 0) if the callback function returns n. It does not return any error code other than 1 or any error codes returned by `callback_fn()`. +### xhClearKeySafe() ```c - pXString xs; +int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg); +``` +This function clears all contents from the hash table. The free function is passed each hash entry struct and `free_arg`, allowing it to free both the value and key, if needed, and the free function is not allowed to return an error code. This function returns 0 for success as long as `free_fn()` is nonnull, otherwise it returns -1. - printf("This string is %s\n", xs->String); + + +## VII Module: XString +The xstring (xs) module is used for managing growable strings. It is based on a structure containing a small initial string buffer to avoid string allocations for small strings. However, it can also perform `realloc()` operations to extend the string space for storing incrementally larger strings. This module allows for strings to contain arbitrary data, even NULL (`'\0'`) characters mid-string. Thus, it can also be used as an extensible buffer for arbitrary binary data. + +- 📖 **Note**: The contents of the XString can be easily referenced with the `xstring->String` field in the xstring struct. + +- ⚠️ **Warning**: Do not mix calls to [`xsNew()`](#xsnew)/[`xsFree()`](#xsfree) with calls to [`xsInit()`](#xsinit)/[`xsDeInit()`](#xsdeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. + +### xsNew() +```c +pXString xsNew() ``` +This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](#nmmalloc) because the XString struct is always a consistant size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. -IMPORTANT NOTE: Do not store pointers to values within the string while you are still adding text to the end of the string. If the string ends up realloc()ing, your pointers will be incorrect. Instead, if data in the middle of the string needs to be pointed to, store offsets from the beginning of the string, not pointers to the string. +### xsFree() +```c +void xsFree(pXString this); +``` +This function frees an XString structure allocated with [`xsNew()`](#xsnew), freeing all associated memory. -For example, this is WRONG: +### xsInit() +```c +int xsInit(pXString this); +``` +This function initializes an XString structure to contain a new, empty string. This function returns 0 if successful, or -1 if an error occurs. + +### xsDeInit() +```c +int xsDeInit(pXString this); +``` +This function deinitializes an XString structure allocated with [`xsInit()`](#xsinit), freeing all associated memory. This function returns 0 if successful, or -1 if an error occurs. + +### xsCheckAlloc() +```c +int xsCheckAlloc(pXString this, int addl_needed); +``` +This function will optionally allocate more memory, if needed, given the currently occupied data area and the additional space required (specified with `addl_needed`). This function returns 0 if successful, or -1 if an error occurs. +### xsConcatenate() ```c - pXString xs; - char* ptr; +int xsConcatenate(pXString this, char* text, int len); +``` +This function concatenates the `text` string onto the end of the XString's value. If `len` is set, that number of characters are copied, including possible null characters (`'\0'`). If `len` is -1, all data up to the null-terminater is copied. This function returns 0 if successful, or -1 if an error occurs. - xsInit(&xs); - xsConcatenate(&xs, "This is the first sentence. ", -1); - ptr = xsStringEnd(&xs); - xsConcatenate(&xs, "This is the second sentence.", -1); +- ⚠️ **Warning**: Do not store pointers to values within the string while adding text to the end of the string. The string may be reallocated to increase space, causing such pointers to break. Instead, use offset indexes into the string and calculate pointers on demand with `xs->String + offset`. + + For example, **DO NOT**: + ```c + XString xs; + if (xsInit(&xs) != 0) goto handle_error; + + if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + char* ptr = xsStringEnd(&xs); /* Stores string pointer! */ + if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + + /** Print will probably read invalid memory. **/ printf("A pointer to the second sentence is '%s'\n", ptr); + + ... + + if (xsDeInit(&xs) != 0) goto handle_error; + ``` + + Instead, use indexes and pointer arithmetic like this: + ```c + XString xs; + if (xsInit(&xs) != 0) goto handle_error; + + if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + int offset = xsStringEnd(&xs) - xs->String; /* Stores index offset. */ + if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + + /** Print will probably work fine. **/ + printf("A pointer to the second sentence is '%s'\n", xs->String + offset); + + ... + + if (xsDeInit(&xs) != 0) goto handle_error; + ``` + +### xsCopy() +```c +int xsCopy(pXString this, char* text, int len); +``` +This function copies the string `text` into the XString, overwriting any previous contents. This function returns 0 if successful, or -1 if an error occurs. + +### xsStringEnd() +```c +char* xsStringEnd(pXString this); +``` +This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some string may contain nulls, using `strlen()` may produce an incorrect result. + +### xsConcatPrintf() +```c +int xsConcatPrintf(pXString this, char* fmt, ...); +``` +This function prints additional data onto the end of the string. It is similar to printf, however, only the following features are supported: +- `%s`: Add a string (`char*`). +- `%d`: Add a number (`int`). +- `%X`: Add something? +- `%%`: Add a `'%'` character. +Attempting to use other features of printf (such as `%lf`, `%c`, `%u`, etc.) will cause unexpected results. + +This function returns 0 if successful, or -1 if an error occurs. + +### xsPrintf() +```c +int xsPrintf(pXString this, char* fmt, ...); +``` +This function works the same as [`xsConcatPrintf()`](#xsconcatprintf), except that it overwrites the previous string instead of appending to it. This function returns 0 if successful, or -1 if an error occurs. + +### xsWrite() +```c +int xsWrite(pXString this, char* buf, int len, int offset, int flags); +``` +This function writes data into the xstring, similar to using the standard fdWrite or objWrite API. This function can thus be used as a value for `write_fn`, for those functions that require this (such as the `expGenerateText()` function). This function returns `len` if successful, or -1 if an error occurs. + +### xsRTrim() +```c +int xsRTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the right side of the xstring. This function returns 0 if successful, or -1 if an error occurs. + +### xsLTrim() +```c +int xsLTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the left side of the xstring. This function returns 0 if successful, or -1 if an error occurs. + +### xsTrim() +```c +int xsTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from both sides of the xstring. This function returns 0 if successful, or -1 if an error occurs. + +### xsFind() +```c +int xsFind(pXString this, char* find, int findlen, int offset) ``` +This function searches for a specific string (`find`) in the xstring, starting at the provided `offset`. `findlen` is the length of the provided string, allowing it to include null characters (pass -1 to have the length calculated using `strlen(find)`). This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). -Instead, use pointer aritmetic and do this: +### xsFind() +```c +int xsFindRev(pXString this, char* find, int findlen, int offset) +``` +This function works the same as [`xsFind()`](#xsfind) except that it searches from the end of the string, resulting in better performance if the value is closer to the end of the string. This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). +### xsSubst() ```c - pXString xs; - int offset; +int xsSubst(pXString this, int offset, int len, char* rep, int replen) +``` +This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsrepalce), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified handle null characters. Both `len` and `replen` can be left blank to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. - xsInit(&xs); - xsConcatenate(&xs, "This is the first sentence. ", -1); - offset = xsStringEnd(&xs) - xs->String; - xsConcatenate(&xs, "This is the second sentence.", -1); - printf("A pointer to the second sentence is '%s'\n",xs->String+offset); +### xsReplace() +```c +int xsReplace(pXString this, char* find, int findlen, int offset, char* rep, int replen); ``` +This function searches an xString for the specified string (`find`) and replaces that string with another specified string (`rep`). Both strings can have their length specified (`findlen` and `replen` respectively), or left as -1 to generate it using `strlen()`. This function returns the starting offset of the replace if successful, or -1 if an error occurs (including the string not being found). +### xsInsertAfter() +```c +int xsInsertAfter(pXString this, char* ins, int inslen, int offset); +``` +This function inserts the specified string (`ins`) at offset (`offset`). The length of the string can be specified (`inslen`), or left as -1 to generate it using `strlen()`. This function returns the new offset after the insertion (i.e. `offset + inslen`), or -1 if an error occurs. -### D. Expression (EXP) - Expression Trees -The expression (EXP) module is used for compiling, evaluating, reverse- evaluating, and passing parameters to expression strings. The expression strings are compiled and stored in an expression tree structure. +### xsGenPrintf_va() +```c +int xsGenPrintf_va(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, va_list va); +``` +This function performs a `printf()` operation to an `xxxWrite()` style function. -Expressions can be stand-alone expression trees, or they can take parameter objects. A parameter object is an open object (from objOpen()) whose values (attributes) are referenced within the expression string. By using such parameter objects, one expression can be compiled and then evaluated for many different objects with diverse attribute values. +In the wise words of Greg Beeley from 2002: +> This routine isn't really all that closely tied to the XString module, but this seemed to be the best place for it. If a `buf` and `buf_size` are supplied (`NULL` otherwise), then `buf` MUST be allocated with the `nmSysMalloc()` routine. Otherwise, **kaboom!** This routine will grow `buf` if it is too small, and will update `buf_size` accordingly. -Expression evaluation results in the top-level expression tree node having the final value of the expression, which may be NULL, and may be an integer, string, datetime, money, or double data type. For example, the final value of +This function returns the printed length (>= 0) on success, or -(errno) if an error occurs. +### xsGenPrintf() +```c +int xsGenPrintf(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, ...); ``` - :myobject:oneattribute == 'yes' +This function works the same as [`xsGenPrintf_va()`](#xsgenprintf_va), but with a more convenient signature for the developer. + +### xsString() +```c +char* xsString(pXString this); ``` +This function returns the stored string after checking for various errors, or returns `NULL` if an error occurs. -would be integer 1 (true) if the attribute's value is indeed 'yes'. +### xsLength() +```c +xsLength(pXString this); +``` +This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. -Reverse expression evaluation takes a given final value and attempts to assign values to the parameter object attributes based on the structure of the expression tree. It is akin to 'solving for X' in algebraic work, but isn't nearly that 'smart'. For example, with the previous expression, if the final value was set to 1 (true), then an objSetAttrValue() function would be issued to set myobject's 'oneattribute' to 'yes'. Trying this with a final value of 0 (false) would result in no assignment to the attribute, since there would be no way of determining the proper value for that attribute (anything other than 'yes' would work). + + +### xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf() +```c +int xsQPrintf_va(pXString this, char* fmt, va_list va); +int xsQPrintf(pXString this, char* fmt, ...); +int xsConcatQPrintf(pXString this, char* fmt, ...); +``` +These functions use the `QPrintf` to add data to an xstring. They return 0 on success, or some other value on failure. + + + +## VIII Module: Expression +The expression (EXP) module is used for compiling, evaluating, reverse-evaluating, and managing parameters for expression strings. The expression strings are compiled and stored in an expression tree structure. + +Expressions can be stand-alone expression trees, or they can take parameter objects. A parameter object is an open object (from `objOpen()`) whose values (attributes) are referenced within the expression string. By using such parameter objects, one expression can be compiled and then evaluated for many different objects with diverse attribute values. + +Expression evaluation results in the top-level expression tree node having the final value of the expression, which may be `NULL`, and may be an integer, string, datetime, money, or double data type. For example, the final value of `:myobject:oneattribute == 'yes'` is the integer 1, `true`, if the attribute's value is indeed `'yes'` (and the integer 0, `false`, otherwise). + +Expression reverse-evaluation takes a given final value and attempts to assign values to the parameter object attributes based on the structure of the expression tree. It is akin to 'solving for X' in algebraic work, but isn't nearly that 'smart'. For example, with the previous expression, if the final value was set to 1 (`true`), then an `objSetAttrValue()` function would be called to set myobject's `oneattribute` to `yes`. Trying this with a final value of 0 (`false`) would result in no assignment to the attribute, since there would be no way of determining the proper value for that attribute (anything other than `yes` would work). Reverse evaluation is typically very useful in updateable joins and views. -Here are the basic expression functions: +The expression module includes the following functions: -#### pExpression expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflags) -This function compiles a textual expression into an expression tree. The 'objlist' lists the parameter objects that are allowed in the expression (see below for param objects maintenance functions). +### expAllocExpression() +```c +pExpression expAllocExpression(); +``` +This function allocates space to store a new expression tree, returning a pointer to the allocated memory or `NULL` if an error occurs. -The 'lxflags' parameter gives a set of lexical analyzer flags for the compilation. These flags alter the manner in which the input string is tokenized. A bitmask; possible values are: +### expFreeExpression() +```c +int expFreeExpression(pExpression this); +``` +This function frees an expression tree allocated using `expAllocExpression()`, returning 0 if successful or -1 if an error occurs. -| Value | Description -| ---------------- | ------------ -| MLX_F_ICASEK | automatically convert all keywords (non-quoted strings) to lowercase. -| MLX_F_POUNDCOMM | allow comment lines that begin with a # sign. -| MLX_F_CCOMM | allow c-style comments /* */ -| MLX_F_CPPCOMM | allow c-plus-plus comments // -| MLX_F_SEMICOMM | allow semicolon comments ;this is a comment -| MLX_F_DASHCOMM | allow double-dash comments --this is a comment -| MLX_F_DASHKW | keywords can include the dash '-'. Otherwise, the keyword is treated as two keywords with a minus sign between them. -| MLX_F_FILENAMES | Treat a non-quoted string beginning with a slash '/' or dot-slash './' as a filename, and allow slashes and dots in the string without quotes needed. -| MLX_F_ICASER | automatically convert all reserved words to lowercase. The use of this flag is highly recommended, and in some cases, required. -| MLX_F_ICASE | same as MLX_F_ICASER | MLX_F_ICASEK. +### expCompileExpression() +```c +pExpression expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflags); +``` +This function compiles a textual expression into an expression tree. The `objlist` lists the parameter objects that are allowed in the expression (see below for param objects maintenance functions). -The 'cmpflags' is a bitmask parameter controlling the compilation of the expression. It can contain the following values: +The `lxflags` parameter is a bitmask that provides flags which will be passed to the lexer. These flags alter the manner in which the input string is tokenized. For information about these flags, see [`mlxOpenSession()`](#mlxopensession). -| Value | Description -| ------------------- | ------------ -| EXPR_CMP_WATCHLIST | A list "value,value,value" is expected first in the expression. -| EXPR_CMP_ASCDESC | Recognize 'asc' and 'desc' following a value as flags to indicate sort order. -| EXPR_CMP_OUTERJOIN | Recognize the *= and =* syntax as outer joins. +The `cmpflags` parameter is a bitmask that provides flags which will be passed to the expression compiler. It can contain the following values: -#### expFreeExpression(pExpression this) -Frees an expression tree. +| Value | Description +| -------------------- | ------------ +| `EXPR_CMP_ASCDESC` | Recognize `asc`/`desc` following a value as flags to indicate sort order. +| `EXPR_CMP_OUTERJOIN` | Recognize the `*=` and `=*` syntax for left and right outer joins. +| `EXPR_CMP_WATCHLIST` | A list (`"value,value,value"`) is expected first in the expression. +| `EXPR_CMP_LATEBIND` | Allow late object-name binding. +| `EXPR_CMP_RUNSERVER` | Compile as a `runserver` expression (for dynamic binding). +| `EXPR_CMP_RUNCLIENT` | Compile as a `runclient` expression (for client-side binding). +| `EXPR_CMP_REVERSE` | Lookup names in the reverse order. -#### int expEvalTree(pExpression this, pParamObjects objlist) -Evaluates an expression against a list of parameter objects. If the evaluation is successful, returns 0 or 1, otherwise -1. +### expCompileExpressionFromLxs() +```c +pExpression expCompileExpressionFromLxs(pLxSession s, pParamObjects objlist, int cmpflags); +``` +This function is similar to [`expCompileExpression()`](#expcompileexpression), excpet that it compiles from a provided lexer session instead of from a string. -#### pParamObjects expCreateParamList() -Allocates a new parameter object list, with no parameters. +### expPodToExpression() +```c +pExpression expPodToExpression(pObjData pod, int type, pExpression provided_exp) +``` +This function builds an expression node from a single piece of data, passed using the `pObjData` of the given datatype. This function can be used to initialize a provided expression (`provided_exp`), or it will allocate a new one if none is provided (aka. `provided_exp` is `NULL`). -#### int expFreeParamList(pParamObjects this) -Frees a parameter object list. +For example, the following code creates an expression representing the integer 1. +```c +int value = 1; +pExpression exp = expPodToExpression(POD(value), DATA_T_INTEGER, NULL); +``` + +This function returns a pointer to the expression if successful, or `NULL` if an error occurs. + +- 📖 **Note**: There is also a `expPtodToExpression()` function for working with the `Ptod` (pointer to object data) struct. + +### expExpressionToPod() +```c +int expExpressionToPod(pExpression this, int type, pObjData pod); +``` +This function reverses the functionality of [`expPodToExpression()`](#exppodtoexpression) to instead read data from an evaluated expression. Be careful, this does not evaluate the expression if it is not already evaluated. This function returns 0 if successful, 1 if the expression is NULL, or -1 if an error occurs. + +- 📖 **Note**: The source code for this function can be a useful reference when interacting with expression structures, such as when implementing the c code for an exp_function. + +- 📖 **Note**: There is also a `expExpressionToPtod()` function for working with the `Ptod` (pointer to object data) struct. + +### expDuplicateExpression() +```c +pExpression expDuplicateExpression(pExpression this); +``` +This function creates a recursive deep copy of the expression and associated expression tree, returning a pointer to this new copy if successful and `NULL` if an error occurs. + +### expIsConstant() +```c +int expIsConstant(pExpression this); +``` +This function returns a truthy value if the provided expression is of a type that is always the same, such as an integer, string, double, etc. Otherwise, it returns a falsy value. + +### expEvalTree() +```c +int expEvalTree(pExpression this, pParamObjects objlist); +``` +This function evaluates the expression using the provided list of parameter objects. It returns 0 if successful or 1 if the result is `NULL`, and -1 if an error occurs. + +### expCreateParamList() +```c +pParamObjects expCreateParamList(); +``` +This function allocates and returns a new parameter object list containing no parameters, or returns `NULL` if an error occurs. -#### int expAddParamToList(pParamObjects this, char* name, pObject obj, int flags) -Adds a parameter to the parameter object list. The 'obj' pointer may be left NULL during the expCompileExpression state of operation but must be set to a value before expEvalTree is called. Otherwise the attributes that reference that parameter object will result in NULL values in the expression (it's technically not an error). Flags can be EXPR_O_CURRENT if the object is to be marked as the current one, or EXPR_O_PARENT if it is to be marked as the parent object. Current and Parent objects can be referenced in an expression like this: +### expFreeParamList() +```c +int expFreeParamList(pParamObjects this); +``` +This function frees a parameter object list, returning 0 if successful and -1 if an error occurs. + +### expAddParamToList() +```c +int expAddParamToList(pParamObjects this, char* name, pObject obj, int flags); +``` +This function adds a parameter to the parameter object list. The `obj` pointer may be left `NULL` during the expCompileExpression state of operation but must be set to a value before expEvalTree is called. Otherwise the attributes that reference that parameter object will result in `NULL` values in the expression. (Although this _technically_ is not an error, it's usually not intended behavior). Flags can be `EXPR_O_CURRENT` if the object is to be marked as the current one, or `EXPR_O_PARENT` if it is to be marked as the parent object. Current and Parent objects can be referenced in an expression like this: ``` - :currentobjattr - ::parentobjattr +:currentobjattr +::parentobjattr ``` -and is thus a shortcut to typing the full object name. +### expModifyParam() +```c +int expModifyParam(pParamObjects this, char* name, pObject replace_obj); +``` +This function is used to update a parameter object with a new open pObject, possibly one returned from `objOpen()` or `objQueryFetch()`. This function returns 0 if successful and -1 if an error occurs. -#### int expModifyParam(pParamObjects this, char* name, pObject replace_obj) -This function is used to update a parameter object with a new open pObject returned from objOpen or objQueryFetch. +### expRemoveParamFromList() +```c +int expRemoveParamFromList(pParamObjects this, char* name); +``` +This function removes a parameter object from the list, returning 0 if successful and -1 if an error occurs. -#### int expRemoveParamFromList(pParamObjects this, char* name) -This function removes a parameter object from the list. +- 📖 **Note**: There is also a `expRemoveParamFromListById()` function. -#### int expReverseEvalTree(pExpression tree, pParamObjects objlist) +### expSetParamFunctions() +```c +int expSetParamFunctions(pParamObjects this, char* name, int (*type_fn)(), int (*get_fn)(), int (*set_fn)()); +``` +This function sets the param accessor functions used to access params on a specific name. Some example function signatures for the `type_fn()`, `get_fn()`, and `set_fn()` are provided below: + +```c +static int ci_GetParamType(void* v, char* attr_name); +static int ci_GetParamValue(void* v, char* attr_name, int datatype, pObjData val); +static int ci_SetParamValue(void* v, char* attr_name, int datatype, pObjData val); +``` + +- `v : void*` is the object provided in `expAddParamToList()` (or a similar function). +- `attr_name : char*` is the string name for the requested attribute. +- `datatype : int` is the data type for the requested attribute. +- `val : pObjectData` is either a buffer in which to store the requested data (`ci_GetParamValue()`) or a buffer containing data that will be copied to the parameter `ci_SetParamValue()`. + +All three of these functions return 0 for success, 1 if the attribute is `NULL`, or -1 if an error occurs. The `expSetParamFunctions()` function returns 0 if the functions were set successfully, or -1 if an error occurs. + +### expReverseEvalTree() +```c +int expReverseEvalTree(pExpression tree, pParamObjects objlist)l +``` This function reverse-evaluates a tree. The results of an expression evaluation can be accessed by examining the @@ -1411,234 +1867,379 @@ top-level tree node. The following properties are useful: There are several other EXP functions used to deal with aggregates and a few other obscure features as well. Aggregates are mostly handled internally by Centrallix so further explanation should not be necessary here. -### E. MTSession (MSS) - Basic Session Management -The next utility module to be described here is the mtsession module (MSS). This module is used for session authentication, error reporting, and for storing session-wide variables such as the currently used date format, current username, and current password (for issuing a login request to a remote server). Care should be taken in the use of Centrallix that its coredump files are NOT in a world-readable location, as the password will be visible in the core file (or just ulimit the core file size to 0). -#### char* mssUserName() -This function returns the current user name. +## IX Module: MTSession +The mtsession (MSS) module is used for session authentication, error reporting, and for storing session-wide variables such as the current date format, username, and password (used when issuing a login request to a remote server). Care should be taken in the use of Centrallix that its coredump files are NOT in a world-readable location, as the password will be visible in the coredump file (or just ulimit the core file size to 0). + -#### char* mssPassword() -This function returns the password used to login to the Centrallix +### mssInitialize() +```c +int mssInitialize(char* authmethod, char* authfile, char* logmethod, int logall, char* log_progname); +``` +This function initializes the session manager and sets global variables used in this module. It returns 0 if successful and -1 if an error occurs. + +### mssUserName() +```c +char* mssUserName(); +``` +This function returns the current user name, or `NULL` an error occurs. -#### int mssSetParam(char* paramname, char* param) -This function sets a session parameter. The parameter MUST be a string value. +### mssPassword() +```c +char* mssPassword(); +``` +This function returns the current user's password that they used to log into Centrallix, or `NULL` an error occurs. -#### char* mssGetParam(char* paramname) -Returns the value of a session parameter. Common ones are: +### mssSetParam() +```c +int mssSetParam(char* paramname, char* param); +``` +This function sets the session parameter of the provided name (`paramname`) to the provided value (`param`). The parameter MUST be a string value. This function returns 0 if successful, or -1 an error occurs. -- dfmt - current date format. -- mfmt - current money format. -- textsize - current max text size from a read of an object's content via objGetAttrValue(obj, "objcontent", POD(&str)) +### mssGetParam() +```c +char* mssGetParam(char* paramname); +``` +Returns the value of a session parameter of the provided name (`paramname`), or `NULL` if an error occurs. Common session parameters include: +- `dfmt`: The current date format. +- `mfmt`: The current money format. +- `textsize`: The current max text size from a read of an object's content via `objGetAttrValue(obj, "objcontent", POD(&str))` -#### int mssError(int clr, char* module, char* message, ...) -Formats and caches an error message for return to the user. If 'clr' is set to 1, the assumption is that the error was JUST discovered and no other module has had reason to do an mssError on the current problem. Setting 'clr' to 1 clears all error messages from the current error message list and adds the current message. +### mssError() +```c +int mssError(int clr, char* module, char* message, ...); +``` +Formats and caches an error message for return to the user. This function returns 0 if successful, or -1 if an error occurred. -'module' is a two-to-five letter abbreviation of the module reporting the error. Typically it is all upper-case. +| Parameter | Type | Description +| --------- | ------------- | ------------ +| crl | int | If set to 1, all previous error messages are cleared. Set this when the error is initially discovered and no other module is likely to have made a relevant `mssError()` call for the current error. +| module | char* | A two-to-five letter abbreviation of the module reporting the error. This is typically the module or driver's abbreviation prefix in full uppercase letters (although that is not required). This is intended to help the developer find the source of the error faster. +| message | char* | A string error message, accepting format specifiers like `%d` and `%s` which are supplied by the argument list, similar to `printf()`. +| ... | ... | Parameters for the formatting. -'message' is a string for the error message. As this function will accept a variable-length argument list, the strings '%d' and '%s' can be included in 'message', and will be substituted with the appropriate integer or string arguments, in a similar way to how printf() works. +Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch the errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. -#### int mssErrorErrno(int clr, char* module, char* message, ...) -Works much the same way as mssError, except checks the current value of 'errno' and includes a description of any error stored there. Used primarily when a system call was at fault for an error occurring. +The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ as an expression being compiled? etc.). However, it is far easier to give too little information that too much, so it can often be best to air on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. -Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch those errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. +- 📖 **Note**: The `mssError()` routines do not cause the calling function to return or exit. The function must still clean up after itself and return an appropriate value (such as `-1` or `NULL`) to indicate failure. -These mssError routines need not be called at every function nesting level when an error happens. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it probably has set one or more error messages in the error list. The calling function only needs to provide context information (e.g. _what_ expression failed compilation?) so that the user has enough information to locate the error. And once the user is told the full context of the expression compilation error, no more information need be returned. +- ⚠️ **Warning**: Even if `-1` is returned, the error message may still be sent to the user in some scenarios. This is not guaranteed, though. -Another example of this is the memory manager, which sets an error message indicating when an nmMalloc() failed. The user probably does not care what kind of structure failed allocation -- he/she only needs to know that the hardware ran out of resources. Thus, upon receiving a NULL from nmMalloc, in most cases another mssError need not be issued. +- ⚠️ **Warning**: `%d` and `%s` are the ONLY supported format specifier for this function. **DO NOT** use any other format specifiers like `%lf`, `%u`, `%lu`, `%c` etc. **DO NOT** attempt to include `%%` for a percent symbol in your error message, as misplaced percent symbols often break this function. If you wish to use these features of printf, it is recommended to print the error message to a buffer and pass that buffer to `mssError()`, as follows: + ```c + char err_buf[256]; + snprintf(err_buf, sizeof(err_buf), + "Incorrect values detected: %u, %g (%lf), '%c'", + unsigned_int_value, double_value, char_value + ); + if (mssError(1, "EXMPL", "%s", err_buf) != 0) + { + fprintf(stderr, "ERROR! %s\n", err_buf); + } + return -1; + ``` -The mssError() routines do not cause the calling function to return. The function must still clean up after itself and return an appropriate value (like -1 or NULL) to indicate failure. -### F. OSML Utility Functions -The OSML provides a set of utility functions that make it easier to write -drivers. Most of them are named obj_internal_XxxYyy or similar. +### mssErrorErrno() +```c +int mssErrorErrno(int clr, char* module, char* message, ...); +``` +This function works the same way as [`mssError`](#mssError), except checks the current value of `errno` and includes a description of any error stored there. This is useful if a system call or other library function is responsible for this error. -#### char* obj_internal_PathPart(pPathname path, int start, int length) -The Pathname structure breaks down a pathname into path elements, which are text strings separated by the directory separator '/'. This function takes the given Pathname structure, and returns the number of path elements requested. For instance, if you have a path: + + +## X Path Handling Functions +The OSML provides a set of utility functions that make it easier to handle path structs when writing drivers. Most of them are named `obj_internal_XxxYyy()` or similar. + +### obj_internal_PathPart() +```c +char* obj_internal_PathPart(pPathname path, int start, int length); +``` +The Pathname structure breaks down a pathname into path elements, which are text strings separated by the directory separator `'/'`. This function takes the given Pathname structure and returns the number of path elements requested (using `length`) after skipping to the `start`th element (where element 0 is the starting `.` that begins any Centrallix path). + +For example, given the path: +```bash +/apps/kardia/data/Kardia_DB/p_partner/rows/1 ``` - /apps/kardia/data/Kardia_DB/p_partner/rows/1 +Centrallix stores the path internally as the following (see [Parsing Path Contents](#parsing-path-contents) and [Parameters](#parameters) above): +```bash +./apps/kardia/data/Kardia_DB/p_partner/rows/1 ``` +Thus, calling `obj_internal_PathPart(pathstruct, 4, 2);` will return `"Kardia_DB/p_partner"` because the `.` is the 0th element, making `Kardia_DB` the 4th element, and we have requested two elements. -that path would be stored internally in Centrallix as: +- 📖 **Note**: The values returned from `obj_internal_PathPart()` use an internal buffer, so they are only valid until the next call to a PathPart function on the given pathname structure. +### obj_internal_AddToPath() +```c +int obj_internal_AddToPath(pPathname path, char* new_element); ``` - ./apps/kardia/data/Kardia_DB/p_partner/rows/1 -``` +This function lengthens the path by one element, adding new_element on to the end of the path. This function is frequently useful for drivers in the QueryFetch routine where the new child object needs to be appended onto the end of the given path. -To just return "Kardia_DB/p_partner", you could call: +This function returns the index of the new element in the path on success, or a value less than 0 on failure. +### obj_internal_CopyPath() +```c +int obj_internal_CopyPath(pPathname dest, pPathname src); ``` - obj_internal_PathPart(pathstruct, 4, 2); +This function copies a pathname structure from the `src` to the `dest`, returning 0 if successful or -1 if an error occurs. + +### obj_internal_FreePathStruct() +```c +void obj_internal_FreePathStruct(pPathname path); ``` +This function frees a pathname structure. -Note that return values from obj_internal_PathPart are only valid until the next call to PathPart on the given pathname structure. -#### int obj_internal_AddToPath(pPathname path, char* new_element) -This function lengthens the path by one element, adding new_element on to the end of the path. This function is frequently useful for drivers in the QueryFetch routine where the new child object needs to be appended onto the end of the given path. -This function returns < 0 on failure, or the index of the new element in the path on success. +## XI Network Connection Functionality +Sometimes, a driver may need to initiate a network connection. This can be done via the `MTASK` module, which provides simple and easy TCP/IP connectivity. It includes many functions, only a few of which are documented below: -#### int obj_internal_CopyPath(pPathname dest, pPathname src) -Copies a pathname structure. +### netConnectTCP() +```c +pFile netConnectTCP(char* host_name, char* service_name, int flags); +``` +This function creates a client socket and connects it to a server on a given TCP service/port and host name. It takes the following three parameters: +- `host_name`: The host name or ascii string for the host's ip address. +- `service_name`: The name of the service (from `/etc/services`) or its numeric representation as a string. +- `flags`: Normally left 0. -#### void obj_internal_FreePathStruct(pPathname path) -Frees a pathname structure. +- 📖 **Note**: The `NET_U_NOBLOCK` flag causes the function to return immediately even if the connection is still being established. Further reads and writes will block until the connection either establishes or fails. -## VI Network Connection Functionality -Sometimes a driver will need to initiate a network connection. This can be done via the MTASK module, which provides simple and easy TCP/IP connectivity. +This function returns the connection file descriptor if successful, or `NULL` if an error occurs. -### pFile netConnectTCP(char* host_name, char* service_name, int flags) -This function connects to a server. The host name or ascii string for its ip address is in 'host_name'. The name of the service (from /etc/services) or its numeric representation in a string is the 'service_name'. Flags can normally be left 0. +### netCloseTCP() +```c +int netCloseTCP(pFile net_filedesc, int linger_msec, int flags); +``` +This function closes a network connection (either a TCP listening, server, or client socket). It will also optionally waits up to `linger_msec` milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If `linger_msec` is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately prior to the close, offering to linger for a few more seconds (perhaps 5 or 10 by specifying 5000 or 10000 msec) can be a good idea. -### int netCloseTCP(pFile net_filedesc, int linger_msec, int flags) -This function closes a network connection, and optionally waits up to 'linger_msec' milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If linger_msec is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately perior to the close, offering to linger for a few more seconds (perhaps 5 or 10, 5000 or 10000 msec), might be a good idea. +### fdWrite() +```c +int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags); +``` +This function writes data to an open file descriptor, from a given `buffer` and `length` of data to write. It also takes an optional seek `offset` and and `flags`, which can be zero or more of: +- `FD_U_NOBLOCK` - If the write can't be performed immediately, don't perform it at all. +- `FD_U_SEEK` - The `offset` value is valid. Seek to it before writing. Not allowed for network connections. +- `FD_U_PACKET` - *ALL* of the data specified by `length` in `buffer` must be written. Normal `write()` semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. -### int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags) -This function writes data to a file descriptor, from a given buffer and length, and to an optional seek offset and with some optional flags. Flags can be the following: +### fdRead() +```c +int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags); +``` +This function works the same as [`fdWrite()`](#fdwrite) except that it reads data instead of writing it. It takes the same flags as above, except that `FD_U_PACKET` now requires that all of `maxlen` bytes must be read before returning. This is good for reading a packet of a known length that might be broken up into fragments by the network (TCP/IP has a maximum frame transmission size of about 1450 bytes). -- `FD_U_NOBLOCK` - If the write can't be performed immediately, don't perform it at all. -- `FD_U_SEEK` - The 'offset' value is valid. Seek to it before writing. Not valid for network connections. -- `FD_U_PACKET` - ALL of the data of 'length' in 'buffer' must be written. Normal write() semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. -#### int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags) -The complement to the above routine. Takes the same flags as the above routine, except FD_U_PACKET means that all of 'maxlen' bytes must be read before returning. This is good for reading a packet that is known to be exactly 'maxlen' bytes long, but which might be broken up into fragments by the network (TCP/IP has a maximum frame transmission size of about 1450 bytes). +## XII Parsing Data +The mtlexer (MLX) module is a lexical analyzer library provided by Centrallix for parsing many types of data. It can parse data from either a `pFile` descriptor or from a string value. This lexical analyzer is also used by the [expression compiler](#viii-module-expression). In simple terms, it's a very fancy string tokenizer. -## VII Parsing Data -Centrallix provides a lexical analyzer library that can be used for parsing many types of data. This module, mtlexer (MLX) can either parse data from a pFile descriptor or from a string value. This lexical analyzer is used by the expression compiler as well. It is basically a very fancy string tokenizer. +### mlxOpenSession() +```c +pLxSession mlxOpenSession(pFile fd, int flags); +``` +This function opens a lexer session, using a file descripter as its source. Some of the more useful values for `flags` include: -### pLxSession mlxOpenSession(pFile fd, int flags) -This function opens a lexer session from a file source. See the 'expression' module description previous in this document for more information on the flags. Some flags of use here but not mentioned in that section are: +| Value | Description +| ----------------- | ------------ +| `MLX_F_ICASEK` | Automatically convert all keywords (non-quoted strings) to lowercase. +| `MLX_F_ICASER` | Automatically convert all reserved words to lowercase. This flag is highly recommended, and in some cases, required. +| `MLX_F_ICASE` | Same as MLX_F_ICASER | MLX_F_ICASEK. +| `MLX_F_POUNDCOMM` | Respect # comment at the start of the line (`#comment`). +| `MLX_F_CCOMM` | Respect c-style comments (`/*comment*/`). +| `MLX_F_CPPCOMM` | Respect c-plus-plus comments (`//comment`). +| `MLX_F_SEMICOMM` | Respect semicolon comments (`;comment`). +| `MLX_F_DASHCOMM` | Respect double-dash comments (`--comment`). +| `MLX_F_EOL` | Return end-of-line as a token. Otherwise, this is considered whitespace. +| `MLX_F_EOF` | Return end-of-file as a token. Otherwise, reaching end of file is an error. +| `MLX_F_ALLOWNUL` | Allow null characters (`'\0'`) in the input stream, which otherwise cause an error. If this flag is set, the caller must ensure that null characters are handled safely. +| `MLX_F_IFSONLY` | Only return string values separated by tabs, spaces, newlines, and carriage returns. For example, normally the brace in `"this{brace"` is a token and that string will result in three tokens, but in `IFSONLY` mode it is just one token. +| `MLX_F_DASHKW` | Keywords can include the dash (`-`). Otherwise, the keyword is treated as two keywords with a minus sign between them. +| `MLX_F_FILENAMES` | Treat a non-quoted string beginning with a slash (`/`) or dot-slash (`./`) as a filename, and allow slashes and dots in the string without requiring quotes. +| `MLX_F_NODISCARD` | Attempt to unread unused buffered data rather than discarding it, allowing the calling function to continue reading with `fdRead()` or another lexer session after the last token is read and the session is closed. The lexer `fdRead()`s in 2k or so chunks for performance, and normally discards this data when done, causing future file decriptors to start at an undefined file location. +| `MLX_F_DBLBRACE` | Treat `{{` and `}}` as double brace tokens, not two single brace tokens. +| `MLX_F_NOUNESC` | Do not remove escapes in strings. +| `MLX_F_SSTRING` | Differentiate between strings values using `""` and `''`. -| Flag | Description -| ------------------- | ------------ -| MLX_F_EOL | Return end-of-line as a token. Otherwise, the end of a line is just considered whitespace. -| MLX_F_EOF | Return end-of-file as a token. Otherwise, if end of file is reached it is an error. -| MLX_F_IFSONLY | Only return string values separated by tabs, spaces, newlines, and carriage returns. For example, normally the brace in "this{brace" is a token and that string will result in three tokens, but in IFSONLY mode it is just one token. -| MLX_F_NODISCARD | This flag indicates to the lexer that the calling function expects to be able to read data normally using fdRead() or another lexer session after the last token is read and the session is closed. The lexer will then attempt to "unread" bytes that it buffered during the lexical analysis process (it does fdRead() operations in 2k or so chunks). If this flag is not specified, up to 2k of information after the last token will be discarded and further fdRead()s on the file descriptor will start at an undefined place in the file. -| MLX_F_ALLOWNUL | Allow NUL characters ('\0') in the input stream. If this flag is not set, then NUL characters result in an error condition. This prevents unwary callers from mis-reading a token returned by mlxStringVal if the token contains a NUL. If ALLOWNUL is turned on, then the caller must ensure that it is safely handling values with NULs. +This function returns a pointer to the new lexer session if successful, or `NULL` if an error occurs. -### pLxSession mlxStringSession(char* str, int flags) -This function opens a lexer session from a text string. Same as the above function except that the flag MLX_F_NODISCARD makes no sense for the string. +### mlxStringSession() +```c +pLxSession mlxStringSession(char* str, int flags); +``` +This function opens a lexer session, using a text string as its source. The flags are the same as [`mlxOpenSession()`](#mlxopensession) above, except that `MLX_F_NODISCARD` has no effect. -### int mlxCloseSession(pLxSession this) -Closes a lexer session. +This function returns a pointer to the new lexer session if successful, or `NULL` if an error occurs. -### int mlxNextToken(pLxSession this) +### mlxCloseSession() +```c +int mlxCloseSession(pLxSession this); +``` +This function closes a lexer session, freeing all associated data. This does not also close the file descriptor used to open the lexer session, as this is assumed to be managed by the caller. This function returns 0 if successful, and -1 if an error occurs. + +### mlxNextToken() +```c +int mlxNextToken(pLxSession this); +``` Returns the type of the next token in the token stream. Valid token types are: -| Token | Meaning | -|----------------------|---------------------------------------| -| MLX_TOK_STRING | String value, as in a "string". | -| MLX_TOK_INTEGER | Integer value. | -| MLX_TOK_EQUALS | = | -| MLX_TOK_OPENBRACE | { | -| MLX_TOK_CLOSEBRACE | } | -| MLX_TOK_ERROR | An error has occurred. | -| MLX_TOK_KEYWORD | An unquoted string. | -| MLX_TOK_COMMA | , | -| MLX_TOK_EOL | End-of-line. | -| MLX_TOK_EOF | End-of-file reached. | -| MLX_TOK_COMPARE | <> != < > >= <= == | -| MLX_TOK_COLON | : | -| MLX_TOK_OPENPAREN | ( | -| MLX_TOK_CLOSEPAREN | ) | -| MLX_TOK_SLASH | / | -| MLX_TOK_PERIOD | . | -| MLX_TOK_PLUS | + | -| MLX_TOK_ASTERISK | * | -| MLX_TOK_RESERVEDWD | Reserved word (special keyword). | -| MLX_TOK_FILENAME | Unquoted string starting with / or ./ | -| MLX_TOK_DOUBLE | Double precision floating point. | -| MLX_TOK_DOLLAR | $ | -| MLX_TOK_MINUS | - | - -### char* mlxStringVal(pLxSession this, int* alloc) -Gets the string value of the current token. If 'alloc' is NULL, only the first 255 bytes of the string will be returned, and the rest will be discarded. If 'alloc' is non-null and set to 0, the routine will set 'alloc' to 1 if it needed to allocate memory for a very long string, otherwise leave it at 0. If 'alloc' is non- null and set to 1, this routine will ALWAYS allocate memory for the string, whether long or short. - -This routine works no matter what the token type, and returns a string representation of the token if not MLX_TOK_STRING. +| Token | Required Flag | Meaning | +|-------------------------|-------------------|---------------------------------------------| +| `MLX_TOK_BEGIN` | - | Beginning of the input stream. | +| `MLX_TOK_STRING` | - | String value, e.g. `"string"`. | +| `MLX_TOK_INTEGER` | - | Integer value, e.g. `42`. | +| `MLX_TOK_EQUALS` | - | `=` | +| `MLX_TOK_OPENBRACE` | - | `{` | +| `MLX_TOK_CLOSEBRACE` | - | `}` | +| `MLX_TOK_ERROR` | - | An error has occurred. | +| `MLX_TOK_KEYWORD` | - | A keyword (unquoted string). | +| `MLX_TOK_COMMA` | - | `,` | +| `MLX_TOK_EOL` | `MLX_F_EOL` | End-of-line. | +| `MLX_TOK_EOF` | `MLX_F_EOF` | End-of-file reached. | +| `MLX_TOK_COMPARE` | - | `<>` `!=` `<` `>` `>=` `<=` `==` | +| `MLX_TOK_COLON` | - | `:` | +| `MLX_TOK_OPENPAREN` | - | `(` | +| `MLX_TOK_CLOSEPAREN` | - | `)` | +| `MLX_TOK_SLASH` | - | `/` | +| `MLX_TOK_PERIOD` | - | `.` | +| `MLX_TOK_PLUS` | - | `+` | +| `MLX_TOK_ASTERISK` | - | `*` | +| `MLX_TOK_RESERVEDWD` | - | Reserved word (special keyword). | +| `MLX_TOK_FILENAME` | `MLX_F_FILENAMES` | Unquoted string starting with / or ./ | +| `MLX_TOK_DOUBLE` | - | Double precision floating point. | +| `MLX_TOK_DOLLAR` | - | `$` | +| `MLX_TOK_MINUS` | - | `-` | +| `MLX_TOK_DBLOPENBRACE` | `MLX_F_DBLBRACE` | `{{` | +| `MLX_TOK_DBLCLOSEBRACE` | `MLX_F_DBLBRACE` | `}}` | +| `MLX_TOK_SYMBOL` | - | `+-=.,<>` etc. | +| `MLX_TOK_SEMICOLON` | - | `;` | +| `MLX_TOK_SSTRING` | `MLX_F_SSTRING` | Single quote string value, e.g. `'string'`. | +| `MLX_TOK_POUND` | - | `#` | +| `MLX_TOK_MAX` | - | Max token value (internal). | + +### mlxStringVal() +```c +char* mlxStringVal(pLxSession this, int* alloc); +``` +This function gets the string value of the current token. If `alloc` is `NULL`, only the first 255 bytes of the string will be returned, and the rest will be discarded. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always allocate memory for the string, whether long or short. + +This routine works no matter what the token type, and returns a string representation of the token if not `MLX_TOK_STRING`. This routine MAY NOT be called twice for the same token. -Note that if MLX_F_ALLOWNUL is enabled, there is no way to tell from the return value of mlxStringVal() whether a NUL in the returned string is the end-of-string terminator, or whether it existed in the input data stream. Thus, this function should not be called when MLX_F_ALLOWNUL is being used. Use mlxCopyToken instead on MLX_TOK_STRING's, as it gives a definitive answer on the token length. (mlxStringVal can still be used on keywords since those will never contain a NUL, by definition). - -### int mlxIntVal(pLxSession this) -Returns the integer value of MLX_TOK_INTEGER tokens, or returns the compare type for MLX_TOK_COMPARE tokens. The compare type is a bitmask of the following flags: +- ⚠️ **Warning**: This function should not be called when `MLX_F_ALLOWNUL` is being used because it may return a null character, giving the caller no way to know whether it is the null-terminator or it simply existed in the input data stream. In this case, `mlxCopyToken()` should be used instead, as it gives a definitive answer on the token length. (`mlxStringVal()` can still be used on keywords, though, since they never contain a null, by definition). -- MLX_CMP_EQUALS -- MLX_CMP_GREATER -- MLX_CMP_LESS +### mlxIntVal() +```c +int mlxIntVal(pLxSession this); +``` +This function returns the integer value of `MLX_TOK_INTEGER` tokens, or returns the compare type for `MLX_TOK_COMPARE` tokens. The compare type is a bitmask of the `MLX_CMP_EQUALS`, `MLX_CMP_GREATER`, and `MLX_CMP_LESS` flags. For `MLX_TOK_DOUBLE` tokens, this function returns the whole part. -For MLX_TOK_DOUBLE tokens, returns the whole part. +### mlxDoubleVal() +```c +double mlxDoubleVal(pLxSession this); +``` +This function returns a double precision floating point number for either `MLX_TOK_INTEGER` or `MLX_TOK_DOUBLE` values. -### double mlxDoubleVal(pLxSession this) -Returns a double precision floating point number for either MLX_TOK_INTEGER or MLX_TOK_DOUBLE values. +### mlxCopyToken() +```c +int mlxCopyToken(pLxSession this, char* buffer, int maxlen); +``` +This function copies the contents of the current token to a string buffer, up to `maxlen` characters. It should be used instead of `mlxStringVal()`, _especially_ where null characters may be involved. This function returns the number of characters copied on success, or -1 on failure, and it can be called multiple times if more data needs to be read from the same token. -### int mlxCopyToken(pLxSession this, char* buffer, int maxlen) -For use instead of mlxStringVal, copies the contents of the current token to a string buffer, up to 'maxlen' characters. Returns the number of characters copied. This function can be called multiple times if more data needs to be read from the token. +### mlxHoldToken() +```c +int mlxHoldToken(pLxSession this); +``` +This function "puts back" a token, causing the next `mlxNextToken()` to return the current token again. This is useful when a function realizes after `mlxNextToken()` that it has read one-too-many. This function returns 0 on success, or -1 if an error occurs. -### int mlxHoldToken(pLxSession this) -Basically causes the next mlxNextToken() to do nothing but return the current token again. Used for when a routine realizes after mlxNextToken() that it has read one-too-many tokens and needs to 'put a token back'. +### mlxSetOptions() +```c +int mlxSetOptions(pLxSession this, int options); +``` +This function sets the options (`MLX_F_xxx`) for an active lexer session. The options that are valid here are `MLX_F_ICASE` and `MLX_F_IFSONLY`. This function returns 0 if successful, or -1 if an error occurs. -### int mlxSetOptions(pLxSession this, int options) -Sets options (MLX_F_xxx) in the middle of a lexer session. The options that are valid here are MLX_F_ICASE and MLX_F_IFSONLY. +### mlxUnsetOptions() +```c +int mlxUnsetOptions(pLxSession this, int options); +``` +Clears options set by [`mlxSetOptions()`](#mlxsetoptions). This function returns 0 if successful, or -1 if an error occurs. -### int mlxUnsetOptions(pLxSession this, int options) -Clears options (see above). +### mlxSetReservedWords() +```c +int mlxSetReservedWords(pLxSession this, char** res_words); +``` +This function sets the lexer to return the list of `res_words` as `MLX_TOK_RESERVEDWD` tokens instead of `MLX_TOK_KEYWORD` tokens. The list of words should be an array of character strings, with the last string in the list being `NULL`. This function returns 0 if successful, or -1 if an error occurs. -### int mlxSetReservedWords(pLxSession this, char** res_words) -Informs the lexer that a certain list of words are to be returned as MLX_TOK_RESERVEDWD instead of MLX_TOK_KEYWORD. The list of words should be an array of character strings, with the last string in the list NULL. mtlexer does not copy this list, so it must be static or have a lifetime greater than that of the lexer session. +- ⚠️ **Warning**: `mtlexer` does not copy this list! Ensure that it has a lifetime longer than that of the lexer session. -### int mlxNoteError(pLxSession this) -Generates an mssError() message of this form: +### mlxNoteError() +```c +int mlxNoteError(pLxSession this); +``` +This function generates an `mssError()` message of the form: +```bash +MLX: Error near '' +``` - MLX: Error near '' +- 📖 **Note**: The calling routine may have detected the error long after the actual place where it occurred. The MLX module just tries to come close :) -NOTE: the calling routine may have detected the error long after the actual place where it occurred. The MLX module just tries to come close :) +### mlxNotePosition() +```c +int mlxNotePosition(pLxSession this); +``` +This function generates an mssError() message of this form: +```bash +MLX: Error at line ## +``` -### int mlxNotePosition(pLxSession this) -Generates an mssError() message of this form: +- 📖 **Note**: If using a `StringSession` instead of a `pFile` session, this may not be accurate, as the string may have come from the middle of a file somewhere. Use with care. - MLX: Error at line ## -NOTE: If using a StringSession instead of a pFile session, this may not be accurate, as the string may have come from the middle of a file somewhere. Use with care. -## VIII Objectsystem Driver Testing -This section contains a list of things that can be done to test an objectsystem driver, to make sure that it is performing all basic operations normally. We will use the test_obj command line interface for testing here. For more information on test_obj commands, see the online Centrallix documentation at: http://www.centrallix.net/docs/docs.php +## XIII Driver Testing +This section contains a list of things that can be done to test an objectsystem driver and ensure that it preforms all basic operations correctly, using the [test_obj command line interface](http://www.centrallix.net/docs/docs.php). -Testing for memory leaks for each of these items is strongly encouraged, by watching memory utilization using nmDeltas() during repetitive operations (e.g., nmDeltas(), open, close, nmDeltas(), open, close, and then nmDeltas() again). +It is strongly recommended to test for invalid reads, writes, frees, and memory leaks during each of these by watching memory utilization using nmDeltas() during repetitive operations (e.g., nmDeltas(), open, close, nmDeltas(), open, close, and then nmDeltas() again). -Testing for more general bugs using the "valgrind" tool is also strongly encouraged, via running these various tests in test_obj while test_obj is running under valgrind. +Testing for more general memory bugs using the "valgrind" tool is also strongly encouraged, via running these various tests in test_obj while test_obj is running under valgrind. To properly test under Valgrind, centrallix-lib must be compiled with the configure flag `--enable-valgrind-integration` turned on. This disables `nmMalloc()` block caching (so that valgrind can properly detect memory leaks and free memory reuse), and it provides better information to valgrind's analyzer regarding MTASK threads. -Magic number checking on data structures is encouraged. To use magic number checking, determine a magic number value for each of your structures, and code that as a constant #define in your code. The magic number should be a 32-bit integer, possibly with 0x00 in either the 2nd or 3rd byte of the integer. Many existing magic number values can be found in the file "magic.h" in centrallix-lib. The 32-bit integer is placed as the first element of the structure, and set using the macro SETMAGIC(), and then tested using the macros ASSERTMAGIC(), and less commonly, ASSERTNOTMAGIC(). ASSERTMAGIC() should be used any time a pointer to the structure crosses an interface boundary. It also may be used at the entry to internal methods/functions, or when traversing linked lists of data structures, or when retrieving data structures from an array. +Magic number checking on data structures is encouraged. To use magic number checking, determine a magic number value for each of your structures, and add a #define for that constant in your code. The magic number should be a 32-bit integer, possibly with 0x00 in either the 2nd or 3rd byte of the integer. Many existing magic number values can be found in [magic.h](../centrallix-lib/include/magic.h). The 32-bit integer is placed as the first element of the structure, and set using the `SETMAGIC()` macro, then tested using the macros `ASSERTMAGIC()` macro or, less commonly, `ASSERTNOTMAGIC()`. Common times to `ASSERTMAGIC()` include: +- Any time a pointer to the structure crosses an interface boundary. +- At the entry to internal methods/functions. +- When traversing linked lists of data structures. +- When retrieving data structures from an array. +- etc. -When used in conjunction with nmMalloc() and nmFree(), ASSERTMAGIC also helps to detect the reuse of already-freed memory, since nmFree() tags the first four bytes of the memory block with the constant MGK_FREEMEM. nmFree() also looks for the constant MGK_FREEMEM in the magic number slot to detect already-freed memory (so do not use that same constant for your own magic numbers). +When used in conjunction with `nmMalloc()` and `nmFree()`, `ASSERTMAGIC` also helps to detect the reuse of already-freed memory, since `nmFree()` tags the first four bytes of the memory block with the constant `MGK_FREEMEM`. `nmFree()` also looks for the constant `MGK_FREEMEM` in the magic number slot to detect already-freed memory. (**DO NOT** use that constant for your own magic numbers!) -To properly test under Valgrind, centrallix-lib must be compiled with the configure flag --enable-valgrind-integration turned on. This disables nmMalloc block caching (so that valgrind can properly detect memory leaks and free memory reuse), and it provides better information to valgrind's analyzer regarding MTASK threads. +The term "**MUST**", as used here, means that the driver will likely cause problems if the functionality is not present. -The term "MUST", as used here, means that the driver will likely cause problems if the functionality is not present. +The term "**SHOULD**" indicates behavior which is desirable, but that might not cause immediate problems if not fully implemented. -The term "SHOULD" indicates behavior which is desirable, but may not cause problems if not fully implemented. +The term "**MAY**" refers to optional, but permissible, behavior. -The term "MAY" refers to optional, but permissible, behavior. -### A. Object opening, closing, creation, and deletion +### A. Opening, closing, creating, and deleting -1. Any object in the driver's subtree, including the node object itself, MUST be able to be opened using objOpen() and then closed using objClose(). Although it does more than just open and close, the "show" command in test_obj can be useful for testing this. +1. Any object in the driver's subtree, including the node object itself, MUST be able to be opened using `xxxOpen()` and then closed using `xxxClose()`. Although it does more than just open and close, the "show" command in test_obj can be useful for testing this. 2. Objects MUST be able to be opened regardless of the location of the node object in the ObjectSystem. For example, don't just test the driver with the node object in the top level directory of the ObjectSystem - also try it in other subdirectories. -3. New objects within the driver's subtree SHOULD be able to be created using objOpen with OBJ_O_CREAT, or using objCreate(). The flags OBJ_O_EXCL and OBJ_O_TRUNC should also be supported, where meaningful. +3. New objects within the driver's subtree SHOULD be able to be created using `xxxOpen()` with `OBJ_O_CREAT`, or using `objCreate()`. The flags `OBJ_O_EXCL` and `OBJ_O_TRUNC` should also be supported, where meaningful. + +4. Where possible, `OBJ_O_AUTONAME` should be supported on object creation. With this, the name of the object will be set to `*` in the pathname structure, and `OBJ_O_CREAT` will also be set. The driver should automatically determine a suitable "name" for the object, and subsequent calls to objGetAttrValue on "name" should return the determined name. A driver MAY choose to return NULL for "name" until after certain object properties have been set and an `xxxCommit()` operation performed. A driver MUST NOT return `*` for the object name unless `*` is truly the name chosen for the object. -4. Where possible, OBJ_O_AUTONAME should be supported on object creation. With this, the name of the object will be set to `*` in the pathname structure, and OBJ_O_CREAT will also be set. The driver should automatically determine a suitable "name" for the object, and subsequent calls to objGetAttrValue on "name" should return the determined name. A driver MAY choose to return NULL for "name" until after certain object properties have been set and an objCommit operation performed. A driver MUST NOT return `*` for the object name unless `*` is truly the name chosen for the object. +5. A driver SHOULD support deletion of any object in its subtree with the exception of the node object itself. Deletion may be done directly with `xxxDelete()`, or on an already-open object using `xxxDeleteObj()`. A driver MAY refuse to delete an object if the object still contains deletable sub-objects. Some objects in the subtree might inherently not be deletable apart from the parent objects of said objects. In those cases, deletion should not succeed. -5. A driver SHOULD support deletion of any object in its subtree with the exception of the node object itself. Deletion may be done directly with objDelete(), or on an already-open object using objDeleteObj(). A driver MAY refuse to delete an object if the object still contains deletable sub-objects. Some objects in the subtree might inherently not be deletable apart from the parent objects of said objects. In those cases, deletion should not succeed. -### B. Object attribute enumeration, getting, and setting. -1. The driver MUST NOT return system attributes (name, inner_type, and so forth) when enumerating with objGetFirst/NextAttr. +### B. Attributes -2. The driver does not need to handle objGetAttrType on the system attributes. The OSML does this. +1. The driver MUST NOT return system attributes (name, inner_type, etc) when enumerating with `xxxGetFirst()`/`xxxNextAttr()`. -3. The driver SHOULD support the attribute last_modification if at all reasonable. Not all objects can have this property however. +2. The driver MAY choose not to handle `xxxGetAttrType` on the system attributes. The OSML handles this. + +3. The driver SHOULD support the attribute `last_modification` if at all reasonable. Not all objects can have this property however. 4. The driver SHOULD support the attribute "annotation" if reasonable to do so. Database drivers should have a configurable "row annotation expression" to auto-generate annotations from existing row content, where reasonable. The driver MAY permit the user to directly set annotation values. The driver MUST return an empty string ("") for any annotation values that are unavailable. @@ -1648,43 +2249,44 @@ The term "MAY" refers to optional, but permissible, behavior. 7. The "show" command in test_obj is a good way to display a list of attributes for an object. -8. Attribute enumeration, retrieval, and modification MUST work equally well on objects returned by objOpen() and objects returned by objQueryFetch(). +8. Attribute enumeration, retrieval, and modification MUST work equally well on objects returned by `xxxOpen()` and objects returned by `xxxQueryFetch()`. -9. If a driver returns an attribute during attribute enumeration, then that attribute MUST return a valid type via objGetAttrType. +9. If a driver returns an attribute during attribute enumeration, then that attribute MUST return a valid type via `xxxGetAttrType`. -10. A driver MUST return -1 and error with a "type mismatch" type of error from objGet/SetAttrValue, if the data type is inappropriate. +10. A driver MUST return -1 and error with a "type mismatch" type of error from `xxxGetAttrValue()`/`xxxSetAttrValue()`, if the data type is inappropriate. 11. A driver MAY choose to perform auto-conversion of data types on certain attributes, but SHOULD NOT perform such auto conversion on a widespread wholesale basis. -12. A driver MAY support the DATA_T_CODE attribute data type. +12. A driver MAY support the `DATA_T_CODE` attribute data type. + +13. Drivers MAY support `DATA_T_INTVEC` and `DATA_T_STRINGVEC`. -13. Drivers MAY support DATA_T_INTVEC and DATA_T_STRINGVEC. +14. Drivers MAY support `xxxAddAttr()` and `xxxOpenAttr()`. -14. Drivers MAY support objAddAttr and objOpenAttr. +15. Drivers MAY support methods on objects. Objects without any methods should be indicated by a `NULL` return value from the method enumeration functions. -15. Drivers MAY support methods on objects. Objects without any methods should be indicated by a NULL return value from the method enumeration functions. +16. When returning attribute values, the value MUST remain valid at least until the next call to `xxxGetAttrValue()`, `xxxSetAttrValue()`, or `xxxGetAttrType()`, or until the object is closed, whichever occurs first. Drivers MUST NOT require the caller to free attribute memory. -16. When returning attribute values, the value MUST remain valid at least until the next call to objGetAttrValue, objSetAttrValue, or objGetAttrType, or until the object is closed, whichever occurs first. Drivers MUST NOT require the caller to free attribute memory. +17. When `xxxSetAttrValue()` is used, drivers MUST NOT depend on the referenced value (in the POD) being valid past the end of the call to `xxxSetAttrValue()`. -17. When objSetAttrValue is used, drivers MUST NOT depend on the referenced value (in the POD) being valid past the end of the call to objSetAttrValue(). -### C. Object querying (for subobjects) +### C. Querying Subobjects -1. If an object cannot support queries for subobjects, the OpenQuery call SHOULD fail. +1. If an object cannot support queries for subobjects, `xxxOpenQuery()` call SHOULD fail. -2. If an object can support the existence of subobjects, but has no subobjects, the OpenQuery should succeed, but calls to QueryFetch MUST return NULL. +2. If an object can support the existence of subobjects, but has no subobjects, the `xxxOpenQuery()` should succeed, but calls to `xxxQueryFetch()` MUST return `NULL`. -3. Objects returned by QueryFetch MUST remain valid even after the query is closed using QueryClose. +3. Objects returned by `xxxQueryFetch()` MUST remain valid even after the query is closed using `xxxQueryClose()`. -4. Objects returned by QueryFetch MUST also be able to be passed to OpenQuery to check for the existence of further subobjects, though the OpenQuery call is permitted to fail as in (C)(1) above. +4. Objects returned by `xxxQueryFetch()` MUST also be able to be passed to `xxxOpenQuery()` to check for the existence of further subobjects, though the `xxxOpenQuery()` call is permitted to fail as in (C)(1) above. -5. Any name returned by objGetAttrValue(name) on a queried subobject MUST be able to be used to open the same object using objOpen(). +5. Any name returned by `xxxGetAttrValue(name)` on a queried subobject MUST be usable to open the same object using `xxxOpen()`. -6. Drivers which connect to resources which are able to perform sorting and/or selection (filtering) of records or objects SHOULD use the OBJ_QY_F_FULLSORT and OBJ_QY_F_FULLQUERY flags (see previous discussion) as well as pass on the sorting and filtering expressions to the remote resource so that resource can do the filtering and/or sorting. +6. Drivers which connect to resources which are able to perform sorting and/or selection (filtering) of records or objects SHOULD use the [`OBJ_QY_F_FULLSORT`](#function-openquery) and [`OBJ_QY_F_FULLQUERY`](#function-openquery) flags. Further, they SHOULD pass on the sorting and filtering expressions to the remote resource so that resource can optimize sorting and/or filtering as needed. -7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then OBJ_QY_F_FULLSORT and/or OBJ_QY_F_FULL- QUERY MUST NOT be used. However the remote resource MAY still provide partial sorting and/or selection of data. +7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then `OBJ_QY_F_FULLSORT` and/or `OBJ_QY_F_FULL`- QUERY MUST NOT be used. However the remote resource MAY still provide partial sorting and/or selection of data. -8. Drivers SHOULD NOT use OBJ_QY_F_FULLSORT and OBJ_QY_F_FULLQUERY if there is no advantage to letting the resource perform these operations (usually, however, if the resource provides such functionality, there is advantage to letting the resource perform those operations. However, the coding burden to provide the filtering and sorting expressions to the resource, and in the correct format for the resource, may be not worth the work). +8. Drivers SHOULD NOT use `OBJ_QY_F_FULLSORT` and `OBJ_QY_F_FULLQUERY` if there is no advantage to letting the resource perform these operations (usually, however, if the resource provides such functionality, there is advantage to letting the resource perform those operations. However, the coding burden to provide the filtering and sorting expressions to the resource, and in the correct format for the resource, may be not worth the work). 9. Testing of query functionality can be done via test_obj's "query", "csv", and "ls" (or "list") commands. To test for nested querying of objects returned from QueryFetch, a SUBTREE select can be used with the "query" or "csv" commands. diff --git a/centrallix-sysdoc/Prefixes.md b/centrallix-sysdoc/Prefixes.md index b0e12c6b0..30e536324 100644 --- a/centrallix-sysdoc/Prefixes.md +++ b/centrallix-sysdoc/Prefixes.md @@ -4,6 +4,7 @@ |---------|--------------------------------------------------------------------- | aud | OSDriver - Linux OSS /dev/dsp audio (plays WAV files on ExecMethod) | bar | BarCode generator module (for prt mgmt) +| cluster | OSDriver - Cluster & search file | dat | OSDriver - Flat data file (CSV/etc) | ev | MTASK internal - event handling | exp | Expression compiler/parser/evaluator diff --git a/centrallix/Makefile.in b/centrallix/Makefile.in index 0d13843de..827a23b59 100644 --- a/centrallix/Makefile.in +++ b/centrallix/Makefile.in @@ -80,6 +80,7 @@ XSUPPORT=stparse.o \ endorsement_utils.o \ obfuscate.o \ json_util.o \ + double_metaphone.o \ double.o SUPPORT=$(patsubst %,utility/%,$(XSUPPORT)) @@ -316,7 +317,6 @@ XEXPRMODS=exp_main.o \ exp_compiler.o \ exp_evaluate.o \ exp_functions.o \ - exp_double_metaphone.o \ exp_generator.o EXPRMODS=$(patsubst %,expression/%,$(XEXPRMODS)) diff --git a/centrallix/centrallix.c b/centrallix/centrallix.c index 75e19d12d..b8a9e4ea8 100644 --- a/centrallix/centrallix.c +++ b/centrallix/centrallix.c @@ -440,7 +440,7 @@ cxDriverInit() stxInitialize(); /* Structure file driver */ qytInitialize(); /* Query Tree driver */ qypInitialize(); /* Query Pivot driver */ - clusterInitialize(); /* Cluster driver */ + clusterInitialize(); /* Cluster driver */ qyInitialize(); /* stored query (aka view) driver */ rptInitialize(); /* report writer driver */ uxpInitialize(); /* UNIX printer access driver */ diff --git a/centrallix/expression/exp_compiler.c b/centrallix/expression/exp_compiler.c index 702b31f84..3fcd83efb 100644 --- a/centrallix/expression/exp_compiler.c +++ b/centrallix/expression/exp_compiler.c @@ -1074,12 +1074,9 @@ expBindExpression(pExpression exp, pParamObjects objlist, int flags) } cm |= EXPR_MASK_EXTREF; } - else if (exp->ObjID == EXPR_CTL_CURRENT) cm |= (1<<(objlist->CurrentID)); - else if (exp->ObjID == EXPR_CTL_PARENT) cm |= (1<<(objlist->ParentID)); - else if (exp->ObjID >= 0) - { - cm |= (1<<(exp->ObjID)); - } + else if (exp->ObjID == EXPR_OBJID_CURRENT) cm |= (1<<(objlist->CurrentID)); + else if (exp->ObjID == EXPR_OBJID_PARENT) cm |= (1<<(objlist->ParentID)); + else if (exp->ObjID >= 0) cm |= (1<<(exp->ObjID)); } /** Check for absolute references in functions **/ diff --git a/centrallix/expression/exp_double_metaphone.c b/centrallix/expression/exp_double_metaphone.c deleted file mode 100644 index 8b7c4cd6f..000000000 --- a/centrallix/expression/exp_double_metaphone.c +++ /dev/null @@ -1,1521 +0,0 @@ -/************************************************************************/ -/* Text-DoubleMetaphone */ -/* Centrallix Core */ -/* */ -/* Copyright 2000, Maurice Aubrey . */ -/* All rights reserved. */ -/* */ -/* This code is copied for redistribution with modification, from the */ -/* gitpan/Text-DoubleMetaphone implementation on GitHub (1), which is */ -/* under the following license. */ -/* */ -/* This code is based heavily on the C++ implementation by Lawrence */ -/* Philips and incorporates several bug fixes courtesy of Kevin */ -/* Atkinson . */ -/* */ -/* This module is free software; you may redistribute it and/or */ -/* modify it under the same terms as Perl itself. */ -/* */ -/* A summary of the relevant content from https://dev.perl.org/licenses */ -/* has been included below for the convenience of the reader. This */ -/* information was collected and saved on September 5th, 2025 and may */ -/* differ from current information. For the most up to date copy of */ -/* this information, please use the link provided above. */ -/* */ -/* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ -/* */ -/* It is free software; you can redistribute it and/or modify it */ -/* under the terms of either: */ -/* */ -/* a) the GNU General Public License (2) as published by the Free */ -/* Software Foundation (3); either version 1 (2), or (at your */ -/* option) any later version (4), or */ -/* */ -/* b) the "Artistic License" (5). */ -/* */ -/* Citations: */ -/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ -/* 2: https://dev.perl.org/licenses/gpl1.html */ -/* 3: http://www.fsf.org */ -/* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ -/* 5: https://dev.perl.org/licenses/artistic.html */ -/* */ -/* Centrallix is published under the GNU General Public License, */ -/* satisfying the above requirement. A summary of this is included */ -/* below for the convenience of the reader. */ -/* */ -/* This program is free software; you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation; either version 2 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program; if not, write to the Free Software */ -/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ -/* 02111-1307 USA */ -/* */ -/* A copy of the GNU General Public License has been included in this */ -/* distribution in the file "COPYING". */ -/* */ -/* Module: exp_double_metaphone.c */ -/* Author: Maurice Aubrey */ -/* Description: This module implements a "sounds like" algorithm by */ -/* Lawrence Philips which he published in the June, 2000 */ -/* issue of C/C++ Users Journal. Double Metaphone is an */ -/* improved version of the original Metaphone algorithm */ -/* written by Philips'. This implementaton was written by */ -/* Maurice Aubrey for C/C++ with bug fixes provided by */ -/* Kevin Atkinson. It was revised by Israel Fuller to */ -/* better align with the Centrallix coding style and */ -/* standards so that it could be included here. */ -/************************************************************************/ - -/*** Note to future programmers reading this file (by Israel Fuller): - *** - *** This file was copied from a GitHub Repo with proper licensing (in case - *** you didn't read the legal stuff above), so feel free to check it out. - *** - *** As for this code, I've modified it to use styling and memory allocation - *** consistent with the rest of the Centrallix codebase. Also, I have added - *** documentation comments and extensive test cases (at the end of the file), - *** however, these reflect my own (possibly incorrect) understanding, which - *** might not line up with the original author. - *** - *** To be honest, though, trying to make this code as readable as possible - *** was very challenging due to all the messy boolean algebra. If there is - *** ever a professional linguist reading this, please factor out some of the - *** logic into local variables with descriptive names so that the rest of us - *** can read this code without our eyes glazing over. - *** - *** If you have any questions, please feel free to reach out to me or Greg. - *** - *** Original Source: https://github.com/gitpan/Text-meta_double_metaphone - ***/ - -#include -#include -#include -#include -#include -#include - -/*** If running in a testing environment, newmalloc is not - *** available, so we fall back to default C memory allocation. - ***/ -#ifndef TESTING -#include "cxlib/newmalloc.h" -#define META_MALLOC(size) nmSysMalloc(size) -#define META_REALLOC(ptr, size) nmSysRealloc(ptr, size) -#define META_FREE(ptr) nmSysFree(ptr) -#else -#include -#define META_MALLOC(size) malloc(size) -#define META_REALLOC(ptr, size) realloc(ptr, size) -#define META_FREE(ptr) free(ptr) -#endif - -/*** Helper function to handle checking for failed memory allocation - *** Author: Israel Fuller. - *** - *** @param ptr Pointer to the memory that should be allocated. - *** @param fname The name of the function invoked to allocate memory. - *** @param size The amount of memory being allocated. - *** @returns The pointer, for chaining. - ***/ -void* meta_check_allocation(void* ptr, const char* fname, const size_t size) - { - if (ptr == NULL) - { - /** Create the most descriptive error message we can. **/ - char error_buf[BUFSIZ]; - snprintf(error_buf, sizeof(error_buf), "exp_double_metaphone.c: Fail - %s(%lu)", fname, size); - perror(error_buf); - - // Throw error for easier locating in a debugger. - fprintf(stderr, "Program will now crash.\n"); - assert(0); - } - return ptr; - } - -/** Malloc shortcut macros. **/ -#define SAFE_MALLOC(size) \ - ({ \ - const size_t sz = (size); \ - memset(meta_check_allocation(META_MALLOC(sz), "META_MALLOC", sz), 0, sz); \ - }) -#define SAFE_REALLOC(ptr, size) \ - ({ \ - const size_t sz = (size); \ - meta_check_allocation(META_REALLOC(ptr, sz), "META_REALLOC", sz); \ - }) - -typedef struct - { - char* str; - size_t length; - size_t bufsize; - int free_str_on_destroy; - } -MetaString; - -/*** Allocates a new MetaString. - *** - *** @param init_str The initial size of the string. - *** @returns The new MetaString. - ***/ -MetaString* meta_new_string(const char* init_str) - { - MetaString *s; - char empty_string[] = ""; - - s = (MetaString*)SAFE_MALLOC(sizeof(MetaString)); - - if (init_str == NULL) - init_str = empty_string; - - s->length = strlen(init_str); - /** Preallocate a bit more for potential growth. **/ - s->bufsize = s->length + 7u; - - s->str = (char*)SAFE_MALLOC(s->bufsize * sizeof(char)); - - strncpy(s->str, init_str, s->length + 1); - s->free_str_on_destroy = 1; - - return s; - } - -/*** Frees a MetaString. - *** - *** @param s The MetaString. - ***/ -void meta_destroy_string(MetaString* s) - { - if (s == NULL) - return; - - if (s->free_str_on_destroy && s->str != NULL) - META_FREE(s->str); - - META_FREE(s); - } - -/*** Increases a MetaString's buffer size. - *** - *** @param s The MetaString* being modified. - *** @param chars_needed Minimum number of characters to increase buffer size. - ***/ -void meta_increase_buffer(MetaString* s, const size_t chars_needed) - { - s->bufsize += chars_needed + 8u; - s->str = SAFE_REALLOC(s->str, s->bufsize * sizeof(char)); - } - -/*** Convert all characters of a MetaString to uppercase. - *** - *** @param s The MetaString being modified. - ***/ -void meta_make_upper(MetaString* s) - { - for (char* i = s->str; i[0] != '\0'; i++) - *i = (char)toupper(*i); - } - -/*** @param s The MetaString being checked. - *** @param pos The character location to check within the MetaString. - *** @returns 1 if the location is out of bounds for the MetaString, - *** 0 otherwise. - ***/ -bool meta_is_out_of_bounds(MetaString* s, unsigned int pos) - { - return (s->length <= pos); - } - -/*** Checks if a character in a MetaString is a vowel. - *** - *** @param s The MetaString being checked. - *** @param pos The character location to check within the MetaString. - ***/ -bool meta_is_vowel(MetaString* s, unsigned int pos) - { - if (meta_is_out_of_bounds(s, pos)) return 0; - - const char c = *(s->str + pos); - return ((c == 'A') || (c == 'E') || (c == 'I') || - (c == 'O') || (c == 'U') || (c == 'Y')); - } - -/*** Search a MetaString for "W", "K", "CZ", or "WITZ", which indicate that the - *** string is Slavo Germanic. - *** - *** @param s The MetaString to be searched. - *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. - ***/ -bool meta_is_slavo_germanic(MetaString* s) - { - return (strstr(s->str, "W") != NULL) - || (strstr(s->str, "K") != NULL) - || (strstr(s->str, "CZ") != NULL) - || (strstr(s->str, "WITZ") != NULL); - } - -/*** @param s The MetaString being checked. - *** @param pos The character location to check within the MetaString. - *** @returns The character at the position in the MetaString, or - *** '\0' if the position is not in the MetaString. - ***/ -char meta_get_char_at(MetaString* s, unsigned int pos) - { - return (meta_is_out_of_bounds(s, pos)) ? '\0' : ((char) *(s->str + pos)); - } - -/*** Checks for to see if any of a list of strings appear in a the given - *** MetaString after the given start position. - *** - *** @attention - Note that the START value is 0 based. - *** - *** @param s The MetaString being modified. - *** @param start The zero-based start of at which to begin searching - *** within the MetaString. - *** @param length The length of the character strings being checked. - *** @returns 1 if any of the character sequences appear after the start - *** in the MetaString and 0 otherwise. - ***/ -bool meta_is_str_at(MetaString* s, unsigned int start, ...) - { - va_list ap; - - /** Should never happen. **/ - if (meta_is_out_of_bounds(s, start)) - return 0; - - const char* pos = (s->str + start); - va_start(ap, start); - - char* test; - do - { - test = va_arg(ap, char*); - if (*test && (strncmp(pos, test, strlen(test)) == 0)) - return true; - } - while (test[0] != '\0'); - - va_end(ap); - - return false; - } - -/*** Adds a string to a MetaString, expanding the MetaString if needed. - *** - *** @param s The MetaString being modified. - *** @param new_str The string being added. - ***/ -void meta_add_str(MetaString* s, const char* new_str) - { - if (new_str == NULL) - return; - - const size_t add_length = strlen(new_str); - if ((s->length + add_length) > (s->bufsize - 1)) - meta_increase_buffer(s, add_length); - - strcat(s->str, new_str); - s->length += add_length; - } - -/*** Computes double metaphone. - *** - *** Example Usage: - *** ```c - *** char* primary_code; - *** char* secondary_code; - *** meta_double_metaphone(input, &primary_code, &secondary_code); - *** ``` - *** - *** @param str The string to compute. - *** @param primary_code A pointer to a buffer where the pointer to a string - *** containing the produced primary code will be stored. - *** @param secondary_code A pointer to a buffer where the pointer to a string - *** containing the produced secondary code will be stored. - ***/ -void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) - { - size_t length; - if (str == NULL || (length = strlen(str)) == 0u) { - fprintf(stderr, "Warning: Call to meta_double_metaphone() with invalid string.\n"); - - /** Double Metaphone on an invalid string yields two empty strings. **/ - *primary_code = (char*)SAFE_MALLOC(sizeof(char)); - *secondary_code = (char*)SAFE_MALLOC(sizeof(char)); - return; - } - unsigned int current = 0; - unsigned int last = (unsigned int)(length - 1); - - /** Pad original so we can index beyond end. **/ - MetaString* original = meta_new_string(str); - meta_make_upper(original); - meta_add_str(original, " "); - - MetaString* primary = meta_new_string(""); - MetaString* secondary = meta_new_string(""); - primary->free_str_on_destroy = 0; - secondary->free_str_on_destroy = 0; - - /** Skip these if they are at start of a word. **/ - if (meta_is_str_at(original, 0, "GN", "KN", "PN", "WR", "PS", "")) - current += 1; - - /** Initial 'X' is pronounced 'Z' e.g. 'Xavier' **/ - const char first_char = meta_get_char_at(original, 0); - if (first_char == 'X') - { - meta_add_str(primary, "S"); /* 'Z' maps to 'S' */ - meta_add_str(secondary, "S"); - current += 1; - } - - /** Precomputing this is useful. **/ - const bool is_slavo_germanic = meta_is_slavo_germanic(original); - - /** Main loop. **/ - while (current < length) - { - const char cur_char = meta_get_char_at(original, current); - const char next_char = meta_get_char_at(original, current + 1); - switch (cur_char) - { - case 'A': - case 'E': - case 'I': - case 'O': - case 'U': - case 'Y': - { - if (current == 0) - { - /** All init vowels now map to 'A'. **/ - meta_add_str(primary, "A"); - meta_add_str(secondary, "A"); - } - current += 1; - break; - } - - case 'B': - { - /** "-mb", e.g", "dumb", already skipped over... **/ - meta_add_str(primary, "P"); - meta_add_str(secondary, "P"); - - current += (next_char == 'B') ? 2 : 1; - break; - } - - case 'C': - { - /** Various germanic. **/ - if ( - (current > 1) - && !meta_is_vowel(original, current - 2) - && meta_is_str_at(original, (current - 1), "ACH", "") - && meta_get_char_at(original, current + 2) != 'I' - && ( - meta_get_char_at(original, current + 2) != 'E' - || meta_is_str_at(original, (current - 2), "BACHER", "MACHER", "") - ) - ) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - current += 2; - break; - } - - /** Special case 'caesar' **/ - if (current == 0 && meta_is_str_at(original, current, "CAESAR", "")) - { - meta_add_str(primary, "S"); - meta_add_str(secondary, "S"); - current += 2; - break; - } - - /** Italian 'chianti' **/ - if (meta_is_str_at(original, current, "CHIA", "")) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - current += 2; - break; - } - - if (meta_is_str_at(original, current, "CH", "")) - { - /** Find 'michael' **/ - if (current > 0 && meta_is_str_at(original, current, "CHAE", "")) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "X"); - current += 2; - break; - } - - /** Greek roots e.g. 'chemistry', 'chorus' **/ - if ( - current == 0 - && meta_is_str_at(original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") - && !meta_is_str_at(original, 0, "CHORE", "") - ) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - current += 2; - break; - } - - /** Germanic, greek, or otherwise 'ch' for 'kh' sound. */ - if ( - meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") - /** 'architect but not 'arch', 'orchestra', 'orchid' **/ - || meta_is_str_at(original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") - || meta_is_str_at(original, (current + 2), "T", "S", "") - || ( - (current == 0 || meta_is_str_at(original, (current - 1), "A", "O", "U", "E", "")) - /** e.g., 'wachtler', 'wechsler', but not 'tichner' **/ - && meta_is_str_at(original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") - ) - ) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - } - else - { - if (current > 0) - { - if (meta_is_str_at(original, 0, "MC", "")) - { - /* e.g., "McHugh" */ - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - } - else - { - meta_add_str(primary, "X"); - meta_add_str(secondary, "K"); - } - } - else - { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); - } - } - current += 2; - break; - } - - /** e.g, 'czerny' **/ - if (meta_is_str_at(original, current, "CZ", "") - && !meta_is_str_at(original, (current - 2), "WICZ", "")) - { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); - current += 2; - break; - } - - /** e.g., 'focaccia' **/ - if (meta_is_str_at(original, (current + 1), "CIA", "")) - { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); - current += 3; - break; - } - - /** Double 'C' rule. **/ - if ( - meta_is_str_at(original, current, "CC", "") - && !(current == 1 && first_char == 'M') /* McClellan exception. */ - ) - { - /** 'bellocchio' but not 'bacchus' **/ - if ( - meta_is_str_at(original, (current + 2), "I", "E", "H", "") - && !meta_is_str_at(original, (current + 2), "HU", "") - ) - { - /** 'accident', 'accede' 'succeed' **/ - if ( - (current == 1 && meta_get_char_at(original, current - 1) == 'A') - || meta_is_str_at(original, (current - 1), "UCCEE", "UCCES", "") - ) - { - meta_add_str(primary, "KS"); - meta_add_str(secondary, "KS"); - /** 'bacci', 'bertucci', other italian **/ - } - else - { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); - } - current += 3; - break; - } - else - { /** Pierce's rule **/ - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - current += 2; - break; - } - } - - if (meta_is_str_at(original, current, "CK", "CG", "CQ", "")) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - current += 2; - break; - } - - if (meta_is_str_at(original, current, "CI", "CE", "CY", "")) - { - /* Italian vs. English */ - if (meta_is_str_at(original, current, "CIO", "CIE", "CIA", "")) - { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); - } - else - { - meta_add_str(primary, "S"); - meta_add_str(secondary, "S"); - } - current += 2; - break; - } - - /** else **/ - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - - /** Name sent in 'mac caffrey', 'mac gregor **/ - if (meta_is_str_at(original, (current + 1), " C", " Q", " G", "")) - current += 3; - else if (meta_is_str_at(original, (current + 1), "C", "K", "Q", "") - && !meta_is_str_at(original, (current + 1), "CE", "CI", "")) - current += 2; - else - current += 1; - break; - } - - case 'D': - { - if (meta_is_str_at(original, current, "DG", "")) - { - if (meta_is_str_at(original, (current + 2), "I", "E", "Y", "")) - { - /** e.g. 'edge' **/ - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); - current += 3; - break; - } - else - { - /** e.g. 'edgar' **/ - meta_add_str(primary, "TK"); - meta_add_str(secondary, "TK"); - current += 2; - break; - } - } - - if (meta_is_str_at(original, current, "DT", "DD", "")) - { - meta_add_str(primary, "T"); - meta_add_str(secondary, "T"); - current += 2; - break; - } - - /** else **/ - meta_add_str(primary, "T"); - meta_add_str(secondary, "T"); - current += 1; - break; - } - - case 'F': - { - current += (next_char == 'F') ? 2 : 1; - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); - break; - } - - case 'G': - { - if (next_char == 'H') - { - /** 'Vghee' */ - if (current > 0 && !meta_is_vowel(original, (current - 1))) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - current += 2; - break; - } - - if (current < 3) - { - /** 'ghislane', 'ghiradelli' **/ - if (current == 0) - { - if (meta_get_char_at(original, (current + 2)) == 'I') - { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); - } - else - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - } - current += 2; - break; - } - } - - if ( - /** Parker's rule (with some further refinements) - e.g., 'hugh' **/ - (current > 1 && meta_is_str_at(original, (current - 2), "B", "H", "D", "")) - /** e.g., 'bough' **/ - || (current > 2 && meta_is_str_at(original, (current - 3), "B", "H", "D", "")) - /** e.g., 'broughton' **/ - || (current > 3 && meta_is_str_at(original, (current - 4), "B", "H", "")) - ) - { - current += 2; - break; - } - else - { - /** e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough' **/ - if ( - current > 2 - && meta_get_char_at(original, (current - 1)) == 'U' - && meta_is_str_at(original, (current - 3), "C", "G", "L", "R", "T", "") - ) - { - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); - } - else if (current > 0 && meta_get_char_at(original, (current - 1)) != 'I') - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - } - - current += 2; - break; - } - } - - if (next_char == 'N') - { - if (current == 1 && !is_slavo_germanic && meta_is_vowel(original, 0)) - { - meta_add_str(primary, "KN"); - meta_add_str(secondary, "N"); - } - else - /** not e.g. 'cagney' **/ - if ( - next_char != 'Y' - && !is_slavo_germanic - && !meta_is_str_at(original, (current + 2), "EY", "") - ) - { - meta_add_str(primary, "N"); - meta_add_str(secondary, "KN"); - } - else - { - meta_add_str(primary, "KN"); - meta_add_str(secondary, "KN"); - } - current += 2; - break; - } - - /** 'tagliaro' **/ - if ( - !is_slavo_germanic - && meta_is_str_at(original, (current + 1), "LI", "") - ) - { - meta_add_str(primary, "KL"); - meta_add_str(secondary, "L"); - current += 2; - break; - } - - /** -ges-,-gep-,-gel-, -gie- at beginning **/ - if ( - current == 0 - && ( - next_char == 'Y' - || meta_is_str_at( - original, (current + 1), - "ES", "EP", "EB", "EL", "EY", "IB", - "IL", "IN", "IE", "EI", "ER", "" - ) - ) - ) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "J"); - current += 2; - break; - } - - /** -ger-, -gy- **/ - if ( - (next_char == 'Y' || meta_is_str_at(original, (current + 1), "ER", "")) - /** Exceptions. **/ - && !meta_is_str_at(original, 0, "DANGER", "RANGER", "MANGER", "") - && !meta_is_str_at(original, (current - 1), "E", "I", "RGY", "OGY", "") - ) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "J"); - current += 2; - break; - } - - /** Italian e.g, 'biaggi' **/ - if ( - meta_is_str_at(original, (current + 1), "E", "I", "Y", "") - || meta_is_str_at(original, (current - 1), "AGGI", "OGGI", "") - ) - { - /** Obvious germanic. **/ - if (meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") - || meta_is_str_at(original, (current + 1), "ET", "")) - { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - } - else - { - /** Always soft, if french ending. **/ - if (meta_is_str_at(original, (current + 1), "IER ", "")) - { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); - } - else - { - meta_add_str(primary, "J"); - meta_add_str(secondary, "K"); - } - } - current += 2; - break; - } - - current += (next_char == 'G') ? 2 : 1; - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - break; - } - - case 'H': - { - /** Only keep if first & before vowel or between 2 vowels. **/ - if ( - (current == 0 || meta_is_vowel(original, (current - 1))) - && meta_is_vowel(original, current + 1) - ) - { - meta_add_str(primary, "H"); - meta_add_str(secondary, "H"); - current += 2; - } - else /* also takes care of 'HH' */ - current += 1; - break; - } - - case 'J': - { - /** Obvious spanish, 'jose', 'san jacinto' **/ - const bool has_jose_next = meta_is_str_at(original, current, "JOSE", ""); - const bool starts_with_san = meta_is_str_at(original, 0, "SAN ", ""); - if (has_jose_next || starts_with_san) - { - if ( - starts_with_san - /** I don't know what this condition means. **/ - || (current == 0 && meta_get_char_at(original, current + 4) == ' ') - ) - { - meta_add_str(primary, "H"); - meta_add_str(secondary, "H"); - } - else - { - meta_add_str(primary, "J"); - meta_add_str(secondary, "H"); - } - current += 1; - break; - } - - if (current == 0 && !has_jose_next) - { - meta_add_str(primary, "J"); /* Yankelovich/Jankelowicz */ - meta_add_str(secondary, "A"); - } - else - { - /** spanish pron. of e.g. 'bajador' **/ - if ( - !is_slavo_germanic - && (next_char == 'A' || next_char == 'O') - && meta_is_vowel(original, (current - 1)) - ) - { - meta_add_str(primary, "J"); - meta_add_str(secondary, "H"); - } - else - { - if (current == last) - { - meta_add_str(primary, "J"); - meta_add_str(secondary, ""); - } - else - { - if ( - !meta_is_str_at(original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") - && !meta_is_str_at(original, (current - 1), "S", "K", "L", "") - ) - { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); - } - } - } - } - - current += (next_char == 'J') ? 2 : 1; - break; - } - - case 'K': - { - current += (next_char == 'K') ? 2 : 1; - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - break; - } - - case 'L': - { - if (next_char == 'L') - { - /** Spanish e.g. 'cabrillo', 'gallegos' **/ - if ( - ( - current == length - 3 - && meta_is_str_at(original, (current - 1), "ILLO", "ILLA", "ALLE", "") - ) - || ( - meta_is_str_at(original, (current - 1), "ALLE", "") - && ( - meta_is_str_at(original, (last - 1), "AS", "OS", "") - || meta_is_str_at(original, last, "A", "O", "") - ) - ) - ) - { - meta_add_str(primary, "L"); - meta_add_str(secondary, ""); - current += 2; - break; - } - current += 2; - } - else - current += 1; - meta_add_str(primary, "L"); - meta_add_str(secondary, "L"); - break; - } - - case 'M': - { - current += ( - ( - meta_is_str_at(original, (current - 1), "UMB", "") - && (current + 1 == last || meta_is_str_at(original, (current + 2), "ER", "")) - ) - /** 'dumb','thumb' **/ - || next_char == 'M' - ) ? 2 : 1; - meta_add_str(primary, "M"); - meta_add_str(secondary, "M"); - break; - } - - case 'N': - { - current += (next_char == 'N') ? 2 : 1; - meta_add_str(primary, "N"); - meta_add_str(secondary, "N"); - break; - } - - case 'P': - { - if (next_char == 'H') - { - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); - current += 2; - break; - } - - /** Also account for "campbell", "raspberry" **/ - current += (meta_is_str_at(original, (current + 1), "P", "B", "")) ? 2 : 1; - meta_add_str(primary, "P"); - meta_add_str(secondary, "P"); - break; - } - - case 'Q': - { - current += (next_char == 'Q') ? 2 : 1; - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); - break; - } - - case 'R': - { - /** French e.g. 'rogier', but exclude 'hochmeier' **/ - const bool no_primary = ( - !is_slavo_germanic - && current == last - && meta_is_str_at(original, (current - 2), "IE", "") - && !meta_is_str_at(original, (current - 4), "ME", "MA", "") - ); - - meta_add_str(primary, (no_primary) ? "" : "R"); - meta_add_str(secondary, "R"); - current += (next_char == 'R') ? 2 : 1; - break; - } - - case 'S': - { - /** Special cases 'island', 'isle', 'carlisle', 'carlysle' **/ - if (meta_is_str_at(original, (current - 1), "ISL", "YSL", "")) - { - current += 1; - break; - } - - /** Special case 'sugar-' **/ - if (current == 0 && meta_is_str_at(original, current, "SUGAR", "")) - { - meta_add_str(primary, "X"); - meta_add_str(secondary, "S"); - current += 1; - break; - } - - if (meta_is_str_at(original, current, "SH", "")) - { - const bool germanic = meta_is_str_at(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); - const char* sound = (germanic) ? "S" : "X"; - meta_add_str(primary, sound); - meta_add_str(secondary, sound); - current += 2; - break; - } - - /** Italian & Armenian. **/ - if (meta_is_str_at(original, current, "SIO", "SIA", "SIAN", "")) - { - meta_add_str(primary, "S"); - meta_add_str(secondary, (is_slavo_germanic) ? "S" : "X"); - current += 3; - break; - } - - /** german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' **/ - /** also, -sz- in slavic language although in hungarian it is pronounced 's' **/ - if (current == 0 && meta_is_str_at(original, (current + 1), "M", "N", "L", "W", "")) - { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); - current += 1; - break; - } - if (meta_is_str_at(original, (current + 1), "Z", "")) - { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); - current += 2; - break; - } - - if (meta_is_str_at(original, current, "SC", "")) - { - /** Schlesinger's rule. **/ - if (meta_get_char_at(original, current + 2) == 'H') - { - /** Dutch origin, e.g. 'school', 'schooner' **/ - if (meta_is_str_at(original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) - { - /** 'schermerhorn', 'schenker' **/ - const bool x_sound = meta_is_str_at(original, (current + 3), "ER", "EN", ""); - meta_add_str(primary, (x_sound) ? "X" : "SK"); - meta_add_str(secondary, "SK"); - current += 3; - break; - } - else - { - const bool s_sound = ( - current == 0 - && !meta_is_vowel(original, 3) - && meta_get_char_at(original, 3) != 'W' - ); - meta_add_str(primary, "X"); - meta_add_str(secondary, (s_sound) ? "S" : "X"); - current += 3; - break; - } - } - - /** Default case. **/ - const char* sound = (meta_is_str_at(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; - meta_add_str(primary, sound); - meta_add_str(secondary, sound); - current += 3; - break; - } - - /** French e.g. 'resnais', 'artois' **/ - const bool no_primary = (current == last && meta_is_str_at(original, (current - 2), "AI", "OI", "")); - meta_add_str(primary, (no_primary) ? "" : "S"); - meta_add_str(secondary, "S"); - current += (meta_is_str_at(original, (current + 1), "S", "Z", "")) ? 2 : 1; - break; - } - - case 'T': - { - if (meta_is_str_at(original, current, "TIA", "TCH", "TION", "")) - { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); - current += 3; - break; - } - - if (meta_is_str_at(original, current, "TH", "TTH", "")) - { - /** Special case 'thomas', 'thames' or germanic. **/ - if ( - meta_is_str_at(original, (current + 2), "OM", "AM", "") - || meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") - ) - meta_add_str(primary, "T"); - else - meta_add_str(primary, "0"); /* Yes, zero. */ - meta_add_str(secondary, "T"); - current += 2; - break; - } - - meta_add_str(primary, "T"); - meta_add_str(secondary, "T"); - current += (meta_is_str_at(original, (current + 1), "T", "D", "")) ? 2 : 1; - break; - } - - case 'V': - { - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); - current += (next_char == 'V') ? 2 : 1; - break; - } - - case 'W': - { - /** Can also be in middle of word. **/ - if (meta_is_str_at(original, current, "WR", "")) - { - meta_add_str(primary, "R"); - meta_add_str(secondary, "R"); - current += 2; - break; - } - - const bool next_is_vowel = meta_is_vowel(original, current + 1); - if (current == 0 && (next_is_vowel || meta_is_str_at(original, current, "WH", ""))) - { - /** Wasserman should match Vasserman. **/ - meta_add_str(primary, "A"); - meta_add_str(secondary, (next_is_vowel) ? "F" : "A"); - } - - /** Arnow should match Arnoff. **/ - if ((current == last && meta_is_vowel(original, current - 1)) - || meta_is_str_at(original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") - || meta_is_str_at(original, 0, "SCH", "") - ) - { - meta_add_str(primary, ""); - meta_add_str(secondary, "F"); - current += 1; - break; - } - - /** Polish e.g. 'filipowicz' **/ - if (meta_is_str_at(original, current, "WICZ", "WITZ", "")) - { - meta_add_str(primary, "TS"); - meta_add_str(secondary, "FX"); - current += 4; - break; - } - - /** Else skip it. **/ - current += 1; - break; - } - - case 'X': - { - /** French e.g. breaux **/ - const bool silent = ( - current == last - && ( - meta_is_str_at(original, (current - 2), "AU", "OU", "") - || meta_is_str_at(original, (current - 3), "IAU", "EAU", "") - ) - ); - if (!silent) - { - meta_add_str(primary, "KS"); - meta_add_str(secondary, "KS"); - } - - current += (meta_is_str_at(original, (current + 1), "C", "X", "")) ? 2 : 1; - break; - } - - case 'Z': - { - /** Chinese pinyin e.g. 'zhao' **/ - if (next_char == 'H') - { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); - current += 2; - break; - } - - const bool has_t_sound = ( - meta_is_str_at(original, (current + 1), "ZO", "ZI", "ZA", "") - || (is_slavo_germanic && current > 0 && meta_get_char_at(original, (current - 1)) != 'T') - ); - meta_add_str(primary, "S"); - meta_add_str(secondary, (has_t_sound) ? "TS" : "S"); - current += (next_char == 'Z') ? 2 : 1; - break; - } - - default: - current += 1; - } - } - - *primary_code = primary->str; - *secondary_code = secondary->str; - - meta_destroy_string(original); - meta_destroy_string(primary); - meta_destroy_string(secondary); - } - -#ifdef TESTING -/*** Built in test cases. - *** - *** These tests have been integrated into the Centrallix testing environment, - *** where they can be run using `export TONLY=exp_fn_double_metaphone_00`, - *** followed by make test, in the Centrallix directory. - *** - *** The can also be run here by executing the following commands in the - *** centrallix/expression directory, which aditionally generates a coverage - *** report. These tests cover all parts of the double metaphone algorithm, - *** although some of the error cases in various helper functions (such as - *** meta_destroy_string(null)) are not covered by testing. - *** - *** Commands: - *** gcc exp_double_metaphone.c -o exp_double_metaphone.o -I .. -DTESTING -fprofile-arcs -ftest-coverage -O0 - *** ./exp_double_metaphone.o - *** gcov exp_double_metaphone.c - ***/ - -unsigned int num_tests_passed = 0u, num_tests_failed = 0u; - -void test(const char* input, const char* expected_primary, const char* expected_secondary) { - char* codes[2]; - - /** Run DoubleMetaphone() and extract results. **/ - char* actual_primary; - char* actual_secondary; - meta_double_metaphone( - input, - memset(&actual_primary, 0, sizeof(actual_primary)), - memset(&actual_secondary, 0, sizeof(actual_secondary)) - ); - - /** Test for correct value. **/ - if (!strcmp(expected_primary, actual_primary) && - !strcmp(expected_secondary, actual_secondary)) - num_tests_passed++; - else - { - printf( - "\nTEST FAILED: \"%s\"\n" - "Expected: %s %s\n" - "Actual: %s %s\n", - input, - expected_primary, expected_secondary, - actual_primary, actual_secondary - ); - num_tests_failed++; - } - } - -// Special thanks to the following websites for double checking the correct results: -// 1: https://words.github.io/double-metaphone -// 2: https://mainegenealogy.net/metaphone_converter.asp -// 3: https://en.toolpage.org/tool/metaphone -void run_tests(void) { - printf("\nRunning tests...\n"); - - /** Test that always fails. **/ - // test("This", "test", "fails."); - - /** Invalid string tests, by Israel. **/ - fprintf(stderr, "There should be two warnings after this line.\n"); - test(NULL, "", ""); - test("", "", ""); - - /** Basic tests, by Israel. **/ - test("Test", "TST", "TST"); - test("Basic", "PSK", "PSK"); - test("Centrallix", "SNTRLKS", "SNTRLKS"); - test("Lawrence", "LRNS", "LRNS"); - test("Philips", "FLPS", "FLPS"); - test("Acceptingness", "AKSPTNNS", "AKSPTNKNS"); - test("Supercalifragilisticexpialidocious", "SPRKLFRJLSTSKSPLTSS", "SPRKLFRKLSTSKSPLTXS"); - test("Suoicodilaipxecitsiligarfilacrepus", "SKTLPKSSTSLKRFLKRPS", "SKTLPKSSTSLKRFLKRPS"); - - /** Match tests from code comments above. **/ - test("Smith", "SM0", "XMT"); - test("Schmidt", "XMT", "SMT"); - test("Snider", "SNTR", "XNTR"); - test("Schneider", "XNTR", "SNTR"); - test("Arnow", "ARN", "ARNF"); - test("Arnoff", "ARNF", "ARNF"); - - /** Tests from examples in code comments above. **/ - test("Accede", "AKST", "AKST"); - test("Accident", "AKSTNT", "AKSTNT"); - test("Actually", "AKTL", "AKTL"); - test("Arch", "ARX", "ARK"); - test("Artois", "ART", "ARTS"); - test("Bacchus", "PKS", "PKS"); - test("Bacci", "PX", "PX"); - test("Bajador", "PJTR", "PHTR"); - test("Bellocchio", "PLX", "PLX"); - test("Bertucci", "PRTX", "PRTX"); - test("Biaggi", "PJ", "PK"); - test("Bough", "P", "P"); - test("Breaux", "PR", "PR"); - test("Broughton", "PRTN", "PRTN"); - test("Cabrillo", "KPRL", "KPR"); - test("Caesar", "SSR", "SSR"); - test("Cagney", "KKN", "KKN"); - test("Campbell", "KMPL", "KMPL"); - test("Carlisle", "KRLL", "KRLL"); - test("Carlysle", "KRLL", "KRLL"); - test("Chemistry", "KMSTR", "KMSTR"); - test("Chianti", "KNT", "KNT"); - test("Chorus", "KRS", "KRS"); - test("Cough", "KF", "KF"); - test("Czerny", "SRN", "XRN"); - test("Dumb", "TM", "TM"); - test("Edgar", "ATKR", "ATKR"); - test("Edge", "AJ", "AJ"); - test("Filipowicz", "FLPTS", "FLPFX"); - test("Focaccia", "FKX", "FKX"); - test("Gallegos", "KLKS", "KKS"); - test("Germanic", "KRMNK", "JRMNK"); - test("Ghiradelli", "JRTL", "JRTL"); - test("Ghislane", "JLN", "JLN"); - test("Gospel", "KSPL", "KSPL"); - test("Gough", "KF", "KF"); - test("Greek", "KRK", "KRK"); - test("Hochmeier", "HKMR", "HKMR"); - test("Hugh", "H", "H"); - test("Island", "ALNT", "ALNT"); - test("Isle", "AL", "AL"); - test("Italian", "ATLN", "ATLN"); - test("Jankelowicz", "JNKLTS", "ANKLFX"); - test("Jose", "HS", "HS"); - test("Laugh", "LF", "LF"); - test("Mac Caffrey", "MKFR", "MKFR"); - test("Mac Gregor", "MKRKR", "MKRKR"); - test("Manager", "MNKR", "MNJR"); - test("McHugh", "MK", "MK"); - test("McLaughlin", "MKLFLN", "MKLFLN"); - test("Michael", "MKL", "MXL"); - test("Middle", "MTL", "MTL"); - test("Orchestra", "ARKSTR", "ARKSTR"); - test("Orchid", "ARKT", "ARKT"); - test("Pinyin", "PNN", "PNN"); - test("Raspberry", "RSPR", "RSPR"); - test("Resnais", "RSN", "RSNS"); - test("Rogier", "RJ", "RJR"); - test("Rough", "RF", "RF"); - test("Salvador", "SLFTR", "SLFTR"); - test("San jacinto", "SNHSNT", "SNHSNT"); - test("Schenker", "XNKR", "SKNKR"); - test("Schermerhorn", "XRMRRN", "SKRMRRN"); - test("Schlesinger", "XLSNKR", "SLSNJR"); - test("School", "SKL", "SKL"); - test("Schooner", "SKNR", "SKNR"); - test("Succeed", "SKST", "SKST"); - test("Sugar", "XKR", "SKR"); - test("Sugary", "XKR", "SKR"); - test("Tagliaro", "TKLR", "TLR"); - test("Thames", "TMS", "TMS"); - test("Thomas", "TMS", "TMS"); - test("Thumb", "0M", "TM"); - test("Tichner", "TXNR", "TKNR"); - test("Tough", "TF", "TF"); - test("Vghee", "FK", "FK"); - test("Wachtler", "AKTLR", "FKTLR"); - test("Wechsler", "AKSLR", "FKSLR"); - test("Word", "ART", "FRT"); - test("Xavier", "SF", "SFR"); - test("Yankelovich", "ANKLFX", "ANKLFK"); - test("Zhao", "J", "J"); - - /** Intereesting Edge Case: "McClellan" **/ - /*** Note: Sources (1) and (3) both include a double K ("MKKLLN"), but the - *** original code on GitHub and mainegenealogy.net do not. I chose "MKLLN" - *** to be correct because I personally do not pronounce the second c. - ***/ - test("McClellan", "MKLLN", "MKLLN"); - - /** Maurice Aubrey's Tests. **/ - /** Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt **/ - test("maurice", "MRS", "MRS"); - test("aubrey", "APR", "APR"); - test("cambrillo", "KMPRL", "KMPR"); - test("heidi", "HT", "HT"); - test("katherine", "K0RN", "KTRN"); - test("catherine", "K0RN", "KTRN"); - test("richard", "RXRT", "RKRT"); - test("bob", "PP", "PP"); - test("eric", "ARK", "ARK"); - test("geoff", "JF", "KF"); - test("dave", "TF", "TF"); - test("ray", "R", "R"); - test("steven", "STFN", "STFN"); - test("bryce", "PRS", "PRS"); - test("randy", "RNT", "RNT"); - test("bryan", "PRN", "PRN"); - test("brian", "PRN", "PRN"); - test("otto", "AT", "AT"); - test("auto", "AT", "AT"); - - /** GPT-5 Coverage Tests. **/ - /*** GPT-5 mini (Preview) running in GitHub Copilot suggested the words - *** after analizing a generated coverage report, and I (Israel) used - *** them to write the tests below. I kept the AI's reasoning for tests, - *** while removing tests that did not contribute any coverage, but after - *** a few reprompts, the AI started just giving words without reasoning. - *** I guess we were both getting pretty tired. - ***/ - test("Abbott", "APT", "APT"); /* double-B ("BB") handling. */ - test("Back", "PK", "PK"); /* "CK"/"CG"/"CQ" branch. */ - test("Bacher", "PKR", "PKR"); /* matches "...BACHER" / ACH special-case. */ - test("Charles", "XRLS", "XRLS"); /* initial "CH" -> the branch that maps to "X"/"X" at start. */ - test("Ghana", "KN", "KN"); /* initial "GH" special-start handling. */ - test("Gnome", "NM", "NM"); /* "GN" sequence handling. */ - test("Raj", "RJ", "R"); /* J at end (exercise J-last behavior). */ - test("Quentin", "KNTN", "KNTN"); /* Q case (Q -> K mapping). */ - test("Who", "A", "A"); /* "WH" at start handling. */ - test("Shoemaker", "XMKR", "XMKR"); /* "SH" general mapping paths. */ - test("Sian", "SN", "XN"); /* "SIO"/"SIA"/"SIAN" branch. */ - test("Scold", "SKLT", "SKLT"); /* "SC" default / "SK" vs other SC subcases. */ - test("Station", "STXN", "STXN"); /* "TION" -> X mapping. */ - test("Match", "MX", "MX"); /* "TCH"/"TIA" -> X mapping. */ - test("Pizza", "PS", "PTS"); /* double-Z ("ZZ") handling. */ - test("Agnes", "AKNS", "ANS"); /* "GN" at index 1 (GN handling that yields KN / N). */ - test("Science", "SNS", "SNS"); /* "SC" followed by I (SC + I/E/Y branch). */ - test("Van Gogh", "FNKK", "FNKK"); - test("Josef", "JSF", "HSF"); - test("Object", "APJKT", "APJKT"); - test("Sholz", "SLS", "SLS"); - test("Scharf", "XRF", "XRF"); - test("Kasia", "KS", "KS"); - test("Van Geller", "FNKLR", "FNKLR"); - - const unsigned int total_tests = num_tests_passed + num_tests_failed; - printf("\nTests completed!\n"); - printf(" > Failed: %u\n", num_tests_failed); - printf(" > Skipped: %u\n", 0u); /* Implementation removed. */ - printf(" > Passed: %u/%u\n", num_tests_passed, total_tests); -} - -int main(void) { - run_tests(); - return 0; -} - -/** Prevent scope leak. **/ -#undef META_FREE -#undef META_MALLOC -#undef META_REALLOC -#undef SAFE_MALLOC -#undef SAFE_REALLOC - -#endif diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index b2e3e84a8..474f5b1dc 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -71,34 +71,11 @@ #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "cxss/cxss.h" +#include "double_metaphone.h" #include "expression.h" #include "obj.h" -/** TODO: Greg - I think this should be moved to datatypes. **/ -/** Should maybe replace duplocate functionality elsewhere. **/ -static char* ci_TypeToStr(const int type) - { - switch (type) - { - case DATA_T_UNAVAILABLE: return "Unknown"; - case DATA_T_INTEGER: return "Integer"; - case DATA_T_STRING: return "String"; - case DATA_T_DOUBLE: return "Double"; - case DATA_T_DATETIME: return "DateTime"; - case DATA_T_INTVEC: return "IntVector"; - case DATA_T_STRINGVEC: return "StringVector"; - case DATA_T_MONEY: return "Money"; - case DATA_T_ARRAY: return "Array"; - case DATA_T_CODE: return "Code"; - case DATA_T_BINARY: return "Binary"; - } - - /** Invalid type. **/ - mssErrorf(1, "Cluster", "Invalid type %d.\n", type); - return "Invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ - } - /****** Evaluator functions follow for expEvalFunction ******/ int exp_fn_getdate(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) @@ -1253,31 +1230,6 @@ int exp_fn_rtrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpre } -int exp_fn_trim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - int ret; - - /** Invoke left trim. **/ - ret = exp_fn_ltrim(tree, objlist, i0, i1, i2); - if (ret != 0) - { - mssErrorf(0, "EXP", "Failed to left trim (error code: %d).", ret); - return ret; - } - - /** Invoke right trim. **/ - ret = exp_fn_rtrim(tree, objlist, i0, i1, i2); - if (ret != 0) - { - mssErrorf(0, "EXP", "Failed to right trim (error code: %d).", ret); - return ret; - } - - /** Success. **/ - return 0; - } - - int exp_fn_right(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { int n,i; @@ -2432,7 +2384,7 @@ int exp_fn_constrain(pExpression tree, pParamObjects objlist, pExpression i0, pE { mssError(1, "EXP", "constrain() expects three numeric parameters: %s is not numeric.", - ci_TypeToStr(i0->DataType) + objTypeToStr(i0->DataType) ); if (i0->DataType == DATA_T_STRING) printf("Value: '%s'\n", i0->String); return -1; @@ -2441,7 +2393,7 @@ int exp_fn_constrain(pExpression tree, pParamObjects objlist, pExpression i0, pE { mssError(1, "EXP", "constrain() expects three numeric parameters of the same data type but got types %s, %s, and %s.", - ci_TypeToStr(i0->DataType), ci_TypeToStr(i1->DataType), ci_TypeToStr(i2->DataType) + objTypeToStr(i0->DataType), objTypeToStr(i1->DataType), objTypeToStr(i2->DataType) ); return -1; } @@ -4158,51 +4110,43 @@ static int exp_fn_verify_schema( pExpression tree, pParamObjects obj_list) { - /** Verify object list and session. **/ - if (obj_list == NULL) - { - mssErrorf(1, "EXP", "%s(\?\?\?) no object list?", fn_name); - return -1; - } - ASSERTMAGIC(obj_list->Session, MGK_OBJSESSION); - - /** Verify expression tree. **/ - ASSERTMAGIC(tree, MGK_EXPRESSION); - - /** Verify parameter number. **/ - const int num_params_actual = tree->Children.nItems; - if (num_params != num_params_actual) - { - mssErrorf(1, "EXP", - "%s(?) expects %u param%s, got %d param%s.", - fn_name, num_params, (num_params > 1) ? "s" : "", num_params_actual, (num_params_actual > 1) ? "s" : "" - ); - return -1; - } - - /** Verify parameter datatypes. **/ - for (int i = 0; i < num_params; i++) - { - const pExpression arg = tree->Children.Items[i]; - ASSERTMAGIC(arg, MGK_EXPRESSION); - - /** Skip null values. **/ - if (arg->Flags & EXPR_F_NULL) continue; - - /** Extract datatypes. **/ - const int expected_datatype = param_types[i]; - const int actual_datatype = arg->DataType; + /** Verify expression tree. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); - /** Verify datatypes. **/ - if (expected_datatype != actual_datatype) + /** Verify parameter number. **/ + const int num_params_actual = tree->Children.nItems; + if (num_params != num_params_actual) { mssErrorf(1, "EXP", - "%s(...) param #%d/%d expects type %s (%d) but got type %s (%d).", - fn_name, i + 1, num_params, ci_TypeToStr(expected_datatype), expected_datatype, ci_TypeToStr(actual_datatype), actual_datatype + "%s(?) expects %u param%s, got %d param%s.", + fn_name, num_params, (num_params > 1) ? "s" : "", num_params_actual, (num_params_actual > 1) ? "s" : "" ); return -1; } - } + + /** Verify parameter datatypes. **/ + for (int i = 0; i < num_params; i++) + { + const pExpression arg = tree->Children.Items[i]; + ASSERTMAGIC(arg, MGK_EXPRESSION); + + /** Skip null values. **/ + if (arg->Flags & EXPR_F_NULL) continue; + + /** Extract datatypes. **/ + const int expected_datatype = param_types[i]; + const int actual_datatype = arg->DataType; + + /** Verify datatypes. **/ + if (expected_datatype != actual_datatype) + { + mssErrorf(1, "EXP", + "%s(...) param #%d/%d expects type %s (%d) but got type %s (%d).", + fn_name, i + 1, num_params, objTypeToStr(expected_datatype), expected_datatype, objTypeToStr(actual_datatype), actual_datatype + ); + return -1; + } + } /** Pass. **/ return 0; @@ -4213,44 +4157,60 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) { const char fn_name[] = "metaphone"; - /** Verify function schema. **/ - if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING }, 1, tree, obj_list) != 0) - { - mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); - return -1; - } + /** Verify function schema. **/ + if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING }, 1, tree, obj_list) != 0) + { + mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + return -1; + } + + /** Allocate space to store metaphone pointers. **/ + char* primary = NULL; + char* secondary = NULL; + + /** Extract string param. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str->Flags & EXPR_F_NULL) + { + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + return 0; + } + const char* str = check_ptr(maybe_str->String); + const size_t str_len = strlen(str); + if (str_len == 0u) + { + primary = ""; + secondary = ""; + goto store_data; + } + + /** Compute DoubleMetaphone. **/ + meta_double_metaphone(str, &primary, &secondary); + + /** Find memory to store the result. **/ + store_data:; + const size_t result_length = strlen(primary) + 1u + strlen(secondary) + 1u; + if (tree->Alloc == 1) nmSysFree(tree->String); + if (result_length < 64) + { + /** We can use the preallocated buffer. **/ + tree->String = tree->Types.StringBuf; + tree->Alloc = 0; + } + else + { + char* result = check_ptr(nmSysMalloc(result_length * sizeof(char*))); + if (result == NULL) return -1; + tree->String = result; + } + + /** Write the result into the selected memory. **/ + sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); - /** Extract string param. **/ - pExpression maybe_str = check_ptr(tree->Children.Items[0]); - if (maybe_str->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; + /** Return the result. **/ tree->DataType = DATA_T_STRING; - return 0; - } - const char* str = check_ptr(maybe_str->String); - const size_t str_len = strlen(str); - if (str_len == 0u) - { - tree->String = ""; - tree->DataType = DATA_T_STRING; - return 0; - } - - /** Compute DoubleMetaphone. **/ - char* primary = NULL; - char* secondary = NULL; - meta_double_metaphone(str, &primary, &secondary); - /** Process result. **/ - const size_t result_length = strlen(primary) + 1u + strlen(secondary) + 1u; - char* result = check_ptr(nmSysMalloc(result_length * sizeof(char*))); - if (result == NULL) return -1; - sprintf(result, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); - - /** Return the result. **/ - tree->String = result; - tree->DataType = DATA_T_STRING; return 0; } @@ -4266,69 +4226,70 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) ***/ static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) { - /** Verify function schema. **/ - if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) - { - mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); - return -1; - } - - /** Extract strings. **/ - pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); - pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); - if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; - tree->DataType = DATA_T_DOUBLE; - return 0; - } - char* str1 = check_ptr(maybe_str1->String); - char* str2 = check_ptr(maybe_str2->String); - - /** Handle either cos_compare() or lev_compare(). **/ - if (fn_name[0] == 'c') - { /* cos_compare() */ - int ret; - - /** Build vectors. **/ - const pVector v1 = check_ptr(ca_build_vector(str1)); - const pVector v2 = check_ptr(ca_build_vector(str2)); - if (v1 == NULL || v2 == NULL) + /** Verify function schema. **/ + if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) { - mssErrorf(1, "EXP", - "%s(\"%s\", \"%s\") - Failed to build vectors.", - fn_name, str1, str2 - ); - ret = -1; + mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + return -1; } - else + + /** Extract strings. **/ + pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); + pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { - /** Compute the similarity. **/ - tree->Types.Double = ca_cos_compare(v1, v2); + tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_DOUBLE; - ret = 0; + return 0; } + char* str1 = check_ptr(maybe_str1->String); + char* str2 = check_ptr(maybe_str2->String); - /** Clean up. **/ - if (v1 != NULL) ca_free_vector(v1); - if (v2 != NULL) ca_free_vector(v2); - return ret; - } - else - { /* lev_compare() */ - double lev_sim = check_double(ca_lev_compare(str1, str2)); - if (isnan(lev_sim)) - { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute levenstein edit distance."); - return -1; + /** Handle either cos_compare() or lev_compare(). **/ + if (fn_name[0] == 'c') + { /* cos_compare() */ + int ret; + + /** Build vectors. **/ + const pVector v1 = check_ptr(ca_build_vector(str1)); + const pVector v2 = check_ptr(ca_build_vector(str2)); + if (v1 == NULL || v2 == NULL) + { + mssErrorf(1, "EXP", + "%s(\"%s\", \"%s\") - Failed to build vectors.", + fn_name, str1, str2 + ); + ret = -1; + } + else + { + /** Compute the similarity. **/ + tree->Types.Double = ca_cos_compare(v1, v2); + tree->DataType = DATA_T_DOUBLE; + ret = 0; + } + + /** Clean up. **/ + if (v1 != NULL) ca_free_vector(v1); + if (v2 != NULL) ca_free_vector(v2); + return ret; + } + else + { /* lev_compare() */ + double lev_sim = check_double(ca_lev_compare(str1, str2)); + if (isnan(lev_sim)) + { + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute levenstein edit distance."); + return -1; + } + + /** Return the computed result. **/ + tree->Types.Double = lev_sim; + tree->DataType = DATA_T_DOUBLE; + return 0; } - /** Return the computed result. **/ - tree->Types.Double = lev_sim; - tree->DataType = DATA_T_DOUBLE; - return 0; - } - return -1; + return -1; } @@ -4346,37 +4307,38 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) { const char fn_name[] = "levenshtein"; - /** Verify function schema. **/ - if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) - { - mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); - return -1; - } + /** Verify function schema. **/ + if (exp_fn_verify_schema(fn_name, (int[]){ DATA_T_STRING, DATA_T_STRING }, 2, tree, obj_list) != 0) + { + mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + return -1; + } + + /** Extract strings. **/ + pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); + pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) + { + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_INTEGER; + return 0; + } + char* str1 = check_ptr(maybe_str1->String); + char* str2 = check_ptr(maybe_str2->String); + + /** Compute edit distance. **/ + /** Length 0 is provided for both strings so that the function will compute it for us. **/ + int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); + if (!check_neg(edit_dist)) + { + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute edit distance.\n", fn_name, str1, str2); + return -1; + } - /** Extract strings. **/ - pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); - pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); - if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; + /** Return the computed distance. **/ + tree->Integer = edit_dist; tree->DataType = DATA_T_INTEGER; - return 0; - } - char* str1 = check_ptr(maybe_str1->String); - char* str2 = check_ptr(maybe_str2->String); - /** Compute edit distance. **/ - /** Length 0 is provided for both strings so that the function will compute it for us. **/ - int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); - if (!check_neg(edit_dist)) - { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\") Failed to compute edit distance.\n", fn_name, str1, str2); - return -1; - } - - /** Return the computed distance. **/ - tree->Integer = edit_dist; - tree->DataType = DATA_T_INTEGER; return 0; } @@ -4503,42 +4465,27 @@ int exp_fn_argon2id(pExpression tree, pParamObjects objlist, pExpression passwor int exp_internal_DefineFunctions() { - /** Initialize library. **/ + /** Initialize clustering library. **/ ca_init(); - /** Function list for EXPR_N_FUNCTION nodes. **/ - - /** General. **/ + /** Function list for EXPR_N_FUNCTION nodes **/ + xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); xhAdd(&EXP.Functions, "user_name", (char*)exp_fn_user_name); xhAdd(&EXP.Functions, "convert", (char*)exp_fn_convert); xhAdd(&EXP.Functions, "wordify", (char*)exp_fn_wordify); xhAdd(&EXP.Functions, "abs", (char*)exp_fn_abs); xhAdd(&EXP.Functions, "ascii", (char*)exp_fn_ascii); xhAdd(&EXP.Functions, "condition", (char*)exp_fn_condition); - xhAdd(&EXP.Functions, "isnull", (char*)exp_fn_isnull); - xhAdd(&EXP.Functions, "eval", (char*)exp_fn_eval); - xhAdd(&EXP.Functions, "truncate", (char*)exp_fn_truncate); - xhAdd(&EXP.Functions, "constrain", (char*)exp_fn_constrain); - xhAdd(&EXP.Functions, "has_endorsement", (char*)exp_fn_has_endorsement); - xhAdd(&EXP.Functions, "rand", (char*)exp_fn_rand); - xhAdd(&EXP.Functions, "nullif", (char*)exp_fn_nullif); - xhAdd(&EXP.Functions, "hash", (char*)exp_fn_hash); - xhAdd(&EXP.Functions, "hmac", (char*)exp_fn_hmac); - xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); - xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); - xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); - - /** Dates. **/ - xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); + xhAdd(&EXP.Functions, "charindex", (char*)exp_fn_charindex); + xhAdd(&EXP.Functions, "upper", (char*)exp_fn_upper); + xhAdd(&EXP.Functions, "lower", (char*)exp_fn_lower); + xhAdd(&EXP.Functions, "mixed", (char*)exp_fn_mixed); + xhAdd(&EXP.Functions, "char_length", (char*)exp_fn_char_length); xhAdd(&EXP.Functions, "datepart", (char*)exp_fn_datepart); - xhAdd(&EXP.Functions, "dateadd", (char*)exp_fn_dateadd); - xhAdd(&EXP.Functions, "datediff", (char*)exp_fn_datediff); - - /** Strings. **/ + xhAdd(&EXP.Functions, "isnull", (char*)exp_fn_isnull); xhAdd(&EXP.Functions, "ltrim", (char*)exp_fn_ltrim); xhAdd(&EXP.Functions, "lztrim", (char*)exp_fn_lztrim); xhAdd(&EXP.Functions, "rtrim", (char*)exp_fn_rtrim); - xhAdd(&EXP.Functions, "trim", (char*)exp_fn_trim); xhAdd(&EXP.Functions, "substring", (char*)exp_fn_substring); xhAdd(&EXP.Functions, "right", (char*)exp_fn_right); xhAdd(&EXP.Functions, "ralign", (char*)exp_fn_ralign); @@ -4548,22 +4495,12 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "escape", (char*)exp_fn_escape); xhAdd(&EXP.Functions, "quote", (char*)exp_fn_quote); xhAdd(&EXP.Functions, "substitute", (char*)exp_fn_substitute); - xhAdd(&EXP.Functions, "upper", (char*)exp_fn_upper); - xhAdd(&EXP.Functions, "lower", (char*)exp_fn_lower); - xhAdd(&EXP.Functions, "mixed", (char*)exp_fn_mixed); - xhAdd(&EXP.Functions, "char_length", (char*)exp_fn_char_length); - xhAdd(&EXP.Functions, "charindex", (char*)exp_fn_charindex); - xhAdd(&EXP.Functions, "dateformat", (char*)exp_fn_dateformat); - xhAdd(&EXP.Functions, "moneyformat", (char*)exp_fn_moneyformat); - - /** Numbering systems (e.g. base 16 aka. hex, base 8 aka. octal, etc.). **/ - xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); - xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); - xhAdd(&EXP.Functions, "to_hex", (char*)exp_fn_to_hex); - xhAdd(&EXP.Functions, "from_hex", (char*)exp_fn_from_hex); - - /** Math. **/ + xhAdd(&EXP.Functions, "eval", (char*)exp_fn_eval); xhAdd(&EXP.Functions, "round", (char*)exp_fn_round); + xhAdd(&EXP.Functions, "dateadd", (char*)exp_fn_dateadd); + xhAdd(&EXP.Functions, "datediff", (char*)exp_fn_datediff); + xhAdd(&EXP.Functions, "truncate", (char*)exp_fn_truncate); + xhAdd(&EXP.Functions, "constrain", (char*)exp_fn_constrain); xhAdd(&EXP.Functions, "sin", (char*)exp_fn_sin); xhAdd(&EXP.Functions, "cos", (char*)exp_fn_cos); xhAdd(&EXP.Functions, "tan", (char*)exp_fn_tan); @@ -4575,23 +4512,35 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "square", (char*)exp_fn_square); xhAdd(&EXP.Functions, "degrees", (char*)exp_fn_degrees); xhAdd(&EXP.Functions, "radians", (char*)exp_fn_radians); + xhAdd(&EXP.Functions, "has_endorsement", (char*)exp_fn_has_endorsement); + xhAdd(&EXP.Functions, "rand", (char*)exp_fn_rand); + xhAdd(&EXP.Functions, "nullif", (char*)exp_fn_nullif); + xhAdd(&EXP.Functions, "dateformat", (char*)exp_fn_dateformat); + xhAdd(&EXP.Functions, "moneyformat", (char*)exp_fn_moneyformat); + xhAdd(&EXP.Functions, "hash", (char*)exp_fn_hash); + xhAdd(&EXP.Functions, "hmac", (char*)exp_fn_hmac); xhAdd(&EXP.Functions, "log10", (char*)exp_fn_log10); xhAdd(&EXP.Functions, "ln", (char*)exp_fn_log_natural); xhAdd(&EXP.Functions, "logn", (char*)exp_fn_log_base_n); xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); - - /** Duplicate detection. **/ + xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); xhAdd(&EXP.Functions, "metaphone", (char*)exp_fn_metaphone); - xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); - xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); xhAdd(&EXP.Functions, "levenshtein", (char*)exp_fn_levenshtein); - - /** Windowing. **/ + xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); + xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); + xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); + xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); + xhAdd(&EXP.Functions, "to_hex", (char*)exp_fn_to_hex); + xhAdd(&EXP.Functions, "from_hex", (char*)exp_fn_from_hex); + xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); + xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); + + /** Windowing **/ xhAdd(&EXP.Functions, "row_number", (char*)exp_fn_row_number); xhAdd(&EXP.Functions, "dense_rank", (char*)exp_fn_dense_rank); xhAdd(&EXP.Functions, "lag", (char*)exp_fn_lag); - /** Aggregate. **/ + /** Aggregate **/ xhAdd(&EXP.Functions, "count", (char*)exp_fn_count); xhAdd(&EXP.Functions, "avg", (char*)exp_fn_avg); xhAdd(&EXP.Functions, "sum", (char*)exp_fn_sum); @@ -4601,8 +4550,7 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "last", (char*)exp_fn_last); xhAdd(&EXP.Functions, "nth", (char*)exp_fn_nth); - - /** Reverse functions. **/ + /** Reverse functions **/ xhAdd(&EXP.ReverseFunctions, "isnull", (char*)exp_fn_reverse_isnull); return 0; diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h new file mode 100644 index 000000000..757195862 --- /dev/null +++ b/centrallix/include/double_metaphone.h @@ -0,0 +1,83 @@ +#ifndef DOUBLE_METAPHONE_H +#define DOUBLE_METAPHONE_H + +/************************************************************************/ +/* Text-DoubleMetaphone */ +/* Centrallix Base Library */ +/* */ +/* Copyright 2000, Maurice Aubrey . */ +/* All rights reserved. */ +/* */ +/* This code is copied for redistribution with modification, from the */ +/* gitpan/Text-DoubleMetaphone implementation on GitHub (1), which is */ +/* under the following license. */ +/* */ +/* This code is based heavily on the C++ implementation by Lawrence */ +/* Philips and incorporates several bug fixes courtesy of Kevin */ +/* Atkinson . */ +/* */ +/* This module is free software; you may redistribute it and/or */ +/* modify it under the same terms as Perl itself. */ +/* */ +/* A summary of the relevant content from https://dev.perl.org/licenses */ +/* has been included below for the convenience of the reader. This */ +/* information was collected and saved on September 5th, 2025 and may */ +/* differ from current information. For the most up to date copy of */ +/* this information, please use the link provided above. */ +/* */ +/* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ +/* */ +/* It is free software; you can redistribute it and/or modify it */ +/* under the terms of either: */ +/* */ +/* a) the GNU General Public License (2) as published by the Free */ +/* Software Foundation (3); either version 1 (2), or (at your */ +/* option) any later version (4), or */ +/* */ +/* b) the "Artistic License" (5). */ +/* */ +/* Citations: */ +/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ +/* 2: https://dev.perl.org/licenses/gpl1.html */ +/* 3: http://www.fsf.org */ +/* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ +/* 5: https://dev.perl.org/licenses/artistic.html */ +/* */ +/* Centrallix is published under the GNU General Public License, */ +/* satisfying the above requirement. A summary of this is included */ +/* below for the convenience of the reader. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: double_metaphone.c, double_metaphone.h */ +/* Author: Maurice Aubrey and Israel Fuller */ +/* Description: This module implements a "sounds like" algorithm by */ +/* Lawrence Philips which he published in the June, 2000 */ +/* issue of C/C++ Users Journal. Double Metaphone is an */ +/* improved version of the original Metaphone algorithm */ +/* written by Philips'. This implementaton was written by */ +/* Maurice Aubrey for C/C++ with bug fixes provided by */ +/* Kevin Atkinson. It was revised by Israel Fuller to */ +/* better align with the Centrallix coding style and */ +/* standards so that it could be included here. */ +/************************************************************************/ + +void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code); + +#endif /* End of .h file. */ diff --git a/centrallix/include/expression.h b/centrallix/include/expression.h index 3b334606b..8d506f72e 100644 --- a/centrallix/include/expression.h +++ b/centrallix/include/expression.h @@ -307,7 +307,6 @@ int exp_internal_SetupControl(pExpression exp); pExpControl exp_internal_LinkControl(pExpControl ctl); int exp_internal_UnlinkControl(pExpControl ctl); -void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code); /*** Evaluator functions ***/ int expEvalIsNull(pExpression tree, pParamObjects objlist); diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index 0db1ea3ef..7015b05e5 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -307,7 +307,7 @@ typedef struct _OA #define OBJ_INFO_F_NO_CONTENT (1<<13) /* object does not have content, objRead() would fail */ #define OBJ_INFO_F_SUPPORTS_INHERITANCE (1<<14) /* object can support inheritance attr cx__inherit, etc. */ #define OBJ_INFO_F_FORCED_LEAF (1<<15) /* object is forced to be a 'leaf' unless ls__type used. */ -#define OBJ_INFO_F_TEMPORARY (1<<16) /* this is a temporary object without a vaoid pathname. */ +#define OBJ_INFO_F_TEMPORARY (1<<16) /* this is a temporary object without a valid pathname. */ /** object virtual attribute - these are attributes which persist only while @@ -735,6 +735,8 @@ void obj_internal_OpenCtlToString(pPathname pathinfo, int pathstart, int pathend int obj_internal_PathToText(pPathname pathinfo, int pathend, pXString str); /** objectsystem datatype functions **/ +int objTypeFromStr(const char* str); +char* objTypeToStr(const int type); int objDataToString(pXString dest, int data_type, void* data_ptr, int flags); double objDataToDouble(int data_type, void* data_ptr); int objDataToInteger(int data_type, void* data_ptr, char* format); diff --git a/centrallix/multiquery/multiquery.c b/centrallix/multiquery/multiquery.c index 069186e80..5ba4f10c0 100644 --- a/centrallix/multiquery/multiquery.c +++ b/centrallix/multiquery/multiquery.c @@ -2124,8 +2124,10 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p } if (xs != NULL) + { strtcpy(new_qs->Source, xs->String, sizeof(new_qs->Source)); - next_state = LookForClause; + next_state = LookForClause; + } } else { diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index c674fe3df..4510e2116 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -127,6 +127,91 @@ char* obj_default_money_fmt = "$0.00"; char* obj_default_null_fmt = "NULL"; +/** Should maybe replace current type parsing in the presentation hints. **/ +/*** Parse the given string into a datatype. The case of the first character + *** is ignored, but all other characters must be capitalized correctly. + *** + *** @attention - This function is optimized to prevent performance hits + *** situations where it may need to be called many thousands of times. + *** + *** @param str The string to be parsed to a datatype. + *** @returns The datatype. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int objTypeFromStr(const char* str) + { + /** All valid types are non-null strings, at least 2 characters long. **/ + if (str == NULL || str[0] == '\0' || str[1] == '\0') return -1; + + /** Check type. **/ + switch (str[0]) + { + case 'A': case 'a': + if (strcmp(str+1, "Array"+1) == 0) return DATA_T_ARRAY; + if (strcmp(str+1, "Any"+1) == 0) return DATA_T_ANY; + break; + + case 'B': case 'b': + if (strcmp(str+1, "Binary"+1) == 0) return DATA_T_BINARY; + break; + + case 'C': case 'c': + if (strcmp(str+1, "Code"+1) == 0) return DATA_T_CODE; + break; + + case 'D': case 'd': + if (strcmp(str+1, "Double"+1) == 0) return DATA_T_DOUBLE; + if (strcmp(str+1, "DateTime"+1) == 0) return DATA_T_DATETIME; + break; + + case 'I': case 'i': + if (strcmp(str+1, "Integer"+1) == 0) return DATA_T_INTEGER; + if (strcmp(str+1, "IntVector"+1) == 0) return DATA_T_INTVEC; + break; + + case 'M': case 'm': + if (strcmp(str+1, "Money"+1) == 0) return DATA_T_MONEY; + break; + + case 'S': case 's': + if (strcmp(str+1, "String"+1) == 0) return DATA_T_STRING; + if (strcmp(str+1, "StringVector"+1) == 0) return DATA_T_STRINGVEC; + break; + + case 'U': case 'u': + if (strcmp(str+1, "Unknown"+1) == 0) return DATA_T_UNAVAILABLE; + if (strcmp(str+1, "Unavailable"+1) == 0) return DATA_T_UNAVAILABLE; + break; + } + + /** Invalid type. **/ + return -1; + } + + +/*** Convert a type to its string name. + *** + *** @param type The type to be converted. + *** @returns A char* to the type name, or + *** "(unknown)" if the type is unknown, or + *** "invalid" if the type number cannot even be a valid type. + ***/ +char* objTypeToStr(const int type) + { + /** Guard out of bounds reads. **/ + if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) + { + /** Invalid type. **/ + mssErrorf(1, "Cluster", "Invalid type %d.\n", type); + + return "invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ + } + + return obj_type_names[type]; + } + + /*** obj_internal_ParseDateLang - looks up a list of language internationalization *** strings inside the date format. WARNING - modifies the "srcptr" data in *** place. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index a41ffbd21..a30ba2d84 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1,31 +1,31 @@ /************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Core */ -/* */ -/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ -/* */ -/* This program is free software; you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation; either version 2 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program; if not, write to the Free Software */ -/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ -/* 02111-1307 USA */ -/* */ -/* A copy of the GNU General Public License has been included in this */ -/* distribution in the file "COPYING". */ -/* */ -/* Module: objdrv_cluster.c */ -/* Author: Israel Fuller */ -/* Creation: September 17, 2025 */ -/* Description: Cluster object driver. */ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: objdrv_cluster.c */ +/* Author: Israel Fuller */ +/* Creation: September 17, 2025 */ +/* Description: Cluster object driver. */ /************************************************************************/ #include @@ -77,9 +77,11 @@ /** ANCHOR[id=temp] **/ /** TODO: Greg - I think this should be moved to mtsession. **/ -/*** I caused at least 10 bugs so far trying to pass format specifiers to - *** mssError without realizing that it didn't support them. Eventually, I - *** got fed up enough with the whole thing to write the following function. +/*** I caused at least 10 bugs early in the project trying to pass format + *** specifiers to mssError() without realizing that it didn't support them. + *** Eventually, I got fed up enough having to write errors to a sting buffer + *** and passing that buffer to mssError(), so I wrote this wrapper that does + *** it for me. Adding this behavior to mssError() would be better, though. ***/ /*** Displays error text to the user. Does not print a stack trace. Does not *** exit the program, allowing for the calling function to fail, generating @@ -101,123 +103,36 @@ ***/ void mssErrorf(int clr, char* module, const char* format, ...) { - /** Prevent interlacing with stdout flushing at a weird time. **/ - check(fflush(stdout)); /* Failure ignored. */ - - /** Insert convenient newline before error stack begins. **/ - if (clr == 1) fprintf(stderr, "\n"); - - /** Process the format with all the same rules as printf(). **/ - char buf[BUFSIZ]; - va_list args; - va_start(args, format); - const int num_chars = vsnprintf(buf, sizeof(buf), format, args); - va_end(args); - - /** Error check vsnprintf, just to be safe. **/ - if (num_chars < 0) - { - perror("vsnprintf() failed"); - fprintf(stderr, "FAIL: mssErrorf(%d, \"%s\", \"%s\", ...)\n", clr, module, format); - return; - } - if (num_chars > BUFSIZ) - fprintf(stderr, "Warning: Error truncated (length %d > buffer size %d).\n", num_chars, BUFSIZ); - - /** Print the error. **/ - const int ret = mssError(clr, module, "%s", buf); - - /** Not sure why you have to error check the error function... **/ - if (ret != 0) fprintf(stderr, "FAIL %d: mssError(%d, \"%s\", \"%%s\", \"%s\")\n", ret, clr, module, buf); - } - - -/** TODO: Greg - I think this should be moved to datatypes. **/ -/** Should maybe replace current type parsing in the presentation hints. **/ -/*** Parse the given string into a datatype. The case of the first character - *** is ignored, but all other characters must be capitalized correctly. - *** - *** @attention - This function is optimized to prevent performance hits - *** situations where it may need to be called many thousands of times. - *** - *** @param str The string to be parsed to a datatype. - *** @returns The datatype. - *** - *** LINK ../../centrallix-lib/include/datatypes.h:72 - ***/ -static int ci_TypeFromStr(const char* str) - { - /** All valid types are non-null strings, at least 2 characters long. **/ - if (str == NULL || str[0] == '\0' || str[1] == '\0') return -1; - - /** Check type. **/ - switch (str[0]) - { - case 'A': case 'a': - if (strcmp(str+1, "Array"+1) == 0) return DATA_T_ARRAY; - if (strcmp(str+1, "Any"+1) == 0) return DATA_T_ANY; - break; - - case 'B': case 'b': - if (strcmp(str+1, "Binary"+1) == 0) return DATA_T_BINARY; - break; - - case 'C': case 'c': - if (strcmp(str+1, "Code"+1) == 0) return DATA_T_CODE; - break; - - case 'D': case 'd': - if (strcmp(str+1, "Double"+1) == 0) return DATA_T_DOUBLE; - if (strcmp(str+1, "DateTime"+1) == 0) return DATA_T_DATETIME; - break; - - case 'I': case 'i': - if (strcmp(str+1, "Integer"+1) == 0) return DATA_T_INTEGER; - if (strcmp(str+1, "IntVector"+1) == 0) return DATA_T_INTVEC; - break; - - case 'M': case 'm': - if (strcmp(str+1, "Money"+1) == 0) return DATA_T_MONEY; - break; - - case 'S': case 's': - if (strcmp(str+1, "String"+1) == 0) return DATA_T_STRING; - if (strcmp(str+1, "StringVector"+1) == 0) return DATA_T_STRINGVEC; - break; - - case 'U': case 'u': - if (strcmp(str+1, "Unknown"+1) == 0) return DATA_T_UNAVAILABLE; - if (strcmp(str+1, "Unavailable"+1) == 0) return DATA_T_UNAVAILABLE; - break; - } - - /** Invalid type. **/ - return -1; + /** Prevent interlacing with stdout flushing at a weird time. **/ + check(fflush(stdout)); /* Failure ignored. */ + + /** Insert convenient newline before error stack begins. **/ + if (clr == 1) fprintf(stderr, "\n"); + + /** Process the format with all the same rules as printf(). **/ + char buf[BUFSIZ]; + va_list args; + va_start(args, format); + const int num_chars = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + /** Error check vsnprintf, just to be safe. **/ + if (num_chars < 0) + { + perror("vsnprintf() failed"); + fprintf(stderr, "FAIL: mssErrorf(%d, \"%s\", \"%s\", ...)\n", clr, module, format); + return; + } + if (num_chars > BUFSIZ) + fprintf(stderr, "Warning: Error truncated (length %d > buffer size %d).\n", num_chars, BUFSIZ); + + /** Print the error. **/ + const int ret = mssError(clr, module, "%s", buf); + + /** Not sure why you have to error check the error function... **/ + if (ret != 0) fprintf(stderr, "FAIL %d: mssError(%d, \"%s\", \"%%s\", \"%s\")\n", ret, clr, module, buf); } -/** TODO: Greg - I think this should be moved to datatypes. **/ -/** Should maybe replace this functionality where it appears elsewhere. **/ -static char* ci_TypeToStr(const int type) - { - switch (type) - { - case DATA_T_UNAVAILABLE: return "Unknown"; - case DATA_T_INTEGER: return "Integer"; - case DATA_T_STRING: return "String"; - case DATA_T_DOUBLE: return "Double"; - case DATA_T_DATETIME: return "DateTime"; - case DATA_T_INTVEC: return "IntVector"; - case DATA_T_STRINGVEC: return "StringVector"; - case DATA_T_MONEY: return "Money"; - case DATA_T_ARRAY: return "Array"; - case DATA_T_CODE: return "Code"; - case DATA_T_BINARY: return "Binary"; - } - - /** Invalid type. **/ - mssErrorf(1, "Cluster", "Invalid type %d.\n", type); - return "Invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ - } /** TODO: Greg - I think this should be moved to xarray. **/ /*** Trims an xArray, returning a new array (with nmSysMalloc). @@ -231,25 +146,25 @@ static char* ci_TypeToStr(const int type) ***/ static void** ci_xaToTrimmedArray(pXArray arr, int array_handling) { - const size_t arr_size = arr->nItems * sizeof(void*); - void** result = check_ptr(nmSysMalloc(arr_size)); - if (result == NULL) return NULL; - memcpy(result, arr->Items, arr_size); - - /** Handle the array. **/ - switch (array_handling) - { - case 0: break; - case 1: check(xaDeInit(arr)); arr->nAlloc = 0; break; /* Failure ignored. */ - case 2: check(xaFree(arr)); break; /* Failure ignored. */ - default: - /** Uh oh, there might be a memory leak... **/ - fprintf(stderr, - "Warning: ci_xaToTrimmedArray(%p, %d) - Unknown value (%d) for array_handling.\n", - arr, array_handling, array_handling - ); - break; - } + const size_t arr_size = arr->nItems * sizeof(void*); + void** result = check_ptr(nmSysMalloc(arr_size)); + if (result == NULL) return NULL; + memcpy(result, arr->Items, arr_size); + + /** Handle the array. **/ + switch (array_handling) + { + case 0: break; + case 1: check(xaDeInit(arr)); arr->nAlloc = 0; break; /* Failure ignored. */ + case 2: check(xaFree(arr)); break; /* Failure ignored. */ + default: + /** Uh oh, there might be a memory leak... **/ + fprintf(stderr, + "Warning: ci_xaToTrimmedArray(%p, %d) - Unknown value (%d) for array_handling.\n", + arr, array_handling, array_handling + ); + break; + } return result; } @@ -257,13 +172,13 @@ static void** ci_xaToTrimmedArray(pXArray arr, int array_handling) /** I got tired of forgetting how to do these. **/ #define ci_file_name(obj) \ ({ \ - __typeof__ (obj) _obj = (obj); \ - obj_internal_PathPart(_obj->Pathname, _obj->SubPtr - 1, 1); \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, _obj->SubPtr - 1, 1); \ }) #define ci_file_path(obj) \ ({ \ - __typeof__ (obj) _obj = (obj); \ - obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ }) @@ -295,17 +210,17 @@ ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[nClusteringAlgorithms] = /** Converts a clustering algorithm to its string name. **/ char* ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) { - switch (clustering_algorithm) - { - case ALGORITHM_NULL: return "NULL algorithm"; - case ALGORITHM_NONE: return "none"; - case ALGORITHM_SLIDING_WINDOW: return "sliding-window"; - case ALGORITHM_KMEANS: return "k-means"; - case ALGORITHM_KMEANS_PLUS_PLUS: return "k-means++"; - case ALGORITHM_KMEDOIDS: return "k-medoids"; - case ALGORITHM_DB_SCAN: return "db-scan"; - default: return "Unknown algorithm"; - } + switch (clustering_algorithm) + { + case ALGORITHM_NULL: return "NULL algorithm"; + case ALGORITHM_NONE: return "none"; + case ALGORITHM_SLIDING_WINDOW: return "sliding-window"; + case ALGORITHM_KMEANS: return "k-means"; + case ALGORITHM_KMEANS_PLUS_PLUS: return "k-means++"; + case ALGORITHM_KMEDOIDS: return "k-medoids"; + case ALGORITHM_DB_SCAN: return "db-scan"; + default: return "Unknown algorithm"; + } } /** Enum representing a similarity measurement algorithm. **/ @@ -325,13 +240,13 @@ SimilarityMeasure ALL_SIMILARITY_MEASURES[nSimilarityMeasures] = /** Converts a similarity measure to its string name. **/ char* ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) { - switch (similarity_measure) - { - case SIMILARITY_NULL: return "NULL similarity measure"; - case SIMILARITY_COSINE: return "cosine"; - case SIMILARITY_LEVENSHTEIN: return "levenshtein"; - default: return "Unknown similarity measure"; - } + switch (similarity_measure) + { + case SIMILARITY_NULL: return "NULL similarity measure"; + case SIMILARITY_COSINE: return "cosine"; + case SIMILARITY_LEVENSHTEIN: return "levenshtein"; + default: return "Unknown similarity measure"; + } } /*** Enum representing the type of data targetted by the driver, @@ -342,7 +257,7 @@ char* ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) *** However, there is currently no allowed `NULL` TargetType. ***/ typedef unsigned char TargetType; -#define TARGET_ROOT (TargetType)1u +#define TARGET_NODE (TargetType)1u #define TARGET_CLUSTER (TargetType)2u #define TARGET_SEARCH (TargetType)3u #define TARGET_CLUSTER_ENTRY (TargetType)4u @@ -374,8 +289,6 @@ char* const ATTR_SEARCH[] = "source", "threshold", "similarity_measure", - "date_created", - "date_computed", END_OF_ARRAY, }; char* const ATTR_CLUSTER_ENTRY[] = @@ -390,13 +303,11 @@ char* const ATTR_SEARCH_ENTRY[] = "key1", "key2", "sim", - "date_created", - "date_computed", END_OF_ARRAY, }; /** Method name list. **/ -char* const METHOD_NAME[] = +char* const METHOD_NAMES[] = { "cache", "stat", @@ -571,7 +482,7 @@ typedef struct _SEARCH /*** Node instance data. *** *** Memory Stats: - *** - Padding: 4 bytes + *** - Padding: 0 bytes *** - Total size: 64 bytes *** *** @note When a .cluster file is openned, there will be only one node for that @@ -582,13 +493,15 @@ typedef struct _SEARCH *** @param SourceData Data from the provided source. *** @param Params A pParam array storing the params in the .cluster file. *** @param nParams The number of specified params. - *** @param ParamList Functions as a "scope" for resolving values during parsing. - *** @param ClusterDatas A pCluster array storing the clusters in the .cluster file. - *** Will be NULL if nClusters = 0. + *** @param ParamList A "scope" for resolving parameter values during parsing. + *** @param ClusterDatas A pCluster array for the clusters in the .cluster file. + *** Will be NULL if `nClusters = 0`. *** @param nClusterDatas The number of specified clusters. - *** @param SearchDatas A SearchData array storing the searches in the .cluster file. + *** @param SearchDatas A SearchData array for the searches in the .cluster file. *** @param nSearches The number of specified searches. *** @param nSearchDatas The parent object used to open this NodeData instance. + *** @param OpenCount The number of open driver instances that are using the + *** NodeData struct. When this reaches 0, the struct should be freed. ***/ typedef struct _NODE { @@ -598,6 +511,7 @@ typedef struct _NODE pSourceData SourceData; pClusterData* ClusterDatas; pSearchData* SearchDatas; + unsigned int OpenCount; unsigned int nParams; unsigned int nClusterDatas; unsigned int nSearchDatas; @@ -630,7 +544,7 @@ typedef struct _NODE *** @param TargetType The type of data targetted (see above). *** @param TargetData If target type is: *** ```csv - *** Root: A pointer to the SourceData struct. + *** Node: A pointer to the SourceData struct. *** Cluster or ClusterEntry: A pointer to the targetted cluster. *** Search or SearchEntry: A pointer to the targetted search. *** ``` @@ -672,7 +586,7 @@ struct XHashTable ClusterDataCache; XHashTable SearchDataCache; } - ClusterDriverCaches; + ClusterDriverCaches = {0}; struct { @@ -775,7 +689,7 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt); /** Format a hint to give to the user. **/ static void ci_GiveHint(const char* hint) { - fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); + fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); } @@ -791,11 +705,12 @@ static void ci_GiveHint(const char* hint) ***/ static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) { - char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.25); - if (guess == NULL) return false; /* No hint. */ + char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.25); + if (guess == NULL) return false; /* No hint. */ + + /** Issue hint. **/ + ci_GiveHint(guess); - /** Issue hint. **/ - ci_GiveHint(guess); return true; } @@ -823,61 +738,63 @@ static int ci_ParseAttribute( { int ret; - /** Get attribute inf. **/ - pStructInf attr_info = stLookup(inf, attr_name); - if (attr_info == NULL) - { - if (required) mssErrorf(1, "Cluster", "'%s' must be specified for clustering.", attr_name); - return 1; - } - ASSERTMAGIC(attr_info, MGK_STRUCTINF); - - /** Allocate expression. **/ - pExpression exp = check_ptr(stGetExpression(attr_info, 0)); - if (exp == NULL) goto err; - - /** Bind parameters. **/ - /** TODO: Greg - What does this return? How do I know if it fails? **/ - expBindExpression(exp, param_list, EXPR_F_RUNSERVER); - - /** Evaluate expression. **/ - ret = expEvalTree(exp, param_list); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Expression evaluation failed (error code %d).", ret); - goto err; - } - - /** Check for data type mismatch. **/ - if (datatype != exp->DataType) - { - mssErrorf(1, "Cluster", - "Expected ['%s' : %s], but got type %s.", - attr_name, ci_TypeToStr(datatype), ci_TypeToStr(exp->DataType) - ); - goto err; - } - - /** Get the data out of the expression. **/ - ret = expExpressionToPod(exp, datatype, data); - if (ret != 0) - { - mssErrorf(1, "Cluster", - "Failed to get ['%s' : %s] using expression \"%s\" (error code %d).", - attr_name, ci_TypeToStr(datatype), exp->Name, ret - ); - goto err; - } - - /** Success. **/ - return 0; - + /** Get attribute inf. **/ + pStructInf attr_info = stLookup(inf, attr_name); + if (attr_info == NULL) + { + if (required) mssErrorf(1, "Cluster", "'%s' must be specified for clustering.", attr_name); + return 1; + } + ASSERTMAGIC(attr_info, MGK_STRUCTINF); + + /** Allocate expression. **/ + pExpression exp = check_ptr(stGetExpression(attr_info, 0)); + if (exp == NULL) goto err; + + /** Bind parameters. **/ + /** TODO: Greg - What does this return? How do I know if it fails? **/ + expBindExpression(exp, param_list, EXPR_F_RUNSERVER); + + /** Evaluate expression. **/ + ret = expEvalTree(exp, param_list); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Expression evaluation failed (error code %d).", ret); + goto err; + } + + /** Check for data type mismatch. **/ + if (datatype != exp->DataType) + { + mssErrorf(1, "Cluster", + "Expected ['%s' : %s], but got type %s.", + attr_name, objTypeToStr(datatype), objTypeToStr(exp->DataType) + ); + goto err; + } + + /** Get the data out of the expression. **/ + ret = expExpressionToPod(exp, datatype, data); + if (ret != 0) + { + mssErrorf(1, "Cluster", + "Failed to get ['%s' : %s] using expression \"%s\" (error code %d).", + attr_name, objTypeToStr(datatype), exp->Name, ret + ); + goto err; + } + + /** Success. **/ + return 0; + err: - mssErrorf(0, "Cluster", - "Failed to parse attribute \"%s\" from group \"%s\"", - attr_name, inf->Name - ); - return -1; + mssErrorf(0, "Cluster", + "Failed to parse attribute \"%s\" from group \"%s\"", + attr_name, inf->Name + ); + + /** Return error. **/ + return -1; } @@ -894,34 +811,34 @@ static int ci_ParseAttribute( ***/ static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) { - /** Get the algorithm attribute. **/ - char* algorithm; - if (ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0) - { - mssErrorf(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); - return ALGORITHM_NULL; - } - - /** Parse known clustering algorithms. **/ - if (!strcasecmp(algorithm, "none")) return ALGORITHM_NONE; - if (!strcasecmp(algorithm, "sliding-window")) return ALGORITHM_SLIDING_WINDOW; - if (!strcasecmp(algorithm, "k-means")) return ALGORITHM_KMEANS; - if (!strcasecmp(algorithm, "k-means++")) return ALGORITHM_KMEANS_PLUS_PLUS; - if (!strcasecmp(algorithm, "k-medoids")) return ALGORITHM_KMEDOIDS; - if (!strcasecmp(algorithm, "db-scan")) return ALGORITHM_DB_SCAN; - - /** Unknown value for clustering algorithm. **/ - mssErrorf(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); - - /** Attempt to give a hint. **/ - char* all_names[nClusteringAlgorithms] = {NULL}; - for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) - all_names[i] = ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); - if (ci_TryHint(algorithm, all_names, nClusteringAlgorithms)); - else if (strcasecmp(algorithm, "sliding") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); - else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); - else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); - else if (strcasecmp(algorithm, "nothing") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + /** Get the algorithm attribute. **/ + char* algorithm; + if (ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0) + { + mssErrorf(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); + return ALGORITHM_NULL; + } + + /** Parse known clustering algorithms. **/ + if (!strcasecmp(algorithm, "none")) return ALGORITHM_NONE; + if (!strcasecmp(algorithm, "sliding-window")) return ALGORITHM_SLIDING_WINDOW; + if (!strcasecmp(algorithm, "k-means")) return ALGORITHM_KMEANS; + if (!strcasecmp(algorithm, "k-means++")) return ALGORITHM_KMEANS_PLUS_PLUS; + if (!strcasecmp(algorithm, "k-medoids")) return ALGORITHM_KMEDOIDS; + if (!strcasecmp(algorithm, "db-scan")) return ALGORITHM_DB_SCAN; + + /** Unknown value for clustering algorithm. **/ + mssErrorf(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); + + /** Attempt to give a hint. **/ + char* all_names[nClusteringAlgorithms] = {NULL}; + for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) + all_names[i] = ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); + if (ci_TryHint(algorithm, all_names, nClusteringAlgorithms)); + else if (strcasecmp(algorithm, "sliding") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + else if (strcasecmp(algorithm, "nothing") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); /** Fail. **/ return ALGORITHM_NULL; @@ -942,30 +859,30 @@ static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObject ***/ static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) { - /** Get the similarity_measure attribute. **/ - char* measure; - if (ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0) - { - mssErrorf(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); - return SIMILARITY_NULL; - } - - /** Parse known clustering algorithms. **/ - if (!strcasecmp(measure, "cosine")) return SIMILARITY_COSINE; - if (!strcasecmp(measure, "levenshtein")) return SIMILARITY_LEVENSHTEIN; - - /** Unknown similarity measure. **/ - mssErrorf(1, "Cluster", "Unknown \"similarity measure\": %s", measure); - - /** Attempt to give a hint. **/ - char* all_names[nSimilarityMeasures] = {NULL}; - for (unsigned int i = 0u; i < nSimilarityMeasures; i++) - all_names[i] = ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); - if (ci_TryHint(measure, all_names, nSimilarityMeasures)); - else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); - else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); - else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); - else if (strcasecmp(measure, "edit-distance") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + /** Get the similarity_measure attribute. **/ + char* measure; + if (ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0) + { + mssErrorf(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); + return SIMILARITY_NULL; + } + + /** Parse known clustering algorithms. **/ + if (!strcasecmp(measure, "cosine")) return SIMILARITY_COSINE; + if (!strcasecmp(measure, "levenshtein")) return SIMILARITY_LEVENSHTEIN; + + /** Unknown similarity measure. **/ + mssErrorf(1, "Cluster", "Unknown \"similarity measure\": %s", measure); + + /** Attempt to give a hint. **/ + char* all_names[nSimilarityMeasures] = {NULL}; + for (unsigned int i = 0u; i < nSimilarityMeasures; i++) + all_names[i] = ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); + if (ci_TryHint(measure, all_names, nSimilarityMeasures)); + else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); + else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-distance") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); /** Fail. **/ return SIMILARITY_NULL; @@ -989,71 +906,78 @@ static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) { char* buf = NULL; - - /** Allocate SourceData. **/ - pSourceData source_data = check_ptr(nmMalloc(sizeof(SourceData))); - if (source_data == NULL) goto err_free; - memset(source_data, 0, sizeof(SourceData)); - - /** Initialize obvious values for SourceData. **/ - source_data->Name = check_ptr(nmSysStrdup(inf->Name)); - if (source_data->Name == NULL) goto err_free; - if (!check(objCurrentDate(&source_data->DateCreated))) goto err_free; - - /** Get source. **/ - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; - source_data->SourcePath = check_ptr(nmSysStrdup(buf)); - if (source_data->SourcePath == NULL) goto err_free; - - /** Get the attribute name to use when querying keys from the source. **/ - if (ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; - source_data->KeyAttr = check_ptr(nmSysStrdup(buf)); - if (source_data->KeyAttr == NULL) goto err_free; - - /** Get the attribute name to use for querying data from the source. **/ - if (ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; - source_data->NameAttr = check_ptr(nmSysStrdup(buf)); - if (source_data->NameAttr == NULL) goto err_free; - - /** Create cache entry key. **/ - const size_t len = strlen(path) + strlen(source_data->SourcePath) + strlen(source_data->KeyAttr) + strlen(source_data->NameAttr) + 5lu; - source_data->Key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (source_data->Key == NULL) goto err_free; - snprintf(source_data->Key, len, "%s?%s->%s:%s", path, source_data->SourcePath, source_data->KeyAttr, source_data->NameAttr); - - /** Check for a cached version. **/ - pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, source_data->Key); - if (source_maybe != NULL) - { - /** Cache hit. **/ + pSourceData source_data = NULL; + + /** Allocate SourceData. **/ + source_data = check_ptr(nmMalloc(sizeof(SourceData))); + if (source_data == NULL) goto err_free; + memset(source_data, 0, sizeof(SourceData)); - /** Cause an immediate invalid read if cache was incorrectly freed. **/ + /** Initialize obvious values for SourceData. **/ + source_data->Name = check_ptr(nmSysStrdup(inf->Name)); + if (source_data->Name == NULL) goto err_free; + if (!check(objCurrentDate(&source_data->DateCreated))) goto err_free; - /** Free data we don't need. **/ - nmSysFree(source_data->Key); - ci_FreeSourceData(source_data); + /** Get source. **/ + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; + source_data->SourcePath = check_ptr(nmSysStrdup(buf)); + if (source_data->SourcePath == NULL) goto err_free; - /** Return the cached source data. **/ - return source_maybe; - } - - /** Cache miss: Add the new object to the cache for next time. **/ - if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->Key, (void*)source_data))) - goto err_free; - - /** Success. **/ - return source_data; - - /** Error handling. **/ + /** Get the attribute name to use when querying keys from the source. **/ + if (ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; + source_data->KeyAttr = check_ptr(nmSysStrdup(buf)); + if (source_data->KeyAttr == NULL) goto err_free; + + /** Get the attribute name to use for querying data from the source. **/ + if (ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; + source_data->NameAttr = check_ptr(nmSysStrdup(buf)); + if (source_data->NameAttr == NULL) goto err_free; + + /** Create cache entry key. **/ + const size_t len = strlen(path) + + strlen(source_data->SourcePath) + + strlen(source_data->KeyAttr) + + strlen(source_data->NameAttr) + 5lu; + source_data->Key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (source_data->Key == NULL) goto err_free; + snprintf(source_data->Key, len, + "%s?%s->%s:%s", + path, source_data->SourcePath, source_data->KeyAttr, source_data->NameAttr + ); + + /** Check for a cached version. **/ + pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, source_data->Key); + if (source_maybe != NULL) + { /* Cache hit. */ + /** Free data we don't need. **/ + nmSysFree(source_data->Key); + ci_FreeSourceData(source_data); + + /** Return the cached source data. **/ + return source_maybe; + } + + /** Cache miss: Add the new object to the cache for next time. **/ + if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->Key, (void*)source_data))) + goto err_free; + + /** Success. **/ + return source_data; + + /** Error handling. **/ err_free: - if (source_data->Key != NULL) nmSysFree(source_data->Key); - if (source_data != NULL) ci_FreeSourceData(source_data); - - mssErrorf(0, "Cluster", - "Failed to parse source data from group \"%s\" in file: %s", - inf->Name, path - ); - return NULL; + if (source_data != NULL) + { + if (source_data->Key != NULL) nmSysFree(source_data->Key); + ci_FreeSourceData(source_data); + } + + mssErrorf(0, "Cluster", + "Failed to parse source data from group \"%s\" in file: %s", + inf->Name, path + ); + + return NULL; } @@ -1074,279 +998,285 @@ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) { int result; + pClusterData cluster_data = NULL; + XArray sub_clusters = {0}; + char* key = NULL; - /** Extract values. **/ - pParamObjects param_list = node_data->ParamList; - pSourceData source_data = node_data->SourceData; - - /** Allocate space for data struct. **/ - pClusterData cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); - if (cluster_data == NULL) goto err; - memset(cluster_data, 0, sizeof(ClusterData)); - - /** Basic Properties. **/ - cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); - if (cluster_data->Name == NULL) goto err_free_cluster; - cluster_data->SourceData = check_ptr(source_data); - if (cluster_data->SourceData == NULL) goto err_free_cluster; - if (!check(objCurrentDate(&cluster_data->DateCreated))) goto err_free_cluster; - - /** Get algorithm. **/ - cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); - if (cluster_data->ClusterAlgorithm == ALGORITHM_NULL) goto err_free_cluster; - - /** Handle no clustering case. **/ - if (cluster_data->ClusterAlgorithm == ALGORITHM_NONE) - { - cluster_data->nClusters = 1u; - goto parsing_done; - } - - /** Get similarity_measure. **/ - cluster_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); - if (cluster_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free_cluster; - - /** Handle sliding window case. **/ - if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) - { - /** Sliding window doesn't allocate any clusters. **/ - cluster_data->nClusters = 0u; - - /** Get window_size. **/ - int window_size; - if (ci_ParseAttribute(inf, "window_size", DATA_T_INTEGER, POD(&window_size), param_list, true, true) != 0) - goto err_free_cluster; - if (window_size < 1) + /** Extract values. **/ + pParamObjects param_list = node_data->ParamList; + pSourceData source_data = node_data->SourceData; + + /** Allocate space for data struct. **/ + cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); + if (cluster_data == NULL) goto err_free; + memset(cluster_data, 0, sizeof(ClusterData)); + + /** Basic Properties. **/ + cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); + if (cluster_data->Name == NULL) goto err_free; + cluster_data->SourceData = check_ptr(source_data); + if (cluster_data->SourceData == NULL) goto err_free; + if (!check(objCurrentDate(&cluster_data->DateCreated))) goto err_free; + + /** Get algorithm. **/ + cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); + if (cluster_data->ClusterAlgorithm == ALGORITHM_NULL) goto err_free; + + /** Handle no clustering case. **/ + if (cluster_data->ClusterAlgorithm == ALGORITHM_NONE) { - mssErrorf(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); - goto err_free_cluster; + cluster_data->nClusters = 1u; + goto parsing_done; } - /** Store value. **/ - cluster_data->MaxIterations = (unsigned int)window_size; - goto parsing_done; - } - - /** Get num_clusters. **/ - int num_clusters; - if (ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0) - goto err_free_cluster; - if (num_clusters < 2) - { - mssErrorf(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); - if (num_clusters == 1) fprintf(stderr, "HINT: Use algorithm=\"none\" to disable clustering.\n"); - goto err_free_cluster; - } - cluster_data->nClusters = (unsigned int)num_clusters; - - /** Get min_improvement. **/ - double improvement; - result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); - if (result == 1) cluster_data->MinImprovement = DEFAULT_MIN_IMPROVEMENT; - else if (result == 0) - { - if (improvement <= 0.0 || 1.0 <= improvement) + /** Get similarity_measure. **/ + cluster_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); + if (cluster_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free; + + /** Handle sliding window case. **/ + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) { - mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %g", improvement); - goto err_free_cluster; + /** Sliding window doesn't allocate any clusters. **/ + cluster_data->nClusters = 0u; + + /** Get window_size. **/ + int window_size; + if (ci_ParseAttribute(inf, "window_size", DATA_T_INTEGER, POD(&window_size), param_list, true, true) != 0) + goto err_free; + if (window_size < 1) + { + mssErrorf(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); + goto err_free; + } + + /** Store value. **/ + cluster_data->MaxIterations = (unsigned int)window_size; + goto parsing_done; } - /** Successfully got value. **/ - cluster_data->MinImprovement = improvement; - } - else if (result == -1) - { - char* str; - result = ci_ParseAttribute(inf, "min_improvement", DATA_T_STRING, POD(&str), param_list, false, true); - if (result != 0) goto err_free_cluster; - if (strcasecmp(str, "none") != 0) + /** Get num_clusters. **/ + int num_clusters; + if (ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0) + goto err_free; + if (num_clusters < 2) { - mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %s", str); - goto err_free_cluster; + mssErrorf(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); + if (num_clusters == 1) fprintf(stderr, "HINT: Use algorithm=\"none\" to disable clustering.\n"); + goto err_free; } + cluster_data->nClusters = (unsigned int)num_clusters; - /** Successfully got none. **/ - cluster_data->MinImprovement = -INFINITY; - } - - /** Get max_iterations. **/ - int max_iterations; - result = ci_ParseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); - if (result == -1) goto err_free_cluster; - if (result == 0) - { - if (max_iterations < 1) + /** Get min_improvement. **/ + double improvement; + result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); + if (result == 1) cluster_data->MinImprovement = DEFAULT_MIN_IMPROVEMENT; + else if (result == 0) { - mssErrorf(1, "Cluster", "Invalid value for [max_iterations : uint]: %d", max_iterations); - goto err_free_cluster; + if (improvement <= 0.0 || 1.0 <= improvement) + { + mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %g", improvement); + goto err_free; + } + + /** Successfully got value. **/ + cluster_data->MinImprovement = improvement; } - cluster_data->MaxIterations = (unsigned int)max_iterations; - } - else cluster_data->MaxIterations = DEFAULT_MAX_ITERATIONS; - - /** Search for sub-clusters. **/ - XArray sub_clusters; - if (!check(xaInit(&sub_clusters, 4u))) goto err_free_cluster; - for (unsigned int i = 0u; i < inf->nSubInf; i++) - { - pStructInf sub_inf = check_ptr(inf->SubInf[i]); - ASSERTMAGIC(sub_inf, MGK_STRUCTINF); - char* name = sub_inf->Name; - - /** Handle various struct types. **/ - const int struct_type = stStructType(sub_inf); - switch (struct_type) + else if (result == -1) { - case ST_T_ATTRIB: + char* str; + result = ci_ParseAttribute(inf, "min_improvement", DATA_T_STRING, POD(&str), param_list, false, true); + if (result != 0) goto err_free; + if (strcasecmp(str, "none") != 0) { - /** Valid attribute names. **/ - char* attrs[] = { - "algorithm", - "similarity_measure", - "num_clusters", - "min_improvement", - "max_iterations", - "window_size", - }; - const unsigned int nattrs = sizeof(attrs) / sizeof(char*); - - /** Ignore valid attribute names. **/ - bool is_valid = false; - for (unsigned int i = 0u; i < nattrs; i++) + mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %s", str); + goto err_free; + } + + /** Successfully got none. **/ + cluster_data->MinImprovement = -INFINITY; + } + + /** Get max_iterations. **/ + int max_iterations; + result = ci_ParseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); + if (result == -1) goto err_free; + if (result == 0) + { + if (max_iterations < 1) + { + mssErrorf(1, "Cluster", "Invalid value for [max_iterations : uint]: %d", max_iterations); + goto err_free; + } + cluster_data->MaxIterations = (unsigned int)max_iterations; + } + else cluster_data->MaxIterations = DEFAULT_MAX_ITERATIONS; + + /** Search for sub-clusters. **/ + if (!check(xaInit(&sub_clusters, 4u))) goto err_free; + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; + + /** Handle various struct types. **/ + const int struct_type = stStructType(sub_inf); + switch (struct_type) + { + case ST_T_ATTRIB: { - if (strcmp(name, attrs[i]) == 0) + /** Valid attribute names. **/ + char* attrs[] = { + "algorithm", + "similarity_measure", + "num_clusters", + "min_improvement", + "max_iterations", + "window_size", + }; + const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < nattrs; i++) { - is_valid = true; - break; + if (strcmp(name, attrs[i]) == 0) + { + is_valid = true; + break; + } } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); + if (ci_TryHint(name, attrs, nattrs)); + else if (strcasecmp(name, "k") == 0) ci_GiveHint("num_clusters"); + else if (strcasecmp(name, "threshold") == 0) ci_GiveHint("min_improvement"); + + break; } - if (is_valid) continue; /* Next inf. */ - /** Give the user a warning, and attempt to give them a hint. **/ - fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); - if (ci_TryHint(name, attrs, nattrs)); - else if (strcasecmp(name, "k") == 0) ci_GiveHint("num_clusters"); - else if (strcasecmp(name, "threshold") == 0) ci_GiveHint("min_improvement"); + case ST_T_SUBGROUP: + { + /** Select array by group type. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (group_type == NULL) goto err_free; + if (strcmp(group_type, "cluster/cluster") != 0) + { + mssErrorf(1, "Cluster", + "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", + name, group_type, inf->Name + ); + continue; + } + + /** Subcluster found. **/ + pClusterData sub_cluster = ci_ParseClusterData(sub_inf, node_data); + if (sub_cluster == NULL) goto err_free; + sub_cluster->Parent = cluster_data; + if (!check_neg(xaAddItem(&sub_clusters, sub_cluster))) goto err_free; + + break; + } - break; - } - - case ST_T_SUBGROUP: - { - /** Select array by group type. **/ - char* group_type = check_ptr(sub_inf->UsrType); - if (group_type == NULL) goto err_free_subclusters; - if (strcmp(group_type, "cluster/cluster") != 0) + default: { mssErrorf(1, "Cluster", - "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", - name, group_type, inf->Name + "Warning: Unknown struct type %d in cluster \"%s\".", + struct_type, inf->Name ); - continue; + goto err_free; } - - /** Subcluster found. **/ - pClusterData sub_cluster = ci_ParseClusterData(sub_inf, node_data); - if (sub_cluster == NULL) goto err_free_subclusters; - sub_cluster->Parent = cluster_data; - if (!check_neg(xaAddItem(&sub_clusters, sub_cluster))) goto err_free_subclusters; - + } + } + cluster_data->nSubClusters = sub_clusters.nItems; + cluster_data->SubClusters = (pClusterData*)ci_xaToTrimmedArray(&sub_clusters, 1); + sub_clusters.nAlloc = 0; + + /** Create the cache key. **/ + parsing_done:; + switch (cluster_data->ClusterAlgorithm) + { + case ALGORITHM_NONE: + { + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 8lu; + key = nmSysMalloc(len * sizeof(char)); + snprintf(key, len, "%s/%s?%u", + source_data->Key, + cluster_data->Name, + ALGORITHM_NONE + ); + break; + } + + case ALGORITHM_SLIDING_WINDOW: + { + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 16lu; + key = nmSysMalloc(len * sizeof(char)); + snprintf(key, len, "%s/%s?%u&%u&%u", + source_data->Key, + cluster_data->Name, + ALGORITHM_SLIDING_WINDOW, + cluster_data->SimilarityMeasure, + cluster_data->MaxIterations + ); break; } default: { - mssErrorf(1, "Cluster", - "Warning: Unknown struct type %d in cluster \"%s\".", - struct_type, inf->Name + const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 32lu; + key = nmSysMalloc(len * sizeof(char)); + snprintf(key, len, "%s/%s?%u&%u&%u&%g&%u", + source_data->Key, + cluster_data->Name, + cluster_data->ClusterAlgorithm, + cluster_data->SimilarityMeasure, + cluster_data->nClusters, + cluster_data->MinImprovement, + cluster_data->MaxIterations ); - goto err_free_subclusters; + break; } } - } - cluster_data->nSubClusters = sub_clusters.nItems; - cluster_data->SubClusters = (pClusterData*)ci_xaToTrimmedArray(&sub_clusters, 1); - - /** Create the cache key. **/ - parsing_done:; - char* key; - switch (cluster_data->ClusterAlgorithm) - { - case ALGORITHM_NONE: - { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 8lu; - key = nmSysMalloc(len * sizeof(char)); - snprintf(key, len, "%s/%s?%u", - source_data->Key, - cluster_data->Name, - ALGORITHM_NONE - ); - break; - } + cluster_data->Key = key; - case ALGORITHM_SLIDING_WINDOW: - { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 16lu; - key = nmSysMalloc(len * sizeof(char)); - snprintf(key, len, "%s/%s?%u&%u&%u", - source_data->Key, - cluster_data->Name, - ALGORITHM_SLIDING_WINDOW, - cluster_data->SimilarityMeasure, - cluster_data->MaxIterations - ); - break; + /** Check for a cached version. **/ + pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, key); + if (cluster_maybe != NULL) + { /* Cache hit. */ + /** Free the parsed cluster that we no longer need. */ + ci_FreeClusterData(cluster_data, false); + nmSysFree(key); + + /** Return the cached cluster. **/ + return cluster_maybe; } - default: + /** Cache miss. **/ + if (!check(xhAdd(&ClusterDriverCaches.ClusterDataCache, key, (void*)cluster_data))) goto err_free; + return cluster_data; + + /** Error cleanup. **/ + err_free: + if (key != NULL) nmSysFree(key); + + if (sub_clusters.nAlloc != 0) { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 32lu; - key = nmSysMalloc(len * sizeof(char)); - snprintf(key, len, "%s/%s?%u&%u&%u&%g&%u", - source_data->Key, - cluster_data->Name, - cluster_data->ClusterAlgorithm, - cluster_data->SimilarityMeasure, - cluster_data->nClusters, - cluster_data->MinImprovement, - cluster_data->MaxIterations - ); - break; + for (unsigned int i = 0u; i < sub_clusters.nItems; i++) + { + pClusterData cur = sub_clusters.Items[i]; + if (cur == NULL) break; + ci_FreeClusterData(cur, true); + } + check(xaDeInit(&sub_clusters)); /* Failure ignored. */ } - } - cluster_data->Key = key; - - /** Check for a cached version. **/ - pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, key); - if (cluster_maybe != NULL) - { /* Cache hit. */ - /** Free the parsed cluster that we no longer need. */ - ci_FreeClusterData(cluster_data, false); - nmSysFree(key); - /** Return the cached cluster. **/ - return cluster_maybe; - } - - /** Cache miss. **/ - if (!check(xhAdd(&ClusterDriverCaches.ClusterDataCache, key, (void*)cluster_data))) goto err_free_key; - return cluster_data; - - /** Error cleanup. **/ - err_free_key: - nmSysFree(key); - - err_free_subclusters: - for (unsigned int i = 0u; i < sub_clusters.nItems; i++) - ci_FreeClusterData(sub_clusters.Items[i], true); - check(xaDeInit(&sub_clusters)); /* Failure ignored. */ - - err_free_cluster: - ci_FreeClusterData(cluster_data, false); - - err: - mssErrorf(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); - return NULL; + if (cluster_data != NULL) ci_FreeClusterData(cluster_data, false); + + mssErrorf(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); + return NULL; } @@ -1365,164 +1295,166 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) *** @returns A new pSearchData struct on success, or NULL on failure. ***/ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) - { - /** Allocate space for search struct. **/ - pSearchData search_data = check_ptr(nmMalloc(sizeof(SearchData))); - if (search_data == NULL) goto err; - memset(search_data, 0, sizeof(SearchData)); - - /** Get basic information. **/ - search_data->Name = check_ptr(nmSysStrdup(inf->Name)); - if (search_data->Name == NULL) goto err_free_search; - if (!check(objCurrentDate(&search_data->DateCreated))) goto err_free_search; + { + pSearchData search_data = NULL; + char* key = NULL; - /** Get source cluster. **/ - char* source_cluster_name; - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), node_data->ParamList, true, true) != 0) return NULL; - for (unsigned int i = 0; i < node_data->nClusterDatas; i++) - { - pClusterData cluster_data = node_data->ClusterDatas[i]; - if (strcmp(source_cluster_name, cluster_data->Name) == 0) - { - /** SourceCluster found. **/ - search_data->SourceCluster = cluster_data; - break; - } + /** Allocate space for search struct. **/ + search_data = check_ptr(nmMalloc(sizeof(SearchData))); + if (search_data == NULL) goto err_free; + memset(search_data, 0, sizeof(SearchData)); - /** Note: Subclusters should probably be parsed here, if they were implemented. **/ - } - - /** Did we find the requested source? **/ - if (search_data->SourceCluster == NULL) - { - /** Print error. **/ - mssErrorf(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); + /** Get basic information. **/ + search_data->Name = check_ptr(nmSysStrdup(inf->Name)); + if (search_data->Name == NULL) goto err_free; + if (!check(objCurrentDate(&search_data->DateCreated))) goto err_free; - /** Attempt to give a hint. **/ - char* cluster_names[node_data->nClusterDatas]; + /** Get source cluster. **/ + char* source_cluster_name; + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), node_data->ParamList, true, true) != 0) return NULL; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) - cluster_names[i] = node_data->ClusterDatas[i]->Name; - ci_TryHint(source_cluster_name, cluster_names, node_data->nClusterDatas); - - /** Fail. **/ - goto err_free_search; - } - - /** Get threshold attribute. **/ - if (ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), node_data->ParamList, true, true) != 0) goto err_free_search; - if (search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold) - { - mssErrorf(1, "Cluster", - "Invalid value for [threshold : 0.0 < x < 1.0 | \"none\"]: %g", - search_data->Threshold - ); - goto err_free_search; - } - - /** Get similarity measure. **/ - search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, node_data->ParamList); - if (search_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free_search; - - /** Check for additional data to warn the user about. **/ - for (unsigned int i = 0u; i < inf->nSubInf; i++) - { - pStructInf sub_inf = check_ptr(inf->SubInf[i]); - ASSERTMAGIC(sub_inf, MGK_STRUCTINF); - char* name = sub_inf->Name; - - /** Handle various struct types. **/ - const int struct_type = stStructType(sub_inf); - switch (struct_type) { - case ST_T_ATTRIB: + pClusterData cluster_data = node_data->ClusterDatas[i]; + if (strcmp(source_cluster_name, cluster_data->Name) == 0) { - /** Valid attribute names. **/ - char* attrs[] = { - "source", - "threshold", - "similarity_measure", - }; - const unsigned int nattrs = sizeof(attrs) / sizeof(char*); - - /** Ignore valid attribute names. **/ - bool is_valid = false; - for (unsigned int i = 0u; i < nattrs; i++) - { - if (strcmp(name, attrs[i]) == 0) - { - is_valid = true; - break; - } - } - if (is_valid) continue; /* Next inf. */ - - /** Give the user a warning, and attempt to give them a hint. **/ - fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); - ci_TryHint(name, attrs, nattrs); - + /** SourceCluster found. **/ + search_data->SourceCluster = cluster_data; break; } - case ST_T_SUBGROUP: - { - /** The spec does not specify any valid sub-groups for searches. **/ - char* group_type = check_ptr(sub_inf->UsrType); - if (group_type == NULL) goto err_free_search; - fprintf(stderr, - "Warning: Unknown group [\"%s\" : \"%s\"] in search \"%s\".\n", - name, group_type, inf->Name - ); - break; - } + /** Note: Subclusters should probably be parsed here, if they were implemented. **/ + } + + /** Did we find the requested source? **/ + if (search_data->SourceCluster == NULL) + { + /** Print error. **/ + mssErrorf(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); - default: + /** Attempt to give a hint. **/ + char* cluster_names[node_data->nClusterDatas]; + for (unsigned int i = 0; i < node_data->nClusterDatas; i++) + cluster_names[i] = node_data->ClusterDatas[i]->Name; + ci_TryHint(source_cluster_name, cluster_names, node_data->nClusterDatas); + + /** Fail. **/ + goto err_free; + } + + /** Get threshold attribute. **/ + if (ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), node_data->ParamList, true, true) != 0) goto err_free; + if (search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold) + { + mssErrorf(1, "Cluster", + "Invalid value for [threshold : 0.0 < x < 1.0 | \"none\"]: %g", + search_data->Threshold + ); + goto err_free; + } + + /** Get similarity measure. **/ + search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, node_data->ParamList); + if (search_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free; + + /** Check for additional data to warn the user about. **/ + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; + + /** Handle various struct types. **/ + const int struct_type = stStructType(sub_inf); + switch (struct_type) { - mssErrorf(1, "Cluster", - "Warning: Unknown struct type %d in search \"%s\".", - struct_type, inf->Name - ); - goto err_free_search; + case ST_T_ATTRIB: + { + /** Valid attribute names. **/ + char* attrs[] = { + "source", + "threshold", + "similarity_measure", + }; + const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < nattrs; i++) + { + if (strcmp(name, attrs[i]) == 0) + { + is_valid = true; + break; + } + } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); + ci_TryHint(name, attrs, nattrs); + + break; + } + + case ST_T_SUBGROUP: + { + /** The spec does not specify any valid sub-groups for searches. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (group_type == NULL) goto err_free; + fprintf(stderr, + "Warning: Unknown group [\"%s\" : \"%s\"] in search \"%s\".\n", + name, group_type, inf->Name + ); + break; + } + + default: + { + mssErrorf(1, "Cluster", + "Warning: Unknown struct type %d in search \"%s\".", + struct_type, inf->Name + ); + goto err_free; + } } } - } - - /** Create cache entry key. **/ - char* source_key = search_data->SourceCluster->Key; - const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; - char* key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (key == NULL) goto err_free_search; - snprintf(key, len, "%s/%s?%g&%u", - source_key, - search_data->Name, - search_data->Threshold, - search_data->SimilarityMeasure - ); - pXHashTable search_cache = &ClusterDriverCaches.SearchDataCache; - - /** Check for a cached version. **/ - pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); - if (search_maybe != NULL) - { /* Cache hit. */ - /** Free the parsed search that we no longer need. **/ - ci_FreeSearchData(search_data); - nmSysFree(key); + /** Create cache entry key. **/ + char* source_key = search_data->SourceCluster->Key; + const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; + key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (key == NULL) goto err_free; + snprintf(key, len, "%s/%s?%g&%u", + source_key, + search_data->Name, + search_data->Threshold, + search_data->SimilarityMeasure + ); + pXHashTable search_cache = &ClusterDriverCaches.SearchDataCache; - /** Return the cached search. **/ - return search_maybe; - } - - /** Cache miss. **/ - check(xhAdd(search_cache, key, (void*)search_data)); - return search_data; - - /** Error cleanup. **/ - err_free_search: - ci_FreeSearchData(search_data); - - err: - mssErrorf(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); - return NULL; + /** Check for a cached version. **/ + pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); + if (search_maybe != NULL) + { /* Cache hit. */ + /** Free the parsed search that we no longer need. **/ + if (search_data != NULL) ci_FreeSearchData(search_data); + if (key != NULL) nmSysFree(key); + + /** Return the cached search. **/ + return search_maybe; + } + + /** Cache miss. **/ + check(xhAdd(search_cache, key, (void*)search_data)); + return search_data; + + /** Error cleanup. **/ + err_free: + if (search_data != NULL) ci_FreeSearchData(search_data); + + mssErrorf(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); + + return NULL; } @@ -1541,284 +1473,283 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) ***/ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) { - int ret; - char* path = check_ptr(ci_file_path(parent)); - if (path == NULL) goto err; - - /** Allocate node struct data. **/ - pNodeData node_data = check_ptr(nmMalloc(sizeof(NodeData))); - if (node_data == NULL) goto err; - memset(node_data, 0, sizeof(NodeData)); - node_data->Parent = parent; - - /** Set up param list. **/ - node_data->ParamList = check_ptr(expCreateParamList()); - if (node_data->ParamList == NULL) goto err; - node_data->ParamList->Session = check_ptr(parent->Session); - if (node_data->ParamList->Session == NULL) goto err; - ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Failed to add parameters to the param list scope (error code %d).", ret); - goto err_free_node; - } - - /** Set the param functions, defined later in the file. **/ - ret = expSetParamFunctions( - node_data->ParamList, - "parameters", - ci_GetParamType, - ci_GetParamValue, - ci_SetParamValue - ); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Failed to set param functions (error code %d).", ret); - goto err_free_node; - } - - /** Detect relevant groups. **/ - XArray param_infs, cluster_infs, search_infs; - memset(¶m_infs, 0, sizeof(XArray)); - memset(&cluster_infs, 0, sizeof(XArray)); - memset(&search_infs, 0, sizeof(XArray)); - if (!check(xaInit(¶m_infs, 8))) goto err_free_arrs; - if (!check(xaInit(&cluster_infs, 8))) goto err_free_arrs; - if (!check(xaInit(&search_infs, 8))) goto err_free_arrs; - for (unsigned int i = 0u; i < inf->nSubInf; i++) - { - pStructInf sub_inf = check_ptr(inf->SubInf[i]); - ASSERTMAGIC(sub_inf, MGK_STRUCTINF); - char* name = sub_inf->Name; - - /** Handle various struct types. **/ - const int struct_type = stStructType(sub_inf); - switch (struct_type) + int ret = -1; + pNodeData node_data = NULL; + XArray param_infs = {0}; + XArray cluster_infs = {0}; + XArray search_infs = {0}; + + /** Get file path. **/ + char* path = check_ptr(ci_file_path(parent)); + if (path == NULL) goto err_free; + + /** Allocate node struct data. **/ + node_data = check_ptr(nmMalloc(sizeof(NodeData))); + if (node_data == NULL) goto err_free; + memset(node_data, 0, sizeof(NodeData)); + node_data->Parent = parent; + + /** Set up param list. **/ + node_data->ParamList = check_ptr(expCreateParamList()); + if (node_data->ParamList == NULL) goto err_free; + node_data->ParamList->Session = check_ptr(parent->Session); + if (node_data->ParamList->Session == NULL) goto err_free; + ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to add parameters to the param list scope (error code %d).", ret); + goto err_free; + } + + /** Set the param functions, defined later in the file. **/ + ret = expSetParamFunctions( + node_data->ParamList, + "parameters", + ci_GetParamType, + ci_GetParamValue, + ci_SetParamValue + ); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to set param functions (error code %d).", ret); + goto err_free; + } + + /** Detect relevant groups. **/ + if (!check(xaInit(¶m_infs, 8))) goto err_free; + if (!check(xaInit(&cluster_infs, 8))) goto err_free; + if (!check(xaInit(&search_infs, 8))) goto err_free; + for (unsigned int i = 0u; i < inf->nSubInf; i++) { - case ST_T_ATTRIB: + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; + + /** Handle various struct types. **/ + const int struct_type = stStructType(sub_inf); + switch (struct_type) { - /** Valid attribute names. **/ - char* attrs[] = { - "source", - "key_attr", - "data_attr", - }; - const unsigned int nattrs = sizeof(attrs) / sizeof(char*); - - /** Ignore valid attribute names. **/ - bool is_valid = false; - for (unsigned int i = 0u; i < nattrs; i++) + case ST_T_ATTRIB: { - if (strcmp(name, attrs[i]) == 0) + /** Valid attribute names. **/ + char* attrs[] = { + "source", + "key_attr", + "data_attr", + }; + const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < nattrs; i++) { - is_valid = true; - break; + if (strcmp(name, attrs[i]) == 0) + { + is_valid = true; + break; + } } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in cluster node \"%s\".\n", name, inf->Name); + ci_TryHint(name, attrs, nattrs); + + break; } - if (is_valid) continue; /* Next inf. */ - /** Give the user a warning, and attempt to give them a hint. **/ - fprintf(stderr, "Warning: Unknown attribute '%s' in cluster node \"%s\".\n", name, inf->Name); - ci_TryHint(name, attrs, nattrs); - - break; - } - - case ST_T_SUBGROUP: - { - /** The spec does not specify any valid sub-groups for searches. **/ - char* group_type = check_ptr(sub_inf->UsrType); - if (group_type == NULL) goto err_free_arrs; - if (strcmp(group_type, "cluster/parameter") == 0) - { - if (!check_neg(xaAddItem(¶m_infs, sub_inf))) - goto err_free_arrs; - } - else if (strcmp(group_type, "cluster/cluster") == 0) + case ST_T_SUBGROUP: { - if (!check_neg(xaAddItem(&cluster_infs, sub_inf))) - goto err_free_arrs; + /** The spec does not specify any valid sub-groups for searches. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (group_type == NULL) goto err_free; + if (strcmp(group_type, "cluster/parameter") == 0) + { + if (!check_neg(xaAddItem(¶m_infs, sub_inf))) + goto err_free; + } + else if (strcmp(group_type, "cluster/cluster") == 0) + { + if (!check_neg(xaAddItem(&cluster_infs, sub_inf))) + goto err_free; + } + else if (strcmp(group_type, "cluster/search") == 0) + { + if (!check_neg(xaAddItem(&search_infs, sub_inf))) + goto err_free; + } + else + { + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, + "Warning: Unknown group type \"%s\" on group \"%s\".\n", + group_type, sub_inf->Name + ); + ci_TryHint(group_type, (char*[]){ + "cluster/parameter", + "cluster/cluster", + "cluster/search", + NULL, + }, 0u); + } + break; } - else if (strcmp(group_type, "cluster/search") == 0) + + default: { - if (!check_neg(xaAddItem(&search_infs, sub_inf))) - goto err_free_arrs; + mssErrorf(1, "Cluster", + "Warning: Unknown struct type %d in search \"%s\".", + struct_type, inf->Name + ); + goto err_free; } - else + } + } + + /** Extract OpenCtl for use below. **/ + bool has_provided_params = parent != NULL + && parent->Pathname != NULL + && parent->Pathname->OpenCtl != NULL + && parent->Pathname->OpenCtl[parent->SubPtr - 1] != NULL + && parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf > 0 + && parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf != NULL; + int num_provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf : 0; + pStruct* provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf : NULL; + + /** Iterate over each param in the structure file. **/ + node_data->nParams = param_infs.nItems; + const size_t params_size = node_data->nParams * sizeof(pParam); + node_data->Params = check_ptr(nmSysMalloc(params_size)); + if (node_data->Params == NULL) goto err_free; + memset(node_data->Params, 0, params_size); + for (unsigned int i = 0u; i < node_data->nParams; i++) + { + pParam param = paramCreateFromInf(param_infs.Items[i]); + if (param == NULL) + { + mssErrorf(0, "Cluster", + "Failed to create param from inf for param #%u: %s", + i, ((pStructInf)param_infs.Items[i])->Name + ); + goto err_free; + } + node_data->Params[i] = param; + + /** Check each provided param to see if the user provided value. **/ + for (unsigned int j = 0u; j < num_provided_params; j++) + { + pStruct provided_param = check_ptr(provided_params[j]); /* Failure ignored. */ + + /** If this provided param value isn't for the param, ignore it. **/ + if (strcmp(provided_param->Name, param->Name) != 0) continue; + + /** Matched! The user is providing a value for this param. **/ + ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, node_data->ParamList->Session); + if (ret != 0) { - /** Give the user a warning, and attempt to give them a hint. **/ - fprintf(stderr, - "Warning: Unknown group type \"%s\" on group \"%s\".\n", - group_type, sub_inf->Name + mssErrorf(0, "Cluster", + "Failed to set param value from struct info.\n" + " > Param #%u: %s\n" + " > Provided Param #%u: %n\n" + " > Error code: %d", + i, param->Name, + j, provided_param->Name, + ret ); - ci_TryHint(group_type, (char*[]){ - "cluster/parameter", - "cluster/cluster", - "cluster/search", - NULL, - }, 0u); + goto err_free; } + + /** Provided value successfully handled, we're done. **/ break; } - default: + /** Invoke param hints parsing. **/ + ret = paramEvalHints(param, node_data->ParamList, node_data->ParamList->Session); + if (ret != 0) { - mssErrorf(1, "Cluster", - "Warning: Unknown struct type %d in search \"%s\".", - struct_type, inf->Name + mssErrorf(0, "Cluster", + "Failed to evaluate parameter hints for parameter \"%s\" (error code %d).", + param->Name, ret ); - goto err_free_arrs; + goto err_free; } } - } - - /** Extract OpenCtl for use below. **/ - bool has_provided_params = parent != NULL - && parent->Pathname != NULL - && parent->Pathname->OpenCtl != NULL - && parent->Pathname->OpenCtl[parent->SubPtr - 1] != NULL - && parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf > 0 - && parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf != NULL; - int num_provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf : 0; - pStruct* provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf : NULL; - - /** Iterate over each param in the structure file. **/ - node_data->nParams = param_infs.nItems; - const size_t params_size = node_data->nParams * sizeof(pParam); - node_data->Params = check_ptr(nmSysMalloc(params_size)); - if (node_data->Params == NULL) goto err_free_arrs; - memset(node_data->Params, 0, params_size); - for (unsigned int i = 0u; i < node_data->nParams; i++) - { - pParam param = paramCreateFromInf(param_infs.Items[i]); - if (param == NULL) - { - mssErrorf(0, "Cluster", - "Failed to create param from inf for param #%u: %s", - i, ((pStructInf)param_infs.Items[i])->Name - ); - goto err_free_arrs; - } - node_data->Params[i] = param; + check(xaDeInit(¶m_infs)); /* Failure ignored. */ + param_infs.nAlloc = 0; - /** Check each provided param to see if the user provided value. **/ - for (unsigned int j = 0u; j < num_provided_params; j++) + /** Iterate over provided parameters and warn the user if they specified a parameter that does not exist. **/ + for (unsigned int i = 0u; i < num_provided_params; i++) { - pStruct provided_param = check_ptr(provided_params[j]); /* Failure ignored. */ + pStruct provided_param = check_ptr(provided_params[i]); /* Failure ignored. */ + char* provided_name = provided_param->Name; - /** If this provided param value isn't for the param, ignore it. **/ - if (strcmp(provided_param->Name, param->Name) != 0) continue; + /** Look to see if this provided param actually exists for this driver instance. **/ + for (unsigned int j = 0u; j < node_data->nParams; j++) + if (strcmp(provided_name, node_data->Params[j]->Name) == 0) + goto next_provided_param; - /** Matched! The user is providing a value for this param. **/ - ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, node_data->ParamList->Session); - if (ret != 0) - { - mssErrorf(0, "Cluster", - "Failed to set param value from struct info.\n" - " > Param #%u: %s\n" - " > Provided Param #%u: %n\n" - " > Error code: %d", - i, param->Name, - j, provided_param->Name, - ret - ); - goto err_free_arrs; - } + /** This param doesn't exist, warn the user and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, ci_file_name(parent)); + char** param_names = check_ptr(nmSysMalloc(node_data->nParams * sizeof(char*))); + for (unsigned int j = 0u; j < node_data->nParams; j++) + param_names[j] = node_data->Params[j]->Name; + ci_TryHint(provided_name, param_names, node_data->nParams); + nmSysFree(param_names); - /** Provided value successfully handled, we're done. **/ - break; + next_provided_param:; } - /** Invoke param hints parsing. **/ - ret = paramEvalHints(param, node_data->ParamList, node_data->ParamList->Session); - if (ret != 0) - { - mssErrorf(0, "Cluster", - "Failed to evaluate parameter hints for parameter \"%s\" (error code %d).", - param->Name, ret - ); - goto err_free_arrs; - } - } - check(xaDeInit(¶m_infs)); /* Failure ignored. */ - param_infs.nAlloc = 0; - - /** Iterate over provided parameters and warn the user if they specified a parameter that does not exist. **/ - for (unsigned int i = 0u; i < num_provided_params; i++) - { - pStruct provided_param = check_ptr(provided_params[i]); /* Failure ignored. */ - char* provided_name = provided_param->Name; - - /** Look to see if this provided param actually exists for this driver instance. **/ - for (unsigned int j = 0u; j < node_data->nParams; j++) - if (strcmp(provided_name, node_data->Params[j]->Name) == 0) - goto next_provided_param; - - /** This param doesn't exist, warn the user and attempt to give them a hint. **/ - fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, ci_file_name(parent)); - char** param_names = check_ptr(nmSysMalloc(node_data->nParams * sizeof(char*))); - for (unsigned int j = 0u; j < node_data->nParams; j++) - param_names[j] = node_data->Params[j]->Name; - ci_TryHint(provided_name, param_names, node_data->nParams); - nmSysFree(param_names); - - next_provided_param:; - } - - /** Parse source data. **/ - node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); - if (node_data->SourceData == NULL) goto err_free_arrs; - - /** Parse each cluster. **/ - node_data->nClusterDatas = cluster_infs.nItems; - if (node_data->nClusterDatas > 0) - { - const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); - node_data->ClusterDatas = check_ptr(nmSysMalloc(clusters_size)); - if (node_data->ClusterDatas == NULL) goto err_free_arrs; - memset(node_data->ClusterDatas, 0, clusters_size); - for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + /** Parse source data. **/ + node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); + if (node_data->SourceData == NULL) goto err_free; + + /** Parse each cluster. **/ + node_data->nClusterDatas = cluster_infs.nItems; + if (node_data->nClusterDatas > 0) { - node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data); - if (node_data->ClusterDatas[i] == NULL) goto err_free_arrs; + const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); + node_data->ClusterDatas = check_ptr(nmSysMalloc(clusters_size)); + if (node_data->ClusterDatas == NULL) goto err_free; + memset(node_data->ClusterDatas, 0, clusters_size); + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + { + node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data); + if (node_data->ClusterDatas[i] == NULL) goto err_free; + } } - } - else node_data->ClusterDatas = NULL; - check(xaDeInit(&cluster_infs)); /* Failure ignored. */ - cluster_infs.nAlloc = 0; - - /** Parse each search. **/ - node_data->nSearchDatas = search_infs.nItems; - if (node_data->nSearchDatas > 0) - { - const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); - node_data->SearchDatas = check_ptr(nmSysMalloc(searches_size)); - if (node_data->SearchDatas == NULL) goto err_free_arrs; - memset(node_data->SearchDatas, 0, searches_size); - for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + else node_data->ClusterDatas = NULL; + check(xaDeInit(&cluster_infs)); /* Failure ignored. */ + cluster_infs.nAlloc = 0; + + /** Parse each search. **/ + node_data->nSearchDatas = search_infs.nItems; + if (node_data->nSearchDatas > 0) { - node_data->SearchDatas[i] = ci_ParseSearchData(search_infs.Items[i], node_data); - if (node_data->SearchDatas[i] == NULL) goto err_free_arrs; + const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); + node_data->SearchDatas = check_ptr(nmSysMalloc(searches_size)); + if (node_data->SearchDatas == NULL) goto err_free; + memset(node_data->SearchDatas, 0, searches_size); + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + { + node_data->SearchDatas[i] = ci_ParseSearchData(search_infs.Items[i], node_data); + if (node_data->SearchDatas[i] == NULL) goto err_free; + } } - } - else node_data->SearchDatas = NULL; - check(xaDeInit(&search_infs)); /* Failure ignored. */ - search_infs.nAlloc = 0; - - /** Success. **/ - return node_data; - - err_free_arrs: - if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); /* Failure ignored. */ - if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); /* Failure ignored. */ - if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); /* Failure ignored. */ - - err_free_node: - ci_FreeNodeData(node_data); + else node_data->SearchDatas = NULL; + check(xaDeInit(&search_infs)); /* Failure ignored. */ + search_infs.nAlloc = 0; + + /** Success. **/ + return node_data; + + err_free: + if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); /* Failure ignored. */ + if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); /* Failure ignored. */ + if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); /* Failure ignored. */ + if (node_data != NULL) ci_FreeNodeData(node_data); + mssErrorf(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); - err: - mssErrorf(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); - return NULL; + return NULL; } @@ -1829,62 +1760,66 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) /** @param source_data A pSourceData struct, freed by this function. **/ static void ci_FreeSourceData(pSourceData source_data) { - /** Guard segfault. **/ - if (source_data == NULL) - { - fprintf(stderr, "Warning: Call to ci_FreeSourceData(NULL);\n"); - return; - } - - /** Free top level attributes, if they exist. **/ - if (source_data->Name != NULL) - { - nmSysFree(source_data->Name); - source_data->Name = NULL; - } - if (source_data->SourcePath != NULL) - { - nmSysFree(source_data->SourcePath); - source_data->SourcePath = NULL; - } - if (source_data->KeyAttr != NULL) - { - nmSysFree(source_data->KeyAttr); - source_data->KeyAttr = NULL; - } - if (source_data->NameAttr != NULL) - { - nmSysFree(source_data->NameAttr); - source_data->NameAttr = NULL; - } - - /** Free fetched data, if it exists. **/ - if (source_data->Strings != NULL) - { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + /** Guard segfault. **/ + if (source_data == NULL) { - nmSysFree(source_data->Strings[i]); - source_data->Strings[i] = NULL; + fprintf(stderr, "Warning: Call to ci_FreeSourceData(NULL);\n"); + return; } - nmSysFree(source_data->Strings); - source_data->Strings = NULL; - } - - /** Free computed vectors, if they exist. **/ - if (source_data->Vectors != NULL) - { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + + /** Free top level attributes, if they exist. **/ + if (source_data->Name != NULL) { - ca_free_vector(source_data->Vectors[i]); - source_data->Vectors[i] = NULL; + nmSysFree(source_data->Name); + source_data->Name = NULL; } - nmSysFree(source_data->Vectors); - source_data->Vectors = NULL; - } - - /** Free the source data struct. **/ - nmFree(source_data, sizeof(SourceData)); - source_data = NULL; + if (source_data->SourcePath != NULL) + { + nmSysFree(source_data->SourcePath); + source_data->SourcePath = NULL; + } + if (source_data->KeyAttr != NULL) + { + nmSysFree(source_data->KeyAttr); + source_data->KeyAttr = NULL; + } + if (source_data->NameAttr != NULL) + { + nmSysFree(source_data->NameAttr); + source_data->NameAttr = NULL; + } + + /** Free fetched data, if it exists. **/ + if (source_data->Strings != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + { + if (source_data->Strings[i] != NULL) + nmSysFree(source_data->Strings[i]); + else continue; + source_data->Strings[i] = NULL; + } + nmSysFree(source_data->Strings); + source_data->Strings = NULL; + } + + /** Free computed vectors, if they exist. **/ + if (source_data->Vectors != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + { + if (source_data->Vectors[i] != NULL) + ca_free_vector(source_data->Vectors[i]); + else continue; + source_data->Vectors[i] = NULL; + } + nmSysFree(source_data->Vectors); + source_data->Vectors = NULL; + } + + /** Free the source data struct. **/ + nmFree(source_data, sizeof(SourceData)); + source_data = NULL; } @@ -1896,55 +1831,58 @@ static void ci_FreeSourceData(pSourceData source_data) ***/ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) { - /** Guard segfault. **/ - if (cluster_data == NULL) - { - fprintf(stderr, "Warning: Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); - return; - } - - /** Free attribute data. **/ - if (cluster_data->Name != NULL) - { - nmSysFree(cluster_data->Name); - cluster_data->Name = NULL; - } - - /** Free computed data, if it exists. **/ - if (cluster_data->Clusters != NULL) - { - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + /** Guard segfault. **/ + if (cluster_data == NULL) { - pCluster cluster = &cluster_data->Clusters[i]; - nmSysFree(cluster->Strings); - nmSysFree(cluster->Vectors); - cluster->Strings = NULL; - cluster->Vectors = NULL; + fprintf(stderr, "Warning: Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + return; } - nmSysFree(cluster_data->Clusters); - nmSysFree(cluster_data->Sims); - cluster_data->Clusters = NULL; - cluster_data->Sims = NULL; - } - - /** Free subclusters recursively. **/ - if (cluster_data->SubClusters != NULL) - { - if (recursive) + + /** Free attribute data. **/ + if (cluster_data->Name != NULL) + { + nmSysFree(cluster_data->Name); + cluster_data->Name = NULL; + } + + /** Free computed data, if it exists. **/ + if (cluster_data->Clusters != NULL) { - for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { - ci_FreeClusterData(cluster_data->SubClusters[i], recursive); - cluster_data->SubClusters[i] = NULL; + pCluster cluster = &cluster_data->Clusters[i]; + if (cluster == NULL) continue; + if (cluster->Strings != NULL) nmSysFree(cluster->Strings); + if (cluster->Vectors != NULL) nmSysFree(cluster->Vectors); + cluster->Strings = NULL; + cluster->Vectors = NULL; } + nmSysFree(cluster_data->Clusters); + nmSysFree(cluster_data->Sims); + cluster_data->Clusters = NULL; + cluster_data->Sims = NULL; } - nmSysFree(cluster_data->SubClusters); - cluster_data->SubClusters = NULL; - } - - /** Free the cluster data struct. **/ - nmFree(cluster_data, sizeof(ClusterData)); - cluster_data = NULL; + + /** Free subclusters recursively. **/ + if (cluster_data->SubClusters != NULL) + { + if (recursive) + { + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + { + if (cluster_data->SubClusters[i] != NULL) + ci_FreeClusterData(cluster_data->SubClusters[i], recursive); + else continue; + cluster_data->SubClusters[i] = NULL; + } + } + nmSysFree(cluster_data->SubClusters); + cluster_data->SubClusters = NULL; + } + + /** Free the cluster data struct. **/ + nmFree(cluster_data, sizeof(ClusterData)); + cluster_data = NULL; } @@ -1952,35 +1890,35 @@ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) /** @param search_data A pSearchData struct, freed by this function. **/ static void ci_FreeSearchData(pSearchData search_data) { - /** Guard segfault. **/ - if (search_data == NULL) - { - fprintf(stderr, "Warning: Call to ci_FreeSearchData(NULL);\n"); - return; - } - - /** Free attribute data. **/ - if (search_data->Name != NULL) - { - nmSysFree(search_data->Name); - search_data->Name = NULL; - } - - /** Free computed data. **/ - if (search_data->Dups != NULL) - { - for (unsigned int i = 0; i < search_data->nDups; i++) + /** Guard segfault. **/ + if (search_data == NULL) { - nmFree(search_data->Dups[i], sizeof(Dup)); - search_data->Dups[i] = NULL; + fprintf(stderr, "Warning: Call to ci_FreeSearchData(NULL);\n"); + return; } - nmSysFree(search_data->Dups); - search_data->Dups = NULL; - } - - /** Free the search data struct. **/ - nmFree(search_data, sizeof(SearchData)); - search_data = NULL; + + /** Free attribute data. **/ + if (search_data->Name != NULL) + { + nmSysFree(search_data->Name); + search_data->Name = NULL; + } + + /** Free computed data. **/ + if (search_data->Dups != NULL) + { + for (unsigned int i = 0; i < search_data->nDups; i++) + { + nmFree(search_data->Dups[i], sizeof(Dup)); + search_data->Dups[i] = NULL; + } + nmSysFree(search_data->Dups); + search_data->Dups = NULL; + } + + /** Free the search data struct. **/ + nmFree(search_data, sizeof(SearchData)); + search_data = NULL; } @@ -1988,82 +1926,82 @@ static void ci_FreeSearchData(pSearchData search_data) /** @param node_data A pNodeData struct, freed by this function. **/ static void ci_FreeNodeData(pNodeData node_data) { - /** Guard segfault. **/ - if (node_data == NULL) - { - fprintf(stderr, "Warning: Call to ci_FreeNodeData(NULL);\n"); - return; - } - - /** Free parsed params, if they exist. **/ - if (node_data->Params != NULL) - { - for (unsigned int i = 0u; i < node_data->nParams; i++) + /** Guard segfault. **/ + if (node_data == NULL) { - if (node_data->Params[i] == NULL) break; - paramFree(node_data->Params[i]); - node_data->Params[i] = NULL; + fprintf(stderr, "Warning: Call to ci_FreeNodeData(NULL);\n"); + return; } - nmSysFree(node_data->Params); - node_data->Params = NULL; - } - if (node_data->ParamList != NULL) - { - expFreeParamList(node_data->ParamList); - node_data->ParamList = NULL; - } - - /** Free parsed clusters, if they exist. **/ - if (node_data->ClusterDatas != NULL) - { - /*** This data is cached, so we should NOT free it! The caching system - *** is responsible for the memory. We only need to free the array - *** holding our pointers to said cached memory. - ***/ - nmSysFree(node_data->ClusterDatas); - node_data->ClusterDatas = NULL; - } - - /** Free parsed searches, if they exist. **/ - if (node_data->SearchDatas != NULL) - { - /*** This data is cached, so we should NOT free it! The caching system - *** is responsible for the memory. We only need to free the array - *** holding our pointers to said cached memory. - ***/ - nmSysFree(node_data->SearchDatas); - node_data->SearchDatas = NULL; - } - - /** Free data source, if one exists. **/ - /*** Note: SourceData is freed last since other free functions may need to - *** access information from this structure when freeing data. - *** (For example, nVector which is used to determine the size of the - *** label struct in each cluster.) - ***/ - if (node_data->SourceData != NULL) - { - /*** This data is cached, so we should NOT free it! The caching system - *** is responsible for the memory. We only need to free the array - *** holding our pointers to said cached memory. - ***/ - node_data->SourceData = NULL; - } - - /** Free the node data. **/ - nmFree(node_data, sizeof(NodeData)); - node_data = NULL; + + /** Free parsed params, if they exist. **/ + if (node_data->Params != NULL) + { + for (unsigned int i = 0u; i < node_data->nParams; i++) + { + if (node_data->Params[i] == NULL) break; + paramFree(node_data->Params[i]); + node_data->Params[i] = NULL; + } + nmSysFree(node_data->Params); + node_data->Params = NULL; + } + if (node_data->ParamList != NULL) + { + expFreeParamList(node_data->ParamList); + node_data->ParamList = NULL; + } + + /** Free parsed clusters, if they exist. **/ + if (node_data->ClusterDatas != NULL) + { + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. + ***/ + nmSysFree(node_data->ClusterDatas); + node_data->ClusterDatas = NULL; + } + + /** Free parsed searches, if they exist. **/ + if (node_data->SearchDatas != NULL) + { + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. + ***/ + nmSysFree(node_data->SearchDatas); + node_data->SearchDatas = NULL; + } + + /** Free data source, if one exists. **/ + /*** Note: SourceData is freed last since other free functions may need to + *** access information from this structure when freeing data. + *** (For example, nVector which is used to determine the size of the + *** label struct in each cluster.) + ***/ + if (node_data->SourceData != NULL) + { + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. + ***/ + node_data->SourceData = NULL; + } + + /** Free the node data. **/ + nmFree(node_data, sizeof(NodeData)); + node_data = NULL; } /** Frees all data in caches for all cluster driver instances. **/ static void ci_ClearCaches(void) { - /*** Free caches in reverse of the order they are created in case - *** cached data relies on its source during the freeing process. - ***/ - check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, ci_CacheFreeSearch, NULL)); /* Failure ignored. */ - check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, ci_CacheFreeCluster, NULL)); /* Failure ignored. */ - check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, ci_CacheFreeSourceData, NULL)); /* Failure ignored. */ + /*** Free caches in reverse of the order they are created in case + *** cached data relies on its source during the freeing process. + ***/ + check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, ci_CacheFreeSearch, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, ci_CacheFreeCluster, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, ci_CacheFreeSourceData, NULL)); /* Failure ignored. */ } @@ -2083,31 +2021,32 @@ static void ci_ClearCaches(void) ***/ static unsigned int ci_SizeOfSourceData(pSourceData source_data) { - /** Guard segfault. **/ - if (source_data == NULL) - { - fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); - return 0u; - } + /** Guard segfault. **/ + if (source_data == NULL) + { + fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); + return 0u; + } + + unsigned int size = 0u; + if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); + if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); + if (source_data->KeyAttr != NULL) size += strlen(source_data->KeyAttr) * sizeof(char); + if (source_data->NameAttr != NULL) size += strlen(source_data->NameAttr) * sizeof(char); + if (source_data->Strings != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + size += strlen(source_data->Strings[i]) * sizeof(char); + size += source_data->nVectors * sizeof(char*); + } + if (source_data->Vectors != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + size += ca_sparse_len(source_data->Vectors[i]) * sizeof(int); + size += source_data->nVectors * sizeof(pVector); + } + size += sizeof(SourceData); - unsigned int size = 0u; - if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); - if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); - if (source_data->KeyAttr != NULL) size += strlen(source_data->KeyAttr) * sizeof(char); - if (source_data->NameAttr != NULL) size += strlen(source_data->NameAttr) * sizeof(char); - if (source_data->Strings != NULL) - { - for (unsigned int i = 0u; i < source_data->nVectors; i++) - size += strlen(source_data->Strings[i]) * sizeof(char); - size += source_data->nVectors * sizeof(char*); - } - if (source_data->Vectors != NULL) - { - for (unsigned int i = 0u; i < source_data->nVectors; i++) - size += ca_sparse_len(source_data->Vectors[i]) * sizeof(int); - size += source_data->nVectors * sizeof(pVector); - } - size += sizeof(SourceData); return size; } @@ -2125,33 +2064,34 @@ static unsigned int ci_SizeOfSourceData(pSourceData source_data) *** @returns The size in bytes of the struct and all internal allocated data. ***/ static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) - { - /** Guard segfault. **/ - if (cluster_data == NULL) - { - fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); - return 0u; - } - - unsigned int size = 0u; - if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); - if (cluster_data->Clusters != NULL) - { - const unsigned int nVectors = cluster_data->SourceData->nVectors; - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - size += cluster_data->Clusters[i].Size * (sizeof(char*) + sizeof(pVector)); - size += nVectors * (sizeof(Cluster) + sizeof(double)); - } - if (cluster_data->SubClusters != NULL) - { - if (recursive) + { + /** Guard segfault. **/ + if (cluster_data == NULL) + { + fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + return 0u; + } + + unsigned int size = 0u; + if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); + if (cluster_data->Clusters != NULL) { - for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) - size += ci_SizeOfClusterData(cluster_data->SubClusters[i], recursive); + const unsigned int nVectors = cluster_data->SourceData->nVectors; + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + size += cluster_data->Clusters[i].Size * (sizeof(char*) + sizeof(pVector)); + size += nVectors * (sizeof(Cluster) + sizeof(double)); + } + if (cluster_data->SubClusters != NULL) + { + if (recursive) + { + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + size += ci_SizeOfClusterData(cluster_data->SubClusters[i], recursive); + } + size += cluster_data->nSubClusters * sizeof(void*); } - size += cluster_data->nSubClusters * sizeof(void*); - } - size += sizeof(ClusterData); + size += sizeof(ClusterData); + return size; } @@ -2169,18 +2109,19 @@ static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursi ***/ static unsigned int ci_SizeOfSearchData(pSearchData search_data) { - /** Guard segfault. **/ - if (search_data == NULL) - { - fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); - return 0u; - } - - unsigned int size = 0u; - if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); - if (search_data->Dups != NULL) size += search_data->nDups * (sizeof(void*) + sizeof(Dup)); - size += sizeof(SearchData); - return size; + /** Guard segfault. **/ + if (search_data == NULL) + { + fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); + return 0u; + } + + unsigned int size = 0u; + if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); + if (search_data->Dups != NULL) size += search_data->nDups * (sizeof(void*) + sizeof(Dup)); + size += sizeof(SearchData); + + return size; } @@ -2201,148 +2142,222 @@ static unsigned int ci_SizeOfSearchData(pSearchData search_data) ***/ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) { - /** If the vectors are already computed, we're done. **/ - if (source_data->Vectors != NULL) return 0; - - /** Time to play shoots-and-ladders in an error-handling jungle of gotos. **/ bool successful = false; int ret; - - /** Record the date and time. **/ - if (!check(objCurrentDate(&source_data->DateComputed))) goto end; - - /** Open the source path specified by the .cluster file. **/ - pObject obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); - if (obj == NULL) - { - mssErrorf(0, "Cluster", - "Failed to open object driver:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n", - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath - ); - goto end; - } - - /** Generate a "query" for retrieving data. **/ - pObjQuery query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); - if (query == NULL) - { - mssErrorf(0, "Cluster", - "Failed to open query:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name - ); - goto end_close; - } - - /** Initialize an xarray to store the retrieved data. **/ - XArray key_xarray, data_xarray, vector_xarray; - memset(&key_xarray, 0, sizeof(XArray)); - memset(&data_xarray, 0, sizeof(XArray)); - memset(&vector_xarray, 0, sizeof(XArray)); - if (!check(xaInit(&key_xarray, 64))) goto end_close_query; - if (!check(xaInit(&data_xarray, 64))) goto end_free_data; - if (!check(xaInit(&vector_xarray, 64))) goto end_free_data; - - /** Fetch data and build vectors. **/ - while (true) - { - pObject entry = objQueryFetch(query, O_RDONLY); - if (entry == NULL) break; /* Done. */ - - /** Data value: Type checking. **/ - const int data_datatype = objGetAttrType(entry, source_data->NameAttr); - if (data_datatype == -1) + pObject obj = NULL; + pObjQuery query = NULL; + XArray key_xarray = {0}; + XArray data_xarray = {0}; + XArray vector_xarray = {0}; + + /** Guard segfault. **/ + if (source_data == NULL) return -1; + + /** If the vectors are already computed, we're done. **/ + if (source_data->Vectors != NULL) return 0; + + /** Record the date and time. **/ + if (!check(objCurrentDate(&source_data->DateComputed))) goto end_free; + + /** Open the source path specified by the .cluster file. **/ + obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); + if (obj == NULL) { mssErrorf(0, "Cluster", - "Failed to get type for %uth entry:\n" + "Failed to open object driver:\n" " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, + " > Source Path: %s\n", source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name - ); - goto end_free_data; - } - if (data_datatype != DATA_T_STRING) - { - mssErrorf(1, "Cluster", - "Type for %uth entry was not a string:\n" - " > Attribute: ['%s':'%s' : %s]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, ci_TypeToStr(data_datatype), - source_data->SourcePath, - obj->Driver->Name + source_data->SourcePath ); - goto end_free_data; + goto end_free; } - /** Data value: Get value from database. **/ - char* data; - ret = objGetAttrValue(entry, source_data->NameAttr, DATA_T_STRING, POD(&data)); - if (ret != 0) + /** Generate a "query" for retrieving data. **/ + query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); + if (query == NULL) { mssErrorf(0, "Cluster", - "Failed to value for %uth entry:\n" + "Failed to open query:\n" " > Attribute: ['%s':'%s' : String]\n" " > Source Path: %s\n" - " > Driver Used: %s\n" - " > Error code: %d\n", - vector_xarray.nItems, + " > Driver Used: %s\n", source_data->KeyAttr, source_data->NameAttr, source_data->SourcePath, - obj->Driver->Name, - ret + obj->Driver->Name ); - goto end_free_data; + goto end_free; } - /** Skip empty strings. **/ - if (strlen(data) == 0) - { - check(fflush(stdout)); /* Failure ignored. */ - continue; - } + /** Initialize an xarray to store the retrieved data. **/ + // memset(&key_xarray, 0, sizeof(XArray)); + // memset(&data_xarray, 0, sizeof(XArray)); + // memset(&vector_xarray, 0, sizeof(XArray)); + if (!check(xaInit(&key_xarray, 64))) goto end_free; + if (!check(xaInit(&data_xarray, 64))) goto end_free; + if (!check(xaInit(&vector_xarray, 64))) goto end_free; - /** Convert the string to a vector. **/ - pVector vector = ca_build_vector(data); - if (vector == NULL) - { - mssErrorf(1, "Cluster", "Failed to build vectors for string \"%s\".", data); - successful = false; - goto end_free_data; - } - if (ca_is_empty(vector)) - { - mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); - successful = false; - goto end_free_data; - } - if (ca_has_no_pairs(vector)) + /** Fetch data and build vectors. **/ + while (true) { - /** Skip pVector with no pairs. **/ - check(fflush(stdout)); /* Failure ignored. */ - ca_free_vector(vector); - continue; + pObject entry = objQueryFetch(query, O_RDONLY); + if (entry == NULL) break; /* Done. */ + + /** Data value: Type checking. **/ + const int data_datatype = objGetAttrType(entry, source_data->NameAttr); + if (data_datatype == -1) + { + mssErrorf(0, "Cluster", + "Failed to get type for %uth entry:\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name + ); + goto end_free; + } + if (data_datatype != DATA_T_STRING) + { + mssErrorf(1, "Cluster", + "Type for %uth entry was not a string:\n" + " > Attribute: ['%s':'%s' : %s]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, objTypeToStr(data_datatype), + source_data->SourcePath, + obj->Driver->Name + ); + goto end_free; + } + + /** Data value: Get value from database. **/ + char* data; + ret = objGetAttrValue(entry, source_data->NameAttr, DATA_T_STRING, POD(&data)); + if (ret != 0) + { + mssErrorf(0, "Cluster", + "Failed to value for %uth entry:\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n" + " > Error code: %d\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name, + ret + ); + goto end_free; + } + + /** Skip empty strings. **/ + if (strlen(data) == 0) + { + check(fflush(stdout)); /* Failure ignored. */ + continue; + } + + /** Convert the string to a vector. **/ + pVector vector = ca_build_vector(data); + if (vector == NULL) + { + mssErrorf(1, "Cluster", "Failed to build vectors for string \"%s\".", data); + successful = false; + goto end_free; + } + if (ca_is_empty(vector)) + { + mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); + successful = false; + goto end_free; + } + if (ca_has_no_pairs(vector)) + { + /** Skip pVector with no pairs. **/ + check(fflush(stdout)); /* Failure ignored. */ + ca_free_vector(vector); + continue; + } + + + /** Key value: Type checking. **/ + const int key_datatype = objGetAttrType(entry, source_data->KeyAttr); + if (key_datatype == -1) + { + mssErrorf(0, "Cluster", + "Failed to get type for key on %uth entry:\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name + ); + goto end_free; + } + if (key_datatype != DATA_T_STRING) + { + mssErrorf(1, "Cluster", + "Type for key on %uth entry was not a string:\n" + " > Attribute: ['%s':'%s' : %s]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, objTypeToStr(key_datatype), + source_data->SourcePath, + obj->Driver->Name + ); + goto end_free; + } + + /** key value: Get value from database. **/ + char* key; + ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); + if (ret != 0) + { + mssErrorf(0, "Cluster", + "Failed to value for key on %uth entry:\n" + " > Attribute: ['%s':'%s' : String]\n" + " > Source Path: %s\n" + " > Driver Used: %s\n" + " > Error code: %d\n", + vector_xarray.nItems, + source_data->KeyAttr, source_data->NameAttr, + source_data->SourcePath, + obj->Driver->Name, + ret + ); + goto end_free; + } + + /** Store values. **/ + char* key_dup = check_ptr(nmSysStrdup(key)); + if (key_dup == NULL) goto end_free; + char* data_dup = check_ptr(nmSysStrdup(data)); + if (data_dup == NULL) goto end_free; + if (!check_neg(xaAddItem(&key_xarray, (void*)key_dup))) goto end_free; + if (!check_neg(xaAddItem(&data_xarray, (void*)data_dup))) goto end_free; + if (!check_neg(xaAddItem(&vector_xarray, (void*)vector))) goto end_free; + + /** Clean up. **/ + ret = objClose(entry); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to close object entry (error code %d).", ret); + // success = false; // Fall-through: Failure ignored. + } } - - /** Key value: Type checking. **/ - const int key_datatype = objGetAttrType(entry, source_data->KeyAttr); - if (key_datatype == -1) + source_data->nVectors = vector_xarray.nItems; + if (source_data->nVectors == 0) { mssErrorf(0, "Cluster", - "Failed to get type for key on %uth entry:\n" + "Data source path did not contain any valid data:\n" " > Attribute: ['%s':'%s' : String]\n" " > Source Path: %s\n" " > Driver Used: %s\n", @@ -2351,126 +2366,92 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) source_data->SourcePath, obj->Driver->Name ); - goto end_free_data; } - if (key_datatype != DATA_T_STRING) + + /** Trim and store keys. **/ + source_data->Keys = (char**)check_ptr(ci_xaToTrimmedArray(&key_xarray, 1)); + if (source_data->Keys == NULL) goto err_free; + key_xarray.nAlloc = 0; + + /** Trim and store data strings. **/ + source_data->Strings = (char**)check_ptr(ci_xaToTrimmedArray(&data_xarray, 1)); + if (source_data->Strings == NULL) goto err_free; + data_xarray.nAlloc = 0; + + /** Trim and store vectors. **/ + source_data->Vectors = (int**)check_ptr(ci_xaToTrimmedArray(&vector_xarray, 1)); + if (source_data->Vectors == NULL) goto err_free; + vector_xarray.nAlloc = 0; + + /** Success. **/ + successful = true; + goto end_free; + + err_free: + if (source_data->Keys != NULL) nmSysFree(source_data->Keys); + if (source_data->Strings != NULL) nmSysFree(source_data->Strings); + if (source_data->Vectors != NULL) nmSysFree(source_data->Vectors); + + end_free: + /** Clean up xarrays. **/ + if (key_xarray.nAlloc != 0) + { + for (unsigned int i = 0u; i < vector_xarray.nItems; i++) + { + char* key = key_xarray.Items[i]; + if (key != NULL) nmSysFree(key); + else break; + } + check(xaDeInit(&key_xarray)); /* Failure ignored. */ + } + if (data_xarray.nAlloc != 0) { - mssErrorf(1, "Cluster", - "Type for key on %uth entry was not a string:\n" - " > Attribute: ['%s':'%s' : %s]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, ci_TypeToStr(key_datatype), - source_data->SourcePath, - obj->Driver->Name - ); - goto end_free_data; + for (unsigned int i = 0u; i < data_xarray.nItems; i++) + { + char* str = data_xarray.Items[i]; + if (str != NULL) nmSysFree(str); + else break; + } + check(xaDeInit(&data_xarray)); /* Failure ignored. */ + } + if (vector_xarray.nAlloc != 0) + { + for (unsigned int i = 0u; i < vector_xarray.nItems; i++) + { + pVector vec = vector_xarray.Items[i]; + if (vec != NULL) ca_free_vector(vec); + else break; + } + check(xaDeInit(&vector_xarray)); /* Failure ignored. */ } - /** key value: Get value from database. **/ - char* key; - ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); - if (ret != 0) + /** Clean up query. **/ + if (query != NULL) { - mssErrorf(0, "Cluster", - "Failed to value for key on %uth entry:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n" - " > Error code: %d\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name, - ret - ); - goto end_free_data; + ret = objQueryClose(query); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to close query (error code %d).", ret); + // success = false; // Fall-through: Failure ignored. + } } - /** Store values. **/ - char* key_dup = check_ptr(nmSysStrdup(key)); - if (key_dup == NULL) goto end_free_data; - char* data_dup = check_ptr(nmSysStrdup(data)); - if (data_dup == NULL) goto end_free_data; - if (!check_neg(xaAddItem(&key_xarray, (void*)key_dup))) goto end_free_data; - if (!check_neg(xaAddItem(&data_xarray, (void*)data_dup))) goto end_free_data; - if (!check_neg(xaAddItem(&vector_xarray, (void*)vector))) goto end_free_data; - - /** Clean up. **/ - ret = objClose(entry); - if (ret != 0) + /** Clean up object. **/ + if (obj != NULL) { - mssErrorf(0, "Cluster", "Failed to close object entry (error code %d).", ret); - // ret = ret; // Fall-through: Failure ignored. + ret = objClose(obj); + if (ret != 0) + { + mssErrorf(0, "Cluster", "Failed to close object driver (error code %d).", ret); + // success = false; // Fall-through: Failure ignored. + } } - } - - source_data->nVectors = vector_xarray.nItems; - if (source_data->nVectors == 0) - { - mssErrorf(0, "Cluster", - "Data source path did not contain any valid data:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name - ); - } - - /** Trim and store: keys, data, and vectors. **/ - source_data->Keys = (char**)check_ptr(ci_xaToTrimmedArray(&key_xarray, 1)); - source_data->Strings = (char**)check_ptr(ci_xaToTrimmedArray(&data_xarray, 1)); - source_data->Vectors = (int**)check_ptr(ci_xaToTrimmedArray(&vector_xarray, 1)); - if (source_data->Keys == NULL) goto end_free_data; - if (source_data->Strings == NULL) goto end_free_data; - if (source_data->Vectors == NULL) goto end_free_data; - - /** Success. **/ - successful = true; - - end_free_data: - if (key_xarray.nAlloc != 0) - { - for (unsigned int i = 0u; i < vector_xarray.nItems; i++) - nmSysFree(key_xarray.Items[i]); - check(xaDeInit(&key_xarray)); /* Failure ignored. */ - } - if (data_xarray.nAlloc != 0) - { - for (unsigned int i = 0u; i < data_xarray.nItems; i++) - nmSysFree(data_xarray.Items[i]); - check(xaDeInit(&data_xarray)); /* Failure ignored. */ - } - if (vector_xarray.nAlloc != 0) - { - for (unsigned int i = 0u; i < vector_xarray.nItems; i++) - ca_free_vector(vector_xarray.Items[i]); - check(xaDeInit(&vector_xarray)); /* Failure ignored. */ - } - - end_close_query: - ret = objQueryClose(query); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Failed to close query (error code %d).", ret); - // ret = ret; // Fall-through: Failure ignored. - } - - end_close: - ret = objClose(obj); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Failed to close object driver (error code %d).", ret); - // ret = ret; // Fall-through: Failure ignored. - } - - end: - if (!successful) mssErrorf(0, "Cluster", "SourceData computation failed."); - return (successful) ? 0 : -1; + + /** Print an error if the function failed. **/ + if (!successful) mssErrorf(0, "Cluster", "SourceData computation failed."); + + /** Return the function status code. **/ + return (successful) ? 0 : -1; } @@ -2488,148 +2469,159 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) ***/ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { - /** If the clusters are already computed, we're done. **/ - if (cluster_data->Clusters != NULL) return 0; - - /** Make source data available. **/ - pSourceData source_data = node_data->SourceData; - - /** We need the SourceData vectors to compute clusters. **/ - if (ci_ComputeSourceData(source_data, node_data->ParamList->Session) != 0) - { - mssErrorf(0, "Cluster", "ClusterData computation failed due to missing SourceData."); - goto err; - } - - /** Record the date and time. **/ - if (!check(objCurrentDate(&cluster_data->DateComputed))) goto err; - - /** Allocate static memory for finding clusters. **/ - const size_t clusters_size = cluster_data->nClusters * sizeof(Cluster); - cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); - if (cluster_data->Clusters == NULL) goto err; - memset(cluster_data->Clusters, 0, clusters_size); - const size_t sims_size = source_data->nVectors * sizeof(double); - cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); - if (cluster_data->Sims == NULL) goto err_free_clusters; - memset(cluster_data->Sims, 0, sims_size); + cluster_data->Sims = NULL; + cluster_data->Clusters = NULL; - /** Execute clustering. **/ - switch (cluster_data->ClusterAlgorithm) - { - case ALGORITHM_NONE: + /** Guard segfaults. **/ + if (cluster_data == NULL || node_data == NULL) return -1; + + /** If the clusters are already computed, we're done. **/ + if (cluster_data->Clusters != NULL) return 0; + + /** Make source data available. **/ + pSourceData source_data = check_ptr(node_data->SourceData); + if (source_data == NULL) + { + mssErrorf(1, "Cluster", "Failed to get source data for cluster computation."); + goto err_free; + } + + /** We need the SourceData vectors to compute clusters. **/ + if (ci_ComputeSourceData(source_data, node_data->ParamList->Session) != 0) { - /** Put all the data into one cluster. **/ - pCluster first_cluster = &cluster_data->Clusters[0]; - first_cluster->Size = source_data->nVectors; - first_cluster->Strings = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(char*))); - if (first_cluster->Strings == NULL) goto err_free_sims; - first_cluster->Vectors = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(pVector))); - if (first_cluster->Vectors == NULL) goto err_free_sims; - memcpy(first_cluster->Strings, source_data->Strings, source_data->nVectors * sizeof(char*)); - memcpy(first_cluster->Vectors, source_data->Vectors, source_data->nVectors * sizeof(pVector)); - break; + mssErrorf(0, "Cluster", "ClusterData computation failed due to missing SourceData."); + goto err_free; } - case ALGORITHM_SLIDING_WINDOW: - /** Computed in each search for efficiency. **/ - memset(cluster_data->Clusters, 0, clusters_size); - break; + /** Record the date and time. **/ + if (!check(objCurrentDate(&cluster_data->DateComputed))) goto err_free; + + /** Allocate static memory for finding clusters. **/ + const size_t clusters_size = cluster_data->nClusters * sizeof(Cluster); + cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); + if (cluster_data->Clusters == NULL) goto err_free; + memset(cluster_data->Clusters, 0, clusters_size); + const size_t sims_size = source_data->nVectors * sizeof(double); + cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); + if (cluster_data->Sims == NULL) goto err_free; + memset(cluster_data->Sims, 0, sims_size); - case ALGORITHM_KMEANS: + /** Execute clustering. **/ + switch (cluster_data->ClusterAlgorithm) { - /** Check for unimplemented similarity measures. **/ - if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) + case ALGORITHM_NONE: { - mssErrorf(1, "Cluster", - "The similarity measure \"%s\" is not implemented.", - ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) - ); - goto err_free_sims; + /** Put all the data into one cluster. **/ + pCluster first_cluster = &cluster_data->Clusters[0]; + first_cluster->Size = source_data->nVectors; + first_cluster->Strings = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(char*))); + if (first_cluster->Strings == NULL) goto err_free; + first_cluster->Vectors = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(pVector))); + if (first_cluster->Vectors == NULL) goto err_free; + memcpy(first_cluster->Strings, source_data->Strings, source_data->nVectors * sizeof(char*)); + memcpy(first_cluster->Vectors, source_data->Vectors, source_data->nVectors * sizeof(pVector)); + break; } - /** Allocate lables. Note: kmeans does not require us to initialize them. **/ - const size_t lables_size = source_data->nVectors * sizeof(unsigned int); - unsigned int* labels = check_ptr(nmSysMalloc(lables_size)); - if (labels == NULL) goto err_free_sims; - - /** Run kmeans. **/ - const bool successful = check(ca_kmeans( - source_data->Vectors, - source_data->nVectors, - cluster_data->nClusters, - cluster_data->MaxIterations, - cluster_data->MinImprovement, - labels, - cluster_data->Sims - )); - if (!successful) goto err_free_sims; - - /** Convert the labels into clusters. **/ - - /** Allocate space for clusters. **/ - XArray indexes_in_cluster[cluster_data->nClusters]; - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - if (!check(xaInit(&indexes_in_cluster[i], 8))) goto err_free_sims; - - /** Iterate through each label and add the index of the specified cluster to the xArray. **/ - for (unsigned long long i = 0llu; i < source_data->nVectors; i++) - if (!check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i))) goto err_free_sims; - nmSysFree(labels); /* Free unused data. */ + case ALGORITHM_SLIDING_WINDOW: + /** Computed in each search for efficiency. **/ + memset(cluster_data->Clusters, 0, clusters_size); + break; - /** Iterate through each cluster, store it, and free the xArray. **/ - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + case ALGORITHM_KMEANS: { - pXArray indexes_in_this_cluster = &indexes_in_cluster[i]; - pCluster cluster = &cluster_data->Clusters[i]; - cluster->Size = indexes_in_this_cluster->nItems; - cluster->Strings = check_ptr(nmSysMalloc(cluster->Size * sizeof(char*))); - if (cluster->Strings == NULL) goto err_free_sims; - cluster->Vectors = check_ptr(nmSysMalloc(cluster->Size * sizeof(pVector))); - if (cluster->Vectors == NULL) goto err_free_sims; - for (unsigned int j = 0u; j < cluster->Size; j++) + /** Check for unimplemented similarity measures. **/ + if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) + { + mssErrorf(1, "Cluster", + "The similarity measure \"%s\" is not implemented.", + ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) + ); + goto err_free; + } + + /** Allocate lables. Note: kmeans does not require us to initialize them. **/ + const size_t lables_size = source_data->nVectors * sizeof(unsigned int); + unsigned int* labels = check_ptr(nmSysMalloc(lables_size)); + if (labels == NULL) goto err_free; + + /** Run kmeans. **/ + const bool successful = check(ca_kmeans( + source_data->Vectors, + source_data->nVectors, + cluster_data->nClusters, + cluster_data->MaxIterations, + cluster_data->MinImprovement, + labels, + cluster_data->Sims + )); + if (!successful) goto err_free; + + /** Convert the labels into clusters. **/ + + /** Allocate space for clusters. **/ + XArray indexes_in_cluster[cluster_data->nClusters]; + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + if (!check(xaInit(&indexes_in_cluster[i], 8))) goto err_free; + + /** Iterate through each label and add the index of the specified cluster to the xArray. **/ + for (unsigned long long i = 0llu; i < source_data->nVectors; i++) + if (!check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i))) goto err_free; + nmSysFree(labels); /* Free unused data. */ + + /** Iterate through each cluster, store it, and free the xArray. **/ + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { - const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[j]; - cluster->Strings[j] = source_data->Strings[index]; - cluster->Vectors[j] = source_data->Vectors[index]; + pXArray indexes_in_this_cluster = &indexes_in_cluster[i]; + pCluster cluster = &cluster_data->Clusters[i]; + cluster->Size = indexes_in_this_cluster->nItems; + cluster->Strings = check_ptr(nmSysMalloc(cluster->Size * sizeof(char*))); + if (cluster->Strings == NULL) goto err_free; + cluster->Vectors = check_ptr(nmSysMalloc(cluster->Size * sizeof(pVector))); + if (cluster->Vectors == NULL) goto err_free; + for (unsigned int j = 0u; j < cluster->Size; j++) + { + const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[j]; + cluster->Strings[j] = source_data->Strings[index]; + cluster->Vectors[j] = source_data->Vectors[index]; + } + check(xaDeInit(indexes_in_this_cluster)); /* Failure ignored. */ } - check(xaDeInit(indexes_in_this_cluster)); /* Failure ignored. */ + + /** k-means done. **/ + break; } - /** k-means is done. **/ - break; + default: + mssErrorf(1, "Cluster", + "Clustering algorithm \"%s\" is not implemented.", + ci_ClusteringAlgorithmToString(cluster_data->ClusterAlgorithm) + ); + goto err_free; } - default: - mssErrorf(1, "Cluster", - "Clustering algorithm \"%s\" is not implemented.", - ci_ClusteringAlgorithmToString(cluster_data->ClusterAlgorithm) - ); - goto err; - } - - /** Success. **/ - return 0; - - err_free_sims: - nmFree(cluster_data->Sims, sims_size); - cluster_data->Sims = NULL; - - err_free_clusters: - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - { - pCluster cluster = &cluster_data->Clusters[i]; - if (cluster->Strings != NULL) nmFree(cluster->Strings, cluster->Size * sizeof(char*)); - else break; - if (cluster->Vectors != NULL) nmFree(cluster->Vectors, cluster->Size * sizeof(pVector)); - else break; - } - nmFree(cluster_data->Clusters, clusters_size); - cluster_data->Clusters = NULL; - - err: - mssErrorf(0, "Cluster", "ClusterData computation failed for \"%s\".", cluster_data->Name); - return -1; + /** Success. **/ + return 0; + + err_free: + if (cluster_data->Sims != NULL) nmFree(cluster_data->Sims, sims_size); + + if (cluster_data->Clusters != NULL) + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + pCluster cluster = &cluster_data->Clusters[i]; + if (cluster->Strings != NULL) nmFree(cluster->Strings, cluster->Size * sizeof(char*)); + else break; + if (cluster->Vectors != NULL) nmFree(cluster->Vectors, cluster->Size * sizeof(pVector)); + else break; + } + nmFree(cluster_data->Clusters, clusters_size); + } + + mssErrorf(0, "Cluster", "ClusterData computation failed for \"%s\".", cluster_data->Name); + + return -1; } @@ -2647,53 +2639,39 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) ***/ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { - int ret; - - /** If the clusters are already computed, we're done. **/ - if (search_data->Dups != NULL) return 0; - - /** We need the cluster data to be computed before we search it. **/ - pClusterData cluster_data = search_data->SourceCluster; - ret = ci_ComputeClusterData(cluster_data, node_data); - if (ret != 0) - { - mssErrorf(0, "Cluster", "SearchData computation failed due to missing clusters."); - goto err; - } + pXArray dups = NULL; - /** Record the date and time. **/ - if (!check(objCurrentDate(&search_data->DateComputed))) goto err; - - /** Execute the search using the specified source and comparison function. **/ - pXArray dups = NULL, dups_temp = NULL; - switch (search_data->SimilarityMeasure) - { - case SIMILARITY_COSINE: + /** If the clusters are already computed, we're done. **/ + if (search_data->Dups != NULL) return 0; + + /** We need the cluster data to be computed before we search it. **/ + pClusterData cluster_data = check_ptr(search_data->SourceCluster); + if (cluster_data == NULL) { - if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) - { - dups_temp = check_ptr(ca_sliding_search( - (void**)cluster_data->SourceData->Vectors, - cluster_data->SourceData->nVectors, - cluster_data->MaxIterations, /* Window size. */ - ca_cos_compare, - search_data->Threshold, - (void**)cluster_data->SourceData->Keys, - dups - )); - if (dups_temp == NULL) - { - mssErrorf(1, "Cluster", "Failed to compute sliding search with cosine similarity measure."); - goto err_free; - } - } - else + mssErrorf(1, "Cluster", "Failed to get cluster data for search computation."); + goto err_free; + } + if (ci_ComputeClusterData(cluster_data, node_data) != 0) + { + mssErrorf(0, "Cluster", "SearchData computation failed due to missing clusters."); + goto err_free; + } + + /** Record the date and time. **/ + if (!check(objCurrentDate(&search_data->DateComputed))) goto err_free; + + /** Execute the search using the specified source and comparison function. **/ + pXArray dups_temp = NULL; + switch (search_data->SimilarityMeasure) + { + case SIMILARITY_COSINE: { - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) { - dups_temp = check_ptr(ca_complete_search( - (void**)cluster_data->Clusters[i].Vectors, - cluster_data->Clusters[i].Size, + dups_temp = check_ptr(ca_sliding_search( + (void**)cluster_data->SourceData->Vectors, + cluster_data->SourceData->nVectors, + cluster_data->MaxIterations, /* Window size. */ ca_cos_compare, search_data->Threshold, (void**)cluster_data->SourceData->Keys, @@ -2701,41 +2679,41 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) )); if (dups_temp == NULL) { - mssErrorf(1, "Cluster", "Failed to compute complete search with cosine similarity measure."); + mssErrorf(1, "Cluster", "Failed to compute sliding search with cosine similarity measure."); goto err_free; } - else dups = dups_temp; } - } - break; - } - - case SIMILARITY_LEVENSHTEIN: - { - if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) - { - dups_temp = check_ptr(ca_sliding_search( - (void**)cluster_data->SourceData->Vectors, - cluster_data->SourceData->nVectors, - cluster_data->MaxIterations, /* Window size. */ - ca_lev_compare, - search_data->Threshold, - (void**)cluster_data->SourceData->Keys, - dups - )); - if (dups_temp == NULL) + else { - mssErrorf(1, "Cluster", "Failed to compute sliding search with Levenstein similarity measure."); - goto err_free; + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + dups_temp = check_ptr(ca_complete_search( + (void**)cluster_data->Clusters[i].Vectors, + cluster_data->Clusters[i].Size, + ca_cos_compare, + search_data->Threshold, + (void**)cluster_data->SourceData->Keys, + dups + )); + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", "Failed to compute complete search with cosine similarity measure."); + goto err_free; + } + else dups = dups_temp; + } } + break; } - else + + case SIMILARITY_LEVENSHTEIN: { - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) { - dups_temp = check_ptr(ca_complete_search( - (void**)cluster_data->Clusters[i].Strings, - cluster_data->Clusters[i].Size, + dups_temp = check_ptr(ca_sliding_search( + (void**)cluster_data->SourceData->Vectors, + cluster_data->SourceData->nVectors, + cluster_data->MaxIterations, /* Window size. */ ca_lev_compare, search_data->Threshold, (void**)cluster_data->SourceData->Keys, @@ -2743,49 +2721,73 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) )); if (dups_temp == NULL) { - mssErrorf(1, "Cluster", "Failed to compute complete search with Levenstein similarity measure."); + mssErrorf(1, "Cluster", "Failed to compute sliding search with Levenstein similarity measure."); goto err_free; } - else dups = dups_temp; } + else + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + dups_temp = check_ptr(ca_complete_search( + (void**)cluster_data->Clusters[i].Strings, + cluster_data->Clusters[i].Size, + ca_lev_compare, + search_data->Threshold, + (void**)cluster_data->SourceData->Keys, + dups + )); + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", "Failed to compute complete search with Levenstein similarity measure."); + goto err_free; + } + else dups = dups_temp; + } + } + break; } - break; + + default: + mssErrorf(1, "Cluster", + "Unknown similarity meansure \"%s\".", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; } + if (dups_temp == NULL) goto err_free; + else dups = dups_temp; + // fprintf(stderr, "Done searching, found %d dups.\n", dups->nItems); - default: - mssErrorf(1, "Cluster", - "Unknown similarity meansure \"%s\".", - ci_SimilarityMeasureToString(search_data->SimilarityMeasure) - ); + /** Store dups. **/ + search_data->nDups = dups->nItems; + search_data->Dups = (dups->nItems == 0) + ? check_ptr(nmSysMalloc(0)) + : ci_xaToTrimmedArray(dups, 2); + if (search_data->Dups == NULL) + { + mssErrorf(1, "Cluster", "Failed to store dups after computing search data."); goto err_free; - } - if (dups_temp == NULL) goto err_free; - else dups = dups_temp; - // fprintf(stderr, "Done searching, found %d dups.\n", dups->nItems); - - /** Store dups. **/ - search_data->nDups = dups->nItems; - search_data->Dups = (dups->nItems == 0) - ? check_ptr(nmSysMalloc(0)) - : ci_xaToTrimmedArray(dups, 2); - - /** Success. **/ - return 0; - + } + + /** Success. **/ + return 0; + err_free: - if (dups != NULL) - { - for (unsigned int i = 0u; i < dups->nItems; i++) + if (search_data->Dups != NULL) nmSysFree(search_data->Dups); + if (dups != NULL) { - if (dups->Items[i] != NULL) nmFree(dups->Items[i], sizeof(Dup)); - else break; + for (unsigned int i = 0u; i < dups->nItems; i++) + { + if (dups->Items[i] != NULL) nmFree(dups->Items[i], sizeof(Dup)); + else break; + } + check(xaFree(dups)); /* Failure ignored. */ } - check(xaFree(dups)); /* Failure ignored. */ - } - - err: - mssErrorf(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); - return -1; + + mssErrorf(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); + + return -1; } @@ -2805,15 +2807,15 @@ static int ci_GetParamType(void* inf_v, const char* attr_name) { pNodeData node_data = (pNodeData)inf_v; - /** Find the parameter. **/ - for (unsigned int i = 0; i < node_data->nParams; i++) - { - pParam param = node_data->Params[i]; - if (strcmp(param->Name, attr_name) != 0) continue; - - /** Parameter found. **/ - return (param->Value == NULL) ? DATA_T_UNAVAILABLE : param->Value->DataType; - } + /** Find the parameter. **/ + for (unsigned int i = 0; i < node_data->nParams; i++) + { + pParam param = node_data->Params[i]; + if (strcmp(param->Name, attr_name) != 0) continue; + + /** Parameter found. **/ + return (param->Value == NULL) ? DATA_T_UNAVAILABLE : param->Value->DataType; + } /** Parameter not found. **/ return DATA_T_UNAVAILABLE; @@ -2848,39 +2850,41 @@ static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData { pNodeData node_data = (pNodeData)inf_v; - /** Find the parameter. **/ - for (unsigned int i = 0; i < node_data->nParams; i++) - { - pParam param = (pParam)node_data->Params[i]; - if (strcmp(param->Name, attr_name) != 0) continue; - - /** Parameter found. **/ - if (param->Value == NULL) return 1; - if (param->Value->Flags & DATA_TF_NULL) return 1; - if (param->Value->DataType != datatype) + /** Find the parameter. **/ + for (unsigned int i = 0; i < node_data->nParams; i++) { - mssErrorf(1, "Cluster", "Type mismatch accessing parameter '%s'.", param->Name); - return -1; + pParam param = (pParam)node_data->Params[i]; + if (strcmp(param->Name, attr_name) != 0) continue; + + /** Parameter found. **/ + if (param->Value == NULL) return 1; + if (param->Value->Flags & DATA_TF_NULL) return 1; + if (param->Value->DataType != datatype) + { + mssErrorf(1, "Cluster", "Type mismatch accessing parameter '%s'.", param->Name); + return -1; + } + + /** Return param value. **/ + if (!check(objCopyData(&(param->Value->Data), val, datatype))) goto err; + return 0; } - /** Return param value. **/ - if (!check(objCopyData(&(param->Value->Data), val, datatype))) goto err; - return 0; - } - err: - mssErrorf(1, "Cluster", - "Failed to get parameter ['%s' : %s]", - attr_name, ci_TypeToStr(datatype) - ); - return -1; + mssErrorf(1, "Cluster", + "Failed to get parameter ['%s' : %s]", + attr_name, objTypeToStr(datatype) + ); + + return -1; } // LINK #functions /** Not implemented. **/ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are imutable."); + return -1; } @@ -2906,152 +2910,159 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData ***/ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { - /** Update statistics. **/ - ClusterStatistics.OpenCalls++; + pNodeData node_data = NULL; + pDriverData driver_data = NULL; - /** If CREAT and EXCL are specified, exclusively create it, failing if the file already exists. **/ - pSnNode node_struct = NULL; - bool can_create = (parent->Mode & O_CREAT) && (parent->SubPtr == parent->Pathname->nElements); - if (can_create && (parent->Mode & O_EXCL)) - { - node_struct = snNewNode(parent->Prev, usr_type); + /** Update statistics. **/ + ClusterStatistics.OpenCalls++; + + /** If CREAT and EXCL are specified, exclusively create it, failing if the file already exists. **/ + pSnNode node_struct = NULL; + bool can_create = (parent->Mode & O_CREAT) && (parent->SubPtr == parent->Pathname->nElements); + if (can_create && (parent->Mode & O_EXCL)) + { + node_struct = snNewNode(parent->Prev, usr_type); + if (node_struct == NULL) + { + mssErrorf(0, "Cluster", "Failed to exclusively create new node struct."); + goto err_free; + } + } + + /** Read the node if it exists. **/ + if (node_struct == NULL) + node_struct = snReadNode(parent->Prev); + + /** If we can't read it, create it (if allowed). **/ + if (node_struct == NULL && can_create) + node_struct = snNewNode(parent->Prev, usr_type); + + /** If there still isn't a node, fail early. **/ if (node_struct == NULL) { - mssErrorf(0, "Cluster", "Failed to exclusively create new node struct."); - goto err; + mssErrorf(0, "Cluster", "Failed to create node struct."); + goto err_free; } - } - - /** Read the node if it exists. **/ - if (node_struct == NULL) - node_struct = snReadNode(parent->Prev); - - /** If we can't read it, create it (if allowed). **/ - if (node_struct == NULL && can_create) - node_struct = snNewNode(parent->Prev, usr_type); - - /** If there still isn't a node, fail early. **/ - if (node_struct == NULL) - { - mssErrorf(0, "Cluster", "Failed to create node struct."); - goto err; - } - - /** Magic. **/ - ASSERTMAGIC(node_struct, MGK_STNODE); - ASSERTMAGIC(node_struct->Data, MGK_STRUCTINF); - - /** Parse node data from the node_struct. **/ - pNodeData node_data = ci_ParseNodeData(node_struct->Data, parent); - if (node_data == NULL) - { - mssErrorf(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(parent)); - goto err; - } - - /** Allocate driver instance data. **/ - pDriverData driver_data = check_ptr(nmMalloc(sizeof(DriverData))); - if (driver_data == NULL) goto err_free_node; - memset(driver_data, 0, sizeof(DriverData)); - driver_data->NodeData = node_data; - - /** Detect target from path. **/ - char* target_name = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); - if (target_name == NULL) - { - /** Target found: Root **/ - driver_data->TargetType = TARGET_ROOT; - driver_data->TargetData = (void*)driver_data->NodeData->SourceData; - return (void*)driver_data; /* Success. */ - } - - /** Search clusters. **/ - for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) - { - pClusterData cluster = node_data->ClusterDatas[i]; - if (strcmp(cluster->Name, target_name) != 0) continue; - /** Target found: Cluster **/ - driver_data->TargetType = TARGET_CLUSTER; + /** Magic. **/ + ASSERTMAGIC(node_struct, MGK_STNODE); + ASSERTMAGIC(node_struct->Data, MGK_STRUCTINF); - /** Check for sub-clusters in the path. **/ - while (true) + /** Parse node data from the node_struct. **/ + node_data = ci_ParseNodeData(node_struct->Data, parent); + if (node_data == NULL) { - /** Decend one path part deeper into the path. **/ - const char* path_part = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); + mssErrorf(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(parent)); + goto err_free; + } + + /** Allocate driver instance data. **/ + driver_data = check_ptr(nmMalloc(sizeof(DriverData))); + if (driver_data == NULL) goto err_free; + memset(driver_data, 0, sizeof(DriverData)); + driver_data->NodeData = node_data; + driver_data->NodeData->OpenCount++; - /** If the path does not go any deeper, we're done. **/ - if (path_part == NULL) - { - driver_data->TargetData = (void*)cluster; - break; - } + /** Detect target from path. **/ + char* target_name = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); + if (target_name == NULL) + { + /** Target found: Root **/ + driver_data->TargetType = TARGET_NODE; + driver_data->TargetData = (void*)driver_data->NodeData->SourceData; + goto success; + } + + /** Search clusters. **/ + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + { + pClusterData cluster = node_data->ClusterDatas[i]; + if (strcmp(cluster->Name, target_name) != 0) continue; + + /** Target found: Cluster **/ + driver_data->TargetType = TARGET_CLUSTER; - /** Need to go deeper: Search for the requested sub-cluster. **/ - for (unsigned int i = 0u; i < cluster->nSubClusters; i++) + /** Check for sub-clusters in the path. **/ + while (true) { - pClusterData sub_cluster = cluster->SubClusters[i]; - if (strcmp(sub_cluster->Name, path_part) != 0) continue; + /** Decend one path part deeper into the path. **/ + const char* path_part = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); - /** Target found: Sub-cluster **/ - cluster = sub_cluster; - goto continue_descent; - } + /** If the path does not go any deeper, we're done. **/ + if (path_part == NULL) + { + driver_data->TargetData = (void*)cluster; + break; + } + + /** Need to go deeper: Search for the requested sub-cluster. **/ + for (unsigned int i = 0u; i < cluster->nSubClusters; i++) + { + pClusterData sub_cluster = cluster->SubClusters[i]; + if (strcmp(sub_cluster->Name, path_part) != 0) continue; + + /** Target found: Sub-cluster **/ + cluster = sub_cluster; + goto continue_descent; + } + + /** Path names sub-cluster that does not exist. **/ + mssErrorf(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); + goto err_free; - /** Path names sub-cluster that does not exist. **/ - mssErrorf(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); - goto err_free_node; + continue_descent:; + } + goto success; + } + + /** Search searches. **/ + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + { + pSearchData search = node_data->SearchDatas[i]; + if (strcmp(search->Name, target_name) != 0) continue; + + /** Target found: Search **/ + driver_data->TargetType = TARGET_SEARCH; + driver_data->TargetData = (void*)search; - continue_descent:; + /** Check for extra, invalid path parts. **/ + char* extra_data = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); + if (extra_data != NULL) + { + mssErrorf(1, "Cluster", "Unknown path part %s.", extra_data); + goto err_free; + } + return (void*)driver_data; /* Success. */ } - return (void*)driver_data; /* Success. */ - } - - /** Search searches. **/ - for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) - { - pSearchData search = node_data->SearchDatas[i]; - if (strcmp(search->Name, target_name) != 0) continue; - - /** Target found: Search **/ - driver_data->TargetType = TARGET_SEARCH; - driver_data->TargetData = (void*)search; - - /** Check for extra, invalid path parts. **/ - char* extra_data = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); - if (extra_data != NULL) + + /** We were unable to find the requested cluster or search. **/ + mssErrorf(1, "Cluster", "\"%s\" is not the name of a declared cluster or search.", target_name); + + /** Attempt to give a hint. **/ { - mssErrorf(1, "Cluster", "Unknown path part %s.", extra_data); - goto err_free_node; + const unsigned int n_targets = node_data->nClusterDatas + node_data->nSearchDatas; + char* target_names[n_targets]; + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + target_names[i] = node_data->ClusterDatas[i]->Name; + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + target_names[i + node_data->nClusterDatas] = node_data->SearchDatas[i]->Name; + ci_TryHint(target_name, target_names, n_targets); } - return (void*)driver_data; /* Success. */ - } - - /** We were unable to find the requested cluster or search. **/ - mssErrorf(1, "Cluster", "\"%s\" is not the name of a declared cluster or search.", target_name); - - /** Attempt to give a hint. **/ - { - const unsigned int n_targets = node_data->nClusterDatas + node_data->nSearchDatas; - char* target_names[n_targets]; - for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) - target_names[i] = node_data->ClusterDatas[i]->Name; - for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) - target_names[i + node_data->nClusterDatas] = node_data->SearchDatas[i]->Name; - ci_TryHint(target_name, target_names, n_targets); - } - - /** Error cleanup. **/ - err_free_node: - if (node_data != NULL) ci_FreeNodeData(node_data); - if (driver_data != NULL) nmFree(driver_data, sizeof(DriverData)); - - err: - mssErrorf(0, "Cluster", - "Failed to open cluster file \"%s\" at: %s", - ci_file_name(parent), ci_file_path(parent) - ); - return NULL; + + /** Error cleanup. **/ + err_free: + if (node_data != NULL) ci_FreeNodeData(node_data); + if (driver_data != NULL) nmFree(driver_data, sizeof(DriverData)); + + mssErrorf(0, "Cluster", + "Failed to open cluster file \"%s\" at: %s", + ci_file_name(parent), ci_file_path(parent) + ); + + return NULL; + + success: + return driver_data; } @@ -3067,21 +3078,20 @@ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_typ int clusterClose(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - ClusterStatistics.CloseCalls++; - /** Entries are shallow copies so we shouldn't do a deep free. **/ - if (driver_data->TargetType == TARGET_CLUSTER_ENTRY - || driver_data->TargetType == TARGET_SEARCH_ENTRY) - { + /** Update statistics. **/ + ClusterStatistics.CloseCalls++; + + /** No work needed. **/ + if (driver_data == NULL) return 0; + + /** Unlink the driver's node data. **/ + pNodeData node_data = driver_data->NodeData; + if (node_data != NULL && --node_data->OpenCount == 0) + ci_FreeNodeData(driver_data->NodeData); + + /** Free driver data. **/ nmFree(driver_data, sizeof(DriverData)); - return 0; - } - - /** Free the node data (which is held in cache). **/ - ci_FreeNodeData(driver_data->NodeData); - - /** Free driver data. **/ - nmFree(driver_data, sizeof(DriverData)); return 0; } @@ -3096,15 +3106,33 @@ int clusterClose(void* inf_v, pObjTrxTree* oxt) *** @param query The query to use on this struct. This is assumed to be *** handled elsewhere, so we don't read it here (unused). *** @param oxt The object system tree, similar to a kind of "scope" (unused). - *** @returns The cluster query. + *** @returns The cluster query, or + *** NULL if an error occurs. ***/ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) { - ClusterStatistics.OpenQueryCalls++; - pClusterQuery cluster_query = check_ptr(nmMalloc(sizeof(ClusterQuery))); - if (cluster_query == NULL) return NULL; - cluster_query->DriverData = (pDriverData)inf_v; - cluster_query->RowIndex = 0u; + pClusterQuery cluster_query = NULL; + pDriverData driver_data = inf_v; + + if (driver_data->TargetType != TARGET_SEARCH + && driver_data->TargetType != TARGET_CLUSTER + && driver_data->TargetType != TARGET_NODE) + { + /** Queries are not supported for this target type. **/ + return NULL; + } + + /** Update statistics. **/ + ClusterStatistics.OpenQueryCalls++; + + /** Allocate memory for the query. **/ + cluster_query = check_ptr(nmMalloc(sizeof(ClusterQuery))); + if (cluster_query == NULL) return NULL; + + /** Initialize the query. **/ + cluster_query->DriverData = (pDriverData)inf_v; + cluster_query->RowIndex = 0u; + return cluster_query; } @@ -3123,76 +3151,124 @@ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) ***/ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { - int ret; pClusterQuery cluster_query = (pClusterQuery)qy_v; + pDriverData driver_data = cluster_query->DriverData; + pDriverData result_data = NULL; - /** Update statistics. **/ - ClusterStatistics.FetchCalls++; - - /** Ensure that the data being fetched exists and is computed. **/ - TargetType target_type = cluster_query->DriverData->TargetType, new_target_type; - unsigned int data_amount = 0u; - switch (target_type) - { - case TARGET_ROOT: - mssErrorf(1, "Cluster", "Querying the root node of a cluster file is not allowed."); - fprintf(stderr, " > Hint: Try / or /\n"); - goto err; + /** Update statistics. **/ + ClusterStatistics.FetchCalls++; + + /** Allocate result struct. **/ + result_data = check_ptr(nmMalloc(sizeof(DriverData))); + if (result_data == NULL) goto err; + + /** Default initialization. **/ + result_data->NodeData = driver_data->NodeData; + result_data->TargetData = driver_data->TargetData; + result_data->TargetType = 0; /* Unset. */ + result_data->TargetIndex = 0; /* Reset. */ + result_data->TargetAttrIndex = 0; /* Reset. */ + result_data->TargetMethodIndex = 0; /* Reset. */ - case TARGET_CLUSTER: + /** Load node data. **/ + pNodeData node_data = driver_data->NodeData; + + /** Ensure that the data being fetched exists and is computed. **/ + const TargetType target_type = driver_data->TargetType; + switch (target_type) { - new_target_type = TARGET_CLUSTER_ENTRY; - pClusterData target = (pClusterData)cluster_query->DriverData->TargetData; - ret = ci_ComputeClusterData(target, cluster_query->DriverData->NodeData); - if (ret != 0) + case TARGET_NODE: { - mssErrorf(0, "Cluster", "Failed to compute ClusterData for query."); - goto err; + unsigned int index = cluster_query->RowIndex++; + + /** Iterate over clusters. **/ + const unsigned int n_cluster_datas = node_data->nClusterDatas; + if (index < n_cluster_datas) + { + /** Fetch a cluster. **/ + result_data->TargetType = TARGET_CLUSTER; + result_data->TargetData = node_data->ClusterDatas[index]; + break; + } + else index -= n_cluster_datas; + + /** Iterate over searches. **/ + const unsigned int n_search_datas = node_data->nSearchDatas; + if (index < n_search_datas) + { + /** Fetch a search. **/ + result_data->TargetType = TARGET_SEARCH; + result_data->TargetData = node_data->SearchDatas[index]; + break; + } + else index -= n_search_datas; + + /** Iteration complete. **/ + goto done; } - data_amount = target->nClusters; - break; - } - - case TARGET_SEARCH: - { - new_target_type = TARGET_SEARCH_ENTRY; - pSearchData target = (pSearchData)cluster_query->DriverData->TargetData; - ret = ci_ComputeSearchData(target, cluster_query->DriverData->NodeData); - if (ret != 0) + + case TARGET_CLUSTER: { - mssErrorf(0, "Cluster", "Failed to compute SearchData for query."); - goto err; + /** Ensure the required data is computed. **/ + pClusterData target = (pClusterData)driver_data->TargetData; + if (ci_ComputeClusterData(target, node_data) != 0) + { + mssErrorf(0, "Cluster", "Failed to compute ClusterData for query."); + goto err; + } + + /** Stop iteration if the requested data does not exist. **/ + if (cluster_query->RowIndex >= target->nClusters) goto done; + + /** Set the data being fetched. **/ + result_data->TargetType = TARGET_CLUSTER_ENTRY; + result_data->TargetIndex = cluster_query->RowIndex++; + + break; + } + + case TARGET_SEARCH: + { + /** Ensure the required data is computed. **/ + pSearchData target = (pSearchData)driver_data->TargetData; + if (ci_ComputeSearchData(target, node_data) != 0) + { + mssErrorf(0, "Cluster", "Failed to compute SearchData for query."); + goto err; + } + + /** Stop iteration if the requested data does not exist. **/ + if (cluster_query->RowIndex >= target->nDups) goto done; + + /** Set the data being fetched. **/ + result_data->TargetType = TARGET_SEARCH_ENTRY; + result_data->TargetIndex = cluster_query->RowIndex++; + + break; } - data_amount = target->nDups; - break; + + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + mssErrorf(1, "Cluster", "Querying a query result is not allowed."); + goto err; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + goto err; } - case TARGET_CLUSTER_ENTRY: - case TARGET_SEARCH_ENTRY: - mssErrorf(1, "Cluster", "Querying a query result is not allowed."); - goto err; + /** Add a link to the NodeData so that it isn't freed while we're using it. **/ + node_data->OpenCount++; - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); - goto err; - } - - /** Check that the requested data exists, returning null if we've reached the end of the data. **/ - if (cluster_query->RowIndex >= data_amount) return NULL; - - /** Create the result struct. **/ - pDriverData driver_data = check_ptr(nmMalloc(sizeof(DriverData))); - if (driver_data == NULL) goto err; - memcpy(driver_data, cluster_query->DriverData, sizeof(DriverData)); - driver_data->TargetType = new_target_type; - driver_data->TargetIndex = cluster_query->RowIndex++; - - /** Success. **/ - return driver_data; - + /** Success. **/ + return result_data; + err: - mssErrorf(0, "Cluster", "Failed to fetch query result."); - return NULL; + mssErrorf(0, "Cluster", "Failed to fetch query result."); + + done: + if (result_data != NULL) nmFree(result_data, sizeof(DriverData)); + return NULL; } @@ -3207,7 +3283,8 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ***/ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) { - nmFree(qy_v, sizeof(ClusterQuery)); + if (qy_v != NULL) nmFree(qy_v, sizeof(ClusterQuery)); + return 0; } @@ -3226,82 +3303,86 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - /** Update statistics. **/ - ClusterStatistics.GetTypeCalls++; - - /** Guard possible segfault. **/ - if (attr_name == NULL) - { - fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); - return DATA_T_UNAVAILABLE; - } - - /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ - if (attr_name[0] == 'k' || attr_name[0] == 's') goto handle_targets; - - /** Types for general attributes. **/ - if (strcmp(attr_name, "name") == 0 - || strcmp(attr_name, "annotation") == 0 - || strcmp(attr_name,"content_type") == 0 - || strcmp(attr_name, "inner_type") == 0 - || strcmp(attr_name,"outer_type") == 0) - return DATA_T_STRING; - if (strcmp(attr_name, "last_modification") == 0) - return DATA_T_DATETIME; - if ((strcmp(attr_name, "date_created") == 0 - || strcmp(attr_name, "date_computed") == 0) - && - (driver_data->TargetType == TARGET_CLUSTER - || driver_data->TargetType == TARGET_SEARCH)) - return DATA_T_DATETIME; - - /** Types for specific data targets. **/ - handle_targets: - switch (driver_data->TargetType) - { - case TARGET_ROOT: - if (strcmp(attr_name, "source") == 0 - || strcmp(attr_name, "data_attr") == 0 - || strcmp(attr_name, "key_attr") == 0) - return DATA_T_STRING; - break; - - case TARGET_CLUSTER: - if (strcmp(attr_name, "algorithm") == 0 - || strcmp(attr_name, "similarity_measure") == 0) - return DATA_T_STRING; - if (strcmp(attr_name, "num_clusters") == 0 - || strcmp(attr_name, "max_iterations") == 0) - return DATA_T_INTEGER; - if (strcmp(attr_name, "min_improvement") == 0) - return DATA_T_DOUBLE; - break; - - case TARGET_SEARCH: - if (strcmp(attr_name, "source") == 0 - || strcmp(attr_name, "similarity_measure") == 0) - return DATA_T_STRING; - if (strcmp(attr_name, "threshold") == 0) - return DATA_T_DOUBLE; - break; - - case TARGET_CLUSTER_ENTRY: - if (strcmp(attr_name, "items") == 0) - return DATA_T_STRINGVEC; - break; - - case TARGET_SEARCH_ENTRY: - if (strcmp(attr_name, "key1") == 0 - || strcmp(attr_name, "key2") == 0) - return DATA_T_STRING; - if (strcmp(attr_name, "sim") == 0) - return DATA_T_DOUBLE; - break; - - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + /** Update statistics. **/ + ClusterStatistics.GetTypeCalls++; + + /** Guard possible segfault. **/ + if (attr_name == NULL) + { + fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); return DATA_T_UNAVAILABLE; - } + } + + /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ + if (attr_name[0] == 'k' || attr_name[0] == 's') goto handle_targets; + + /** Types for general attributes. **/ + if (strcmp(attr_name, "name") == 0 + || strcmp(attr_name, "annotation") == 0 + || strcmp(attr_name,"content_type") == 0 + || strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name,"outer_type") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "last_modification") == 0) + return DATA_T_DATETIME; + if (strcmp(attr_name, "date_created") == 0 + || strcmp(attr_name, "date_computed") == 0) + { + return (driver_data->TargetType == TARGET_CLUSTER + || driver_data->TargetType == TARGET_CLUSTER_ENTRY + || driver_data->TargetType == TARGET_SEARCH + || driver_data->TargetType == TARGET_SEARCH_ENTRY) + ? DATA_T_DATETIME /* Target has date attr. */ + : DATA_T_UNAVAILABLE; /* Target does not have date attr. */ + } + + /** Types for specific data targets. **/ + handle_targets: + switch (driver_data->TargetType) + { + case TARGET_NODE: + if (strcmp(attr_name, "source") == 0 + || strcmp(attr_name, "data_attr") == 0 + || strcmp(attr_name, "key_attr") == 0) + return DATA_T_STRING; + break; + + case TARGET_CLUSTER: + if (strcmp(attr_name, "algorithm") == 0 + || strcmp(attr_name, "similarity_measure") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "num_clusters") == 0 + || strcmp(attr_name, "max_iterations") == 0) + return DATA_T_INTEGER; + if (strcmp(attr_name, "min_improvement") == 0) + return DATA_T_DOUBLE; + break; + + case TARGET_SEARCH: + if (strcmp(attr_name, "source") == 0 + || strcmp(attr_name, "similarity_measure") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "threshold") == 0) + return DATA_T_DOUBLE; + break; + + case TARGET_CLUSTER_ENTRY: + if (strcmp(attr_name, "items") == 0) + return DATA_T_STRINGVEC; + break; + + case TARGET_SEARCH_ENTRY: + if (strcmp(attr_name, "key1") == 0 + || strcmp(attr_name, "key2") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "sim") == 0) + return DATA_T_DOUBLE; + break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return DATA_T_UNAVAILABLE; + } return DATA_T_UNAVAILABLE; } @@ -3329,287 +3410,320 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - ClusterStatistics.GetValCalls++; - - /** Guard possible segfault. **/ - if (attr_name == NULL) - { - fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); - return DATA_T_UNAVAILABLE; - } - - /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ - if ((attr_name[0] == 'k' && datatype == DATA_T_STRING) /* key1, key2 : string */ - || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ - ) goto handle_targets; - - /** Type check. **/ - const int expected_datatype = clusterGetAttrType(inf_v, attr_name, NULL); - if (datatype != expected_datatype) - { - mssErrorf(1, "Cluster", - "Type mismatch: Accessing attribute ['%s' : %s] as type %s.", - attr_name, ci_TypeToStr(expected_datatype), ci_TypeToStr(datatype) - ); - return -1; - } - /** Handle name and annotation. **/ - if (strcmp(attr_name, "name") == 0) - { - ClusterStatistics.GetValCalls_name++; - switch (driver_data->TargetType) - { - case TARGET_ROOT: - val->String = ((pSourceData)driver_data->TargetData)->Name; - break; - - case TARGET_CLUSTER: - case TARGET_CLUSTER_ENTRY: - val->String = ((pClusterData)driver_data->TargetData)->Name; - break; - - case TARGET_SEARCH: - case TARGET_SEARCH_ENTRY: - val->String = ((pSearchData)driver_data->TargetData)->Name; - break; - - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - return -1; - } + /** Update statistics. **/ + ClusterStatistics.GetValCalls++; - return 0; - } - if (strcmp(attr_name, "annotation") == 0) - { - switch (driver_data->TargetType) + /** Guard possible segfault. **/ + if (attr_name == NULL) { - case TARGET_ROOT: val->String = "Clustering driver."; break; - case TARGET_CLUSTER: val->String = "Clustering driver: Cluster."; break; - case TARGET_CLUSTER_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; - case TARGET_SEARCH: val->String = "Clustering driver: Search."; break; - case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; - - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - return -1; + fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); + return DATA_T_UNAVAILABLE; } - return 0; - } - - /** Return the appropriate types. **/ - if (strcmp(attr_name, "outer_type") == 0) - { - val->String = "system/row"; - return 0; - } - if (strcmp(attr_name, "content_type") == 0 - || strcmp(attr_name, "inner_type") == 0) - { - val->String = "system/void"; - return 0; - } - - /** Last modification is not implemented yet. **/ - if (strcmp(attr_name, "last_modification") == 0) return 1; /* null */ - - /** Handle creation and computation dates. **/ - if (strcmp(attr_name, "date_created") == 0) - { - switch (driver_data->TargetType) + + /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ + if ((attr_name[0] == 'k' && datatype == DATA_T_STRING) /* key1, key2 : string */ + || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ + ) goto handle_targets; + + /** Type check. **/ + const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); + if (datatype != expected_datatype) { - case TARGET_ROOT: - case TARGET_CLUSTER_ENTRY: - case TARGET_SEARCH_ENTRY: - /** Attribute is not defined for this target type. **/ - return -1; - - case TARGET_CLUSTER: - val->DateTime = &((pClusterData)driver_data->TargetData)->DateCreated; - return 0; - - case TARGET_SEARCH: - val->DateTime = &((pSearchData)driver_data->TargetData)->DateCreated; - return 0; + mssErrorf(1, "Cluster", + "Type mismatch: Accessing attribute ['%s' : %s] as type %s.", + attr_name, objTypeToStr(expected_datatype), objTypeToStr(datatype) + ); + return -1; } - return -1; - } - if (strcmp(attr_name, "date_computed") == 0) - { - switch (driver_data->TargetType) + + /** Handle name. **/ + if (strcmp(attr_name, "name") == 0) { - case TARGET_ROOT: - case TARGET_CLUSTER_ENTRY: - case TARGET_SEARCH_ENTRY: - /** Attribute is not defined for this target type. **/ - return -1; - - case TARGET_CLUSTER: + ClusterStatistics.GetValCalls_name++; + switch (driver_data->TargetType) { - pClusterData target = (pClusterData)driver_data->TargetData; - pDateTime date_time = &target->DateComputed; - if (date_time->Value == 0) return 1; /* null */ - else val->DateTime = date_time; - return 0; + case TARGET_NODE: + val->String = ((pSourceData)driver_data->TargetData)->Name; + break; + + case TARGET_CLUSTER: + case TARGET_CLUSTER_ENTRY: + val->String = ((pClusterData)driver_data->TargetData)->Name; + break; + + case TARGET_SEARCH: + case TARGET_SEARCH_ENTRY: + val->String = ((pSearchData)driver_data->TargetData)->Name; + break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; } - case TARGET_SEARCH: - { - pSearchData target = (pSearchData)driver_data->TargetData; - pDateTime date_time = &target->DateComputed; - if (date_time->Value == 0) return 1; /* null */ - else val->DateTime = date_time; - return 0; - } + return 0; } - /** Default: Unknown type. **/ - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - return -1; - } - - /** Handle attributes for specific data targets. **/ - handle_targets: - switch (driver_data->TargetType) - { - case TARGET_ROOT: - if (strcmp(attr_name, "source") == 0) - { - val->String = ((pSourceData)driver_data->TargetData)->SourcePath; - return 0; - } - if (strcmp(attr_name, "key_attr") == 0) - { - val->String = ((pSourceData)driver_data->TargetData)->KeyAttr; - return 0; - } - if (strcmp(attr_name, "name_attr") == 0) + /** Handle annotation. **/ + if (strcmp(attr_name, "annotation") == 0) + { + switch (driver_data->TargetType) { - val->String = ((pSourceData)driver_data->TargetData)->NameAttr; - return 0; + case TARGET_NODE: val->String = "Clustering driver."; break; + case TARGET_CLUSTER: val->String = "Clustering driver: Cluster."; break; + case TARGET_CLUSTER_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + case TARGET_SEARCH: val->String = "Clustering driver: Search."; break; + case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; } - break; + return 0; + } - case TARGET_CLUSTER: + /** Handle various types. **/ + if (strcmp(attr_name, "outer_type") == 0) { - pClusterData target = (pClusterData)driver_data->TargetData; - - if (strcmp(attr_name, "algorithm") == 0) - { - val->String = ci_ClusteringAlgorithmToString(target->ClusterAlgorithm); - return 0; - } - if (strcmp(attr_name, "similarity_measure") == 0) - { - val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); - return 0; - } - if (strcmp(attr_name, "num_clusters") == 0) - { - if (target->nClusters > INT_MAX) - fprintf(stderr, "Warning: 'num_clusters' value of %u exceeds INT_MAX (%d).\n", target->nClusters, INT_MAX); - val->Integer = (int)target->nClusters; - return 0; - } - if (strcmp(attr_name, "max_iterations") == 0) - { - if (target->MaxIterations > INT_MAX) - fprintf(stderr, "Warning: 'max_iterations' value of %u exceeds INT_MAX (%d).\n", target->MaxIterations, INT_MAX); - val->Integer = (int)target->MaxIterations; - return 0; - } - if (strcmp(attr_name, "min_improvement") == 0) + val->String = "system/row"; + return 0; + } + if (strcmp(attr_name, "content_type") == 0 + || strcmp(attr_name, "inner_type") == 0) + { + val->String = "system/void"; + return 0; + } + if (strcmp(attr_name, "internal_type") == 0) + { + switch (driver_data->TargetType) { - val->Double = target->MinImprovement; - return 0; + case TARGET_NODE: val->String = "system/cluster"; break; + case TARGET_CLUSTER: val->String = "cluster/cluster"; break; + case TARGET_CLUSTER_ENTRY: val->String = "cluster/entry"; break; + case TARGET_SEARCH: val->String = "cluster/search"; break; + case TARGET_SEARCH_ENTRY: val->String = "search/entry"; break; + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; } - break; + + return 0; } - case TARGET_SEARCH: + /** Last modification is not implemented. **/ + if (strcmp(attr_name, "last_modification") == 0) { - pSearchData target = (pSearchData)driver_data->TargetData; - - if (strcmp(attr_name, "source") == 0) - { - val->String = target->SourceCluster->Name; - return 0; - } - if (strcmp(attr_name, "similarity_measure") == 0) - { - val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); - return 0; - } - if (strcmp(attr_name, "threshold") == 0) + if (driver_data->TargetType == TARGET_CLUSTER + || driver_data->TargetType == TARGET_CLUSTER_ENTRY + || driver_data->TargetType == TARGET_SEARCH + || driver_data->TargetType == TARGET_SEARCH_ENTRY) + goto date_computed; + else return 1; /* null */ + } + + /** Handle date_created. **/ + if (strcmp(attr_name, "date_created") == 0) + { + switch (driver_data->TargetType) { - val->Double = target->Threshold; - return 0; + case TARGET_NODE: + /** Attribute is not defined for this target type. **/ + return -1; + + case TARGET_CLUSTER: + case TARGET_CLUSTER_ENTRY: + val->DateTime = &((pClusterData)driver_data->TargetData)->DateCreated; + return 0; + + case TARGET_SEARCH: + case TARGET_SEARCH_ENTRY: + val->DateTime = &((pSearchData)driver_data->TargetData)->DateCreated; + return 0; } + return -1; } - case TARGET_CLUSTER_ENTRY: + /** Handle date_computed. **/ + if (strcmp(attr_name, "date_computed") == 0) { - pClusterData target = (pClusterData)driver_data->TargetData; - pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; - - if (strcmp(attr_name, "items") == 0) + date_computed: + switch (driver_data->TargetType) { - /** Static variable to prevent leaking StringVec from previous calls. **/ - static StringVec* vec = NULL; - if (vec != NULL) nmFree(vec, sizeof(StringVec)); + case TARGET_NODE: + /** Attribute is not defined for this target type. **/ + return -1; - /** Allocate and initialize the requested data. **/ - val->StringVec = vec = check_ptr(nmMalloc(sizeof(StringVec))); - if (val->StringVec == NULL) return -1; - val->StringVec->nStrings = target_cluster->Size; - val->StringVec->Strings = target_cluster->Strings; + case TARGET_CLUSTER: + case TARGET_CLUSTER_ENTRY: + { + pClusterData target = (pClusterData)driver_data->TargetData; + pDateTime date_time = &target->DateComputed; + if (date_time->Value == 0) return 1; /* null */ + else val->DateTime = date_time; + return 0; + } - /** Success. **/ - return 0; + case TARGET_SEARCH: + case TARGET_SEARCH_ENTRY: + { + pSearchData target = (pSearchData)driver_data->TargetData; + pDateTime date_time = &target->DateComputed; + if (date_time->Value == 0) return 1; /* null */ + else val->DateTime = date_time; + return 0; + } } - break; + + /** Default: Unknown type. **/ + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; } - case TARGET_SEARCH_ENTRY: + /** Handle attributes for specific data targets. **/ + handle_targets: + switch (driver_data->TargetType) { - pSearchData target = (pSearchData)driver_data->TargetData; - pDup target_dup = target->Dups[driver_data->TargetIndex]; + case TARGET_NODE: + if (strcmp(attr_name, "source") == 0) + { + /** TODO: THAT'S NOT A SOURCE DATA STRUCT!?!?!?!?!?!?!??!?!?!? */ + val->String = ((pSourceData)driver_data->TargetData)->SourcePath; + fprintf(stderr, "Got source: \"%s\"", val->String); + return 0; + } + if (strcmp(attr_name, "key_attr") == 0) + { + val->String = ((pSourceData)driver_data->TargetData)->KeyAttr; + return 0; + } + if (strcmp(attr_name, "name_attr") == 0) + { + val->String = ((pSourceData)driver_data->TargetData)->NameAttr; + return 0; + } + break; - if (strcmp(attr_name, "sim") == 0) + case TARGET_CLUSTER: { - ClusterStatistics.GetValCalls_sim++; - val->Double = target_dup->similarity; - return 0; + pClusterData target = (pClusterData)driver_data->TargetData; + + if (strcmp(attr_name, "algorithm") == 0) + { + val->String = ci_ClusteringAlgorithmToString(target->ClusterAlgorithm); + return 0; + } + if (strcmp(attr_name, "similarity_measure") == 0) + { + val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + return 0; + } + if (strcmp(attr_name, "num_clusters") == 0) + { + if (target->nClusters > INT_MAX) + fprintf(stderr, "Warning: 'num_clusters' value of %u exceeds INT_MAX (%d).\n", target->nClusters, INT_MAX); + val->Integer = (int)target->nClusters; + return 0; + } + if (strcmp(attr_name, "max_iterations") == 0) + { + if (target->MaxIterations > INT_MAX) + fprintf(stderr, "Warning: 'max_iterations' value of %u exceeds INT_MAX (%d).\n", target->MaxIterations, INT_MAX); + val->Integer = (int)target->MaxIterations; + return 0; + } + if (strcmp(attr_name, "min_improvement") == 0) + { + val->Double = target->MinImprovement; + return 0; + } + break; } - if (strcmp(attr_name, "key1") == 0) + + case TARGET_SEARCH: { - ClusterStatistics.GetValCalls_key1++; - val->String = target_dup->key1; - return 0; + pSearchData target = (pSearchData)driver_data->TargetData; + + if (strcmp(attr_name, "source") == 0) + { + val->String = target->SourceCluster->Name; + return 0; + } + if (strcmp(attr_name, "similarity_measure") == 0) + { + val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + return 0; + } + if (strcmp(attr_name, "threshold") == 0) + { + val->Double = target->Threshold; + return 0; + } + } + + case TARGET_CLUSTER_ENTRY: + { + pClusterData target = (pClusterData)driver_data->TargetData; + pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; + + if (strcmp(attr_name, "items") == 0) + { + /** Static variable to prevent leaking StringVec from previous calls. **/ + static StringVec* vec = NULL; + if (vec != NULL) nmFree(vec, sizeof(StringVec)); + + /** Allocate and initialize the requested data. **/ + val->StringVec = vec = check_ptr(nmMalloc(sizeof(StringVec))); + if (val->StringVec == NULL) return -1; + val->StringVec->nStrings = target_cluster->Size; + val->StringVec->Strings = target_cluster->Strings; + + /** Success. **/ + return 0; + } + break; } - if (strcmp(attr_name, "key2") == 0) + + case TARGET_SEARCH_ENTRY: { - ClusterStatistics.GetValCalls_key2++; - val->String = target_dup->key2; - return 0; + pSearchData target = (pSearchData)driver_data->TargetData; + pDup target_dup = target->Dups[driver_data->TargetIndex]; + + if (strcmp(attr_name, "sim") == 0) + { + ClusterStatistics.GetValCalls_sim++; + val->Double = target_dup->similarity; + return 0; + } + if (strcmp(attr_name, "key1") == 0) + { + ClusterStatistics.GetValCalls_key1++; + val->String = target_dup->key1; + return 0; + } + if (strcmp(attr_name, "key2") == 0) + { + ClusterStatistics.GetValCalls_key2++; + val->String = target_dup->key2; + return 0; + } + break; } - break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return -1; } - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - return -1; - } - - /** Unknown attribute. **/ - char* name; - clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); - mssErrorf(1, "Cluster", - "Unknown attribute '%s' for cluster object %s (target type: %u, \"%s\").", - attr_name, driver_data->NodeData->SourceData->Name, driver_data->TargetType, name - ); - + /** Unknown attribute. **/ + char* name; + clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); + mssErrorf(1, "Cluster", + "Unknown attribute '%s' for cluster object %s (target type: %u, \"%s\").", + attr_name, driver_data->NodeData->SourceData->Name, driver_data->TargetType, name + ); + return -1; } @@ -3632,285 +3746,293 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; + pObjPresentationHints hints = NULL; + pParamObjects tmp_list = NULL; - /** Malloc presentation hints struct. **/ - pObjPresentationHints hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); - if (hints == NULL) goto err; - memset(hints, 0, sizeof(ObjPresentationHints)); - - /** Hints that are the same for all attributes. **/ - hints->GroupID = -1; - hints->VisualLength2 = 1; - hints->Style |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; - hints->StyleMask |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; - - /** Temporary param list for compiling expressions. **/ - pParamObjects tmp_list = check_ptr(expCreateParamList()); - if (hints == NULL) goto err; - - /** Search for the requested attribute through attributes common to all instances. **/ - if (strcmp(attr_name, "name") == 0) - { - hints->Length = 32; - hints->VisualLength = 16; - goto success; - } - if (strcmp(attr_name, "annotation") == 0) - { - hints->Length = 36; - hints->VisualLength = 36; - goto success; - } - if (strcmp(attr_name, "inner_type") == 0 - || strcmp(attr_name, "inner_type") == 0 - || strcmp(attr_name, "outer_type") == 0 - || strcmp(attr_name, "content_type") == 0 - || strcmp(attr_name, "last_modification") == 0) - { - hints->VisualLength = 30; - goto success; - } - - /** Handle date created and date computed. */ - if (strcmp(attr_name, "date_created") == 0 - || strcmp(attr_name, "date_computed") == 0) - { - if (driver_data->TargetType == TARGET_CLUSTER || driver_data->TargetType == TARGET_SEARCH) + /** Malloc presentation hints struct. **/ + hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); + if (hints == NULL) goto err_free; + memset(hints, 0, sizeof(ObjPresentationHints)); + + /** Hints that are the same for all attributes. **/ + hints->GroupID = -1; + hints->VisualLength2 = 1; + hints->Style |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; + hints->StyleMask |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; + + /** Temporary param list for compiling expressions. **/ + tmp_list = check_ptr(expCreateParamList()); + if (hints == NULL) goto err_free; + + /** Search for the requested attribute through attributes common to all instances. **/ + if (strcmp(attr_name, "name") == 0) { - hints->Length = 24; - hints->VisualLength = 20; - hints->Format = check_ptr(nmSysStrdup("datetime")); /* Failure ignored. */ - goto success; + hints->Length = 32; + hints->VisualLength = 16; + goto end; + } + if (strcmp(attr_name, "annotation") == 0) + { + hints->Length = 36; + hints->VisualLength = 36; + goto end; + } + if (strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name, "outer_type") == 0 + || strcmp(attr_name, "content_type") == 0 + || strcmp(attr_name, "last_modification") == 0) + { + hints->VisualLength = 30; + goto end; } - else goto unknown_attribute; - } - - /** Search by target type. **/ - switch (driver_data->TargetType) - { - case TARGET_ROOT: - if (strcmp(attr_name, "source") == 0) - { - hints->Length = _PC_PATH_MAX; - hints->VisualLength = 64; - hints->FriendlyName = check_ptr(nmSysStrdup("Source Path")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "key_attr") == 0) - { - hints->Length = 255; - hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Key Attribute Name")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "data_attr") == 0) - { - hints->Length = 255; - hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Data Attribute Name")); /* Failure ignored. */ - goto success; - } - break; - case TARGET_CLUSTER: - if (strcmp(attr_name, "num_clusters") == 0) - { - /** Min and max values. **/ - hints->MinValue = expCompileExpression("2", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - - /** Other hints. **/ - hints->Length = 8; - hints->VisualLength = 4; - hints->FriendlyName = check_ptr(nmSysStrdup("Number of Clusters")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "min_improvement") == 0) - { - /** Min and max values. **/ - hints->DefaultExpr = expCompileExpression("0.0001", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - - /** Other hints. **/ - hints->Length = 16; - hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Minimum Improvement Threshold")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "max_iterations") == 0) - { - /** Min and max values. **/ - hints->DefaultExpr = expCompileExpression("64", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - - /** Other hints. **/ - hints->Length = 8; - hints->VisualLength = 4; - hints->FriendlyName = check_ptr(nmSysStrdup("Maximum Iterations")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "algorithm") == 0) + /** Handle date created and date computed. */ + if (strcmp(attr_name, "date_created") == 0 + || strcmp(attr_name, "date_computed") == 0) + { + if (driver_data->TargetType == TARGET_CLUSTER + || driver_data->TargetType == TARGET_CLUSTER_ENTRY + || driver_data->TargetType == TARGET_SEARCH + || driver_data->TargetType == TARGET_SEARCH_ENTRY) { - /** Enum values. **/ - check(xaInit(&(hints->EnumList), nClusteringAlgorithms)); /* Failure ignored. */ - for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) - check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); /* Failure ignored. */ - - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - char buf[8]; - snprintf(buf, sizeof(buf), "%d", nClusteringAlgorithms); - hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - - /** Display flags. **/ - hints->Style |= OBJ_PH_STYLE_BUTTONS; - hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; - - /** Other hints. **/ hints->Length = 24; hints->VisualLength = 20; - hints->FriendlyName = check_ptr(nmSysStrdup("Clustering Algorithm")); /* Failure ignored. */ - goto success; + hints->Format = check_ptr(nmSysStrdup("datetime")); /* Failure ignored. */ + goto end; } - /** Fall-through: Start of overlapping region. **/ + else goto unknown_attribute; + } - case TARGET_SEARCH: - if (strcmp(attr_name, "similarity_measure") == 0) - { - /** Enum values. **/ - check(xaInit(&(hints->EnumList), nSimilarityMeasures)); /* Failure ignored. */ - for (unsigned int i = 0u; i < nSimilarityMeasures; i++) - check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); /* Failure ignored. */ + /** Search by target type. **/ + switch (driver_data->TargetType) + { + case TARGET_NODE: + if (strcmp(attr_name, "source") == 0) + { + hints->Length = _PC_PATH_MAX; + hints->VisualLength = 64; + hints->FriendlyName = check_ptr(nmSysStrdup("Source Path")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "key_attr") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Key Attribute Name")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "data_attr") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Data Attribute Name")); /* Failure ignored. */ + goto end; + } + break; + + case TARGET_CLUSTER: + if (strcmp(attr_name, "num_clusters") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("2", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + hints->FriendlyName = check_ptr(nmSysStrdup("Number of Clusters")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "min_improvement") == 0) + { + /** Min and max values. **/ + hints->DefaultExpr = expCompileExpression("0.0001", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Minimum Improvement Threshold")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "max_iterations") == 0) + { + /** Min and max values. **/ + hints->DefaultExpr = expCompileExpression("64", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + hints->FriendlyName = check_ptr(nmSysStrdup("Maximum Iterations")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "algorithm") == 0) + { + /** Enum values. **/ + check(xaInit(&(hints->EnumList), nClusteringAlgorithms)); /* Failure ignored. */ + for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) + check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); /* Failure ignored. */ + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + char buf[8]; + snprintf(buf, sizeof(buf), "%d", nClusteringAlgorithms); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Display flags. **/ + hints->Style |= OBJ_PH_STYLE_BUTTONS; + hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; - /** Display flags. **/ - hints->Style |= OBJ_PH_STYLE_BUTTONS; - hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; + /** Other hints. **/ + hints->Length = 24; + hints->VisualLength = 20; + hints->FriendlyName = check_ptr(nmSysStrdup("Clustering Algorithm")); /* Failure ignored. */ + goto end; + } + /** Fall-through: Start of overlapping region. **/ + + case TARGET_SEARCH: + if (strcmp(attr_name, "similarity_measure") == 0) + { + /** Enum values. **/ + check(xaInit(&(hints->EnumList), nSimilarityMeasures)); /* Failure ignored. */ + for (unsigned int i = 0u; i < nSimilarityMeasures; i++) + check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); /* Failure ignored. */ + + /** Display flags. **/ + hints->Style |= OBJ_PH_STYLE_BUTTONS; + hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + char buf[8]; + snprintf(buf, sizeof(buf), "%d", nSimilarityMeasures); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 32; + hints->VisualLength = 20; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Measure")); /* Failure ignored. */ + goto end; + } - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - char buf[8]; - snprintf(buf, sizeof(buf), "%d", nSimilarityMeasures); - hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + /** End of overlapping region. **/ + if (driver_data->TargetType == TARGET_CLUSTER) break; - /** Other hints. **/ - hints->Length = 32; - hints->VisualLength = 20; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Measure")); /* Failure ignored. */ - goto success; - } - - /** End of overlapping region. **/ - if (driver_data->TargetType == TARGET_CLUSTER) break; + if (strcmp(attr_name, "source") == 0) + { + hints->Length = 64; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Source Cluster Name")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "threshold") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Threshold")); /* Failure ignored. */ + goto end; + } + break; - if (strcmp(attr_name, "source") == 0) - { - hints->Length = 64; - hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Source Cluster Name")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "threshold") == 0) + case TARGET_CLUSTER_ENTRY: { - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + pClusterData target = (pClusterData)check_ptr(driver_data->TargetData); + if (target == NULL) goto err_free; - /** Other hints. **/ - hints->Length = 16; - hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Threshold")); /* Failure ignored. */ - goto success; + if (strcmp(attr_name, "items") == 0) + { + /** Other hints. **/ + hints->Length = 65536; + hints->VisualLength = 256; + hints->FriendlyName = check_ptr(nmSysStrdup("Cluster Data")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "sim") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ + goto end; + } + break; } - break; - - case TARGET_CLUSTER_ENTRY: - { - pClusterData target = (pClusterData)check_ptr(driver_data->TargetData); - if (target == NULL) goto err; - if (strcmp(attr_name, "items") == 0) - { - /** Other hints. **/ - hints->Length = 65536; - hints->VisualLength = 256; - hints->FriendlyName = check_ptr(nmSysStrdup("Cluster Data")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "sim") == 0) + case TARGET_SEARCH_ENTRY: { - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + pSearchData target = (pSearchData)check_ptr(driver_data->TargetData); + if (target == NULL) goto err_free; - /** Other hints. **/ - hints->Length = 16; - hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ - goto success; + if (strcmp(attr_name, "key1") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Key 1")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "key2") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Key 2")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "sim") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ + goto end; + } + break; } - break; - } - - case TARGET_SEARCH_ENTRY: - { - pSearchData target = (pSearchData)check_ptr(driver_data->TargetData); - if (target == NULL) goto err; - if (strcmp(attr_name, "key1") == 0) - { - hints->Length = 255; - hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Key 1")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "key2") == 0) - { - hints->Length = 255; - hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Key 2")); /* Failure ignored. */ - goto success; - } - if (strcmp(attr_name, "sim") == 0) - { - /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - - /** Other hints. **/ - hints->Length = 16; - hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ - goto success; - } - break; + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + goto err_free; } - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - goto err; - } - - /** Unknown attribute. **/ + /** Unknown attribute. **/ unknown_attribute:; - char* name; - check(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); /* Failure ignored. */ - mssErrorf(1, "Cluster", - "Unknown attribute '%s' for cluster object %s (target type: %u, \"%s\").", - attr_name, driver_data->NodeData->SourceData->Name, driver_data->TargetType, name - ); - - /** Error cleanup. **/ - err: - if (tmp_list != NULL) check(expFreeParamList(tmp_list)); /* Failure ignored. */ - if (hints != NULL) nmFree(hints, sizeof(ObjPresentationHints)); - mssErrorf(0, "Cluster", "Failed execute generate presentation hints."); - return NULL; + mssErrorf(1, "Cluster", "Unknown attribute '%s'.", attr_name); + + /** Error cleanup. **/ + err_free: + if (hints != NULL) nmFree(hints, sizeof(ObjPresentationHints)); + hints = NULL; + + /** Construct the clearest error message that we can. **/ + char* name = NULL; + char* internal_type = NULL; + check(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); /* Failure ignored. */ + check(clusterGetAttrValue(inf_v, "internal_type", DATA_T_STRING, POD(&internal_type), NULL)); /* Failure ignored. */ + mssErrorf(0, "Cluster", + "Failed to get presentation hints for object '%s' : \"%s\".", + name, internal_type + ); + + end: + if (tmp_list != NULL) check(expFreeParamList(tmp_list)); /* Failure ignored. */ - /** Success. **/ - success: - check(expFreeParamList(tmp_list)); /* Failure ignored. */ return hints; } @@ -3928,7 +4050,9 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - driver_data->TargetAttrIndex = 0u; + + driver_data->TargetAttrIndex = 0u; + return clusterGetNextAttr(inf_v, oxt); } @@ -3946,18 +4070,19 @@ char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - const unsigned int i = driver_data->TargetAttrIndex++; - switch (driver_data->TargetType) - { - case TARGET_ROOT: return ATTR_ROOT[i]; - case TARGET_CLUSTER: return ATTR_CLUSTER[i]; - case TARGET_SEARCH: return ATTR_SEARCH[i]; - case TARGET_CLUSTER_ENTRY: return ATTR_CLUSTER_ENTRY[i]; - case TARGET_SEARCH_ENTRY: return ATTR_SEARCH_ENTRY[i]; - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - return NULL; - } + + const unsigned int i = driver_data->TargetAttrIndex++; + switch (driver_data->TargetType) + { + case TARGET_NODE: return ATTR_ROOT[i]; + case TARGET_CLUSTER: return ATTR_CLUSTER[i]; + case TARGET_SEARCH: return ATTR_SEARCH[i]; + case TARGET_CLUSTER_ENTRY: return ATTR_CLUSTER_ENTRY[i]; + case TARGET_SEARCH_ENTRY: return ATTR_SEARCH_ENTRY[i]; + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return NULL; + } } @@ -3974,71 +4099,71 @@ int clusterInfo(void* inf_v, pObjectInfo info) pDriverData driver_data = (pDriverData)inf_v; pNodeData node_data = (pNodeData)driver_data->NodeData; - /** Reset flags buffer. **/ - info->Flags = 0; - - /** Disallow unsupported functionality. **/ - info->Flags |= OBJ_INFO_F_CANT_ADD_ATTR; - info->Flags |= OBJ_INFO_F_CANT_HAVE_CONTENT; - info->Flags |= OBJ_INFO_F_NO_CONTENT; - - switch (driver_data->TargetType) - { - case TARGET_ROOT: - info->nSubobjects = node_data->nClusterDatas + node_data->nSearchDatas; - info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; - info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; - info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; - break; - - case TARGET_CLUSTER: - info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; - info->Flags |= OBJ_INFO_F_HAS_SUBOBJ; /* Data must not be empty. */ - - /*** Clusters always have one label per vector. - *** If we know how many vectors are in the dataset, - *** we know how many labels this cluster will have, - *** even if it hasn't been computed yet. - ***/ - if (node_data->SourceData->Vectors != NULL) - { - info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; - info->nSubobjects = node_data->SourceData->nVectors; - } - break; + /** Reset flags buffer. **/ + info->Flags = 0; + + /** Disallow unsupported functionality. **/ + info->Flags |= OBJ_INFO_F_CANT_ADD_ATTR; + info->Flags |= OBJ_INFO_F_CANT_HAVE_CONTENT; + info->Flags |= OBJ_INFO_F_NO_CONTENT; - case TARGET_SEARCH: + switch (driver_data->TargetType) { - pSearchData search_data = (pSearchData)driver_data->TargetData; - info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; - if (search_data->Dups != NULL) - { - info->nSubobjects = search_data->nDups; + case TARGET_NODE: + info->nSubobjects = node_data->nClusterDatas + node_data->nSearchDatas; + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; + break; + + case TARGET_CLUSTER: + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_HAS_SUBOBJ; /* Data must not be empty. */ + + /*** Clusters always have one label per vector. + *** If we know how many vectors are in the dataset, + *** we know how many labels this cluster will have, + *** even if it hasn't been computed yet. + ***/ + if (node_data->SourceData->Vectors != NULL) + { + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->nSubobjects = node_data->SourceData->nVectors; + } + break; + + case TARGET_SEARCH: + { + pSearchData search_data = (pSearchData)driver_data->TargetData; + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + if (search_data->Dups != NULL) + { + info->nSubobjects = search_data->nDups; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; + } + break; } - break; + + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + /** No Subobjects. **/ + info->Flags |= OBJ_INFO_F_CANT_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_NO_SUBOBJ; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->nSubobjects = 0; + break; + + default: + mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + goto err; } - case TARGET_CLUSTER_ENTRY: - case TARGET_SEARCH_ENTRY: - /** No Subobjects. **/ - info->Flags |= OBJ_INFO_F_CANT_HAVE_SUBOBJ; - info->Flags |= OBJ_INFO_F_NO_SUBOBJ; - info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; - info->nSubobjects = 0; - break; + return 0; - default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); - goto err; - } - - return 0; - err: - mssErrorf(0, "Cluster", "Failed execute get info."); - return -1; + mssErrorf(0, "Cluster", "Failed execute get info."); + return -1; } @@ -4058,7 +4183,9 @@ int clusterInfo(void* inf_v, pObjectInfo info) char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - driver_data->TargetMethodIndex = 0u; + + driver_data->TargetMethodIndex = 0u; + return clusterGetNextMethod(inf_v, oxt); } @@ -4076,7 +4203,8 @@ char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; - return METHOD_NAME[driver_data->TargetMethodIndex++]; + + return METHOD_NAMES[driver_data->TargetMethodIndex++]; } @@ -4084,90 +4212,89 @@ char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) /** Intended for use in xhForEach(). **/ static int ci_PrintEntry(pXHashEntry entry, void* arg) { - /** Extract entry. **/ - char* key = entry->Key; - void* data = entry->Data; - - /** Extract args. **/ - void** args = (void**)arg; - unsigned int* type_id_ptr = (unsigned int*)args[0]; - unsigned int* total_bytes_ptr = (unsigned int*)args[1]; - unsigned long long* less_ptr = (unsigned long long*)args[2]; - char* path = (char*)args[3]; - - /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return 0; - - /** Handle type. **/ - char* type; - char* name; - unsigned int bytes; - switch (*type_id_ptr) - { - case 1u: - { - pSourceData source_data = (pSourceData)data; - - /** Compute size. **/ - bytes = ci_SizeOfSourceData(source_data); - - /** If less is specified, skip uncomputed source. **/ - if (*less_ptr > 0llu && source_data->Vectors == NULL) goto no_print; - - /** Compute printing information. **/ - type = "Source"; - name = source_data->Name; - break; - } - case 2u: - { - pClusterData cluster_data = (pClusterData)data; - - /** Compute size. **/ - bytes = ci_SizeOfClusterData(cluster_data, false); - - /** If less is specified, skip uncomputed source. **/ - if (*less_ptr > 0llu && cluster_data->Clusters == NULL) goto no_print; - - /** Compute printing information. **/ - type = "Cluster"; - name = cluster_data->Name; - break; - } - case 3u: + /** Extract entry. **/ + char* key = entry->Key; + void* data = entry->Data; + + /** Extract args. **/ + void** args = (void**)arg; + unsigned int* type_id_ptr = (unsigned int*)args[0]; + unsigned int* total_bytes_ptr = (unsigned int*)args[1]; + unsigned long long* less_ptr = (unsigned long long*)args[2]; + char* path = (char*)args[3]; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return 0; + + /** Handle type. **/ + char* type; + char* name; + unsigned int bytes; + switch (*type_id_ptr) { - pSearchData search_data = (pSearchData)data; - - /** Compute size. **/ - bytes = ci_SizeOfSearchData(search_data); - - /** If less is specified, skip uncomputed source. **/ - if (*less_ptr > 0llu && search_data->Dups == NULL) goto no_print; - - /** Compute printing information. **/ - type = "Search"; - name = search_data->Name; - break; + case 1u: + { + pSourceData source_data = (pSourceData)data; + + /** Compute size. **/ + bytes = ci_SizeOfSourceData(source_data); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && source_data->Vectors == NULL) goto no_print; + + /** Compute printing information. **/ + type = "Source"; + name = source_data->Name; + break; + } + case 2u: + { + pClusterData cluster_data = (pClusterData)data; + + /** Compute size. **/ + bytes = ci_SizeOfClusterData(cluster_data, false); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && cluster_data->Clusters == NULL) goto no_print; + + /** Compute printing information. **/ + type = "Cluster"; + name = cluster_data->Name; + break; + } + case 3u: + { + pSearchData search_data = (pSearchData)data; + + /** Compute size. **/ + bytes = ci_SizeOfSearchData(search_data); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && search_data->Dups == NULL) goto no_print; + + /** Compute printing information. **/ + type = "Search"; + name = search_data->Name; + break; + } + default: + mssErrorf(0, "Cluster", "Unknown type_id %u.", *type_id_ptr); + return -1; } - default: - mssErrorf(0, "Cluster", "Unknown type_id %u.", *type_id_ptr); - return -1; - } - - - /** Print the cache entry data. **/ - char buf[12]; - snprint_bytes(buf, sizeof(buf), bytes); - printf("%-8s %-16s %-12s \"%s\"\n", type, name, buf, key); - + + /** Print the cache entry data. **/ + char buf[12]; + snprint_bytes(buf, sizeof(buf), bytes); + printf("%-8s %-16s %-12s \"%s\"\n", type, name, buf, key); + goto increment_total; + + no_print: + (*less_ptr)++; + increment_total: - *total_bytes_ptr += bytes; + *total_bytes_ptr += bytes; return 0; - - no_print: - (*less_ptr)++; - goto increment_total; } @@ -4175,16 +4302,16 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) /** Intended for use in xhClearKeySafe(). **/ static void ci_CacheFreeSourceData(pXHashEntry entry, void* path) { - /** Extract hash entry. **/ - char* key = entry->Key; - pSourceData source_data = (pSourceData)entry->Data; - - /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; - - /** Free data. **/ - ci_FreeSourceData(source_data); - nmSysFree(key); + /** Extract hash entry. **/ + char* key = entry->Key; + pSourceData source_data = (pSourceData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + ci_FreeSourceData(source_data); + nmSysFree(key); } @@ -4192,16 +4319,16 @@ static void ci_CacheFreeSourceData(pXHashEntry entry, void* path) /** Intended for use in xhClearKeySafe(). **/ static void ci_CacheFreeCluster(pXHashEntry entry, void* path) { - /** Extract hash entry. **/ - char* key = entry->Key; - pClusterData cluster_data = (pClusterData)entry->Data; - - /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; - - /** Free data. **/ - ci_FreeClusterData(cluster_data, false); - nmSysFree(key); + /** Extract hash entry. **/ + char* key = entry->Key; + pClusterData cluster_data = (pClusterData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + ci_FreeClusterData(cluster_data, false); + nmSysFree(key); } @@ -4209,16 +4336,16 @@ static void ci_CacheFreeCluster(pXHashEntry entry, void* path) /** Intended for use in xhClearKeySafe(). **/ static void ci_CacheFreeSearch(pXHashEntry entry, void* path) { - /** Extract hash entry. **/ - char* key = entry->Key; - pSearchData search_data = (pSearchData)entry->Data; - - /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; - - /** Free data. **/ - ci_FreeSearchData(search_data); - nmSysFree(key); + /** Extract hash entry. **/ + char* key = entry->Key; + pSearchData search_data = (pSearchData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + ci_FreeSearchData(search_data); + nmSysFree(key); } @@ -4234,128 +4361,133 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx { pDriverData driver_data = (pDriverData)inf_v; - /** Cache management method. **/ - if (strcmp(method_name, "cache") == 0) - { - char* path = NULL; - - /** Second parameter is required. **/ - if (param->String == NULL) - { - mssErrorf(1, "Cluster", - "[param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] is required for the cache method." - ); - goto err; - } - - /** 'show' and 'show_all'. **/ - bool show = false; - unsigned long long skip_uncomputed = 0llu; - if (strcmp(param->String, "show_less") == 0) - /** Specify show_less to skip uncomputed caches. **/ - skip_uncomputed = 1ull; - if (skip_uncomputed == 1ull || strcmp(param->String, "show") == 0) - { - show = true; - path = ci_file_path(driver_data->NodeData->Parent); - } - if (strcmp(param->String, "show_all") == 0) show = true; - - if (show) + /** Cache management method. **/ + if (strcmp(method_name, "cache") == 0) { - /** Print cache info table. **/ - int ret = 0; - unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; - bool failed = false; - printf("\nShowing cache for "); - if (path != NULL) printf("\"%s\":\n", path); - else printf("all files:\n"); - printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); - failed |= !check(xhForEach( - &ClusterDriverCaches.SourceDataCache, - ci_PrintEntry, - (void*[]){&i, &source_bytes, (void*)&skip_uncomputed, path} - )); - i++; - failed |= !check(xhForEach( - &ClusterDriverCaches.ClusterDataCache, - ci_PrintEntry, - (void*[]){&i, &cluster_bytes, (void*)&skip_uncomputed, path} - )); - i++; - failed |= !check(xhForEach( - &ClusterDriverCaches.SearchDataCache, - ci_PrintEntry, - (void*[]){&i, &search_bytes, (void*)&skip_uncomputed, path} - )); - if (failed) + char* path = NULL; + + /** Second parameter is required. **/ + if (param->String == NULL) + { + mssErrorf(1, "Cluster", + "[param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] is required for the cache method." + ); + goto err; + } + + /** 'show' and 'show_all'. **/ + bool show = false; + unsigned long long skip_uncomputed = 0llu; + if (strcmp(param->String, "show_less") == 0) + /** Specify show_less to skip uncomputed caches. **/ + skip_uncomputed = 1ull; + if (skip_uncomputed == 1ull || strcmp(param->String, "show") == 0) { - mssErrorf(0, "Cluster", "Unexpected error occurred while showhing caches."); - ret = -1; + show = true; + path = ci_file_path(driver_data->NodeData->Parent); } - - /** Precomputations. **/ - unsigned int total_caches = 0u - + (unsigned int)ClusterDriverCaches.SourceDataCache.nItems - + (unsigned int)ClusterDriverCaches.ClusterDataCache.nItems - + (unsigned int)ClusterDriverCaches.SearchDataCache.nItems; - if (total_caches <= skip_uncomputed) printf("All caches skipped, nothing to show...\n"); + if (strcmp(param->String, "show_all") == 0) show = true; - /** Print stats. **/ - char buf[16]; - printf("\nCache Stats:\n"); - printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); - printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprint_bytes(buf, sizeof(buf), source_bytes)); - printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprint_bytes(buf, sizeof(buf), cluster_bytes)); - printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprint_bytes(buf, sizeof(buf), search_bytes)); - printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); + if (show) + { + /** Print cache info table. **/ + int ret = 0; + unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; + bool failed = false; + printf("\nShowing cache for "); + if (path != NULL) printf("\"%s\":\n", path); + else printf("all files:\n"); + printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); + failed |= !check(xhForEach( + &ClusterDriverCaches.SourceDataCache, + ci_PrintEntry, + (void*[]){&i, &source_bytes, (void*)&skip_uncomputed, path} + )); + i++; + failed |= !check(xhForEach( + &ClusterDriverCaches.ClusterDataCache, + ci_PrintEntry, + (void*[]){&i, &cluster_bytes, (void*)&skip_uncomputed, path} + )); + i++; + failed |= !check(xhForEach( + &ClusterDriverCaches.SearchDataCache, + ci_PrintEntry, + (void*[]){&i, &search_bytes, (void*)&skip_uncomputed, path} + )); + if (failed) + { + mssErrorf(0, "Cluster", "Unexpected error occurred while showhing caches."); + ret = -1; + } + + /** Precomputations. **/ + unsigned int total_caches = 0u + + (unsigned int)ClusterDriverCaches.SourceDataCache.nItems + + (unsigned int)ClusterDriverCaches.ClusterDataCache.nItems + + (unsigned int)ClusterDriverCaches.SearchDataCache.nItems; + if (total_caches <= skip_uncomputed) printf("All caches skipped, nothing to show...\n"); + + /** Print stats. **/ + char buf[16]; + printf("\nCache Stats:\n"); + printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); + printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprint_bytes(buf, sizeof(buf), source_bytes)); + printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprint_bytes(buf, sizeof(buf), cluster_bytes)); + printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprint_bytes(buf, sizeof(buf), search_bytes)); + printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); + + /** Print skip stats (if anything was skipped.) **/ + if (skip_uncomputed > 0llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); + + return ret; + } - /** Print skip stats (if anything was skipped.) **/ - if (skip_uncomputed > 0llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); + /** 'drop_all'. **/ + if (strcmp(param->String, "drop_all") == 0) + { + printf("\nDropping cache for all files:\n"); + ci_ClearCaches(); + return 0; + } - return ret; + /** Unknown parameter. **/ + mssErrorf(1, "Cluster", + "Expected [param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] for the cache method, but got: \"%s\"", + param->String + ); + goto err; } - /** 'drop_all'. **/ - if (strcmp(param->String, "drop_all") == 0) + if (strcmp(method_name, "stat") == 0) { - printf("\nDropping cache for all files:\n"); - ci_ClearCaches(); + char buf[12]; + printf("Cluster Driver Statistics:\n"); + printf(" Stat Name %12s\n", "Value"); + printf(" OpenCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); + printf(" OpenQueryCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); + printf(" FetchCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); + printf(" CloseCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); + printf(" GetTypeCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); + printf(" GetValCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); + printf(" GetValCalls_name %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); + printf(" GetValCalls_key1 %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); + printf(" GetValCalls_key2 %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); + printf(" GetValCalls_sim %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); + printf("\n"); + + nmStats(); + return 0; } - - /** Unknown parameter. **/ - mssErrorf(1, "Cluster", - "Expected [param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] for the cache method, but got: \"%s\"", - param->String - ); - goto err; - } - - if (strcmp(method_name, "stat") == 0) - { - char buf[12]; - printf("Cluster Driver Statistics:\n"); - printf(" Stat Name %12s\n", "Value"); - printf(" OpenCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); - printf(" OpenQueryCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); - printf(" FetchCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); - printf(" CloseCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); - printf(" GetTypeCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); - printf(" GetValCalls %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); - printf(" GetValCalls_name %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); - printf(" GetValCalls_key1 %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); - printf(" GetValCalls_key2 %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); - printf(" GetValCalls_sim %12s\n", snprint_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); - return 0; - } - - /** Unknown parameter. **/ - mssErrorf(1, "Cluster", "Unknown command: \"%s\"", method_name); + /** Unknown parameter. **/ + mssErrorf(1, "Cluster", "Unknown command: \"%s\"", method_name); + err: - mssErrorf(0, "Cluster", "Failed execute command."); - return -1; + mssErrorf(0, "Cluster", "Failed execute command."); + + return -1; } @@ -4366,56 +4498,65 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx /** Not implemented. **/ int clusterCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterCreate() is not implemented."); + mssErrorf(1, "Cluster", "clusterCreate() is not implemented."); + return -ENOSYS; } /** Not implemented. **/ int clusterDelete(pObject obj, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterDelete() is not implemented."); + mssErrorf(1, "Cluster", "clusterDelete() is not implemented."); + return -1; } /** Not implemented. **/ int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterDeleteObj() is not implemented."); + mssErrorf(1, "Cluster", "clusterDeleteObj() is not implemented."); + return -1; } /** Not implemented. **/ int clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterRead() not implemented."); - fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); + mssErrorf(1, "Cluster", "clusterRead() not implemented."); + fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); + return -1; } /** Not implemented. **/ int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterWrite() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterWrite() not implemented because clusters are imutable."); + return -1; } /** Not implemented. **/ int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are imutable."); + return -1; } /** Not implemented. **/ int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are imutable."); + return -1; } /** Not implemented. **/ void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterOpenAttr() not implemented."); + mssErrorf(1, "Cluster", "clusterOpenAttr() not implemented."); + return NULL; } /** Not implemented. **/ int clusterCommit(void* inf_v, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are imutable."); + return 0; } @@ -4431,74 +4572,83 @@ int clusterCommit(void* inf_v, pObjTrxTree* oxt) ***/ int clusterInitialize(void) { - /** Allocate the driver. **/ - pObjDriver drv = (pObjDriver)check_ptr(nmMalloc(sizeof(ObjDriver))); - if (drv == NULL) goto err; - memset(drv, 0, sizeof(ObjDriver)); - - /** Initialize caches. **/ - memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); - if (!check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0))) goto err; - if (!check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0))) goto err; - if (!check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0))) goto err; - - /** Initialize statistics. **/ - memset(&ClusterStatistics, 0, sizeof(ClusterStatistics)); - - /** Setup the structure. **/ - if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err; - if (!check(xaInit(&drv->RootContentTypes, 1))) goto err; - if (!check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster"))) goto err; - drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ - - /** Setup the function references. **/ - drv->Open = clusterOpen; - drv->OpenChild = NULL; - drv->Close = clusterClose; - drv->Create = clusterCreate; - drv->Delete = clusterDelete; - drv->DeleteObj = clusterDeleteObj; - drv->OpenQuery = clusterOpenQuery; - drv->QueryDelete = NULL; - drv->QueryFetch = clusterQueryFetch; - drv->QueryClose = clusterQueryClose; - drv->Read = clusterRead; - drv->Write = clusterWrite; - drv->GetAttrType = clusterGetAttrType; - drv->GetAttrValue = clusterGetAttrValue; - drv->GetFirstAttr = clusterGetFirstAttr; - drv->GetNextAttr = clusterGetNextAttr; - drv->SetAttrValue = clusterSetAttrValue; - drv->AddAttr = clusterAddAttr; - drv->OpenAttr = clusterOpenAttr; - drv->GetFirstMethod = clusterGetFirstMethod; - drv->GetNextMethod = clusterGetNextMethod; - drv->ExecuteMethod = clusterExecuteMethod; - drv->PresentationHints = clusterPresentationHints; - drv->Info = clusterInfo; - drv->Commit = clusterCommit; - drv->GetQueryCoverageMask = NULL; - drv->GetQueryIdentityPath = NULL; - - /** Register the driver. **/ - if (!check(objRegisterDriver(drv))) goto err; - - /** Register structs used in this project with the newmalloc memory management system. **/ - nmRegister(sizeof(SourceData), "ClusterSourceData"); - nmRegister(sizeof(Cluster), "Cluster"); - nmRegister(sizeof(ClusterData), "ClusterData"); - nmRegister(sizeof(SearchData), "ClusterSearch"); - nmRegister(sizeof(NodeData), "ClusterNodeData"); - nmRegister(sizeof(DriverData), "ClusterDriverData"); - nmRegister(sizeof(ClusterQuery), "ClusterQuery"); - nmRegister(sizeof(ClusterDriverCaches), "ClusterDriverCaches"); - - /** Success. **/ - return 0; - - /** Error cleanup. **/ - err: - if (drv != NULL) nmFree(drv, sizeof(ObjDriver)); - mssErrorf(1, "Cluster", "Failed to initialize cluster driver.\n"); - return -1; + /** Allocate the driver. **/ + pObjDriver drv = (pObjDriver)check_ptr(nmMalloc(sizeof(ObjDriver))); + if (drv == NULL) goto err_free; + memset(drv, 0, sizeof(ObjDriver)); + + /** Initialize caches. **/ + // memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); + if (!check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0))) goto err_free; + if (!check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0))) goto err_free; + if (!check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0))) goto err_free; + + /** Initialize statistics. **/ + memset(&ClusterStatistics, 0, sizeof(ClusterStatistics)); + + /** Setup the structure. **/ + if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; + if (!check(xaInit(&drv->RootContentTypes, 1))) goto err_free; + if (!check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster"))) goto err_free; + drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ + + /** Setup the function references. **/ + drv->Open = clusterOpen; + drv->OpenChild = NULL; + drv->Close = clusterClose; + drv->Create = clusterCreate; + drv->Delete = clusterDelete; + drv->DeleteObj = clusterDeleteObj; + drv->OpenQuery = clusterOpenQuery; + drv->QueryDelete = NULL; + drv->QueryFetch = clusterQueryFetch; + drv->QueryClose = clusterQueryClose; + drv->Read = clusterRead; + drv->Write = clusterWrite; + drv->GetAttrType = clusterGetAttrType; + drv->GetAttrValue = clusterGetAttrValue; + drv->GetFirstAttr = clusterGetFirstAttr; + drv->GetNextAttr = clusterGetNextAttr; + drv->SetAttrValue = clusterSetAttrValue; + drv->AddAttr = clusterAddAttr; + drv->OpenAttr = clusterOpenAttr; + drv->GetFirstMethod = clusterGetFirstMethod; + drv->GetNextMethod = clusterGetNextMethod; + drv->ExecuteMethod = clusterExecuteMethod; + drv->PresentationHints = clusterPresentationHints; + drv->Info = clusterInfo; + drv->Commit = clusterCommit; + drv->GetQueryCoverageMask = NULL; + drv->GetQueryIdentityPath = NULL; + + /** Register the driver. **/ + if (!check(objRegisterDriver(drv))) goto err_free; + + /** Register structs used in this project with the newmalloc memory management system. **/ + nmRegister(sizeof(SourceData), "ClusterSourceData"); + nmRegister(sizeof(Cluster), "Cluster"); + nmRegister(sizeof(ClusterData), "ClusterData"); + nmRegister(sizeof(SearchData), "ClusterSearch"); + nmRegister(sizeof(NodeData), "ClusterNodeData"); + nmRegister(sizeof(DriverData), "ClusterDriverData"); + nmRegister(sizeof(ClusterQuery), "ClusterQuery"); + nmRegister(sizeof(ClusterDriverCaches), "ClusterDriverCaches"); + + /** Success. **/ + return 0; + + /** Error cleanup. **/ + err_free: + if (ClusterDriverCaches.SourceDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SourceDataCache)); /* Failure ignored. **/ + if (ClusterDriverCaches.ClusterDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.ClusterDataCache)); /* Failure ignored. **/ + if (ClusterDriverCaches.SearchDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SearchDataCache)); /* Failure ignored. **/ + if (drv != NULL) + { + if (drv->RootContentTypes.nAlloc != 0) check(xaDeInit(&drv->RootContentTypes)); /* Failure ignored. */ + nmFree(drv, sizeof(ObjDriver)); + } + + mssErrorf(1, "Cluster", "Failed to initialize cluster driver.\n"); + + return -1; } diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index 5ef492de3..355a89f41 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -767,7 +767,13 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) fdQPrintf(TESTOBJ.Output, "%[,%]%STR", i!=0, ptr); else { - while (strpbrk(ptr, "\r\n")) *(strpbrk(ptr, "\r\n")) = ' '; + char* cur; + while (1) + { + cur = strpbrk(ptr, "\r\n"); + if (cur == NULL) break; + else *cur = ' '; + } fdQPrintf(TESTOBJ.Output, "%[,%]\"%STR&DSYB\"", i!=0, ptr); } diff --git a/centrallix/tests/test_levenshtein_00.cmp b/centrallix/tests/test_levenshtein_00.cmp index 2a084162d..b95f2d44a 100644 --- a/centrallix/tests/test_levenshtein_00.cmp +++ b/centrallix/tests/test_levenshtein_00.cmp @@ -1,18 +1,18 @@ -Attribute [case1]: string "pass" -Attribute [case2]: string "pass" -Attribute [case3]: string "pass" -Attribute [case4]: string "pass" -Attribute [case5]: string "pass" -Attribute [case6]: string "pass" -Attribute [case7]: string "pass" -Attribute [case8]: string "pass" -Attribute [case9]: string "pass" -Attribute [case10]: string "pass" -Attribute [case11]: string "pass" -Attribute [case12]: string "pass" -Attribute [case13]: string "pass" -Attribute [case14]: string "pass" -Attribute [case15]: string "pass" -Attribute [case16]: string "pass" -Attribute [case17]: string "pass" -Attribute [case18]: string "pass" +Attribute [case1]: integer 0 +Attribute [case2]: integer 1 +Attribute [case3]: integer 1 +Attribute [case4]: integer 1 +Attribute [case5]: integer 2 +Attribute [case6]: integer 1 +Attribute [case7]: integer 1 +Attribute [case8]: integer 1 +Attribute [case9]: integer 1 +Attribute [case10]: integer 1 +Attribute [case11]: integer 2 +Attribute [case12]: integer 1 +Attribute [case13]: integer 1 +Attribute [case14]: integer 2 +Attribute [case15]: integer 0 +Attribute [case16]: integer 0 +Attribute [case17]: integer 133 +Attribute [case18]: integer 254 diff --git a/centrallix/tests/test_levenshtein_00.to b/centrallix/tests/test_levenshtein_00.to index 33f78e5f8..a92bdd743 100644 --- a/centrallix/tests/test_levenshtein_00.to +++ b/centrallix/tests/test_levenshtein_00.to @@ -1,25 +1,25 @@ -##NAME Levenshtein String Comparison +##NAME Levenshtein Basic Comparisons # Kitten tests. -query select case1 = condition(levenshtein('kitten', 'kitten') == 0, 'pass', 'fail') -- 0 edits -query select case2 = condition(levenshtein('kitten', 'skitten') == 1, 'pass', 'fail') -- 1 insert -query select case3 = condition(levenshtein('kitten', 'itten') == 1, 'pass', 'fail') -- 1 delete -query select case4 = condition(levenshtein('kitten', 'mitten') == 1, 'pass', 'fail') -- 1 replace -query select case5 = condition(levenshtein('kitten', 'smitten') == 2, 'pass', 'fail') -- 1 insert and one replace -query select case6 = condition(levenshtein('kitten', 'iktten') == 1, 'pass', 'fail') -- 1 transpose -query select case7 = condition(levenshtein('kitten', 'kittens') == 1, 'pass', 'fail') -- 1 insert (end) -query select case8 = condition(levenshtein('kitten', 'kitte') == 1, 'pass', 'fail') -- 1 delete (end) -query select case9 = condition(levenshtein('kitten', 'kittem') == 1, 'pass', 'fail') -- 1 replace (end) -query select case10 = condition(levenshtein('kitten', 'kittne') == 1, 'pass', 'fail') -- 1 transpose (end) +query select case1 = levenshtein('kitten', 'kitten') -- 0 edits +query select case2 = levenshtein('kitten', 'skitten') -- 1 insert +query select case3 = levenshtein('kitten', 'itten') -- 1 delete +query select case4 = levenshtein('kitten', 'mitten') -- 1 replace +query select case5 = levenshtein('kitten', 'smitten') -- 1 insert and 1 replace +query select case6 = levenshtein('kitten', 'iktten') -- 1 transpose +query select case7 = levenshtein('kitten', 'kittens') -- 1 insert (end) +query select case8 = levenshtein('kitten', 'kitte') -- 1 delete (end) +query select case9 = levenshtein('kitten', 'kittem') -- 1 replace (end) +query select case10 = levenshtein('kitten', 'kittne') -- 1 transpose (end) # Alternate words. -query select case11 = condition(levenshtein('lawn', 'flown') == 2, 'pass', 'fail') -- 1 insert and one replace -query select case12 = condition(levenshtein('hello', 'hello!') == 1, 'pass', 'fail') -- 1 insert (end) -query select case13 = condition(levenshtein('zert', 'zerf') == 1, 'pass', 'fail') -- 1 replace (end) -query select case14 = condition(levenshtein('llearr', 'lear') == 2, 'pass', 'fail') -- 2 deletes (start & end) +query select case11 = levenshtein('lawn', 'flown') -- 1 insert and 1 replace +query select case12 = levenshtein('hello', 'hello!') -- 1 insert (end) +query select case13 = levenshtein('zert', 'zerf') -- 1 replace (end) +query select case14 = levenshtein('llearr', 'lear') -- 2 deletes (start & end) # Edge cases. -query select case15 = condition(levenshtein('', '') == 0, 'pass', 'fail') -- 0 edits -query select case16 = condition(levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...') == 0, 'pass', 'fail') -- 0 edits. -query select case17 = condition(levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is quite a lengthy string. I do not expect the function to compute any longer string since this one is a full 254 characters. That is plenty, even if someone adds many contact details to their record!! Thus, this test should cover most cases we see.') == 133, 'pass', 'fail') -- 133 edits. -query select case18 = condition(levenshtein('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB') == 254, 'pass', 'fail') -- 254 replaces. +query select case15 = levenshtein('', '') -- 0 edits +query select case16 = levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...') -- 0 edits. +query select case17 = levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is quite a lengthy string. I do not expect the function to compute any longer string since this one is a full 254 characters. That is plenty, even if someone adds many contact details to their record!! Thus, this test should cover most cases we see.') -- 133 edits. +query select case18 = levenshtein('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB') -- 254 replaces. diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c new file mode 100644 index 000000000..3faf27c3c --- /dev/null +++ b/centrallix/utility/double_metaphone.c @@ -0,0 +1,1545 @@ +/************************************************************************/ +/* Text-DoubleMetaphone */ +/* Centrallix Base Library */ +/* */ +/* Copyright 2000, Maurice Aubrey . */ +/* All rights reserved. */ +/* */ +/* This code is copied for redistribution with modification, from the */ +/* gitpan/Text-DoubleMetaphone implementation on GitHub (1), which is */ +/* under the following license. */ +/* */ +/* This code is based heavily on the C++ implementation by Lawrence */ +/* Philips and incorporates several bug fixes courtesy of Kevin */ +/* Atkinson . */ +/* */ +/* This module is free software; you may redistribute it and/or */ +/* modify it under the same terms as Perl itself. */ +/* */ +/* A summary of the relevant content from https://dev.perl.org/licenses */ +/* has been included below for the convenience of the reader. This */ +/* information was collected and saved on September 5th, 2025 and may */ +/* differ from current information. For the most up to date copy of */ +/* this information, please use the link provided above. */ +/* */ +/* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ +/* */ +/* It is free software; you can redistribute it and/or modify it */ +/* under the terms of either: */ +/* */ +/* a) the GNU General Public License (2) as published by the Free */ +/* Software Foundation (3); either version 1 (2), or (at your */ +/* option) any later version (4), or */ +/* */ +/* b) the "Artistic License" (5). */ +/* */ +/* Citations: */ +/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ +/* 2: https://dev.perl.org/licenses/gpl1.html */ +/* 3: http://www.fsf.org */ +/* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ +/* 5: https://dev.perl.org/licenses/artistic.html */ +/* */ +/* Centrallix is published under the GNU General Public License, */ +/* satisfying the above requirement. A summary of this is included */ +/* below for the convenience of the reader. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: double_metaphone.c, double_metaphone.h */ +/* Author: Maurice Aubrey and Israel Fuller */ +/* Description: This module implements a "sounds like" algorithm by */ +/* Lawrence Philips which he published in the June, 2000 */ +/* issue of C/C++ Users Journal. Double Metaphone is an */ +/* improved version of the original Metaphone algorithm */ +/* written by Philips'. This implementaton was written by */ +/* Maurice Aubrey for C/C++ with bug fixes provided by */ +/* Kevin Atkinson. It was revised by Israel Fuller to */ +/* better align with the Centrallix coding style and */ +/* standards so that it could be included here. */ +/************************************************************************/ + +/*** Note to future programmers reading this file (by Israel Fuller): + *** + *** This file was copied from a GitHub Repo with proper licensing (in case + *** you didn't read the legal stuff above), so feel free to check it out. + *** + *** As for this code, I've modified it to use styling and memory allocation + *** consistent with the rest of the Centrallix codebase. Also, I have added + *** documentation comments and extensive test cases (at the end of the file), + *** however, these reflect my own (possibly incorrect) understanding, which + *** might not line up with the original author. + *** + *** To be honest, though, trying to make this code as readable as possible + *** was very challenging due to all the messy boolean algebra. If there is + *** ever a professional linguist reading this, please factor out some of the + *** logic into local variables with descriptive names so that the rest of us + *** can read this code without our eyes glazing over. + *** + *** If you have any questions, please feel free to reach out to me or Greg. + *** + *** Original Source: https://github.com/gitpan/Text-meta_double_metaphone + ***/ + +#include +#include +#include +#include +#include +#include + +/*** If running in a testing environment, newmalloc is not + *** available, so we fall back to default C memory allocation. + ***/ +#ifndef TESTING +#include "cxlib/newmalloc.h" +#define META_MALLOC(size) nmSysMalloc(size) +#define META_REALLOC(ptr, size) nmSysRealloc(ptr, size) +#define META_FREE(ptr) nmSysFree(ptr) +#else +#include +#define META_MALLOC(size) malloc(size) +#define META_REALLOC(ptr, size) realloc(ptr, size) +#define META_FREE(ptr) free(ptr) +#endif + +/*** Helper function to handle checking for failed memory allocation + *** Author: Israel Fuller. + *** + *** @param ptr Pointer to the memory that should be allocated. + *** @param fname The name of the function invoked to allocate memory. + *** @param size The amount of memory being allocated. + *** @returns The pointer, for chaining. + ***/ +void* meta_check_allocation(void* ptr, const char* fname, const size_t size) + { + if (ptr == NULL) + { + /** Create the most descriptive error message we can. **/ + char error_buf[BUFSIZ]; + snprintf(error_buf, sizeof(error_buf), "exp_double_metaphone.c: Fail - %s(%lu)", fname, size); + perror(error_buf); + + // Throw error for easier locating in a debugger. + fprintf(stderr, "Program will now crash.\n"); + assert(0); + } + + return ptr; + } + +/** Malloc shortcut macros. **/ +#define SAFE_MALLOC(size) \ + ({ \ + const size_t sz = (size); \ + memset(meta_check_allocation(META_MALLOC(sz), "META_MALLOC", sz), 0, sz); \ + }) +#define SAFE_REALLOC(ptr, size) \ + ({ \ + const size_t sz = (size); \ + meta_check_allocation(META_REALLOC(ptr, sz), "META_REALLOC", sz); \ + }) + +typedef struct + { + char* str; + size_t length; + size_t bufsize; + int free_str_on_destroy; + } +MetaString; + +/*** Allocates a new MetaString. + *** + *** @param init_str The initial size of the string. + *** @returns The new MetaString. + ***/ +MetaString* meta_new_string(const char* init_str) + { + MetaString *s; + char empty_string[] = ""; + + s = (MetaString*)SAFE_MALLOC(sizeof(MetaString)); + + if (init_str == NULL) + init_str = empty_string; + + s->length = strlen(init_str); + /** Preallocate a bit more for potential growth. **/ + s->bufsize = s->length + 7u; + + s->str = (char*)SAFE_MALLOC(s->bufsize * sizeof(char)); + + strncpy(s->str, init_str, s->length + 1); + s->free_str_on_destroy = 1; + + return s; + } + +/*** Frees a MetaString. + *** + *** @param s The MetaString. + ***/ +void meta_destroy_string(MetaString* s) + { + if (s == NULL) + return; + + if (s->free_str_on_destroy && s->str != NULL) + META_FREE(s->str); + + META_FREE(s); + } + +/*** Increases a MetaString's buffer size. + *** + *** @param s The MetaString* being modified. + *** @param chars_needed Minimum number of characters to increase buffer size. + ***/ +void meta_increase_buffer(MetaString* s, const size_t chars_needed) + { + s->bufsize += chars_needed + 8u; + s->str = SAFE_REALLOC(s->str, s->bufsize * sizeof(char)); + } + +/*** Convert all characters of a MetaString to uppercase. + *** + *** @param s The MetaString being modified. + ***/ +void meta_make_upper(MetaString* s) + { + for (char* i = s->str; i[0] != '\0'; i++) + *i = (char)toupper(*i); + } + +/*** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + *** @returns 1 if the location is out of bounds for the MetaString, + *** 0 otherwise. + ***/ +bool meta_is_out_of_bounds(MetaString* s, unsigned int pos) + { + return (s->length <= pos); + } + +/*** Checks if a character in a MetaString is a vowel. + *** + *** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + ***/ +bool meta_is_vowel(MetaString* s, unsigned int pos) + { + if (meta_is_out_of_bounds(s, pos)) return 0; + + const char c = *(s->str + pos); + + return ((c == 'A') || (c == 'E') || (c == 'I') || + (c == 'O') || (c == 'U') || (c == 'Y')); + } + +/*** Search a MetaString for "W", "K", "CZ", or "WITZ", which indicate that the + *** string is Slavo Germanic. + *** + *** @param s The MetaString to be searched. + *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. + ***/ +bool meta_is_slavo_germanic(MetaString* s) + { + return (strstr(s->str, "W") != NULL) + || (strstr(s->str, "K") != NULL) + || (strstr(s->str, "CZ") != NULL) + || (strstr(s->str, "WITZ") != NULL); + } + +/*** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + *** @returns The character at the position in the MetaString, or + *** '\0' if the position is not in the MetaString. + ***/ +char meta_get_char_at(MetaString* s, unsigned int pos) + { + return (meta_is_out_of_bounds(s, pos)) ? '\0' : ((char) *(s->str + pos)); + } + +/*** Checks for to see if any of a list of strings appear in a the given + *** MetaString after the given start position. + *** + *** @attention - Note that the START value is 0 based. + *** + *** @param s The MetaString being modified. + *** @param start The zero-based start of at which to begin searching + *** within the MetaString. + *** @param length The length of the character strings being checked. + *** @returns 1 if any of the character sequences appear after the start + *** in the MetaString and 0 otherwise. + ***/ +bool meta_is_str_at(MetaString* s, unsigned int start, ...) + { + va_list ap; + + /** Should never happen. **/ + if (meta_is_out_of_bounds(s, start)) + return 0; + + const char* pos = (s->str + start); + va_start(ap, start); + + char* test; + do + { + test = va_arg(ap, char*); + if (*test && (strncmp(pos, test, strlen(test)) == 0)) + return true; + } + while (test[0] != '\0'); + + va_end(ap); + + return false; + } + +/*** Adds a string to a MetaString, expanding the MetaString if needed. + *** + *** @param s The MetaString being modified. + *** @param new_str The string being added. + ***/ +void meta_add_str(MetaString* s, const char* new_str) + { + if (new_str == NULL) + return; + + const size_t add_length = strlen(new_str); + if ((s->length + add_length) > (s->bufsize - 1)) + meta_increase_buffer(s, add_length); + + strcat(s->str, new_str); + s->length += add_length; + } + +/*** Computes double metaphone. + *** + *** Example Usage: + *** ```c + *** char* primary_code; + *** char* secondary_code; + *** meta_double_metaphone(input, &primary_code, &secondary_code); + *** ``` + *** + *** @param str The string to compute. + *** @param primary_code A pointer to a buffer where the pointer to a string + *** containing the produced primary code will be stored. + *** @param secondary_code A pointer to a buffer where the pointer to a string + *** containing the produced secondary code will be stored. + ***/ +void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) + { + size_t length; + + if (primary_code == NULL) + { + fprintf(stderr, "Warning: Call to meta_double_metaphone() is missing a pointer to store primary code.\n"); + return; + } + + if (secondary_code == NULL) + { + fprintf(stderr, "Warning: Call to meta_double_metaphone() is missing a pointer to store secondary code.\n"); + return; + } + + if (str == NULL || (length = strlen(str)) == 0u) + { + fprintf(stderr, "Warning: Call to meta_double_metaphone() with invalid string.\n"); + + /** Double Metaphone on an invalid string yields two empty strings. **/ + *primary_code = (char*)SAFE_MALLOC(sizeof(char)); + *secondary_code = (char*)SAFE_MALLOC(sizeof(char)); + return; + } + unsigned int current = 0; + unsigned int last = (unsigned int)(length - 1); + + /** Pad original so we can index beyond end. **/ + MetaString* original = meta_new_string(str); + meta_make_upper(original); + meta_add_str(original, " "); + + MetaString* primary = meta_new_string(""); + MetaString* secondary = meta_new_string(""); + primary->free_str_on_destroy = 0; + secondary->free_str_on_destroy = 0; + + /** Skip these if they are at start of a word. **/ + if (meta_is_str_at(original, 0, "GN", "KN", "PN", "WR", "PS", "")) + current += 1; + + /** Initial 'X' is pronounced 'Z' e.g. 'Xavier' **/ + const char first_char = meta_get_char_at(original, 0); + if (first_char == 'X') + { + meta_add_str(primary, "S"); /* 'Z' maps to 'S' */ + meta_add_str(secondary, "S"); + current += 1; + } + + /** Precomputing this is useful. **/ + const bool is_slavo_germanic = meta_is_slavo_germanic(original); + + /** Main loop. **/ + while (current < length) + { + const char cur_char = meta_get_char_at(original, current); + const char next_char = meta_get_char_at(original, current + 1); + switch (cur_char) + { + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + case 'Y': + { + if (current == 0) + { + /** All init vowels now map to 'A'. **/ + meta_add_str(primary, "A"); + meta_add_str(secondary, "A"); + } + current += 1; + break; + } + + case 'B': + { + /** "-mb", e.g", "dumb", already skipped over... **/ + meta_add_str(primary, "P"); + meta_add_str(secondary, "P"); + + current += (next_char == 'B') ? 2 : 1; + break; + } + + case 'C': + { + /** Various germanic. **/ + if ( + (current > 1) + && !meta_is_vowel(original, current - 2) + && meta_is_str_at(original, (current - 1), "ACH", "") + && meta_get_char_at(original, current + 2) != 'I' + && ( + meta_get_char_at(original, current + 2) != 'E' + || meta_is_str_at(original, (current - 2), "BACHER", "MACHER", "") + ) + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + /** Special case 'caesar' **/ + if (current == 0 && meta_is_str_at(original, current, "CAESAR", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "S"); + current += 2; + break; + } + + /** Italian 'chianti' **/ + if (meta_is_str_at(original, current, "CHIA", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + if (meta_is_str_at(original, current, "CH", "")) + { + /** Find 'michael' **/ + if (current > 0 && meta_is_str_at(original, current, "CHAE", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "X"); + current += 2; + break; + } + + /** Greek roots e.g. 'chemistry', 'chorus' **/ + if ( + current == 0 + && meta_is_str_at(original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") + && !meta_is_str_at(original, 0, "CHORE", "") + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + /** Germanic, greek, or otherwise 'ch' for 'kh' sound. */ + if ( + meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + /** 'architect but not 'arch', 'orchestra', 'orchid' **/ + || meta_is_str_at(original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") + || meta_is_str_at(original, (current + 2), "T", "S", "") + || ( + (current == 0 || meta_is_str_at(original, (current - 1), "A", "O", "U", "E", "")) + /** e.g., 'wachtler', 'wechsler', but not 'tichner' **/ + && meta_is_str_at(original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") + ) + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + else + { + if (current > 0) + { + if (meta_is_str_at(original, 0, "MC", "")) + { + /* e.g., "McHugh" */ + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + else + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "K"); + } + } + else + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + } + } + current += 2; + break; + } + + /** e.g, 'czerny' **/ + if (meta_is_str_at(original, current, "CZ", "") + && !meta_is_str_at(original, (current - 2), "WICZ", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + current += 2; + break; + } + + /** e.g., 'focaccia' **/ + if (meta_is_str_at(original, (current + 1), "CIA", "")) + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + current += 3; + break; + } + + /** Double 'C' rule. **/ + if ( + meta_is_str_at(original, current, "CC", "") + && !(current == 1 && first_char == 'M') /* McClellan exception. */ + ) + { + /** 'bellocchio' but not 'bacchus' **/ + if ( + meta_is_str_at(original, (current + 2), "I", "E", "H", "") + && !meta_is_str_at(original, (current + 2), "HU", "") + ) + { + /** 'accident', 'accede' 'succeed' **/ + if ( + (current == 1 && meta_get_char_at(original, current - 1) == 'A') + || meta_is_str_at(original, (current - 1), "UCCEE", "UCCES", "") + ) + { + meta_add_str(primary, "KS"); + meta_add_str(secondary, "KS"); + /** 'bacci', 'bertucci', other italian **/ + } + else + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + } + current += 3; + break; + } + else + { /** Pierce's rule **/ + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + } + + if (meta_is_str_at(original, current, "CK", "CG", "CQ", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + if (meta_is_str_at(original, current, "CI", "CE", "CY", "")) + { + /* Italian vs. English */ + if (meta_is_str_at(original, current, "CIO", "CIE", "CIA", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + } + else + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "S"); + } + current += 2; + break; + } + + /** else **/ + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + + /** Name sent in 'mac caffrey', 'mac gregor **/ + if (meta_is_str_at(original, (current + 1), " C", " Q", " G", "")) + current += 3; + else if (meta_is_str_at(original, (current + 1), "C", "K", "Q", "") + && !meta_is_str_at(original, (current + 1), "CE", "CI", "")) + current += 2; + else + current += 1; + break; + } + + case 'D': + { + if (meta_is_str_at(original, current, "DG", "")) + { + if (meta_is_str_at(original, (current + 2), "I", "E", "Y", "")) + { + /** e.g. 'edge' **/ + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + current += 3; + break; + } + else + { + /** e.g. 'edgar' **/ + meta_add_str(primary, "TK"); + meta_add_str(secondary, "TK"); + current += 2; + break; + } + } + + if (meta_is_str_at(original, current, "DT", "DD", "")) + { + meta_add_str(primary, "T"); + meta_add_str(secondary, "T"); + current += 2; + break; + } + + /** else **/ + meta_add_str(primary, "T"); + meta_add_str(secondary, "T"); + current += 1; + break; + } + + case 'F': + { + current += (next_char == 'F') ? 2 : 1; + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + break; + } + + case 'G': + { + if (next_char == 'H') + { + /** 'Vghee' */ + if (current > 0 && !meta_is_vowel(original, (current - 1))) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + current += 2; + break; + } + + if (current < 3) + { + /** 'ghislane', 'ghiradelli' **/ + if (current == 0) + { + if (meta_get_char_at(original, (current + 2)) == 'I') + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + } + else + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + current += 2; + break; + } + } + + if ( + /** Parker's rule (with some further refinements) - e.g., 'hugh' **/ + (current > 1 && meta_is_str_at(original, (current - 2), "B", "H", "D", "")) + /** e.g., 'bough' **/ + || (current > 2 && meta_is_str_at(original, (current - 3), "B", "H", "D", "")) + /** e.g., 'broughton' **/ + || (current > 3 && meta_is_str_at(original, (current - 4), "B", "H", "")) + ) + { + current += 2; + break; + } + else + { + /** e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough' **/ + if ( + current > 2 + && meta_get_char_at(original, (current - 1)) == 'U' + && meta_is_str_at(original, (current - 3), "C", "G", "L", "R", "T", "") + ) + { + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + } + else if (current > 0 && meta_get_char_at(original, (current - 1)) != 'I') + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + + current += 2; + break; + } + } + + if (next_char == 'N') + { + if (current == 1 && !is_slavo_germanic && meta_is_vowel(original, 0)) + { + meta_add_str(primary, "KN"); + meta_add_str(secondary, "N"); + } + else + /** not e.g. 'cagney' **/ + if ( + next_char != 'Y' + && !is_slavo_germanic + && !meta_is_str_at(original, (current + 2), "EY", "") + ) + { + meta_add_str(primary, "N"); + meta_add_str(secondary, "KN"); + } + else + { + meta_add_str(primary, "KN"); + meta_add_str(secondary, "KN"); + } + current += 2; + break; + } + + /** 'tagliaro' **/ + if ( + !is_slavo_germanic + && meta_is_str_at(original, (current + 1), "LI", "") + ) + { + meta_add_str(primary, "KL"); + meta_add_str(secondary, "L"); + current += 2; + break; + } + + /** -ges-,-gep-,-gel-, -gie- at beginning **/ + if ( + current == 0 + && ( + next_char == 'Y' + || meta_is_str_at( + original, (current + 1), + "ES", "EP", "EB", "EL", "EY", "IB", + "IL", "IN", "IE", "EI", "ER", "" + ) + ) + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "J"); + current += 2; + break; + } + + /** -ger-, -gy- **/ + if ( + (next_char == 'Y' || meta_is_str_at(original, (current + 1), "ER", "")) + /** Exceptions. **/ + && !meta_is_str_at(original, 0, "DANGER", "RANGER", "MANGER", "") + && !meta_is_str_at(original, (current - 1), "E", "I", "RGY", "OGY", "") + ) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "J"); + current += 2; + break; + } + + /** Italian e.g, 'biaggi' **/ + if ( + meta_is_str_at(original, (current + 1), "E", "I", "Y", "") + || meta_is_str_at(original, (current - 1), "AGGI", "OGGI", "") + ) + { + /** Obvious germanic. **/ + if (meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + || meta_is_str_at(original, (current + 1), "ET", "")) + { + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + } + else + { + /** Always soft, if french ending. **/ + if (meta_is_str_at(original, (current + 1), "IER ", "")) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + } + else + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "K"); + } + } + current += 2; + break; + } + + current += (next_char == 'G') ? 2 : 1; + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + break; + } + + case 'H': + { + /** Only keep if first & before vowel or between 2 vowels. **/ + if ( + (current == 0 || meta_is_vowel(original, (current - 1))) + && meta_is_vowel(original, current + 1) + ) + { + meta_add_str(primary, "H"); + meta_add_str(secondary, "H"); + current += 2; + } + else /* also takes care of 'HH' */ + current += 1; + break; + } + + case 'J': + { + /** Obvious spanish, 'jose', 'san jacinto' **/ + const bool has_jose_next = meta_is_str_at(original, current, "JOSE", ""); + const bool starts_with_san = meta_is_str_at(original, 0, "SAN ", ""); + if (has_jose_next || starts_with_san) + { + if ( + starts_with_san + /** I don't know what this condition means. **/ + || (current == 0 && meta_get_char_at(original, current + 4) == ' ') + ) + { + meta_add_str(primary, "H"); + meta_add_str(secondary, "H"); + } + else + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "H"); + } + current += 1; + break; + } + + if (current == 0 && !has_jose_next) + { + meta_add_str(primary, "J"); /* Yankelovich/Jankelowicz */ + meta_add_str(secondary, "A"); + } + else + { + /** spanish pron. of e.g. 'bajador' **/ + if ( + !is_slavo_germanic + && (next_char == 'A' || next_char == 'O') + && meta_is_vowel(original, (current - 1)) + ) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "H"); + } + else + { + if (current == last) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, ""); + } + else + { + if ( + !meta_is_str_at(original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") + && !meta_is_str_at(original, (current - 1), "S", "K", "L", "") + ) + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + } + } + } + } + + current += (next_char == 'J') ? 2 : 1; + break; + } + + case 'K': + { + current += (next_char == 'K') ? 2 : 1; + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + break; + } + + case 'L': + { + if (next_char == 'L') + { + /** Spanish e.g. 'cabrillo', 'gallegos' **/ + if ( + ( + current == length - 3 + && meta_is_str_at(original, (current - 1), "ILLO", "ILLA", "ALLE", "") + ) + || ( + meta_is_str_at(original, (current - 1), "ALLE", "") + && ( + meta_is_str_at(original, (last - 1), "AS", "OS", "") + || meta_is_str_at(original, last, "A", "O", "") + ) + ) + ) + { + meta_add_str(primary, "L"); + meta_add_str(secondary, ""); + current += 2; + break; + } + current += 2; + } + else + current += 1; + meta_add_str(primary, "L"); + meta_add_str(secondary, "L"); + break; + } + + case 'M': + { + current += ( + ( + meta_is_str_at(original, (current - 1), "UMB", "") + && (current + 1 == last || meta_is_str_at(original, (current + 2), "ER", "")) + ) + /** 'dumb','thumb' **/ + || next_char == 'M' + ) ? 2 : 1; + meta_add_str(primary, "M"); + meta_add_str(secondary, "M"); + break; + } + + case 'N': + { + current += (next_char == 'N') ? 2 : 1; + meta_add_str(primary, "N"); + meta_add_str(secondary, "N"); + break; + } + + case 'P': + { + if (next_char == 'H') + { + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + current += 2; + break; + } + + /** Also account for "campbell", "raspberry" **/ + current += (meta_is_str_at(original, (current + 1), "P", "B", "")) ? 2 : 1; + meta_add_str(primary, "P"); + meta_add_str(secondary, "P"); + break; + } + + case 'Q': + { + current += (next_char == 'Q') ? 2 : 1; + meta_add_str(primary, "K"); + meta_add_str(secondary, "K"); + break; + } + + case 'R': + { + /** French e.g. 'rogier', but exclude 'hochmeier' **/ + const bool no_primary = ( + !is_slavo_germanic + && current == last + && meta_is_str_at(original, (current - 2), "IE", "") + && !meta_is_str_at(original, (current - 4), "ME", "MA", "") + ); + + meta_add_str(primary, (no_primary) ? "" : "R"); + meta_add_str(secondary, "R"); + current += (next_char == 'R') ? 2 : 1; + break; + } + + case 'S': + { + /** Special cases 'island', 'isle', 'carlisle', 'carlysle' **/ + if (meta_is_str_at(original, (current - 1), "ISL", "YSL", "")) + { + current += 1; + break; + } + + /** Special case 'sugar-' **/ + if (current == 0 && meta_is_str_at(original, current, "SUGAR", "")) + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "S"); + current += 1; + break; + } + + if (meta_is_str_at(original, current, "SH", "")) + { + const bool germanic = meta_is_str_at(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); + const char* sound = (germanic) ? "S" : "X"; + meta_add_str(primary, sound); + meta_add_str(secondary, sound); + current += 2; + break; + } + + /** Italian & Armenian. **/ + if (meta_is_str_at(original, current, "SIO", "SIA", "SIAN", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, (is_slavo_germanic) ? "S" : "X"); + current += 3; + break; + } + + /** german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' **/ + /** also, -sz- in slavic language although in hungarian it is pronounced 's' **/ + if (current == 0 && meta_is_str_at(original, (current + 1), "M", "N", "L", "W", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + current += 1; + break; + } + if (meta_is_str_at(original, (current + 1), "Z", "")) + { + meta_add_str(primary, "S"); + meta_add_str(secondary, "X"); + current += 2; + break; + } + + if (meta_is_str_at(original, current, "SC", "")) + { + /** Schlesinger's rule. **/ + if (meta_get_char_at(original, current + 2) == 'H') + { + /** Dutch origin, e.g. 'school', 'schooner' **/ + if (meta_is_str_at(original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) + { + /** 'schermerhorn', 'schenker' **/ + const bool x_sound = meta_is_str_at(original, (current + 3), "ER", "EN", ""); + meta_add_str(primary, (x_sound) ? "X" : "SK"); + meta_add_str(secondary, "SK"); + current += 3; + break; + } + else + { + const bool s_sound = ( + current == 0 + && !meta_is_vowel(original, 3) + && meta_get_char_at(original, 3) != 'W' + ); + meta_add_str(primary, "X"); + meta_add_str(secondary, (s_sound) ? "S" : "X"); + current += 3; + break; + } + } + + /** Default case. **/ + const char* sound = (meta_is_str_at(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; + meta_add_str(primary, sound); + meta_add_str(secondary, sound); + current += 3; + break; + } + + /** French e.g. 'resnais', 'artois' **/ + const bool no_primary = (current == last && meta_is_str_at(original, (current - 2), "AI", "OI", "")); + meta_add_str(primary, (no_primary) ? "" : "S"); + meta_add_str(secondary, "S"); + current += (meta_is_str_at(original, (current + 1), "S", "Z", "")) ? 2 : 1; + break; + } + + case 'T': + { + if (meta_is_str_at(original, current, "TIA", "TCH", "TION", "")) + { + meta_add_str(primary, "X"); + meta_add_str(secondary, "X"); + current += 3; + break; + } + + if (meta_is_str_at(original, current, "TH", "TTH", "")) + { + /** Special case 'thomas', 'thames' or germanic. **/ + if ( + meta_is_str_at(original, (current + 2), "OM", "AM", "") + || meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + ) + meta_add_str(primary, "T"); + else + meta_add_str(primary, "0"); /* Yes, zero. */ + meta_add_str(secondary, "T"); + current += 2; + break; + } + + meta_add_str(primary, "T"); + meta_add_str(secondary, "T"); + current += (meta_is_str_at(original, (current + 1), "T", "D", "")) ? 2 : 1; + break; + } + + case 'V': + { + meta_add_str(primary, "F"); + meta_add_str(secondary, "F"); + current += (next_char == 'V') ? 2 : 1; + break; + } + + case 'W': + { + /** Can also be in middle of word. **/ + if (meta_is_str_at(original, current, "WR", "")) + { + meta_add_str(primary, "R"); + meta_add_str(secondary, "R"); + current += 2; + break; + } + + const bool next_is_vowel = meta_is_vowel(original, current + 1); + if (current == 0 && (next_is_vowel || meta_is_str_at(original, current, "WH", ""))) + { + /** Wasserman should match Vasserman. **/ + meta_add_str(primary, "A"); + meta_add_str(secondary, (next_is_vowel) ? "F" : "A"); + } + + /** Arnow should match Arnoff. **/ + if ((current == last && meta_is_vowel(original, current - 1)) + || meta_is_str_at(original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") + || meta_is_str_at(original, 0, "SCH", "") + ) + { + meta_add_str(primary, ""); + meta_add_str(secondary, "F"); + current += 1; + break; + } + + /** Polish e.g. 'filipowicz' **/ + if (meta_is_str_at(original, current, "WICZ", "WITZ", "")) + { + meta_add_str(primary, "TS"); + meta_add_str(secondary, "FX"); + current += 4; + break; + } + + /** Else skip it. **/ + current += 1; + break; + } + + case 'X': + { + /** French e.g. breaux **/ + const bool silent = ( + current == last + && ( + meta_is_str_at(original, (current - 2), "AU", "OU", "") + || meta_is_str_at(original, (current - 3), "IAU", "EAU", "") + ) + ); + if (!silent) + { + meta_add_str(primary, "KS"); + meta_add_str(secondary, "KS"); + } + + current += (meta_is_str_at(original, (current + 1), "C", "X", "")) ? 2 : 1; + break; + } + + case 'Z': + { + /** Chinese pinyin e.g. 'zhao' **/ + if (next_char == 'H') + { + meta_add_str(primary, "J"); + meta_add_str(secondary, "J"); + current += 2; + break; + } + + const bool has_t_sound = ( + meta_is_str_at(original, (current + 1), "ZO", "ZI", "ZA", "") + || (is_slavo_germanic && current > 0 && meta_get_char_at(original, (current - 1)) != 'T') + ); + meta_add_str(primary, "S"); + meta_add_str(secondary, (has_t_sound) ? "TS" : "S"); + current += (next_char == 'Z') ? 2 : 1; + break; + } + + default: + current += 1; + } + } + + *primary_code = primary->str; + *secondary_code = secondary->str; + + meta_destroy_string(original); + meta_destroy_string(primary); + meta_destroy_string(secondary); + } + +#ifdef TESTING +/*** Built in test cases, written by Israel with inspiration from comments in + *** the above code, test cases written by Maurice Aubrey, and some words + *** suggested by AI. + *** + *** These tests have been integrated into the Centrallix testing environment, + *** where they can be run using `export TONLY=exp_fn_double_metaphone_00`, + *** followed by make test, in the Centrallix directory. + *** + *** The can also be run here by executing the following commands in the + *** centrallix/expression directory, which aditionally generates a coverage + *** report. These tests cover all parts of the double metaphone algorithm, + *** although some of the error cases in various helper functions (such as + *** meta_destroy_string(null)) are not covered by testing. + *** + *** Commands: + *** gcc exp_double_metaphone.c -o exp_double_metaphone.o -I .. -DTESTING -fprofile-arcs -ftest-coverage -O0 + *** ./exp_double_metaphone.o + *** gcov exp_double_metaphone.c + ***/ + +unsigned int num_tests_passed = 0u, num_tests_failed = 0u; + +void test(const char* input, const char* expected_primary, const char* expected_secondary) + { + char* codes[2]; + + /** Run DoubleMetaphone() and extract results. **/ + char* actual_primary; + char* actual_secondary; + meta_double_metaphone( + input, + memset(&actual_primary, 0, sizeof(actual_primary)), + memset(&actual_secondary, 0, sizeof(actual_secondary)) + ); + + /** Test for correct value. **/ + if (!strcmp(expected_primary, actual_primary) && + !strcmp(expected_secondary, actual_secondary)) + num_tests_passed++; + else + { + printf( + "\nTEST FAILED: \"%s\"\n" + "Expected: %s %s\n" + "Actual: %s %s\n", + input, + expected_primary, expected_secondary, + actual_primary, actual_secondary + ); + num_tests_failed++; + } + } + +// Special thanks to the following websites for double checking the correct results: +// 1: https://words.github.io/double-metaphone +// 2: https://mainegenealogy.net/metaphone_converter.asp +// 3: https://en.toolpage.org/tool/metaphone +void run_tests(void) + { + printf("\nRunning tests...\n"); + + /** Test that always fails. **/ + // test("This", "test", "fails."); + + /** Invalid string tests, by Israel. **/ + fprintf(stderr, "Expect two warnings between these two lines:\n"); + fprintf(stderr, "----------------\n"); + test(NULL, "", ""); + test("", "", ""); + fprintf(stderr, "----------------\n"); + + /** Basic tests, by Israel. **/ + test("Test", "TST", "TST"); + test("Basic", "PSK", "PSK"); + test("Centrallix", "SNTRLKS", "SNTRLKS"); + test("Lawrence", "LRNS", "LRNS"); + test("Philips", "FLPS", "FLPS"); + test("Acceptingness", "AKSPTNNS", "AKSPTNKNS"); + test("Supercalifragilisticexpialidocious", "SPRKLFRJLSTSKSPLTSS", "SPRKLFRKLSTSKSPLTXS"); + test("Suoicodilaipxecitsiligarfilacrepus", "SKTLPKSSTSLKRFLKRPS", "SKTLPKSSTSLKRFLKRPS"); + + /** Match tests, from code comments above. **/ + test("Smith", "SM0", "XMT"); + test("Schmidt", "XMT", "SMT"); + test("Snider", "SNTR", "XNTR"); + test("Schneider", "XNTR", "SNTR"); + test("Arnow", "ARN", "ARNF"); + test("Arnoff", "ARNF", "ARNF"); + + /** Example tests, from examples in code comments above. **/ + test("Accede", "AKST", "AKST"); + test("Accident", "AKSTNT", "AKSTNT"); + test("Actually", "AKTL", "AKTL"); + test("Arch", "ARX", "ARK"); + test("Artois", "ART", "ARTS"); + test("Bacchus", "PKS", "PKS"); + test("Bacci", "PX", "PX"); + test("Bajador", "PJTR", "PHTR"); + test("Bellocchio", "PLX", "PLX"); + test("Bertucci", "PRTX", "PRTX"); + test("Biaggi", "PJ", "PK"); + test("Bough", "P", "P"); + test("Breaux", "PR", "PR"); + test("Broughton", "PRTN", "PRTN"); + test("Cabrillo", "KPRL", "KPR"); + test("Caesar", "SSR", "SSR"); + test("Cagney", "KKN", "KKN"); + test("Campbell", "KMPL", "KMPL"); + test("Carlisle", "KRLL", "KRLL"); + test("Carlysle", "KRLL", "KRLL"); + test("Chemistry", "KMSTR", "KMSTR"); + test("Chianti", "KNT", "KNT"); + test("Chorus", "KRS", "KRS"); + test("Cough", "KF", "KF"); + test("Czerny", "SRN", "XRN"); + test("Dumb", "TM", "TM"); + test("Edgar", "ATKR", "ATKR"); + test("Edge", "AJ", "AJ"); + test("Filipowicz", "FLPTS", "FLPFX"); + test("Focaccia", "FKX", "FKX"); + test("Gallegos", "KLKS", "KKS"); + test("Germanic", "KRMNK", "JRMNK"); + test("Ghiradelli", "JRTL", "JRTL"); + test("Ghislane", "JLN", "JLN"); + test("Gospel", "KSPL", "KSPL"); + test("Gough", "KF", "KF"); + test("Greek", "KRK", "KRK"); + test("Hochmeier", "HKMR", "HKMR"); + test("Hugh", "H", "H"); + test("Island", "ALNT", "ALNT"); + test("Isle", "AL", "AL"); + test("Italian", "ATLN", "ATLN"); + test("Jankelowicz", "JNKLTS", "ANKLFX"); + test("Jose", "HS", "HS"); + test("Laugh", "LF", "LF"); + test("Mac Caffrey", "MKFR", "MKFR"); + test("Mac Gregor", "MKRKR", "MKRKR"); + test("Manager", "MNKR", "MNJR"); + test("McHugh", "MK", "MK"); + test("McLaughlin", "MKLFLN", "MKLFLN"); + test("Michael", "MKL", "MXL"); + test("Middle", "MTL", "MTL"); + test("Orchestra", "ARKSTR", "ARKSTR"); + test("Orchid", "ARKT", "ARKT"); + test("Pinyin", "PNN", "PNN"); + test("Raspberry", "RSPR", "RSPR"); + test("Resnais", "RSN", "RSNS"); + test("Rogier", "RJ", "RJR"); + test("Rough", "RF", "RF"); + test("Salvador", "SLFTR", "SLFTR"); + test("San jacinto", "SNHSNT", "SNHSNT"); + test("Schenker", "XNKR", "SKNKR"); + test("Schermerhorn", "XRMRRN", "SKRMRRN"); + test("Schlesinger", "XLSNKR", "SLSNJR"); + test("School", "SKL", "SKL"); + test("Schooner", "SKNR", "SKNR"); + test("Succeed", "SKST", "SKST"); + test("Sugar", "XKR", "SKR"); + test("Sugary", "XKR", "SKR"); + test("Tagliaro", "TKLR", "TLR"); + test("Thames", "TMS", "TMS"); + test("Thomas", "TMS", "TMS"); + test("Thumb", "0M", "TM"); + test("Tichner", "TXNR", "TKNR"); + test("Tough", "TF", "TF"); + test("Vghee", "FK", "FK"); + test("Wachtler", "AKTLR", "FKTLR"); + test("Wechsler", "AKSLR", "FKSLR"); + test("Word", "ART", "FRT"); + test("Xavier", "SF", "SFR"); + test("Yankelovich", "ANKLFX", "ANKLFK"); + test("Zhao", "J", "J"); + + /** Interesting Edge Case: "McClellan" **/ + /*** Note: Sources (1) and (3) both include a double K ("MKKLLN"), but the + *** original code on GitHub and mainegenealogy.net do not. I chose "MKLLN" + *** to be correct because I personally do not pronounce the second c. + ***/ + test("McClellan", "MKLLN", "MKLLN"); + + /** Maurice Aubrey's Tests. **/ + /** Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt **/ + test("maurice", "MRS", "MRS"); + test("aubrey", "APR", "APR"); + test("cambrillo", "KMPRL", "KMPR"); + test("heidi", "HT", "HT"); + test("katherine", "K0RN", "KTRN"); + test("catherine", "K0RN", "KTRN"); + test("richard", "RXRT", "RKRT"); + test("bob", "PP", "PP"); + test("eric", "ARK", "ARK"); + test("geoff", "JF", "KF"); + test("dave", "TF", "TF"); + test("ray", "R", "R"); + test("steven", "STFN", "STFN"); + test("bryce", "PRS", "PRS"); + test("randy", "RNT", "RNT"); + test("bryan", "PRN", "PRN"); + test("brian", "PRN", "PRN"); + test("otto", "AT", "AT"); + test("auto", "AT", "AT"); + + /** GPT-5 Coverage Tests. **/ + /*** GPT-5 mini (Preview) running in GitHub Copilot suggested the words + *** after analizing a generated coverage report, and I (Israel) used + *** them to write the tests below. I kept the AI's reasoning for tests, + *** while removing tests that did not contribute any coverage, but after + *** a few reprompts, the AI started just giving words without reasoning. + *** I guess we were both getting pretty tired of writing tests. + ***/ + test("Abbott", "APT", "APT"); /* double-B ("BB") handling. */ + test("Back", "PK", "PK"); /* "CK"/"CG"/"CQ" branch. */ + test("Bacher", "PKR", "PKR"); /* matches "...BACHER" / ACH special-case. */ + test("Charles", "XRLS", "XRLS"); /* initial "CH" -> the branch that maps to "X"/"X" at start. */ + test("Ghana", "KN", "KN"); /* initial "GH" special-start handling. */ + test("Gnome", "NM", "NM"); /* "GN" sequence handling. */ + test("Raj", "RJ", "R"); /* J at end (exercise J-last behavior). */ + test("Quentin", "KNTN", "KNTN"); /* Q case (Q -> K mapping). */ + test("Who", "A", "A"); /* "WH" at start handling. */ + test("Shoemaker", "XMKR", "XMKR"); /* "SH" general mapping paths. */ + test("Sian", "SN", "XN"); /* "SIO"/"SIA"/"SIAN" branch. */ + test("Scold", "SKLT", "SKLT"); /* "SC" default / "SK" vs other SC subcases. */ + test("Station", "STXN", "STXN"); /* "TION" -> X mapping. */ + test("Match", "MX", "MX"); /* "TCH"/"TIA" -> X mapping. */ + test("Pizza", "PS", "PTS"); /* double-Z ("ZZ") handling. */ + test("Agnes", "AKNS", "ANS"); /* "GN" at index 1 (GN handling that yields KN / N). */ + test("Science", "SNS", "SNS"); /* "SC" followed by I (SC + I/E/Y branch). */ + test("Van Gogh", "FNKK", "FNKK"); + test("Josef", "JSF", "HSF"); + test("Object", "APJKT", "APJKT"); + test("Sholz", "SLS", "SLS"); + test("Scharf", "XRF", "XRF"); + test("Kasia", "KS", "KS"); + test("Van Geller", "FNKLR", "FNKLR"); + + const unsigned int total_tests = num_tests_passed + num_tests_failed; + printf("\nTests completed!\n"); + printf(" > Failed: %u\n", num_tests_failed); + printf(" > Skipped: %u\n", 0u); /* Implementation removed. */ + printf(" > Passed: %u/%u\n", num_tests_passed, total_tests); + } + +int main(void) + { + run_tests(); + + return 0; + } + +/** Prevent scope leak. **/ +#undef META_FREE +#undef META_MALLOC +#undef META_REALLOC +#undef SAFE_MALLOC +#undef SAFE_REALLOC + +#endif From b6abca77753711d05263ad6300bc6c8b5a2d1d90 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 11 Dec 2025 16:34:32 -0700 Subject: [PATCH 033/361] Finish exp_functions.c work. Add log() and trim() exp functions (with tests). Add optional variables in schemas. Fix styling for schema verification. Fix log() being treated as a reserved word. --- centrallix/expression/exp_functions.c | 963 ++++++++++++++---------- centrallix/multiquery/multiquery.c | 17 +- centrallix/tests/test_expfn_log_00.cmp | 37 +- centrallix/tests/test_expfn_log_00.to | 59 +- centrallix/tests/test_expfn_trim_00.cmp | 10 + centrallix/tests/test_expfn_trim_00.to | 19 + 6 files changed, 655 insertions(+), 450 deletions(-) create mode 100644 centrallix/tests/test_expfn_trim_00.cmp create mode 100644 centrallix/tests/test_expfn_trim_00.to diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 647b157e8..772672bca 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -85,14 +85,16 @@ *** skipped for other types. *** *** Valid Flags: + *** - `EXP_ARG_OPTIONAL`: The arg is optional. It is not valid a required + *** argument after an optional one. *** - `EXP_ARG_NOT_NULL`: Expect the arg to not be null. *** - `EXP_ARG_FORCE_TYPE`: Run type check on null args (not recommended). - *** - `EXP_ARG_NON_EMPTY`: Expect string to be non-empty. Expect a - *** stringvec or intvec to have elements (does not check them). - *** - `EXP_ARG_POSITIVE`: Expect a positive or zero value for int, double, - *** money, or datetime. (Includes NON_NAN: NAN is not positive). - *** - `EXP_ARG_NEGATIVE`: Expect a negative or zero value for int, double, - *** money, or datetime. (Includes NON_NAN: NAN is not negative). + *** - `EXP_ARG_NON_EMPTY`: Expect string to be non-empty. Expect a + *** stringvec or intvec to have elements (does not check them). + *** - `EXP_ARG_POSITIVE`: Expect a positive or zero value for int, double, + *** money, or datetime. (Includes NON_NAN: NAN is not positive). + *** - `EXP_ARG_NEGATIVE`: Expect a negative or zero value for int, double, + *** money, or datetime. (Includes NON_NAN: NAN is not negative). *** - `EXP_ARG_NON_NAN`: Expect a double to be a number, not NAN. *** *** @attention - Checks like `EXP_ARG_NON_EMPTY`, `EXP_ARG_NON_NAN`, etc. also @@ -105,252 +107,258 @@ typedef struct } ArgExpect, *pArgExpect; +#define EXP_ARG_END (ArgExpect){NULL, -1} #define EXP_ARG_NO_FLAGS (0) -#define EXP_ARG_NOT_NULL (1 << 0) -#define EXP_ARG_FORCE_TYPE (1 << 1) -#define EXP_ARG_NON_EMPTY (1 << 2) -#define EXP_ARG_NEGATIVE (1 << 3) -#define EXP_ARG_POSITIVE (1 << 4) -#define EXP_ARG_NON_NAN (1 << 5) +#define EXP_ARG_OPTIONAL (1 << 0) +#define EXP_ARG_NOT_NULL (1 << 1) +#define EXP_ARG_FORCE_TYPE (1 << 2) +#define EXP_ARG_NON_EMPTY (1 << 3) +#define EXP_ARG_NEGATIVE (1 << 4) +#define EXP_ARG_POSITIVE (1 << 5) +#define EXP_ARG_NON_NAN (1 << 6) /*** An internal function used by the schema verifier (below) to verify each - *** argument of the schema. + *** argument of the provided schema. + *** + *** @param fn_name The name of the expression function to be verified. + *** @param arg The argument to be verified. + *** @param arg_expect The expectation struct which specifies the requirements + *** for this argument. + *** @returns 0 if the expectations are successfully met, + *** -1 if an expectation is violated (and mssError() is called). ***/ static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) { - /** The expectation struct cannot be NULL. **/ - if (arg_expect == NULL) - { - mssErrorf(1, "EXP", - "%s(...): Expectation struct cannot be NULL", - fn_name - ); - return -1; - } - - /** Extract values. **/ - ASSERTMAGIC(arg, MGK_EXPRESSION); - int actual_datatype = arg->DataType; - - /** Check for a provided NULL value. **/ - if (arg->Flags & EXPR_F_NULL) - { - if (arg_expect->Flags & EXP_ARG_NOT_NULL) + /** The expectation struct cannot be NULL. **/ + if (arg_expect == NULL) { mssErrorf(1, "EXP", - "%s(...): Expects a non-null value, but got NULL : %s (%d).", - fn_name, ci_TypeToStr(actual_datatype), actual_datatype + "%s(...): Expectation struct cannot be NULL", + fn_name ); return -1; } - /** Skip type checks unless forced. **/ - if (!(arg_expect->Flags & EXP_ARG_FORCE_TYPE)) goto skip_type_checks; - } - - /** No type checking required. **/ - if (arg_expect->Datatypes == NULL) goto skip_type_checks; - - /** No types given: Probably a mistake. **/ - if (arg_expect->Datatypes[0] == -1) - { - mssErrorf(1, "EXP", - "%s(...): Array of allowed Datatypes is empty.", - fn_name - ); - return -1; - } - - /** Verify Datatypes. **/ - bool found = false; - for (int j = 0; arg_expect->Datatypes[j] != -1; j++) - { - const int expected_datatype = arg_expect->Datatypes[j]; - if (expected_datatype == actual_datatype) - { - found = true; - break; - } - } - - /** Handle failure. **/ - if (!found) - { - /** Accumulate additional valid types. **/ - char buf[256] = {'\0'}; - int cur = 0, j = 1; - while (true) - { - int datatype = arg_expect->Datatypes[j++]; - if (datatype == -1) break; - - cur += snprintf( - buf + cur, 256 - cur, - " or %s (%d)", - ci_TypeToStr(datatype), datatype - ); - } + /** Extract values. **/ + ASSERTMAGIC(arg, MGK_EXPRESSION); + int actual_datatype = arg->DataType; - /** Print error. **/ - int first_datatype = arg_expect->Datatypes[0]; - mssErrorf(1, "EXP", - "%s(...): Expects type %s (%d)%s but got type %s (%d).", - fn_name, ci_TypeToStr(first_datatype), first_datatype, buf, ci_TypeToStr(actual_datatype), actual_datatype - ); - return -1; - } - - skip_type_checks: - /** All flag checks not implemented above should pass on NULL values. **/ - if (arg->Flags & EXPR_F_NULL) return 0; - - /** Verify other Flags by type, if specified. **/ - switch (actual_datatype) - { - case DATA_T_INTEGER: + /** Check for a provided NULL value. **/ + if (arg->Flags & EXPR_F_NULL) { - int value = arg->Integer; - if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) + if (arg_expect->Flags & EXP_ARG_NOT_NULL) { mssErrorf(1, "EXP", - "%s(...): Expects positive int but got %d.", - fn_name, value + "%s(...): Expects a non-null value, but got NULL : %s (%d).", + fn_name, objTypeToStr(actual_datatype), actual_datatype ); return -1; } - if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) - { - mssErrorf(1, "EXP", - "%s(...): Expects negative int but got %d.", - fn_name, value - ); - return -1; - } - break; + + /** Skip type checks for NULL values (unless they are forced). **/ + if (!(arg_expect->Flags & EXP_ARG_FORCE_TYPE)) goto skip_type_checks; } - case DATA_T_DOUBLE: + /** Skip type checks if none are requested. **/ + if (arg_expect->Datatypes == NULL) goto skip_type_checks; + + /** Type checks requested, but no valid types given: Likely a mistake. **/ + if (arg_expect->Datatypes[0] == -1) { - double value = arg->Types.Double; - if (arg_expect->Flags & EXP_ARG_NON_NAN && isnan(value)) - { - mssErrorf(1, "EXP", - "%s(...): Expects non-nan double but got %g.", - fn_name, value - ); - return -1; - } - if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) - { - mssErrorf(1, "EXP", - "%s(...): Expects positive double but got %g.", - fn_name, value - ); - return -1; - } - if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) + mssErrorf(1, "EXP", "%s(...): Invalid Schema! Empty array of allowed datatypes.", fn_name); + fprintf(stderr, "Hint: To skip type checks, pass NULL for the array of data types.\n"); + return -1; + } + + /** Verify datatypes. **/ + bool found = false; + for (int j = 0; arg_expect->Datatypes[j] != -1; j++) + { + const int expected_datatype = arg_expect->Datatypes[j]; + if (expected_datatype == actual_datatype) { - mssErrorf(1, "EXP", - "%s(...): Expects negative double but got %g.", - fn_name, value - ); - return -1; + found = true; + break; } - break; } - case DATA_T_STRING: + /** Handle failure. **/ + if (!found) { - char* str = arg->String; - if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str[0] == '\0') + /** Accumulate additional valid types. **/ + char buf[256] = {'\0'}; + int cur = 0, j = 1; + while (true) { - mssErrorf(1, "EXP", - "%s(...): Expects string to contain characters, but got \"\".", - fn_name + int datatype = arg_expect->Datatypes[j++]; + if (datatype == -1) break; + + cur += snprintf( + buf + cur, 256 - cur, + " or %s (%d)", + objTypeToStr(datatype), datatype ); - return -1; } - break; + + /** Print error. **/ + int first_datatype = arg_expect->Datatypes[0]; + mssErrorf(1, "EXP", + "%s(...): Expects type %s (%d)%s but got type %s (%d).", + fn_name, objTypeToStr(first_datatype), first_datatype, buf, objTypeToStr(actual_datatype), actual_datatype + ); + return -1; } - case DATA_T_DATETIME: + skip_type_checks: + /** All flag checks not implemented above should pass on NULL values. **/ + if (arg->Flags & EXPR_F_NULL) return 0; + + /** Verify other Flags by type, if specified. **/ + switch (actual_datatype) { - pDateTime value = &arg->Types.Date; - if (arg_expect->Flags & EXP_ARG_POSITIVE && value->Value < 0) + case DATA_T_INTEGER: { - mssErrorf(1, "EXP", - "%s(...): Expects positive date offset but got %llu.", - fn_name, value->Value - ); - return -1; + int value = arg->Integer; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive int but got %d.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative int but got %d.", + fn_name, value + ); + return -1; + } + break; } - if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->Value > 0) + + case DATA_T_DOUBLE: { - mssErrorf(1, "EXP", - "%s(...): Expects negative date offset but got %llu.", - fn_name, value->Value - ); - return -1; + double value = arg->Types.Double; + if (arg_expect->Flags & EXP_ARG_NON_NAN && isnan(value)) + { + mssErrorf(1, "EXP", + "%s(...): Expects non-nan double but got %g.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive double but got %g.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative double but got %g.", + fn_name, value + ); + return -1; + } + break; } - break; - } - - case DATA_T_MONEY: - { - pMoneyType value = &arg->Types.Money; - if (arg_expect->Flags & EXP_ARG_POSITIVE && value->WholePart < 0) + + case DATA_T_STRING: { - mssErrorf(1, "EXP", - "%s(...): Expects positive money value but got $%d.%g.", - fn_name, value->WholePart, (double)value->FractionPart / 100.0 - ); - return -1; + char* str = arg->String; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str[0] == '\0') + { + mssErrorf(1, "EXP", + "%s(...): Expects string to contain characters, but got \"\".", + fn_name + ); + return -1; + } + break; } - if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->WholePart > 0) + + case DATA_T_DATETIME: { - mssErrorf(1, "EXP", - "%s(...): Expects negative money value but got $%d.%d.", - fn_name, value->WholePart, (double)value->FractionPart / 100.0 - ); - return -1; + pDateTime value = &arg->Types.Date; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value->Value < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive date offset but got %llu.", + fn_name, value->Value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->Value > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative date offset but got %llu.", + fn_name, value->Value + ); + return -1; + } + break; } - } - - case DATA_T_STRINGVEC: - { - pStringVec str_vec = &arg->Types.StrVec; - if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str_vec->nStrings == 0) + + case DATA_T_MONEY: { - mssErrorf(1, "EXP", - "%s(...): Expects StringVec to contain strings, but got [].", - fn_name - ); - return -1; + pMoneyType value = &arg->Types.Money; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value->WholePart < 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects positive money value but got $%d.%g.", + fn_name, value->WholePart, (double)value->FractionPart / 100.0 + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->WholePart > 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects negative money value but got $%d.%d.", + fn_name, value->WholePart, (double)value->FractionPart / 100.0 + ); + return -1; + } } - break; - } - - case DATA_T_INTVEC: - { - pIntVec int_vec = &arg->Types.IntVec; - if (arg_expect->Flags & EXP_ARG_NON_EMPTY && int_vec->nIntegers == 0) + + case DATA_T_STRINGVEC: { - mssErrorf(1, "EXP", - "%s(...): Expects IntVec to contain strings, but got [].", - fn_name - ); - return -1; + pStringVec str_vec = &arg->Types.StrVec; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str_vec->nStrings == 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects StringVec to contain strings, but got [].", + fn_name + ); + return -1; + } + break; + } + + case DATA_T_INTVEC: + { + pIntVec int_vec = &arg->Types.IntVec; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && int_vec->nIntegers == 0) + { + mssErrorf(1, "EXP", + "%s(...): Expects IntVec to contain strings, but got [].", + fn_name + ); + return -1; + } + break; } - break; } - } return 0; } /*** Verify that arguments passed to a function match some expected values. *** - *** @param fn_name The name of the function (for error messages). *** @param arg_expects A pointer to an array of ArgExpect structs, each *** representing expectations for a single argument, in the order they *** are passed to the function. @@ -358,7 +366,7 @@ static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgEx *** function (and the length of arg_expects). *** @param tree The tree containing the actual arguments passed. *** @param obj_list The object list scope which was passed to the function. - *** @returns 0 if all arguments are successfully verified, or + *** @returns 0 if verification passes, or *** -1 if an error occurs or arguments are incorrect. *** *** @attention - Promises that an error message will be printed with a call @@ -366,50 +374,79 @@ static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgEx *** *** Example: *** ```c - *** char fn_name[] = "example"; - *** if (exp_fn_i_verify_schema(fn_name, + *** if (exp_fn_i_verify_schema( *** (ArgExpect[]){ - *** {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, -1}, EXP_PARAM_NOT_NULL}, - *** {(int[]){DATA_T_STRING, -1}, 0} - *** }, 2, - *** tree, obj_list + *** {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_DATETIME, -1}, EXP_ARG_NOT_NULL}, + *** {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + *** {(int[]){DATA_T_STRING, -1}, EXP_ARG_OPTIONAL}, + *** EXP_ARG_END + *** }, tree *** ) != 0) *** { - *** mssErrorf(0, "EXP", "%s(?) Call does not match function schema.", fn_name); + *** mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); *** return -1; *** } *** ``` ***/ -static int exp_fn_i_verify_schema( - const char* fn_name, - const ArgExpect* arg_expects, - const int num_args, - pExpression tree, - pParamObjects obj_list) +static int exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) { - /** Verify expression tree. **/ - ASSERTMAGIC(tree, MGK_EXPRESSION); - - /** Verify argument count. **/ - const int num_args_actual = tree->Children.nItems; - if (num_args != num_args_actual) - { - mssErrorf(1, "EXP", - "%s(?): Expects %u argument%s, got %d argument%s.", - fn_name, num_args, (num_args == 1) ? "" : "s", num_args_actual, (num_args_actual == 1) ? "" : "s" - ); - return -1; - } - - /** Verify argument datatypes. **/ - for (int i = 0; i < num_args; i++) - { - if (exp_fn_i_verify_arg(fn_name, tree->Children.Items[i], &arg_expects[i]) != 0) + /** Verify expression tree. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + + /** Count arguments. **/ + unsigned int req_args = 0u, opt_args = 0u; + for (unsigned int i = 0; arg_expects[i].Flags != EXP_ARG_END.Flags; i++) { - mssErrorf(0, "EXP", "%s(...): Error while reading arg #%d/%d.", fn_name, i + 1, num_args); - return -1; + if (arg_expects[i].Flags & EXP_ARG_OPTIONAL) + opt_args++; + else if (opt_args > 0) + { + /** Required argument follows optional argument (not allowed). **/ + mssErrorf(1, "EXP", "%s(?): Invalid Schema! Required argument #%u after optional argument.", tree->Name, i); + return -1; + } + else + req_args++; + } + const unsigned int total_args = req_args + opt_args; + + /** Verify argument count. **/ + const int actual_args = tree->Children.nItems; + if (opt_args == 0) + { + if (actual_args != req_args) + { + mssErrorf(1, "EXP", + "%s(?): Expects %u argument%s, got %d argument%s.", + tree->Name, req_args, (req_args == 1) ? "" : "s", actual_args, (actual_args == 1) ? "" : "s" + ); + return -1; + } + } + else + { + if (actual_args < req_args || total_args < actual_args) + { + mssErrorf(1, "EXP", + "%s(?): Expects between %u and %u arguments, got %d argument%s.", + tree->Name, req_args, total_args, actual_args, (actual_args == 1) ? "" : "s" + ); + return -1; + } + } + + /** Verify arguments. **/ + for (int i = 0; i < actual_args; i++) + { + if (exp_fn_i_verify_arg(tree->Name, tree->Children.Items[i], &arg_expects[i]) != 0) + { + mssErrorf(0, "EXP", + "%s(...): Error while reading arg #%d/%d.", + tree->Name, i + 1, max(i + 1, req_args) + ); + return -1; + } } - } /** Pass. **/ return 0; @@ -445,7 +482,7 @@ static int exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) default: mssError(1, "EXP", "%s (%d) is not a numeric type.", - ci_TypeToStr(numeric_expr->DataType), numeric_expr->DataType + objTypeToStr(numeric_expr->DataType), numeric_expr->DataType ); return -1; } @@ -456,6 +493,55 @@ static int exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) return 0; } +/*** Free the given tree's result string, if it has one. + *** + *** @param tree The affected tree. + ***/ +static void exp_fn_i_free_result_string(pExpression tree) + { + /** If no string is allocated, no work is needed. **/ + if (tree->Alloc == 0) return; + + /** Free the string, if it exists. **/ + if (tree->String != NULL) nmSysFree(tree->String); + + /** No string is allocated anymore. */ + tree->Alloc = 0; + } + +/*** Ensure that the allocated result string is long enough to store a given + *** amount of required data. This function promises that `tree->String` will + *** point to at least `required_space` bytes after it returns 0. + *** + *** @param tree The affected tree. + *** @param required_space The number of bytes required. + *** @returns 0 if successful, or + *** -1 if an error occurs. + ***/ +static int exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) + { + /** Free the previous string (if needed) so we can store a new one. **/ + exp_fn_i_free_result_string(tree); + + /** Decide how to allocate space. **/ + if (required_space <= 64) + { + /** We can use the preallocated buffer. **/ + tree->String = tree->Types.StringBuf; + tree->Alloc = 0; + } + else + { + /** We need to allocate new memory. **/ + char* result = check_ptr(nmSysMalloc(required_space * sizeof(char*))); + if (result == NULL) return -1; + tree->String = result; + tree->Alloc = 1; + } + + return 0; + } + /****** Evaluator functions follow for expEvalFunction ******/ @@ -1505,108 +1591,155 @@ int exp_fn_reverse(pExpression tree, pParamObjects objlist, pExpression i0, pExp /** Leading zero trim. */ int exp_fn_lztrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { - char* ptr; - - if (!i0 || i0->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; + /** Expect one nullable string parameter. **/ + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0) + { + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; + } + + /** Extract the arg string. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str->Flags & EXPR_F_NULL) + { + /** Propegate null values. **/ + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + return 0; + } + char* str = maybe_str->String; + + /*** We don't need to allocate new memory or copy anything because we + *** can simply point to the first character in the previous string + *** that isn't trimmed. + ***/ + + /** Iterate over all the characters that need to be removed. **/ + while (*str == '0' && (str[1] >= '0' && str[1] <= '9')) str++; + + /** Return the results using the tree. **/ + exp_fn_i_free_result_string(tree); tree->DataType = DATA_T_STRING; - return 0; - } - if (i0->DataType != DATA_T_STRING) - { - mssError(1,"EXP","lztrim() only works on STRING data types"); - return -1; - } - if (tree->Alloc && tree->String) - { - nmSysFree(tree->String); - } - tree->DataType = DATA_T_STRING; - ptr = i0->String; - while(*ptr == '0' && (ptr[1] >= '0' && ptr[1] <= '9')) ptr++; - tree->String = ptr; - tree->Alloc = 0; + tree->String = str; + return 0; } -int exp_fn_ltrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +/** Left trim spaces. **/ +int exp_fn_ltrim(pExpression tree) { - char* ptr; - - if (!i0 || i0->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; + /** Expect one nullable string parameter. **/ + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0) + { + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; + } + + /** Extract the arg string. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str->Flags & EXPR_F_NULL) + { + /** Propegate null values. **/ + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + return 0; + } + char* str = maybe_str->String; + + /*** We don't need to allocate new memory or copy anything because we + *** can simply point to the first character in the previous string + *** that isn't trimmed. + ***/ + + /** Iterate until we find the a charater that isn't a space. **/ + /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ + while (*str == ' ') str++; + + /** Return the results using the tree. **/ + exp_fn_i_free_result_string(tree); tree->DataType = DATA_T_STRING; - return 0; - } - if (i0->DataType != DATA_T_STRING) - { - mssError(1,"EXP","ltrim() only works on STRING data types"); - return -1; - } - if (tree->Alloc && tree->String) - { - nmSysFree(tree->String); - } - tree->DataType = DATA_T_STRING; - ptr = i0->String; - while(*ptr == ' ') ptr++; - tree->String = ptr; - tree->Alloc = 0; + tree->String = str; + return 0; } -int exp_fn_rtrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +/** Right trim spaces. **/ +int exp_fn_rtrim(pExpression tree) { - char* ptr; - int n; - - if (!i0 || i0->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; - tree->DataType = DATA_T_STRING; - return 0; - } - if (i0->DataType != DATA_T_STRING) - { - mssError(1,"EXP","rtrim() only works on STRING data types"); - return -1; - } - if (tree->Alloc && tree->String) - { - nmSysFree(tree->String); - } - tree->Alloc = 0; - tree->DataType = DATA_T_STRING; - ptr = i0->String + strlen(i0->String); - while(ptr > i0->String && ptr[-1] == ' ') ptr--; - if (ptr == i0->String + strlen(i0->String)) - { - /** optimization for strings are still the same **/ - tree->String = i0->String; - } - else - { - /** have to copy because we removed spaces **/ - n = ptr - i0->String; - if (n < 63) + /** Expect one nullable string parameter. **/ + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0) { - tree->String = tree->Types.StringBuf; - memcpy(tree->String, i0->String, n); - tree->String[n] = '\0'; - tree->Alloc = 0; + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; } - else + + /** Extract the arg string. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str->Flags & EXPR_F_NULL) { - tree->String = (char*)nmSysMalloc(n+1); - memcpy(tree->String, i0->String, n); - tree->String[n] = '\0'; - tree->Alloc = 1; + /** Propegate null values. **/ + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + return 0; } - } + char* str = maybe_str->String; + + /** Trim spaces from the end of the string. **/ + /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ + const int len = strlen(str); + int n = len; + while (n > 0 && str[n - 1] == ' ') n--; + + /** Optimization for strings that are still the same. **/ + if (n == len) + { + tree->String = str; + goto end; + } + + /** We need to copy to remove spaces. **/ + if (!check(exp_fn_i_alloc_result_string(tree, n + 1))) return -1; + memcpy(tree->String, str, n); + tree->String[n] = '\0'; + + end: + /** Return the results in the tree. **/ + tree->DataType = DATA_T_STRING; + tree->Alloc = 0; + + return 0; + } + + +/** Left and right trim spaces. **/ +int exp_fn_trim(pExpression tree) + { + /** Left trim the expression. **/ + exp_fn_ltrim(tree); + + /** Temporarily override the arg1 str pointer with the result from ltrim(). **/ + pExpression arg1 = tree->Children.Items[0]; + char* arg1_str = arg1->String; + arg1->String = tree->String; + tree->Alloc = 0; + + /** Right trim the expression, which will use the overriden string above. **/ + exp_fn_rtrim(tree); + + /** Restore the arg1 tree. **/ + arg1->String = arg1_str; + return 0; } @@ -3635,75 +3768,104 @@ int exp_fn_from_base64(pExpression tree, pParamObjects objlist, pExpression i0, return -1; } -static int exp_fn_i_do_math(pExpression tree, pParamObjects obj_list, const char* fn_name, double (*math)(), int arg_num) +static int exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) { - /** Verify function schema: expect arg_num numeric values. **/ - ArgExpect expects[arg_num]; - for (int i = 0; i < arg_num; i++) - expects[i] = (ArgExpect){(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}; - if (exp_fn_i_verify_schema(fn_name, expects, arg_num, tree, obj_list) != 0) - { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); - return -1; - } - - /** Null checks. **/ - for (int i = 0; i < arg_num; i++) - { - pExpression arg = tree->Children.Items[i]; - if (arg->Flags & EXPR_F_NULL) + /** Verify function schema: expect arg_num numeric values. **/ + ArgExpect expects[arg_num + 1]; + for (int i = 0; i < arg_num; i++) + expects[i] = (ArgExpect){(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}; + expects[arg_num] = EXP_ARG_END; + if (exp_fn_i_verify_schema(expects, tree) != 0) { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; + } + + /** Null checks. **/ + for (int i = 0; i < arg_num; i++) + { + pExpression arg = tree->Children.Items[i]; + if (arg->Flags & EXPR_F_NULL) + { + tree->DataType = DATA_T_DOUBLE; + tree->Flags |= EXPR_F_NULL; + return 0; + } } - } - - /** Maximum supported args. **/ - if (arg_num > 4) - { - mssErrorf(1, "EXP", "%s(...): exp_fn_i_do_math() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", fn_name, __FILE__, __LINE__); - return -1; - } - /** Get the numbers for the args. **/ - double n[4]; - for (int i = 0; i < arg_num; i++) - { - if (!check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i])))) + /** Maximum supported args. **/ + if (arg_num > 4) { - mssErrorf(0, "EXP", "%s(...): Failed to get arg%d.", fn_name, i); + mssErrorf(1, "EXP", "%s(...): exp_fn_i_do_math() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", tree->Name, __FILE__, __LINE__); return -1; } - } + + /** Get the numbers for the args. **/ + double n[4]; + for (int i = 0; i < arg_num; i++) + { + if (!check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i])))) + { + mssErrorf(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); + return -1; + } + } + + /** Return results. **/ + tree->DataType = DATA_T_DOUBLE; + tree->Types.Double = math(n[0], n[1], n[2], n[3]); /* Call function with all supported args. */ - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = math(n[0], n[1], n[2], n[3]); /* Call function with max supported args. */ return 0; } -int exp_fn_log_natural(pExpression tree, pParamObjects obj_list) +int exp_fn_power(pExpression tree) { - return exp_fn_i_do_math(tree, obj_list, "ln", log, 1); + return exp_fn_i_do_math(tree, pow, 2); } -int exp_fn_log10(pExpression tree, pParamObjects obj_list) +int exp_fn_ln(pExpression tree) { - return exp_fn_i_do_math(tree, obj_list, "log10", log10, 1); + return exp_fn_i_do_math(tree, log, 1); } - -/** This is why we need lambdas in C. **/ -double exp_fn_i_log_base_n(double x, double base) +int exp_fn_log10(pExpression tree) { - return log(x) / log(base); + return exp_fn_i_do_math(tree, log10, 1); } -int exp_fn_log_base_n(pExpression tree, pParamObjects obj_list) - { - return exp_fn_i_do_math(tree, obj_list, "logn", exp_fn_i_log_base_n, 2); - } -int exp_fn_power(pExpression tree, pParamObjects obj_list) +int exp_fn_log(pExpression tree) { - return exp_fn_i_do_math(tree, obj_list, "power", pow, 2); + /** Verify function schema: A number and an optional base. **/ + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}, + {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_OPTIONAL}, + EXP_ARG_END, + }, tree) != 0) + { + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; + } + + /** Extract args. **/ + double number, base; + if (!check(exp_fn_i_get_number(check_ptr(tree->Children.Items[0]), &number))) + { + mssErrorf(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); + return -1; + } + if (tree->Children.nItems > 1) + { + if (!check(exp_fn_i_get_number(check_ptr(tree->Children.Items[1]), &base))) + { + mssErrorf(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); + return -1; + } + } + else base = M_E; + + /** Return the results in the tree. **/ + tree->DataType = DATA_T_DOUBLE; + tree->Types.Double = log(number) / log(base); + + return 0; } @@ -4371,10 +4533,10 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) const char fn_name[] = "metaphone"; /** Verify function schema. **/ - if (exp_fn_i_verify_schema(fn_name, - (ArgExpect[]){{(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}}, 1, - tree, obj_list - ) != 0) + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0) { mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); return -1; @@ -4404,27 +4566,11 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) /** Compute DoubleMetaphone. **/ meta_double_metaphone(str, &primary, &secondary); - /** Find memory to store the result. **/ - store_data:; - const size_t result_length = strlen(primary) + 1u + strlen(secondary) + 1u; - if (tree->Alloc == 1) nmSysFree(tree->String); - if (result_length < 64) - { - /** We can use the preallocated buffer. **/ - tree->String = tree->Types.StringBuf; - tree->Alloc = 0; - } - else - { - char* result = check_ptr(nmSysMalloc(result_length * sizeof(char*))); - if (result == NULL) return -1; - tree->String = result; - } - - /** Write the result into the selected memory. **/ + /** Store the results. **/ + store_data:; + const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; + if (!check(exp_fn_i_alloc_result_string(tree, length))) return -1; sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); - - /** Return the result. **/ tree->DataType = DATA_T_STRING; return 0; @@ -4443,13 +4589,11 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) { /** Verify function schema. **/ - if (exp_fn_i_verify_schema(fn_name, - (ArgExpect[]){ + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, - {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS} - }, 2, - tree, obj_list - ) != 0) + EXP_ARG_END, + }, tree) != 0) { mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); return -1; @@ -4530,13 +4674,11 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) const char fn_name[] = "levenshtein"; /** Verify function schema. **/ - if (exp_fn_i_verify_schema(fn_name, - (ArgExpect[]){ + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, - {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS} - }, 2, - tree, obj_list - ) != 0) + EXP_ARG_END, + }, tree) != 0) { mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); return -1; @@ -4714,6 +4856,7 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "ltrim", (char*)exp_fn_ltrim); xhAdd(&EXP.Functions, "lztrim", (char*)exp_fn_lztrim); xhAdd(&EXP.Functions, "rtrim", (char*)exp_fn_rtrim); + xhAdd(&EXP.Functions, "trim", (char*)exp_fn_trim); xhAdd(&EXP.Functions, "substring", (char*)exp_fn_substring); xhAdd(&EXP.Functions, "right", (char*)exp_fn_right); xhAdd(&EXP.Functions, "ralign", (char*)exp_fn_ralign); @@ -4748,8 +4891,8 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "hash", (char*)exp_fn_hash); xhAdd(&EXP.Functions, "hmac", (char*)exp_fn_hmac); xhAdd(&EXP.Functions, "log10", (char*)exp_fn_log10); - xhAdd(&EXP.Functions, "ln", (char*)exp_fn_log_natural); - xhAdd(&EXP.Functions, "logn", (char*)exp_fn_log_base_n); + xhAdd(&EXP.Functions, "ln", (char*)exp_fn_ln); + xhAdd(&EXP.Functions, "log", (char*)exp_fn_log); xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); xhAdd(&EXP.Functions, "metaphone", (char*)exp_fn_metaphone); diff --git a/centrallix/multiquery/multiquery.c b/centrallix/multiquery/multiquery.c index 5ba4f10c0..438c011fa 100644 --- a/centrallix/multiquery/multiquery.c +++ b/centrallix/multiquery/multiquery.c @@ -1046,11 +1046,14 @@ mq_internal_ParseSelectItem(pQueryStructure item_qs, pLxSession lxs) n_tok = 0; while(1) { + /** Get the next token. **/ t = mlxNextToken(lxs); if (t == MLX_TOK_ERROR || t == MLX_TOK_EOF) break; n_tok++; - if ((t == MLX_TOK_RESERVEDWD || t == MLX_TOK_COMMA || t == MLX_TOK_SEMICOLON) && parenlevel <= 0) + + /** Special handling for certain token types. **/ + if ((t == MLX_TOK_COMMA || t == MLX_TOK_SEMICOLON) && parenlevel <= 0) break; if (t == MLX_TOK_OPENPAREN) parenlevel++; @@ -1061,9 +1064,19 @@ mq_internal_ParseSelectItem(pQueryStructure item_qs, pLxSession lxs) break; } - /** Copy it to the raw data **/ + /** Get the token string. **/ ptr = mlxStringVal(lxs,NULL); if (!ptr) break; + + /** Skip all reserved words except log(). **/ + if (t == MLX_TOK_RESERVEDWD && parenlevel <= 0) + { + /** Treat "log" as a keyword to allow the log function to be handled properly. **/ + if (strcmp(ptr, "log") == 0) t = MLX_TOK_KEYWORD; + else break; + }; + + /** Copy the token string into item_qs->RawData. **/ if (t == MLX_TOK_STRING) xsConcatQPrintf(&item_qs->RawData, "%STR&DQUOT", ptr); else diff --git a/centrallix/tests/test_expfn_log_00.cmp b/centrallix/tests/test_expfn_log_00.cmp index c072b68f2..130056813 100644 --- a/centrallix/tests/test_expfn_log_00.cmp +++ b/centrallix/tests/test_expfn_log_00.cmp @@ -16,19 +16,28 @@ Attribute [log10(0.01)]: double -2.0 Attribute [log10(1.234)]: double 0.09131516 Attribute [log10(1e-10)]: double -10.0 Attribute [log10(1e10)]: double 10.0 -Attribute [logn(8, 2)]: double 3.0 -Attribute [logn(1000, 10)]: double 3.0 -Attribute [logn(10, 0)]: double -0.0 -Attribute [logn(10, 1)]: double inf.0 -Attribute [logn(8, -2)]: double nan.0 -Attribute [logn(0, 2)]: double -inf.0 -Attribute [logn(-8, 2)]: double nan.0 -Attribute [logn(1, 2)]: double 0.0 -Attribute [logn(1e10, 10)]: double 10.0 -Attribute [logn(8, 0.5)]: double -3.0 +Attribute [log(8, 2)]: double 3.0 +Attribute [log(1000, 10)]: double 3.0 +Attribute [log(10, 0)]: double -0.0 +Attribute [log(10, 1)]: double inf.0 +Attribute [log(8, -2)]: double nan.0 +Attribute [log(0, 2)]: double -inf.0 +Attribute [log(-8, 2)]: double nan.0 +Attribute [log(1, 2)]: double 0.0 +Attribute [log(1e10, 10)]: double 10.0 +Attribute [log(8, 0.5)]: double -3.0 +Attribute [log(1)]: integer 1 +Attribute [log(e)]: integer 1 +Attribute [log(0)]: integer 1 +Attribute [log(-1)]: integer 1 +Attribute [log(10)]: integer 1 +Attribute [log(1.5)]: integer 1 +Attribute [log(1e-10)]: integer 1 +Attribute [log(1e10)+]: integer 1 +Attribute [log(1e10)-]: integer 1 Attribute [ln(2.718281828)]: double 1.0 Attribute [log10(3.14159)]: double 0.49714951 -Attribute [logn(10, 1.1)]: double 0.04139269 -Attribute [logn(1.1, 10)]: double 24.15885793 -Attribute [logn(10, 0.001)]: double -3.0 -Attribute [logn(0.1, 1000)]: double -3.0 +Attribute [log(10, 1.1)]: double 0.04139269 +Attribute [log(1.1, 10)]: double 24.15885793 +Attribute [log(10, 0.001)]: double -3.0 +Attribute [log(0.1, 1000)]: double -3.0 diff --git a/centrallix/tests/test_expfn_log_00.to b/centrallix/tests/test_expfn_log_00.to index e454e4003..c73140236 100644 --- a/centrallix/tests/test_expfn_log_00.to +++ b/centrallix/tests/test_expfn_log_00.to @@ -12,33 +12,44 @@ query select 'ln(1e10)+' = ln(10000000000.0) > 23.0 -- Expect true (value query select 'ln(1e10)-' = ln(10000000000.0) < 23.1 -- Expect true (value is ~23.02585). # Log base 10: log10(x) -query select 'log10(1)' = log10(1) -- Expect 0. -query select 'log10(10)' = log10(10) -- Expect 1. -query select 'log10(0)' = log10(0) -- Expect -inf. -query select 'log10(-10)' = log10(-10) -- Expect NaN. -query select 'log10(100)' = log10(100) -- Expect 2. -query select 'log10(0.01)' = log10(0.01) -- Expect -2. -query select 'log10(1.234)' = round(log10(1.234), 8) -- Expect ~0.091315. -query select 'log10(1e-10)' = log10(0.0000000001) -- Expect ~-10. -query select 'log10(1e10)' = log10(10000000000.0) -- Expect ~10. +query select 'log10(1)' = log10(1) -- Expect 0. +query select 'log10(10)' = log10(10) -- Expect 1. +query select 'log10(0)' = log10(0) -- Expect -inf. +query select 'log10(-10)' = log10(-10) -- Expect NaN. +query select 'log10(100)' = log10(100) -- Expect 2. +query select 'log10(0.01)' = log10(0.01) -- Expect -2. +query select 'log10(1.234)' = round(log10(1.234), 8) -- Expect ~0.091315. +query select 'log10(1e-10)' = log10(0.0000000001) -- Expect ~-10. +query select 'log10(1e10)' = log10(10000000000.0) -- Expect ~10. -# General base n of x: logn(x, n) +# General base n of x: log(x, n) # Edge cases: base <= 0 or base == 1 (invalid), x <= 0 (invalid) -query select 'logn(8, 2)' = logn(8, 2) -- Expect 3. -query select 'logn(1000, 10)' = logn(1000, 10) -- Expect 3. -query select 'logn(10, 0)' = logn(10, 0) -- Expect -0.0 (base 0 is undefined). -query select 'logn(10, 1)' = logn(10, 1) -- Expect inf (base 1 is undefined). -query select 'logn(8, -2)' = logn(8, -2) -- Expect NaN (negative base). -query select 'logn(0, 2)' = logn(0, 2) -- Expect -inf (x=0). -query select 'logn(-8, 2)' = logn(-8, 2) -- Expect NaN or error (x negative). -query select 'logn(1, 2)' = logn(1, 2) -- Expect 0. -query select 'logn(1e10, 10)' = logn(10000000000.0, 10) -- Expect 10. -query select 'logn(8, 0.5)' = logn(8, 0.5) -- Expect negative value. +query select 'log(8, 2)' = log(8, 2) -- Expect 3. +query select 'log(1000, 10)' = log(1000, 10) -- Expect 3. +query select 'log(10, 0)' = log(10, 0) -- Expect -0.0 (base 0 is undefined). +query select 'log(10, 1)' = log(10, 1) -- Expect inf (base 1 is undefined). +query select 'log(8, -2)' = log(8, -2) -- Expect NaN (negative base). +query select 'log(0, 2)' = log(0, 2) -- Expect -inf (x=0). +query select 'log(-8, 2)' = log(-8, 2) -- Expect NaN or error (x negative). +query select 'log(1, 2)' = log(1, 2) -- Expect 0. +query select 'log(1e10, 10)' = log(10000000000.0, 10) -- Expect 10. +query select 'log(8, 0.5)' = log(8, 0.5) -- Expect negative value. + +# log(x) = ln(x) +query select 'log(1)' = (log(1) == ln(1)) +query select 'log(e)' = (log(2.71828182845) == ln(2.71828182845)) +query select 'log(0)' = (log(0) == ln(0)) +query select 'log(-1)' = (log(-1) == ln(-1)) +query select 'log(10)' = (log(10) == ln(10)) +query select 'log(1.5)' = (log(1.5) == ln(1.5)) +query select 'log(1e-10)' = (log(0.0000000001) == ln(0.0000000001)) +query select 'log(1e10)+' = (log(10000000000.0) == ln(10000000000.0)) +query select 'log(1e10)-' = (log(10000000000.0) == ln(10000000000.0)) -- Additional double/int mixed cases query select 'ln(2.718281828)' = round(ln(2.718281828), 8) -- Expect ~1 (close to e). query select 'log10(3.14159)' = round(log10(3.14159), 8) -- Expect ~0.49715. -query select 'logn(10, 1.1)' = round(logn(1.1, 10), 8) -- Expect 0.04139289. -query select 'logn(1.1, 10)' = round(logn(10, 1.1), 8) -- Expect 24.15885793. -query select 'logn(10, 0.001)' = round(logn(0.001, 10), 8) -- Expect ~-0.33333333... -query select 'logn(0.1, 1000)' = round(logn(1000, 0.1), 8) -- Expect ~-0.33333333... +query select 'log(10, 1.1)' = round(log(1.1, 10), 8) -- Expect 0.04139289. +query select 'log(1.1, 10)' = round(log(10, 1.1), 8) -- Expect 24.15885793. +query select 'log(10, 0.001)' = round(log(0.001, 10), 8) -- Expect ~-0.33333333... +query select 'log(0.1, 1000)' = round(log(1000, 0.1), 8) -- Expect ~-0.33333333... diff --git a/centrallix/tests/test_expfn_trim_00.cmp b/centrallix/tests/test_expfn_trim_00.cmp new file mode 100644 index 000000000..5515d9aa1 --- /dev/null +++ b/centrallix/tests/test_expfn_trim_00.cmp @@ -0,0 +1,10 @@ +Attribute [trim("White space on the left o]: string "No white space on the left or right side." +Attribute [trim(" White space on the l]: string "White space on the left side." +Attribute [trim("White space on the right ]: string "White space on the right side." +Attribute [trim(" White space on the r]: string "White space on the right and the left side." +Attribute [trim("With tab character ")]: string "With tab character " +Attribute [trim("With newline character +")]: string "With newline character +" +Attribute [trim("")]: string "" +Attribute [trim(null)]: string NULL diff --git a/centrallix/tests/test_expfn_trim_00.to b/centrallix/tests/test_expfn_trim_00.to new file mode 100644 index 000000000..746de6ebc --- /dev/null +++ b/centrallix/tests/test_expfn_trim_00.to @@ -0,0 +1,19 @@ +##NAME trim() function + +query select 'trim("White space on the left or right side.")' = trim("No white space on the left or right side.") + +query select 'trim(" White space on the left side.")' = trim(" White space on the left side.") + +query select 'trim("White space on the right side. ")' = trim("White space on the right side. ") + +query select 'trim(" White space on the right and the left side. ")' = trim(" White space on the right and the left side. ") + +query select 'trim("With tab character\t")' = trim("With tab character\t") + +query select 'trim("With newline character\n")' = trim("With newline character\n") + +# query select 'trim("\r With carriage return character.")' = trim("\r With carriage return character") + +query select 'trim("")' = trim("") + +query select 'trim(null)' = trim(null) From 8c86b5feb473b0243ca8f8a44b7e453079701131 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 11 Dec 2025 17:15:38 -0700 Subject: [PATCH 034/361] Organize docs. Move docs for newmalloc, xarray, xhash, xstring, mtsession, and mtask out of OSDriver_Authoring.md and into their own files. Add the imported date to OSDriver_Authoring.md. --- centrallix-sysdoc/Libraries/mtask.md | 95 +++ centrallix-sysdoc/Libraries/mtsession.md | 141 +++++ centrallix-sysdoc/Libraries/newmalloc.md | 168 ++++++ centrallix-sysdoc/Libraries/xarray.md | 219 +++++++ centrallix-sysdoc/Libraries/xhash.md | 126 ++++ centrallix-sysdoc/Libraries/xstring.md | 298 +++++++++ centrallix-sysdoc/OSDriver_Authoring.md | 733 +---------------------- 7 files changed, 1057 insertions(+), 723 deletions(-) create mode 100644 centrallix-sysdoc/Libraries/mtask.md create mode 100644 centrallix-sysdoc/Libraries/mtsession.md create mode 100644 centrallix-sysdoc/Libraries/newmalloc.md create mode 100644 centrallix-sysdoc/Libraries/xarray.md create mode 100644 centrallix-sysdoc/Libraries/xhash.md create mode 100644 centrallix-sysdoc/Libraries/xstring.md diff --git a/centrallix-sysdoc/Libraries/mtask.md b/centrallix-sysdoc/Libraries/mtask.md new file mode 100644 index 000000000..e3395ce0e --- /dev/null +++ b/centrallix-sysdoc/Libraries/mtask.md @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Handling Network Connection + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [Handling Network Connection](#the-mtsession-library) + - [Introduction](#introduction) + - [netConnectTCP()](#netconnecttcp) + - [netCloseTCP()](#netclosetcp) + - [fdWrite()](#fdwrite) + - [fdRead()](#fdread) + + +## Introduction +The `MTASK` module provides simple and easy TCP/IP connectivity. It includes many functions, only a few of which are documented below: + +- ⚠️ **Warning**: This documentation is incomplete, as many relevant functions are not explained here. You can help by expanding it. + + +## netConnectTCP() +```c +pFile netConnectTCP(char* host_name, char* service_name, int flags); +``` +This function creates a client socket and connects it to a server on a given TCP service/port and host name. It takes the following three parameters: +- `host_name`: The host name or ascii string for the host's ip address. +- `service_name`: The name of the service (from `/etc/services`) or its numeric representation as a string. +- `flags`: Normally left 0. + +- 📖 **Note**: The `NET_U_NOBLOCK` flag causes the function to return immediately even if the connection is still being established. Further reads and writes will block until the connection either establishes or fails. + +This function returns the connection file descriptor if successful, or `NULL` if an error occurs. + + +## netCloseTCP() +```c +int netCloseTCP(pFile net_filedesc, int linger_msec, int flags); +``` +This function closes a network connection (either a TCP listening, server, or client socket). It will also optionally waits up to `linger_msec` milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If `linger_msec` is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately prior to the close, offering to linger for a few more seconds (perhaps 5 or 10 by specifying 5000 or 10000 msec) can be a good idea. + + +## fdWrite() +```c +int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags); +``` +This function writes data to an open file descriptor, from a given `buffer` and `length` of data to write. It also takes an optional seek `offset` and and `flags`, which can be zero or more of: +- `FD_U_NOBLOCK` - If the write can't be performed immediately, don't perform it at all. +- `FD_U_SEEK` - The `offset` value is valid. Seek to it before writing. Not allowed for network connections. +- `FD_U_PACKET` - *ALL* of the data specified by `length` in `buffer` must be written. Normal `write()` semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. + + +## fdRead() +```c +int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags); +``` +This function works the same as [`fdWrite()`](#fdwrite) except that it reads data instead of writing it. It takes the same flags as above, except that `FD_U_PACKET` now requires that all of `maxlen` bytes must be read before returning. This is good for reading a packet of a known length that might be broken up into fragments by the network (TCP/IP has a maximum frame transmission size of about 1450 bytes). diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md new file mode 100644 index 000000000..a6c946e4d --- /dev/null +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The MTSession Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The MTSession Library](#the-mtsession-library) + - [Introduction](#introduction) + - [mssUserName()](#mssusername) + - [mssPassword()](#msspassword) + - [mssSetParam()](#msssetparam) + - [mssGetParam()](#mssgetparam) + - [mssError()](#msserror) + - [mssErrorErrno()](#msserrorerrno) + + +## Introduction +The mtsession (MSS) module is used for session authentication, error reporting, and for storing session-wide variables such as the current date format, username, and password (used when issuing a login request to a remote server). Care should be taken in the use of Centrallix that its coredump files are NOT in a world-readable location, as the password will be visible in the coredump file (or just ulimit the core file size to 0). + + +- ⚠️ **Warning**: This documentation is incomplete, as several relevant functions are not explained here. You can help by expanding it. + + +## mssInitialize() +```c +int mssInitialize(char* authmethod, char* authfile, char* logmethod, int logall, char* log_progname); +``` +This function initializes the session manager and sets global variables used in this module. It returns 0 if successful and -1 if an error occurs. + + +## mssUserName() +```c +char* mssUserName(); +``` +This function returns the current user name, or `NULL` an error occurs. + + +## mssPassword() +```c +char* mssPassword(); +``` +This function returns the current user's password that they used to log into Centrallix, or `NULL` an error occurs. + + +## mssSetParam() +```c +int mssSetParam(char* paramname, char* param); +``` +This function sets the session parameter of the provided name (`paramname`) to the provided value (`param`). The parameter MUST be a string value. This function returns 0 if successful, or -1 an error occurs. + + +## mssGetParam() +```c +char* mssGetParam(char* paramname); +``` +Returns the value of a session parameter of the provided name (`paramname`), or `NULL` if an error occurs. Common session parameters include: +- `dfmt`: The current date format. +- `mfmt`: The current money format. +- `textsize`: The current max text size from a read of an object's content via `objGetAttrValue(obj, "objcontent", POD(&str))` + + +## mssError() +```c +int mssError(int clr, char* module, char* message, ...); +``` +Formats and caches an error message for return to the user. This function returns 0 if successful, or -1 if an error occurred. + +| Parameter | Type | Description +| --------- | ------------- | ------------ +| crl | int | If set to 1, all previous error messages are cleared. Set this when the error is initially discovered and no other module is likely to have made a relevant `mssError()` call for the current error. +| module | char* | A two-to-five letter abbreviation of the module reporting the error. This is typically the module or driver's abbreviation prefix in full uppercase letters (although that is not required). This is intended to help the developer find the source of the error faster. +| message | char* | A string error message, accepting format specifiers like `%d` and `%s` which are supplied by the argument list, similar to `printf()`. +| ... | ... | Parameters for the formatting. + +Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch the errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. + +The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ as an expression being compiled? etc.). However, it is far easier to give too little information that too much, so it can often be best to air on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. + +- 📖 **Note**: The `mssError()` routines do not cause the calling function to return or exit. The function must still clean up after itself and return an appropriate value (such as `-1` or `NULL`) to indicate failure. + +- ⚠️ **Warning**: Even if `-1` is returned, the error message may still be sent to the user in some scenarios. This is not guaranteed, though. + +- ⚠️ **Warning**: `%d` and `%s` are the ONLY supported format specifier for this function. **DO NOT** use any other format specifiers like `%lf`, `%u`, `%lu`, `%c` etc. **DO NOT** attempt to include `%%` for a percent symbol in your error message, as misplaced percent symbols often break this function. If you wish to use these features of printf, it is recommended to print the error message to a buffer and pass that buffer to `mssError()`, as follows: + ```c + char err_buf[256]; + snprintf(err_buf, sizeof(err_buf), + "Incorrect values detected: %u, %g (%lf), '%c'", + unsigned_int_value, double_value, char_value + ); + if (mssError(1, "EXMPL", "%s", err_buf) != 0) + { + fprintf(stderr, "ERROR! %s\n", err_buf); + } + return -1; + ``` + + + +## mssErrorErrno() +```c +int mssErrorErrno(int clr, char* module, char* message, ...); +``` +This function works the same way as [`mssError`](#mssError), except checks the current value of `errno` and includes a description of any error stored there. This is useful if a system call or other library function is responsible for this error. diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md new file mode 100644 index 000000000..c77d90f12 --- /dev/null +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Memory Management in Centrallix + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [Memory Management in Centrallix](#objectsystem-driver-interface) + - [Introduction](#introduction) + - [nmMalloc()](#nmmalloc) + - [nmFree()](#nmfree) + - [nmStats()](#nmstats) + - [nmRegister()](#nmregister) + - [nmDebug()](#nmdebug) + - [nmDeltas()](#nmdeltas) + - [nmSysMalloc()](#nmsysmalloc) + - [nmSysRealloc()](#nmsysrealloc) + - [nmSysStrdup()](#nmsysstrdup) + - [nmSysFree()](#nmsysfree) + + +## Introduction +Centrallix has its own memory management wrapper that caches deallocated blocks of memory by size for faster reuse. This wrapper also detects double-freeing of blocks (sometimes), making debugging of memory problems just a little bit easier. + +In addition, the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. `malloc()`, and on how many blocks of each size/type are `malloc()`ed and cached. This information can be helpful for tracking down memory leaks. Empirical testing has shown an increase of performance of around 50% or more in programs that use newmalloc. + +One caveat is that this memory manager does not provide `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or [`nmSysMalloc()`](#nmsysmalloc), [`nmSysFree()`](#nmsysfree), and [`nmSysRealloc()`](#nmsysrealloc) should be used for blocks of memory that might vary in size. + +The newmalloc module can be accessed by adding `#include "cxlib/newmalloc.h"` to the include section of a .c file in centrallix, or `#include "newmalloc.h"` in centrallix-lib. + +- 📖 **Note**: This memory manager is usually the wrong choice for blocks of memory of arbitrary, inconsistent sizes. It is intended for allocating structures quickly that are of a specific size. For example, allocated space for a struct that is always the same size. + +- 🥱 **tl;dr**: Use `nmMalloc()` for structs, not for strings. + +- ⚠️ **Warning**: Do not mix and match, even though calling `free()` on a block obtained from `nmMalloc()` or calling `nmFree()` on a block obtained from `malloc()` might not crash the program immediately. However, it may result in either inefficient use of the memory manager, or a significant memory leak, respectively. These practices will also lead to incorrect results from the statistics and block count mechanisms. + +The newmalloc module provides the following functions: + + +## nmMalloc() +```c +void* nmMalloc(int size); +``` +This function allocates a block of the given `size`. It returns `NULL` if the memory could not be allocated. + + +## nmFree() +```c +void nmFree(void* ptr, int size); +``` +This function frees the block of memory. + +- ⚠️ **Warning**: The caller **must know the size of the block.** Getting this wrong is very bad!! For structures, this is trivial, simply use `sizeof()`, exactly the same as with `nmMalloc()`. + + +## nmStats() +```c +void nmStats(void); +``` +Prints statistics about the memory manager, for debugging and optimizing. + +For example: +``` +NewMalloc subsystem statistics: + nmMalloc: 20244967 calls, 19908369 hits (98.337%) + nmFree: 20233966 calls + bigblks: 49370 too big, 32768 largest size +``` + +- ⚠️ **Warning**: Centrallix-lib must be built with the configure option `--enable-debugging` for this function to work. Otherwise, all the stats will be zeros. + + +## nmRegister() +```c +void nmRegister(int size, char* name); +``` +Registers an inteligent name for block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production usecases, but using it can make tracking down memory leaks easier. + +This function is usually called in a module's `Initialize()` function on each of the structures the module uses internally. + + +## nmDebug() +```c +void nmDebug(void); +``` +Prints a listing of block allocation counts, giving (by size): +- The number of blocks allocated but not yet freed. +- The number of blocks in the cache. +- The total allocations for this block size. +- A list of names (from [`nmRegister()`](#nmregister)) for that block size. + + +## nmDeltas() +```c +void nmDeltas(void); +``` +Prints a listing of all blocks whose allocation count has changed, and by how much, since the last `nmDeltas()` call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. + + +## nmSysMalloc() +```c +void* nmSysMalloc(int size); +``` +Allocates memory without using the block-caching algorithm. This is roughly equivalent to `malloc()`, but pointers returned by malloc and this function are not compatible - i.e., you cannot `free()` something that was [`nmSysMalloc()`](#nmsysmalloc)'ed, nor can you [`nmSysFree()`](#nmsysfree) something that was `malloc()`'ed. + +- 📖 **Note**: This function is much better to use on variable-sized blocks of memory. `nmMalloc()` is better for fixed-size blocks, such as for structs. + + +## nmSysRealloc() +```c +void* nmSysRealloc(void* ptr, int newsize); +``` +Changes the size of an allocated block of memory that was obtained from [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). The new pointer may be different if the block needs to be moved. This is the rough equivalent of `realloc()`. + +- 📖 **Note**: If you are `realloc()`'ing a block of memory and need to store pointers to data somewhere inside the block, it is often better to store an offset rather than a full pointer. This is because a full pointer becomes invalid if a [`nmSysRealloc()`](#nmsysrealloc) causes the block to move. + + +## nmSysStrdup() +```c +char* nmSysStrdup(const char* str); +``` +Allocates memory using the [`nmSysMalloc()`](#nmsysmalloc) function and copies the string `str` into this memory. It is a rough equivalent of `strdup()`. The resulting pointer can be free'd using [`nmSysFree()`](#nmsysfree). + + +## nmSysFree() +```c +void nmSysFree(void* ptr); +``` +Frees a block of memory allocated by [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md new file mode 100644 index 000000000..48004cc1d --- /dev/null +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The XArray Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The XArray Library](#the-xarray-library) + - [Introduction](#introduction) + - [xaNew()](#xanew) + - [xaFree()](#xafree) + - [xaInit()](#xainit) + - [xaDeInit()](#xadeinit) + - [xaAddItem()](#xaadditem) + - [xaAddItemSorted()](#xaadditemsorted) + - [xaAddItemSortedInt32()](#xaadditemsortedint32) + - [xaGetItem()](#xagetitem) + - [xaFindItem()](#xafinditem) + - [xaFindItemR()](#xafinditemr) + - [xaRemoveItem()](#xaremoveitem) + - [xaClear()](#xaclear) + - [xaClearR()](#xaclearr) + - [xaCount()](#xacount) + - [xaInsertBefore()](#xainsertbefore) + - [xaInsertAfter()](#xainsertafter) + + +## Introduction +The xarray (xa) module is intended to manage sized growable arrays, similar to a light-weight arraylist implementation. It includes the `XArray`, which has the following fields: +- `nItems : int`: The number of items in the array. +- `nAlloc : int`: Internal variable to store the size of the allocated memory. +- `Items : void**`: The allocated array of items. + +- 📖 **Note**: Some code occasionally sets `nAlloc` to 0 after an XArray struct has been deinitialized to indicate that the relevant data is no longer allocated. Other than this, it is only used internally by the library. + +- ⚠️ **Warning**: Do not mix calls to [`xaNew()`](#xanew)/[`xaFree()`](#xafree) with calls to [`xaInit()`](#xainit)/[`xaDeInit()`](#xadeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. + + +## xaNew() +```c +pXArray xaNew(int init_size); +``` +Allocates a new `XArray` struct on the heap (using [`nmMalloc()`](#nmmalloc) for caching) and returns a pointer to it, or returns `NULL` if an error occurs. + + +## xaFree() +```c +int xaFree(pXArray this); +``` +Frees a `pXArray` allocated using [`xaNew`](#xanew), returning 0 if successful or -1 if an error occurs. + + +## xaInit() +```c +int xaInit(pXArray this, int init_size); +``` +This function initializes an allocated (but uninitialized) xarray. It makes room for `init_size` items initially, but this is only an optimization. A typical value for `init_size` is 16. Remember to [`xaDeInit`](#xadeinit) this xarray, do **not** [`xaFree`](#xafree) it. + +This function returns 0 on success, or -1 if an error occurs. + + +## xaDeInit() +```c +int xaDeInit(pXArray this); +``` +This function de-initializes an xarray, but does not free the XArray structure itself. This is useful if the structure is a local variable allocated using [`xaInit()`](#xainit). + +This function returns 0 on success, or -1 if an error occurs. + +For example: +```c +XArray arr; +if (xaInit(&arr, 16) != 0) goto handle_error; + +/** Use the xarray. **/ + +if (arr.nAlloc != 0 && xaDeInit(&arr) != 0) goto handle_error; +arr.nAlloc = 0; +``` + + +## xaAddItem() +```c +int xaAddItem(pXArray this, void* item); +``` +This function adds an item to the end of the xarray. The item is assumed to be a `void*`, but this function will _not_ follow pointeres stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). + +This function returns 0 on success, or -1 if an error occurs. + + +## xaAddItemSorted() +```c +int xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen); +``` +This function adds an item to a sorted xarray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers are stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). + + +## xaAddItemSortedInt32() +```c +int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset) +``` + + + +## xaGetItem() +```c +void* xaGetItem(pXArray this, int index) +``` +This function returns an item given a specific index into the xarray, or `NULL` if the index is out of bounds. If the bounds check needs to be omitted for performance and the caller can otherwise verify that no out of bounds read is possible (e.g. because they are iterating from 0 to `xarray->nItems`), the caller should access `xarray->Items` directly. Either way, the result may need to be typecasted or stored in a variable of a specific type for it to be useable, and error checking for `NULL` values should be used. + + +## xaFindItem() +```c +int xaFindItem(pXArray this, void* item); +``` +This function returns array index for the provided item in the array, or -1 if the item could not be found. Requires an exact match, so two `void*` pointing to different memory with identical contents are not considered equal by this function. If the data is actually another datatype typecasted as a `void*`, all 8 bytes must be identical for a match. + +For example: +```c +void* data = &some_data; + +XArray xa; +xaInit(&xa, 16); + +... + +xaAddItem(&xa, data); + +... + +int item_id = xaFindItem(&xa, data); +assert(data == xa.Items[item_id]); +``` + + +## xaFindItemR() +```c +int xaFindItemR(pXArray this, void* item); +``` +This function works the same as [`xaFindItem()`](#xafinditem), however it iterates in reverse, giving a slight performance boost, especially for finding items near the end of the array. + + +## xaRemoveItem() +```c +int xaRemoveItem(pXArray this, int index) +``` +This function removes an item from the xarray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. + + +## xaClear() +```c +int xaClear(pXArray this, int (*free_fn)(), void* free_arg); +``` +This function removes all elements from the xarray, leaving it empty. `free_fn()` is invoked on each element with a `void*` to the element to be freed as the first argument and `free_arg` as the second argument (the return value of `free_fn()` is always ignored). This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. + + +## xaClearR() +```c +int xaClearR(pXArray this, int (*free_fn)(), void* free_arg); +``` +This function works the same as [`xaClear()`](#xaclear), except that it is slightly faster because the free function is evaluated on items in reverse order. + + +## xaCount() +```c +int xaCount(pXArray this); +``` +This function returns the number of items in the xarray, or -1 on error. It is equivalent to accessing `xarray->nItems` (although the latter expression will not return an error). + + +## xaInsertBefore() +```c +int xaInsertBefore(pXArray this, int index, void* item) +``` +This function inserts an item before the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. + + +## xaInsertAfter() +```c +int xaInsertAfter(pXArray this, int index, void* item) +``` +This function inserts an item after the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. diff --git a/centrallix-sysdoc/Libraries/xhash.md b/centrallix-sysdoc/Libraries/xhash.md new file mode 100644 index 000000000..5d4ca8028 --- /dev/null +++ b/centrallix-sysdoc/Libraries/xhash.md @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The XHash Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The XHash Library](#the-xhash-library) + - [Introduction](#introduction) + - [xhInitialize()](#xhinitialize) + - [xhInit()](#xhinit) + - [xhDeInit()](#xhdeinit) + - [xhAdd()](#xhadd) + - [xhRemove()](#xhremove) + - [xhLookup()](#xhlookup) + - [xhClear()](#xhclear) + - [xhForEach()](#xhforeach) + - [xhClearKeySafe()](#xhclearkeysafe) + + +## Introduction +The xhash (xh) module provides an extensible hash table interface. The hash table is a table of linked lists of items, so collisions and overflows are handled by this data structure (although excessive collisions still cause a performance loss). This implementation also supports variable-length keys for more flexible usecases. + +- ⚠️ **Warning**: All `xhXYZ()` function calls assume that the `pXHashTable this` arg points to a valid hashtable struct. All non-init functions assume that this struct has been validly initialized and has not yet been freed. If these conditions are not met, the resulting behavior is undefined. + + +## xhInitialize() +```c +int xhInitialize(); +``` +Initialize the random number table for hash computation, returning 0 on success or -1 if an error occurs. Normally, you can assume someone else has already called this during program startup. + + +## xhInit() +```c +int xhInit(pXHashTable this, int rows, int keylen); +``` +This function initializes a hash table, setting the number of rows and the key length. Specify a `keylen` of 0 for for variable length keys (aka. null-terminated strings). The `rows` should be an odd number, preferably prime (although that isn't required). `rows` **SHOULD NOT** be a power of 2. Providing this value allows the caller to optimize it based on how much data they expect to be stored in the hash table. If this value is set to 1, the hash search degenerates to a linear array search with extra overhead. Thus, the value should be large enough to comfortably accommodate the elements with minimal collisions. Typical values include 31, 251, or 255 (though 255 is not prime). + + +## xhDeInit() +```c +int xhDeInit(pXHashTable this); +``` +This function deinitializes a hash table struct, freeing all rows. Note that the stored data is not freed and neither are the keys as this data is assumed to be the responsibility of the caller. Returns 0 on success, or -1 if an error occurs. + + +## xhAdd() +```c +int xhAdd(pXHashTable this, char* key, char* data); +``` +Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that they item is hashed, as they are assumed to be the responsibility of the caller. This function returns 0 on success, or -1 if an error occurs. + + +## xhRemove() +```c +int xhRemove(pXHashTable this, char* key); +``` +This function removes an item with the given key value from the hash table. It returns 0 if the item was successfully removed, or -1 if an error occurs (including failing to find the item). + + +## xhLookup() +```c +char* xhLookup(pXHashTable this, char* key); +``` +This function returns a pointer to the data associated with the given key, or `NULL` if an error occurs (including failing to find the key). + + +## xhClear() +```c +int xhClear(pXHashTable this, int (*free_fn)(), void* free_arg); +``` +Clears all items from a hash table. If a `free_fn()` is provided, it will be invoked with each data pointer as the first argument and `free_arg` as the second argument as items are removed. The return value of the `free_fn()` is ignored. This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. + + +## xhForEach() +```c +int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); +``` +This function executes an operation on each entry of the hash table entry. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `void*` argument specified using `each_arg`. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. + +This function returns 0 if the function executes successfully, 1 if the callback function is `NULL`, or n (where n != 0) if the callback function returns n. It does not return any error code other than 1 or any error codes returned by `callback_fn()`. + + +## xhClearKeySafe() +```c +int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg); +``` +This function clears all contents from the hash table. The free function is passed each hash entry struct and `free_arg`, allowing it to free both the value and key, if needed, and the free function is not allowed to return an error code. This function returns 0 for success as long as `free_fn()` is nonnull, otherwise it returns -1. diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md new file mode 100644 index 000000000..4ecce289d --- /dev/null +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The XString Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The XString Library](#the-xstring-library) + - [Introduction](#introduction) + - [xsNew()](#xsnew) + - [xsFree()](#xsfree) + - [xsInit()](#xsinit) + - [xsDeInit()](#xsdeinit) + - [xsCheckAlloc()](#xscheckalloc) + - [xsConcatenate()](#xsconcatenate) + - [xsCopy()](#xscopy) + - [xsStringEnd()](#xsstringend) + - [xsConcatPrintf()](#xsconcatprintf) + - [xsPrintf()](#xsprintf) + - [xsWrite()](#xswrite) + - [xsRTrim()](#xsrtrim) + - [xsLTrim()](#xsltrim) + - [xsTrim()](#xstrim) + - [xsFind()](#xsfind) + - [xsFindRev()](#xsfindrev) + - [xsSubst()](#xssubst) + - [xsReplace()](#xsreplace) + - [xsInsertAfter()](#xsinsertafter) + - [xsGenPrintf_va()](#xsgenprintf_va) + - [xsGenPrintf()](#xsgenprintf) + - [xsString()](#xsstring) + - [xsLength()](#xslength) + - [xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf()](#xsqprintf_va-xsqprintf--xsconcatqprintf) + + +## Introduction +The xstring (xs) module is used for managing growable strings. It is based on a structure containing a small initial string buffer to avoid string allocations for small strings. However, it can also perform `realloc()` operations to extend the string space for storing incrementally larger strings. This module allows for strings to contain arbitrary data, even NULL (`'\0'`) characters mid-string. Thus, it can also be used as an extensible buffer for arbitrary binary data. + +- 📖 **Note**: The contents of the XString can be easily referenced with the `xstring->String` field in the xstring struct. + +- ⚠️ **Warning**: Do not mix calls to [`xsNew()`](#xsnew)/[`xsFree()`](#xsfree) with calls to [`xsInit()`](#xsinit)/[`xsDeInit()`](#xsdeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. + + +## xsNew() +```c +pXString xsNew() +``` +This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](#nmmalloc) because the XString struct is always a consistant size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. + + +## xsFree() +```c +void xsFree(pXString this); +``` +This function frees an XString structure allocated with [`xsNew()`](#xsnew), freeing all associated memory. + + +## xsInit() +```c +int xsInit(pXString this); +``` +This function initializes an XString structure to contain a new, empty string. This function returns 0 if successful, or -1 if an error occurs. + + +## xsDeInit() +```c +int xsDeInit(pXString this); +``` +This function deinitializes an XString structure allocated with [`xsInit()`](#xsinit), freeing all associated memory. This function returns 0 if successful, or -1 if an error occurs. + + +## xsCheckAlloc() +```c +int xsCheckAlloc(pXString this, int addl_needed); +``` +This function will optionally allocate more memory, if needed, given the currently occupied data area and the additional space required (specified with `addl_needed`). This function returns 0 if successful, or -1 if an error occurs. + + +## xsConcatenate() +```c +int xsConcatenate(pXString this, char* text, int len); +``` +This function concatenates the `text` string onto the end of the XString's value. If `len` is set, that number of characters are copied, including possible null characters (`'\0'`). If `len` is -1, all data up to the null-terminater is copied. This function returns 0 if successful, or -1 if an error occurs. + +- ⚠️ **Warning**: Do not store pointers to values within the string while adding text to the end of the string. The string may be reallocated to increase space, causing such pointers to break. Instead, use offset indexes into the string and calculate pointers on demand with `xs->String + offset`. + + For example, **DO NOT**: + ```c + XString xs; + if (xsInit(&xs) != 0) goto handle_error; + + if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + char* ptr = xsStringEnd(&xs); /* Stores string pointer! */ + if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + + /** Print will probably read invalid memory. **/ + printf("A pointer to the second sentence is '%s'\n", ptr); + + ... + + if (xsDeInit(&xs) != 0) goto handle_error; + ``` + + Instead, use indexes and pointer arithmetic like this: + ```c + XString xs; + if (xsInit(&xs) != 0) goto handle_error; + + if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + int offset = xsStringEnd(&xs) - xs->String; /* Stores index offset. */ + if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + + /** Print will probably work fine. **/ + printf("A pointer to the second sentence is '%s'\n", xs->String + offset); + + ... + + if (xsDeInit(&xs) != 0) goto handle_error; + ``` + + +## xsCopy() +```c +int xsCopy(pXString this, char* text, int len); +``` +This function copies the string `text` into the XString, overwriting any previous contents. This function returns 0 if successful, or -1 if an error occurs. + + +## xsStringEnd() +```c +char* xsStringEnd(pXString this); +``` +This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some string may contain nulls, using `strlen()` may produce an incorrect result. + + +## xsConcatPrintf() +```c +int xsConcatPrintf(pXString this, char* fmt, ...); +``` +This function prints additional data onto the end of the string. It is similar to printf, however, only the following features are supported: +- `%s`: Add a string (`char*`). +- `%d`: Add a number (`int`). +- `%X`: Add something? +- `%%`: Add a `'%'` character. +Attempting to use other features of printf (such as `%lf`, `%c`, `%u`, etc.) will cause unexpected results. + +This function returns 0 if successful, or -1 if an error occurs. + + +## xsPrintf() +```c +int xsPrintf(pXString this, char* fmt, ...); +``` +This function works the same as [`xsConcatPrintf()`](#xsconcatprintf), except that it overwrites the previous string instead of appending to it. This function returns 0 if successful, or -1 if an error occurs. + + +## xsWrite() +```c +int xsWrite(pXString this, char* buf, int len, int offset, int flags); +``` +This function writes data into the xstring, similar to using the standard fdWrite or objWrite API. This function can thus be used as a value for `write_fn`, for those functions that require this (such as the `expGenerateText()` function). This function returns `len` if successful, or -1 if an error occurs. + + +## xsRTrim() +```c +int xsRTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the right side of the xstring. This function returns 0 if successful, or -1 if an error occurs. + + +## xsLTrim() +```c +int xsLTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the left side of the xstring. This function returns 0 if successful, or -1 if an error occurs. + + +## xsTrim() +```c +int xsTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from both sides of the xstring. This function returns 0 if successful, or -1 if an error occurs. + + +## xsFind() +```c +int xsFind(pXString this, char* find, int findlen, int offset) +``` +This function searches for a specific string (`find`) in the xstring, starting at the provided `offset`. `findlen` is the length of the provided string, allowing it to include null characters (pass -1 to have the length calculated using `strlen(find)`). This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). + + +## xsFind() +```c +int xsFindRev(pXString this, char* find, int findlen, int offset) +``` +This function works the same as [`xsFind()`](#xsfind) except that it searches from the end of the string, resulting in better performance if the value is closer to the end of the string. This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). + + +## xsSubst() +```c +int xsSubst(pXString this, int offset, int len, char* rep, int replen) +``` +This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsrepalce), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified handle null characters. Both `len` and `replen` can be left blank to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. + + +## xsReplace() +```c +int xsReplace(pXString this, char* find, int findlen, int offset, char* rep, int replen); +``` +This function searches an xString for the specified string (`find`) and replaces that string with another specified string (`rep`). Both strings can have their length specified (`findlen` and `replen` respectively), or left as -1 to generate it using `strlen()`. This function returns the starting offset of the replace if successful, or -1 if an error occurs (including the string not being found). + + +## xsInsertAfter() +```c +int xsInsertAfter(pXString this, char* ins, int inslen, int offset); +``` +This function inserts the specified string (`ins`) at offset (`offset`). The length of the string can be specified (`inslen`), or left as -1 to generate it using `strlen()`. This function returns the new offset after the insertion (i.e. `offset + inslen`), or -1 if an error occurs. + + +## xsGenPrintf_va() +```c +int xsGenPrintf_va(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, va_list va); +``` +This function performs a `printf()` operation to an `xxxWrite()` style function. + +In the wise words of Greg Beeley from 2002: +> This routine isn't really all that closely tied to the XString module, but this seemed to be the best place for it. If a `buf` and `buf_size` are supplied (`NULL` otherwise), then `buf` MUST be allocated with the `nmSysMalloc()` routine. Otherwise, **kaboom!** This routine will grow `buf` if it is too small, and will update `buf_size` accordingly. + +This function returns the printed length (>= 0) on success, or -(errno) if an error occurs. + + +## xsGenPrintf() +```c +int xsGenPrintf(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, ...); +``` +This function works the same as [`xsGenPrintf_va()`](#xsgenprintf_va), but with a more convenient signature for the developer. + + +## xsString() +```c +char* xsString(pXString this); +``` +This function returns the stored string after checking for various errors, or returns `NULL` if an error occurs. + + +## xsLength() +```c +xsLength(pXString this); +``` +This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. + + + + +## xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf() +```c +int xsQPrintf_va(pXString this, char* fmt, va_list va); +int xsQPrintf(pXString this, char* fmt, ...); +int xsConcatQPrintf(pXString this, char* fmt, ...); +``` +These functions use the `QPrintf` to add data to an xstring. They return 0 on success, or some other value on failure. diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 6283a8855..a6b3f783f 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -37,6 +37,8 @@ **Date**: January 13, 1999 +**Imported**: August 13, 2001 + **Updated**: December 11, 2025 **License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. @@ -98,70 +100,7 @@ - [stparse: stAddValue()](#stparse-staddvalue) - [stparse: stFreeInf()](#stparse-stfreeinf) - [stparse: Using Fields Directly](#stparse-using-fields-directly) - - [IV Memory Management in Centrallix](#iv-memory-management-in-centrallix) - - [nmMalloc()](#nmmalloc) - - [nmFree()](#nmfree) - - [nmStats()](#nmstats) - - [nmRegister()](#nmregister) - - [nmDebug()](#nmdebug) - - [nmDeltas()](#nmdeltas) - - [nmSysMalloc()](#nmsysmalloc) - - [nmSysRealloc()](#nmsysrealloc) - - [nmSysStrdup()](#nmsysstrdup) - - [nmSysFree()](#nmsysfree) - - [V Module: XArray](#v-module-xarray) - - [xaNew()](#xanew) - - [xaFree()](#xafree) - - [xaInit()](#xainit) - - [xaDeInit()](#xadeinit) - - [xaAddItem()](#xaadditem) - - [xaAddItemSorted()](#xaadditemsorted) - - [xaAddItemSortedInt32()](#xaadditemsortedint32) - - [xaGetItem()](#xagetitem) - - [xaFindItem()](#xafinditem) - - [xaFindItemR()](#xafinditemr) - - [xaRemoveItem()](#xaremoveitem) - - [xaClear()](#xaclear) - - [xaClearR()](#xaclearr) - - [xaCount()](#xacount) - - [xaInsertBefore()](#xainsertbefore) - - [xaInsertAfter()](#xainsertafter) - - [VI Module: XHash](#vi-module-xhash) - - [xhInitialize()](#xhinitialize) - - [xhInit()](#xhinit) - - [xhDeInit()](#xhdeinit) - - [xhAdd()](#xhadd) - - [xhRemove()](#xhremove) - - [xhLookup()](#xhlookup) - - [xhClear()](#xhclear) - - [xhForEach()](#xhforeach) - - [xhClearKeySafe()](#xhclearkeysafe) - - [VII Module: XString](#vii-module-xstring) - - [xsNew()](#xsnew) - - [xsFree()](#xsfree) - - [xsInit()](#xsinit) - - [xsDeInit()](#xsdeinit) - - [xsCheckAlloc()](#xscheckalloc) - - [xsConcatenate()](#xsconcatenate) - - [xsCopy()](#xscopy) - - [xsStringEnd()](#xsstringend) - - [xsConcatPrintf()](#xsconcatprintf) - - [xsPrintf()](#xsprintf) - - [xsWrite()](#xswrite) - - [xsRTrim()](#xsrtrim) - - [xsLTrim()](#xsltrim) - - [xsTrim()](#xstrim) - - [xsFind()](#xsfind) - - [xsFindRev()](#xsfindrev) - - [xsSubst()](#xssubst) - - [xsReplace()](#xsreplace) - - [xsInsertAfter()](#xsinsertafter) - - [xsGenPrintf_va()](#xsgenprintf_va) - - [xsGenPrintf()](#xsgenprintf) - - [xsString()](#xsstring) - - [xsLength()](#xslength) - - [xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf()](#xsqprintf_va-xsqprintf--xsconcatqprintf) - - [VIII Module: Expression](#viii-module-expression) + - [IV Module: Expression](#viii-module-expression) - [expCompileExpression())](#expallocexpression) - [expFreeExpression()](#expfreeexpression) - [expCompileExpression()](#expcompileexpression) @@ -178,24 +117,12 @@ - [expRemoveParamFromList()](#expremoveparamfromlist) - [expSetParamFunctions()](#expsetparamfunctions) - [expReverseEvalTree()](#expreverseevaltree) - - [IX MTSession](#ix-module-mtsession) - - [mssUserName()](#mssusername) - - [mssPassword()](#msspassword) - - [mssSetParam()](#msssetparam) - - [mssGetParam()](#mssgetparam) - - [mssError()](#msserror) - - [mssErrorErrno()](#msserrorerrno) - - [X Path Handling Functions](#x-path-handling-functions) + - [V Path Handling Functions](#x-path-handling-functions) - [obj_internal_PathPart()](#obj_internal_pathpart) - [obj_internal_AddToPath()](#obj_internal_addtopath) - [obj_internal_CopyPath](#obj_internal_copypath) - [obj_internal_FreePathStruct()](#obj_internal_freepathstruct) - - [XI Network Connection Functionality](#vi-network-connection-functionality) - - [netConnectTCP()](#netconnecttcp) - - [netCloseTCP()](#netclosetcp) - - [fdWrite()](#fdwrite) - - [fdRead()](#fdread) - - [XII Parsing Data](#xii-parsing-data) + - [VI Parsing Data](#xii-parsing-data) - [mlxOpenSession()](#mlxopensession) - [mlxStringSession()](#mlxstringsession) - [mlxCloseSession()](#mlxclosesession) @@ -210,7 +137,7 @@ - [mlxSetReservedWords()](#mlxsetreservedwords) - [mlxNoteError()](#mlxnoteerror) - [mlxNotePosition()](#mlxnoteposition) - - [XIII Driver Testing](#xiii-driver-testing) + - [VII Driver Testing](#xiii-driver-testing) - [Object opening, closing, creation, and deletion](#aobject-opening-closing-creation-and-deletion) - [Object attribute enumeration, getting, and setting.](#bobject-attribute-enumeration-getting-and-setting) - [Object querying (for subobjects)](#cobject-querying-for-subobjects) @@ -1174,527 +1101,7 @@ for (unsigned int i = 0u; i < inf->nSubInf; i++) -## IV Memory Management in Centrallix -Centrallix has its own memory management wrapper that caches deallocated blocks of memory by size for faster reuse. This wrapper also detects double-freeing of blocks (sometimes), making debugging of memory problems just a little bit easier. - -In addition, the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. `malloc()`, and on how many blocks of each size/type are `malloc()`ed and cached. This information can be helpful for tracking down memory leaks. Empirical testing has shown an increase of performance of around 50% or more in programs that use newmalloc. - -One caveat is that this memory manager does not provide `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or [`nmSysMalloc()`](#nmsysmalloc), [`nmSysFree()`](#nmsysfree), and [`nmSysRealloc()`](#nmsysrealloc) should be used for blocks of memory that might vary in size. - -- 📖 **Note**: This memory manager is usually the wrong choice for blocks of memory of arbitrary, inconsistent sizes. It is intended for allocating structures quickly that are of a specific size. For example, allocated space for a struct that is always the same size. - -- 🥱 **tl;dr**: Use `nmMalloc()` for structs, not for strings. - -- ⚠️ **Warning**: Do not mix and match, even though calling `free()` on a block obtained from `nmMalloc()` or calling `nmFree()` on a block obtained from `malloc()` might not crash the program immediately. However, it may result in either inefficient use of the memory manager, or a significant memory leak, respectively. These practices will also lead to incorrect results from the statistics and block count mechanisms. - - -The following are the functions for the newmalloc module: - -### nmMalloc() -```c -void* nmMalloc(int size); -``` -This function allocates a block of the given `size`. It returns `NULL` if the memory could not be allocated. - - -### nmFree() -```c -void nmFree(void* ptr, int size); -``` -This function frees the block of memory. - -- ⚠️ **Warning**: The caller **must know the size of the block.** Getting this wrong is very bad!! For structures, this is trivial, simply use `sizeof()`, exactly the same as with `nmMalloc()`. - - -### nmStats() -```c -void nmStats(void); -``` -Prints statistics about the memory manager, for debugging and optimizing. - -For example: -``` -NewMalloc subsystem statistics: - nmMalloc: 20244967 calls, 19908369 hits (98.337%) - nmFree: 20233966 calls - bigblks: 49370 too big, 32768 largest size -``` - -- ⚠️ **Warning**: Centrallix-lib must be built with the configure option `--enable-debugging` for this function to work. Otherwise, all the stats will be zeros. - - -### nmRegister() -```c -void nmRegister(int size, char* name); -``` -Registers an inteligent name for block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production usecases, but using it can make tracking down memory leaks easier. - -This function is usually called in a module's `Initialize()` function on each of the structures the module uses internally. - - -### nmDebug() -```c -void nmDebug(void); -``` -Prints a listing of block allocation counts, giving (by size): -- The number of blocks allocated but not yet freed. -- The number of blocks in the cache. -- The total allocations for this block size. -- A list of names (from [`nmRegister()`](#nmregister)) for that block size. - - -### nmDeltas() -```c -void nmDeltas(void); -``` -Prints a listing of all blocks whose allocation count has changed, and by how much, since the last `nmDeltas()` call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. - - -### nmSysMalloc() -```c -void* nmSysMalloc(int size); -``` -Allocates memory without using the block-caching algorithm. This is roughly equivalent to `malloc()`, but pointers returned by malloc and this function are not compatible - i.e., you cannot `free()` something that was [`nmSysMalloc()`](#nmsysmalloc)'ed, nor can you [`nmSysFree()`](#nmsysfree) something that was `malloc()`'ed. - -- 📖 **Note**: This function is much better to use on variable-sized blocks of memory. `nmMalloc()` is better for fixed-size blocks, such as for structs. - - -### nmSysRealloc() -```c -void* nmSysRealloc(void* ptr, int newsize); -``` -Changes the size of an allocated block of memory that was obtained from [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). The new pointer may be different if the block needs to be moved. This is the rough equivalent of `realloc()`. - -- 📖 **Note**: If you are `realloc()`'ing a block of memory and need to store pointers to data somewhere inside the block, it is often better to store an offset rather than a full pointer. This is because a full pointer becomes invalid if a [`nmSysRealloc()`](#nmsysrealloc) causes the block to move. - - -### nmSysStrdup() -```c -char* nmSysStrdup(const char* str); -``` -Allocates memory using the [`nmSysMalloc()`](#nmsysmalloc) function and copies the string `str` into this memory. It is a rough equivalent of `strdup()`. The resulting pointer can be free'd using [`nmSysFree()`](#nmsysfree). - - -### nmSysFree() -```c -void nmSysFree(void* ptr); -``` -Frees a block of memory allocated by [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). - - - -## V Module: XArray -The xarray (xa) module is intended to manage sized growable arrays, similar to a light-weight arraylist implementation. It includes the `XArray`, which has the following fields: -- `nItems : int`: The number of items in the array. -- `nAlloc : int`: Internal variable to store the size of the allocated memory. -- `Items : void**`: The allocated array of items. - -- 📖 **Note**: Some code occasionally sets `nAlloc` to 0 after an XArray struct has been deinitialized to indicate that the relevant data is no longer allocated. Other than this, it is only used internally by the library. - -- ⚠️ **Warning**: Do not mix calls to [`xaNew()`](#xanew)/[`xaFree()`](#xafree) with calls to [`xaInit()`](#xainit)/[`xaDeInit()`](#xadeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. - - -### xaNew() -```c -pXArray xaNew(int init_size); -``` -Allocates a new `XArray` struct on the heap (using [`nmMalloc()`](#nmmalloc) for caching) and returns a pointer to it, or returns `NULL` if an error occurs. - -### xaFree() -```c -int xaFree(pXArray this); -``` -Frees a `pXArray` allocated using [`xaNew`](#xanew), returning 0 if successful or -1 if an error occurs. - -### xaInit() -```c -int xaInit(pXArray this, int init_size); -``` -This function initializes an allocated (but uninitialized) xarray. It makes room for `init_size` items initially, but this is only an optimization. A typical value for `init_size` is 16. Remember to [`xaDeInit`](#xadeinit) this xarray, do **not** [`xaFree`](#xafree) it. - -This function returns 0 on success, or -1 if an error occurs. - -### xaDeInit() -```c -int xaDeInit(pXArray this); -``` -This function de-initializes an xarray, but does not free the XArray structure itself. This is useful if the structure is a local variable allocated using [`xaInit()`](#xainit). - -This function returns 0 on success, or -1 if an error occurs. - -For example: -```c -XArray arr; -if (xaInit(&arr, 16) != 0) goto handle_error; - -/** Use the xarray. **/ - -if (arr.nAlloc != 0 && xaDeInit(&arr) != 0) goto handle_error; -arr.nAlloc = 0; -``` - -### xaAddItem() -```c -int xaAddItem(pXArray this, void* item); -``` -This function adds an item to the end of the xarray. The item is assumed to be a `void*`, but this function will _not_ follow pointeres stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). - -This function returns 0 on success, or -1 if an error occurs. - -### xaAddItemSorted() -```c -int xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen); -``` -This function adds an item to a sorted xarray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers are stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). - -### xaAddItemSortedInt32() -```c -int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset) -``` - - -### xaGetItem() -```c -void* xaGetItem(pXArray this, int index) -``` -This function returns an item given a specific index into the xarray, or `NULL` if the index is out of bounds. If the bounds check needs to be omitted for performance and the caller can otherwise verify that no out of bounds read is possible (e.g. because they are iterating from 0 to `xarray->nItems`), the caller should access `xarray->Items` directly. Either way, the result may need to be typecasted or stored in a variable of a specific type for it to be useable, and error checking for `NULL` values should be used. - -### xaFindItem() -```c -int xaFindItem(pXArray this, void* item); -``` -This function returns array index for the provided item in the array, or -1 if the item could not be found. Requires an exact match, so two `void*` pointing to different memory with identical contents are not considered equal by this function. If the data is actually another datatype typecasted as a `void*`, all 8 bytes must be identical for a match. - -For example: -```c -void* data = &some_data; - -XArray xa; -xaInit(&xa, 16); - -... - -xaAddItem(&xa, data); - -... - -int item_id = xaFindItem(&xa, data); -assert(data == xa.Items[item_id]); -``` - -### xaFindItemR() -```c -int xaFindItemR(pXArray this, void* item); -``` -This function works the same as [`xaFindItem()`](#xafinditem), however it iterates in reverse, giving a slight performance boost, especially for finding items near the end of the array. - -### xaRemoveItem(pXArray this, int index) -```c -int xaRemoveItem(pXArray this, int index) -``` -This function removes an item from the xarray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. - -### xaClear() -```c -int xaClear(pXArray this, int (*free_fn)(), void* free_arg); -``` -This function removes all elements from the xarray, leaving it empty. `free_fn()` is invoked on each element with a `void*` to the element to be freed as the first argument and `free_arg` as the second argument (the return value of `free_fn()` is always ignored). This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. - -### xaClearR() -```c -int xaClearR(pXArray this, int (*free_fn)(), void* free_arg); -``` -This function works the same as [`xaClear()`](#xaclear), except that it is slightly faster because the free function is evaluated on items in reverse order. - -### xaCount() -```c -int xaCount(pXArray this); -``` -This function returns the number of items in the xarray, or -1 on error. It is equivalent to accessing `xarray->nItems` (although the latter expression will not return an error). - -### xaInsertBefore() -```c -int xaInsertBefore(pXArray this, int index, void* item) -``` -This function inserts an item before the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. - -### xaInsertAfter() -```c -int xaInsertAfter(pXArray this, int index, void* item) -``` -This function inserts an item after the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. - - - -## VI Module: XHash -The xhash (xh) module provides an extensible hash table interface. The hash table is a table of linked lists of items, so collisions and overflows are handled by this data structure (although excessive collisions still cause a performance loss). This implementation also supports variable-length keys for more flexible usecases. - -- ⚠️ **Warning**: All `xhXYZ()` function calls assume that the `pXHashTable this` arg points to a valid hashtable struct. All non-init functions assume that this struct has been validly initialized and has not yet been freed. If these conditions are not met, the resulting behavior is undefined. - -### xhInitialize() -```c -int xhInitialize(); -``` -Initialize the random number table for hash computation, returning 0 on success or -1 if an error occurs. Normally, you can assume someone else has already called this during program startup. - -### xhInit() -```c -int xhInit(pXHashTable this, int rows, int keylen); -``` -This function initializes a hash table, setting the number of rows and the key length. Specify a `keylen` of 0 for for variable length keys (aka. null-terminated strings). The `rows` should be an odd number, preferably prime (although that isn't required). `rows` **SHOULD NOT** be a power of 2. Providing this value allows the caller to optimize it based on how much data they expect to be stored in the hash table. If this value is set to 1, the hash search degenerates to a linear array search with extra overhead. Thus, the value should be large enough to comfortably accommodate the elements with minimal collisions. Typical values include 31, 251, or 255 (though 255 is not prime). - -### xhDeInit() -```c -int xhDeInit(pXHashTable this); -``` -This function deinitializes a hash table struct, freeing all rows. Note that the stored data is not freed and neither are the keys as this data is assumed to be the responsibility of the caller. Returns 0 on success, or -1 if an error occurs. - -### xhAdd() -```c -int xhAdd(pXHashTable this, char* key, char* data); -``` -Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that they item is hashed, as they are assumed to be the responsibility of the caller. This function returns 0 on success, or -1 if an error occurs. - -### xhRemove() -```c -int xhRemove(pXHashTable this, char* key); -``` -This function removes an item with the given key value from the hash table. It returns 0 if the item was successfully removed, or -1 if an error occurs (including failing to find the item). - -### xhLookup() -```c -char* xhLookup(pXHashTable this, char* key); -``` -This function returns a pointer to the data associated with the given key, or `NULL` if an error occurs (including failing to find the key). - -### xhClear() -```c -int xhClear(pXHashTable this, int (*free_fn)(), void* free_arg); -``` -Clears all items from a hash table. If a `free_fn()` is provided, it will be invoked with each data pointer as the first argument and `free_arg` as the second argument as items are removed. The return value of the `free_fn()` is ignored. This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. - -### xhForEach() -```c -int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); -``` -This function executes an operation on each entry of the hash table entry. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `void*` argument specified using `each_arg`. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. - -This function returns 0 if the function executes successfully, 1 if the callback function is `NULL`, or n (where n != 0) if the callback function returns n. It does not return any error code other than 1 or any error codes returned by `callback_fn()`. - -### xhClearKeySafe() -```c -int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg); -``` -This function clears all contents from the hash table. The free function is passed each hash entry struct and `free_arg`, allowing it to free both the value and key, if needed, and the free function is not allowed to return an error code. This function returns 0 for success as long as `free_fn()` is nonnull, otherwise it returns -1. - - - -## VII Module: XString -The xstring (xs) module is used for managing growable strings. It is based on a structure containing a small initial string buffer to avoid string allocations for small strings. However, it can also perform `realloc()` operations to extend the string space for storing incrementally larger strings. This module allows for strings to contain arbitrary data, even NULL (`'\0'`) characters mid-string. Thus, it can also be used as an extensible buffer for arbitrary binary data. - -- 📖 **Note**: The contents of the XString can be easily referenced with the `xstring->String` field in the xstring struct. - -- ⚠️ **Warning**: Do not mix calls to [`xsNew()`](#xsnew)/[`xsFree()`](#xsfree) with calls to [`xsInit()`](#xsinit)/[`xsDeInit()`](#xsdeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. - -### xsNew() -```c -pXString xsNew() -``` -This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](#nmmalloc) because the XString struct is always a consistant size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. - -### xsFree() -```c -void xsFree(pXString this); -``` -This function frees an XString structure allocated with [`xsNew()`](#xsnew), freeing all associated memory. - -### xsInit() -```c -int xsInit(pXString this); -``` -This function initializes an XString structure to contain a new, empty string. This function returns 0 if successful, or -1 if an error occurs. - -### xsDeInit() -```c -int xsDeInit(pXString this); -``` -This function deinitializes an XString structure allocated with [`xsInit()`](#xsinit), freeing all associated memory. This function returns 0 if successful, or -1 if an error occurs. - -### xsCheckAlloc() -```c -int xsCheckAlloc(pXString this, int addl_needed); -``` -This function will optionally allocate more memory, if needed, given the currently occupied data area and the additional space required (specified with `addl_needed`). This function returns 0 if successful, or -1 if an error occurs. - -### xsConcatenate() -```c -int xsConcatenate(pXString this, char* text, int len); -``` -This function concatenates the `text` string onto the end of the XString's value. If `len` is set, that number of characters are copied, including possible null characters (`'\0'`). If `len` is -1, all data up to the null-terminater is copied. This function returns 0 if successful, or -1 if an error occurs. - -- ⚠️ **Warning**: Do not store pointers to values within the string while adding text to the end of the string. The string may be reallocated to increase space, causing such pointers to break. Instead, use offset indexes into the string and calculate pointers on demand with `xs->String + offset`. - - For example, **DO NOT**: - ```c - XString xs; - if (xsInit(&xs) != 0) goto handle_error; - - if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; - char* ptr = xsStringEnd(&xs); /* Stores string pointer! */ - if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; - - /** Print will probably read invalid memory. **/ - printf("A pointer to the second sentence is '%s'\n", ptr); - - ... - - if (xsDeInit(&xs) != 0) goto handle_error; - ``` - - Instead, use indexes and pointer arithmetic like this: - ```c - XString xs; - if (xsInit(&xs) != 0) goto handle_error; - - if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; - int offset = xsStringEnd(&xs) - xs->String; /* Stores index offset. */ - if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; - - /** Print will probably work fine. **/ - printf("A pointer to the second sentence is '%s'\n", xs->String + offset); - - ... - - if (xsDeInit(&xs) != 0) goto handle_error; - ``` - -### xsCopy() -```c -int xsCopy(pXString this, char* text, int len); -``` -This function copies the string `text` into the XString, overwriting any previous contents. This function returns 0 if successful, or -1 if an error occurs. - -### xsStringEnd() -```c -char* xsStringEnd(pXString this); -``` -This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some string may contain nulls, using `strlen()` may produce an incorrect result. - -### xsConcatPrintf() -```c -int xsConcatPrintf(pXString this, char* fmt, ...); -``` -This function prints additional data onto the end of the string. It is similar to printf, however, only the following features are supported: -- `%s`: Add a string (`char*`). -- `%d`: Add a number (`int`). -- `%X`: Add something? -- `%%`: Add a `'%'` character. -Attempting to use other features of printf (such as `%lf`, `%c`, `%u`, etc.) will cause unexpected results. - -This function returns 0 if successful, or -1 if an error occurs. - -### xsPrintf() -```c -int xsPrintf(pXString this, char* fmt, ...); -``` -This function works the same as [`xsConcatPrintf()`](#xsconcatprintf), except that it overwrites the previous string instead of appending to it. This function returns 0 if successful, or -1 if an error occurs. - -### xsWrite() -```c -int xsWrite(pXString this, char* buf, int len, int offset, int flags); -``` -This function writes data into the xstring, similar to using the standard fdWrite or objWrite API. This function can thus be used as a value for `write_fn`, for those functions that require this (such as the `expGenerateText()` function). This function returns `len` if successful, or -1 if an error occurs. - -### xsRTrim() -```c -int xsRTrim(pXString this); -``` -This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the right side of the xstring. This function returns 0 if successful, or -1 if an error occurs. - -### xsLTrim() -```c -int xsLTrim(pXString this); -``` -This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the left side of the xstring. This function returns 0 if successful, or -1 if an error occurs. - -### xsTrim() -```c -int xsTrim(pXString this); -``` -This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from both sides of the xstring. This function returns 0 if successful, or -1 if an error occurs. - -### xsFind() -```c -int xsFind(pXString this, char* find, int findlen, int offset) -``` -This function searches for a specific string (`find`) in the xstring, starting at the provided `offset`. `findlen` is the length of the provided string, allowing it to include null characters (pass -1 to have the length calculated using `strlen(find)`). This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). - -### xsFind() -```c -int xsFindRev(pXString this, char* find, int findlen, int offset) -``` -This function works the same as [`xsFind()`](#xsfind) except that it searches from the end of the string, resulting in better performance if the value is closer to the end of the string. This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). - -### xsSubst() -```c -int xsSubst(pXString this, int offset, int len, char* rep, int replen) -``` -This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsrepalce), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified handle null characters. Both `len` and `replen` can be left blank to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. - -### xsReplace() -```c -int xsReplace(pXString this, char* find, int findlen, int offset, char* rep, int replen); -``` -This function searches an xString for the specified string (`find`) and replaces that string with another specified string (`rep`). Both strings can have their length specified (`findlen` and `replen` respectively), or left as -1 to generate it using `strlen()`. This function returns the starting offset of the replace if successful, or -1 if an error occurs (including the string not being found). - -### xsInsertAfter() -```c -int xsInsertAfter(pXString this, char* ins, int inslen, int offset); -``` -This function inserts the specified string (`ins`) at offset (`offset`). The length of the string can be specified (`inslen`), or left as -1 to generate it using `strlen()`. This function returns the new offset after the insertion (i.e. `offset + inslen`), or -1 if an error occurs. - -### xsGenPrintf_va() -```c -int xsGenPrintf_va(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, va_list va); -``` -This function performs a `printf()` operation to an `xxxWrite()` style function. - -In the wise words of Greg Beeley from 2002: -> This routine isn't really all that closely tied to the XString module, but this seemed to be the best place for it. If a `buf` and `buf_size` are supplied (`NULL` otherwise), then `buf` MUST be allocated with the `nmSysMalloc()` routine. Otherwise, **kaboom!** This routine will grow `buf` if it is too small, and will update `buf_size` accordingly. - -This function returns the printed length (>= 0) on success, or -(errno) if an error occurs. - -### xsGenPrintf() -```c -int xsGenPrintf(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, ...); -``` -This function works the same as [`xsGenPrintf_va()`](#xsgenprintf_va), but with a more convenient signature for the developer. - -### xsString() -```c -char* xsString(pXString this); -``` -This function returns the stored string after checking for various errors, or returns `NULL` if an error occurs. - -### xsLength() -```c -xsLength(pXString this); -``` -This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. - - - -### xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf() -```c -int xsQPrintf_va(pXString this, char* fmt, va_list va); -int xsQPrintf(pXString this, char* fmt, ...); -int xsConcatQPrintf(pXString this, char* fmt, ...); -``` -These functions use the `QPrintf` to add data to an xstring. They return 0 on success, or some other value on failure. - - - -## VIII Module: Expression +## IV Module: Expression The expression (EXP) module is used for compiling, evaluating, reverse-evaluating, and managing parameters for expression strings. The expression strings are compiled and stored in an expression tree structure. Expressions can be stand-alone expression trees, or they can take parameter objects. A parameter object is an open object (from `objOpen()`) whose values (attributes) are referenced within the expression string. By using such parameter objects, one expression can be compiled and then evaluated for many different objects with diverse attribute values. @@ -1868,89 +1275,7 @@ There are several other EXP functions used to deal with aggregates and a few oth -## IX Module: MTSession -The mtsession (MSS) module is used for session authentication, error reporting, and for storing session-wide variables such as the current date format, username, and password (used when issuing a login request to a remote server). Care should be taken in the use of Centrallix that its coredump files are NOT in a world-readable location, as the password will be visible in the coredump file (or just ulimit the core file size to 0). - - -### mssInitialize() -```c -int mssInitialize(char* authmethod, char* authfile, char* logmethod, int logall, char* log_progname); -``` -This function initializes the session manager and sets global variables used in this module. It returns 0 if successful and -1 if an error occurs. - -### mssUserName() -```c -char* mssUserName(); -``` -This function returns the current user name, or `NULL` an error occurs. - -### mssPassword() -```c -char* mssPassword(); -``` -This function returns the current user's password that they used to log into Centrallix, or `NULL` an error occurs. - -### mssSetParam() -```c -int mssSetParam(char* paramname, char* param); -``` -This function sets the session parameter of the provided name (`paramname`) to the provided value (`param`). The parameter MUST be a string value. This function returns 0 if successful, or -1 an error occurs. - -### mssGetParam() -```c -char* mssGetParam(char* paramname); -``` -Returns the value of a session parameter of the provided name (`paramname`), or `NULL` if an error occurs. Common session parameters include: -- `dfmt`: The current date format. -- `mfmt`: The current money format. -- `textsize`: The current max text size from a read of an object's content via `objGetAttrValue(obj, "objcontent", POD(&str))` - -### mssError() -```c -int mssError(int clr, char* module, char* message, ...); -``` -Formats and caches an error message for return to the user. This function returns 0 if successful, or -1 if an error occurred. - -| Parameter | Type | Description -| --------- | ------------- | ------------ -| crl | int | If set to 1, all previous error messages are cleared. Set this when the error is initially discovered and no other module is likely to have made a relevant `mssError()` call for the current error. -| module | char* | A two-to-five letter abbreviation of the module reporting the error. This is typically the module or driver's abbreviation prefix in full uppercase letters (although that is not required). This is intended to help the developer find the source of the error faster. -| message | char* | A string error message, accepting format specifiers like `%d` and `%s` which are supplied by the argument list, similar to `printf()`. -| ... | ... | Parameters for the formatting. - -Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch the errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. - -The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ as an expression being compiled? etc.). However, it is far easier to give too little information that too much, so it can often be best to air on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. - -- 📖 **Note**: The `mssError()` routines do not cause the calling function to return or exit. The function must still clean up after itself and return an appropriate value (such as `-1` or `NULL`) to indicate failure. - -- ⚠️ **Warning**: Even if `-1` is returned, the error message may still be sent to the user in some scenarios. This is not guaranteed, though. - -- ⚠️ **Warning**: `%d` and `%s` are the ONLY supported format specifier for this function. **DO NOT** use any other format specifiers like `%lf`, `%u`, `%lu`, `%c` etc. **DO NOT** attempt to include `%%` for a percent symbol in your error message, as misplaced percent symbols often break this function. If you wish to use these features of printf, it is recommended to print the error message to a buffer and pass that buffer to `mssError()`, as follows: - ```c - char err_buf[256]; - snprintf(err_buf, sizeof(err_buf), - "Incorrect values detected: %u, %g (%lf), '%c'", - unsigned_int_value, double_value, char_value - ); - if (mssError(1, "EXMPL", "%s", err_buf) != 0) - { - fprintf(stderr, "ERROR! %s\n", err_buf); - } - return -1; - ``` - - -### mssErrorErrno() -```c -int mssErrorErrno(int clr, char* module, char* message, ...); -``` -This function works the same way as [`mssError`](#mssError), except checks the current value of `errno` and includes a description of any error stored there. This is useful if a system call or other library function is responsible for this error. - - - - -## X Path Handling Functions +## V Path Handling Functions The OSML provides a set of utility functions that make it easier to handle path structs when writing drivers. Most of them are named `obj_internal_XxxYyy()` or similar. ### obj_internal_PathPart() @@ -1993,45 +1318,7 @@ This function frees a pathname structure. -## XI Network Connection Functionality -Sometimes, a driver may need to initiate a network connection. This can be done via the `MTASK` module, which provides simple and easy TCP/IP connectivity. It includes many functions, only a few of which are documented below: - -### netConnectTCP() -```c -pFile netConnectTCP(char* host_name, char* service_name, int flags); -``` -This function creates a client socket and connects it to a server on a given TCP service/port and host name. It takes the following three parameters: -- `host_name`: The host name or ascii string for the host's ip address. -- `service_name`: The name of the service (from `/etc/services`) or its numeric representation as a string. -- `flags`: Normally left 0. - -- 📖 **Note**: The `NET_U_NOBLOCK` flag causes the function to return immediately even if the connection is still being established. Further reads and writes will block until the connection either establishes or fails. - -This function returns the connection file descriptor if successful, or `NULL` if an error occurs. - -### netCloseTCP() -```c -int netCloseTCP(pFile net_filedesc, int linger_msec, int flags); -``` -This function closes a network connection (either a TCP listening, server, or client socket). It will also optionally waits up to `linger_msec` milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If `linger_msec` is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately prior to the close, offering to linger for a few more seconds (perhaps 5 or 10 by specifying 5000 or 10000 msec) can be a good idea. - -### fdWrite() -```c -int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags); -``` -This function writes data to an open file descriptor, from a given `buffer` and `length` of data to write. It also takes an optional seek `offset` and and `flags`, which can be zero or more of: -- `FD_U_NOBLOCK` - If the write can't be performed immediately, don't perform it at all. -- `FD_U_SEEK` - The `offset` value is valid. Seek to it before writing. Not allowed for network connections. -- `FD_U_PACKET` - *ALL* of the data specified by `length` in `buffer` must be written. Normal `write()` semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. - -### fdRead() -```c -int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags); -``` -This function works the same as [`fdWrite()`](#fdwrite) except that it reads data instead of writing it. It takes the same flags as above, except that `FD_U_PACKET` now requires that all of `maxlen` bytes must be read before returning. This is good for reading a packet of a known length that might be broken up into fragments by the network (TCP/IP has a maximum frame transmission size of about 1450 bytes). - - -## XII Parsing Data +## VI Parsing Data The mtlexer (MLX) module is a lexical analyzer library provided by Centrallix for parsing many types of data. It can parse data from either a `pFile` descriptor or from a string value. This lexical analyzer is also used by the [expression compiler](#viii-module-expression). In simple terms, it's a very fancy string tokenizer. ### mlxOpenSession() @@ -2197,7 +1484,7 @@ MLX: Error at line ## -## XIII Driver Testing +## VII Driver Testing This section contains a list of things that can be done to test an objectsystem driver and ensure that it preforms all basic operations correctly, using the [test_obj command line interface](http://www.centrallix.net/docs/docs.php). It is strongly recommended to test for invalid reads, writes, frees, and memory leaks during each of these by watching memory utilization using nmDeltas() during repetitive operations (e.g., nmDeltas(), open, close, nmDeltas(), open, close, and then nmDeltas() again). From 63fa5ba91fc854f18197a2b8bac2995abc572757 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 11 Dec 2025 17:32:56 -0700 Subject: [PATCH 035/361] Fix wrong stAddValue() info caused by reading old code. --- centrallix-sysdoc/OSDriver_Authoring.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index a6b3f783f..2b3dd707e 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -1053,20 +1053,7 @@ This function adds a node of type `ST_T_SUBGROUP` to either an `ST_T_SUBGROUP` o ```c int stAddValue(pStructInf inf, char* strval, int intval); ``` -This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If `strval` is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. If the string is non-static, and has a lifetime less than the `ST_T_ATTRIB` tree node, then the following procedure should be used to allocate a new string which will have the correct lifetime: (In this example, `str` is the string pointer to the string.) - -```c -pStructInf attr_inf = stAddAttr(my_parent_inf, "my_attr"); -if (attr_inf == NULL) goto error_handling; - -char* new_str = (char*)nmSysMalloc(strlen(str) + 1lu); -if (new_str == NULL) goto error_handling; -strcpy(new_str, str); -stAddValue(attr_inf, new_str, 0); -attr_inf->StrAlloc[0] = 1; -``` - -With this method (making a copy of the string and then setting the StrAlloc value for that string), the string is automatically freed when the StructInf tree node is freed by the stparse module. +This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If `strval` is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. From d0d4f544435897ddc10c953c78f9302527e3da2f Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 11 Dec 2025 17:38:17 -0700 Subject: [PATCH 036/361] Clean up stale TOODs. --- centrallix-sysdoc/OSDriver_Authoring.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 2b3dd707e..4b90a117b 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -214,15 +214,8 @@ Using the example above, we can query from the database using a statement like ` ## II Interface This section describes the standard interface between the OSML and the ObjectSystem driver itself. Every driver should implement certain required functions. (**Note**: Many drivers "implement" some required functions to simply fail with a not implemented or not supported error. For example, most database drivers "implement" `Read()` and `Write()` this way because database content should be queried, not read). Various optional functions are also available, which a driver is not required to implement. - The driver should implement an `Initialize()` function, as well as the following (* indicates required functions): + | Function Name | Description | --------------------------------------------------------- | ------------ | [Open](#function-open)* | Opens a new driver instance object on a given node object. @@ -485,7 +478,6 @@ int xxxDeleteObj(void* inf_v, pObjTrxTree* oxt); ```c int xxxRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt); ``` - The `Read()` function reads content from objects that have content, similar to reading content from a file. If the object does or can have content, the driver should handle these functions as is appropriate. Otherwise, the driver should return a failure code (-1) and call `mssError()` in these functions. @@ -511,7 +503,6 @@ Each of these routines should return -1 on failure and return the number of byte ```c int xxxWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt); ``` - The `Write()` function is very similar to the `Read()` function above, allowing the caller to write data to objects of supporting drivers with content. However, the third argument (`max_cnt`) is replaced with `cnt`, specifying the number of bytes of data in the buffer that should be written. @@ -638,8 +629,6 @@ The following five attributes are required (all are of type `DATA_T_STRING`): The `last_modification : DATA_T_DATETIME` attribute is a sixth, optional attribute that may be useful in some situations. This attribute should indicate the last time that the object's content was modified or updated. - - ### Function: GetAttrType() ```c From 3b866275fdfd0c8d805b0008c10e8f3d7502bc57 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 12 Dec 2025 12:36:39 -0700 Subject: [PATCH 037/361] Fix more styling mistakes. --- centrallix-lib/include/glyph.h | 1 + centrallix-lib/src/clusters.c | 69 ++++++--- centrallix-lib/src/util.c | 42 +++-- centrallix/expression/exp_functions.c | 130 +++++++++------- centrallix/objectsystem/obj_datatypes.c | 6 +- centrallix/osdrivers/objdrv_cluster.c | 198 +++++++++++++++++------- centrallix/utility/double_metaphone.c | 56 +++++-- 7 files changed, 342 insertions(+), 160 deletions(-) diff --git a/centrallix-lib/include/glyph.h b/centrallix-lib/include/glyph.h index 649437fac..4636d6b47 100644 --- a/centrallix-lib/include/glyph.h +++ b/centrallix-lib/include/glyph.h @@ -41,6 +41,7 @@ #ifdef ENABLE_GLYPHS #define glyph_print(s) printf("%s", s); + /*** Initialize a simple debug visualizer to make pretty patterns in the *** developer's terminal. Great for when you need to run a long task and *** want a super simple way to make sure it's still working. diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index d404e863e..505c3c272 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -56,7 +56,8 @@ *** @param c2 The second character in the pair. *** @returns The resulting hash. ***/ -static unsigned int hash_char_pair(const unsigned char c1, const unsigned char c2) +static unsigned int +hash_char_pair(const unsigned char c1, const unsigned char c2) { const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); @@ -88,7 +89,8 @@ typedef struct *** An int < 0 if p2's hash is larger. *** 0 if p1 and p2 have identical hashes. ***/ -static int charpair_cmp(const void *p1, const void *p2) +static int +charpair_cmp(const void *p1, const void *p2) { const CharPair *a = p1, *b = p2; return a->hash - b->hash; @@ -139,7 +141,8 @@ static int charpair_cmp(const void *p1, const void *p2) *** @param str The string to be divided into pairs and hashed to make the vector. *** @returns The sparse vector built using the hashed character pairs. ***/ -pVector ca_build_vector(const char* str) +pVector +ca_build_vector(const char* str) { unsigned char* chars = NULL; CharPair* char_pairs = NULL; @@ -253,9 +256,12 @@ pVector ca_build_vector(const char* str) *** *** @param sparse_vector The sparse vector being freed. ***/ -void ca_free_vector(pVector sparse_vector) +void +ca_free_vector(pVector sparse_vector) { nmSysFree(sparse_vector); + + return; } /*** Compute the length of a sparsely allocated vector. @@ -263,7 +269,8 @@ void ca_free_vector(pVector sparse_vector) *** @param vector The vector. *** @returns The computed length. ***/ -unsigned int ca_sparse_len(const pVector vector) +unsigned int +ca_sparse_len(const pVector vector) { unsigned int i = 0u; @@ -286,13 +293,16 @@ unsigned int ca_sparse_len(const pVector vector) *** *** @param vector The vector. ***/ -void ca_print_vector(const pVector vector) +void +ca_print_vector(const pVector vector) { const unsigned int len = ca_sparse_len(vector); printf("Vector: [%d", vector[0]); for (unsigned int i = 1u; i < len; i++) printf(", %d", vector[i]); printf("]"); + + return; } /*** Compute the magnitude of a sparsely allocated vector. @@ -300,7 +310,8 @@ void ca_print_vector(const pVector vector) *** @param vector The vector. *** @returns The computed magnitude. ***/ -static double magnitude_sparse(const pVector vector) +static double +magnitude_sparse(const pVector vector) { unsigned int magnitude = 0u; @@ -323,7 +334,8 @@ static double magnitude_sparse(const pVector vector) *** @param centroid The centroid. *** @returns The computed magnitude. ***/ -static double magnitude_dense(const pCentroid centroid) +static double +magnitude_dense(const pCentroid centroid) { double magnitude = 0.0; @@ -340,7 +352,8 @@ static double magnitude_dense(const pCentroid centroid) *** @param remaining The location to save the remaining number of characters. *** @param param_value The location to save the param_value of the token. ***/ -static void parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) +static void +parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) { if (token < 0) { @@ -354,6 +367,8 @@ static void parse_vector_token(const int token, unsigned int* remaining, unsigne *remaining = 1u; *param_value = (unsigned)(token); } + + return; } /*** Calculate the similarity on sparsely allocated vectors. Comparing @@ -365,7 +380,8 @@ static void parse_vector_token(const int token, unsigned int* remaining, unsigne *** 1 indicates identical and *** 0 indicates completely different. ***/ -static double sparse_similarity(const pVector v1, const pVector v2) +static double +sparse_similarity(const pVector v1, const pVector v2) { /** Calculate dot product. **/ unsigned int vec1_remaining = 0u, vec2_remaining = 0u; @@ -416,7 +432,8 @@ static double sparse_similarity(const pVector v1, const pVector v2) *** 1 indicates identical and *** 0 indicates completely different. ***/ -static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) +static double +sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) { double dot_product = 0.0; @@ -462,7 +479,8 @@ static double sparse_similarity_to_centroid(const pVector v1, const pCentroid c2 *** @attention - `Complexity`: O(nm), where n and m are the lengths of str1 *** and str2 (respectively). ***/ -int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) +int +ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) { int result = -1; unsigned int** lev_matrix = NULL; @@ -574,7 +592,8 @@ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, c *** @param v2 A `pVector` to the second string to compare. *** @returns The cosine similarity between the two strings. ***/ -double ca_cos_compare(void* v1, void* v2) +double +ca_cos_compare(void* v1, void* v2) { if (v1 == v2) return 1.0; @@ -603,7 +622,8 @@ double ca_cos_compare(void* v1, void* v2) *** @param str2 A `char*` to the second string to compare. *** @returns The levenshtein similarity between the two strings, or NAN on failure. ***/ -double ca_lev_compare(void* str1, void* str2) +double +ca_lev_compare(void* str1, void* str2) { /** Input validation checks. **/ if (str1 == NULL || str2 == NULL) return 0.0; @@ -634,7 +654,8 @@ double ca_lev_compare(void* str1, void* str2) *** @returns true if they are equal, *** false if any element is different. ***/ -bool ca_eql(pVector v1, pVector v2) +bool +ca_eql(pVector v1, pVector v2) { const unsigned int len = ca_sparse_len(v1); @@ -653,7 +674,8 @@ bool ca_eql(pVector v1, pVector v2) *** @param num_clusters The number of centroids (k). *** @returns The average cluster size. ***/ -static double get_cluster_size( +static double +get_cluster_size( pVector* vectors, const unsigned int num_vectors, unsigned int* labels, @@ -748,7 +770,8 @@ static double get_cluster_size( *** *** - `O(nk + nd)` ***/ -int ca_kmeans( +int +ca_kmeans( pVector* vectors, const unsigned int num_vectors, const unsigned int num_clusters, @@ -924,7 +947,8 @@ int ca_kmeans( *** @returns A pointer to the most similar piece of data found in the data *** array, or NULL if the most similar data did not meet the threshold. ***/ -void* ca_most_similar( +void* +ca_most_similar( void* target, void** data, const unsigned int num_data, @@ -934,10 +958,11 @@ void* ca_most_similar( void* most_similar = NULL; double best_sim = -INFINITY; + /** Iterate over all data options to find the one with the highest similarity. **/ for (unsigned int i = 0u; (num_data == 0u) ? (data[i] != NULL) : (i < num_data); i++) { const double sim = check_double(similarity(target, data[i])); - if (isnan(sim)) continue; /* Skip this comparison. */ + if (isnan(sim)) continue; /* Skip failed comparison. */ if (sim > best_sim && sim > threshold) { most_similar = data[i]; @@ -970,7 +995,8 @@ void* ca_most_similar( *** @returns An xArray holding all of the duplocates found, or NULL if an *** error occurs. ***/ -pXArray ca_sliding_search( +pXArray +ca_sliding_search( void** data, const unsigned int num_data, const unsigned int window_size, @@ -1051,7 +1077,8 @@ pXArray ca_sliding_search( *** @returns An xArray holding all of the duplocates found. If maybe_dups is *** not NULL, this will be that xArray, to allow for chaining. ***/ -pXArray ca_complete_search( +pXArray +ca_complete_search( void** data, const unsigned int num_data, const double (*similarity)(void*, void*), diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 6dbc8bd22..6e35fb99b 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -111,7 +111,8 @@ static char* units_metric[N_UNITS] = {"bytes", "KB", "MB", "GB"}; *** to the buffer.. *** @returns buf, for chaining. ***/ -char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) +char* +snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) { char** units = (USE_METRIC) ? units_metric : units_cs; const double unit_size = (USE_METRIC) ? 1000.0 : 1024.0; @@ -148,7 +149,8 @@ char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) *** @param value The value to write into the buffer. *** @returns `buf`, or `NULL` if `buf_size` is 0. */ -char* snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) +char* +snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) { if (buf_size == 0) return NULL; if (value == 0) @@ -175,7 +177,8 @@ char* snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) return buf; } -void fprint_mem(FILE* out) +void +fprint_mem(FILE* out) { FILE* fp = fopen("/proc/self/statm", "r"); if (fp == NULL) { perror("fopen()"); return; } @@ -199,9 +202,12 @@ void fprint_mem(FILE* out) fprintf(out, "Memory used: %ld bytes (%s)\n", resident_bytes, buf); fprintf(out, "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", share, text, lib, data); + + return; } -static double get_time(void) +static double +get_time(void) { struct timespec ts; @@ -210,7 +216,8 @@ static double get_time(void) return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9f; } -pTimer timer_init(pTimer timer) +pTimer +timer_init(pTimer timer) { if (timer == NULL) return NULL; timer->start = NAN; @@ -219,12 +226,14 @@ pTimer timer_init(pTimer timer) return timer; } -pTimer timer_new(void) +pTimer +timer_new(void) { return timer_init(nmMalloc(sizeof(Timer))); } -pTimer timer_start(pTimer timer) +pTimer +timer_start(pTimer timer) { if (!timer) return timer; timer->start = get_time(); @@ -232,7 +241,8 @@ pTimer timer_start(pTimer timer) return timer; } -pTimer timer_stop(pTimer timer) +pTimer +timer_stop(pTimer timer) { if (!timer) return timer; timer->total += get_time() - timer->start; @@ -240,22 +250,28 @@ pTimer timer_stop(pTimer timer) return timer; } -double timer_get(pTimer timer) +double +timer_get(pTimer timer) { return (timer) ? timer->total : NAN; } -pTimer timer_reset(pTimer timer) +pTimer +timer_reset(pTimer timer) { return timer_init(timer); } -void timer_de_init(pTimer timer) {} +void +timer_de_init(pTimer timer) {} -void timer_free(pTimer timer) +void +timer_free(pTimer timer) { timer_de_init(timer); nmFree(timer, sizeof(Timer)); + + return; } /*** Function for failing on error, assuming the error came from a library or @@ -271,4 +287,6 @@ void print_err(int code, const char* function_name, const char* file_name, const if (errno != 0) perror(error_buf); else if (code != 0) fprintf(stderr, "%s (error code %d).\n", error_buf, code); else fprintf(stderr, "%s.\n", error_buf); + + return; } diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 772672bca..70bbf480d 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -127,7 +127,8 @@ typedef struct *** @returns 0 if the expectations are successfully met, *** -1 if an expectation is violated (and mssError() is called). ***/ -static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) +static int +exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) { /** The expectation struct cannot be NULL. **/ if (arg_expect == NULL) @@ -388,7 +389,8 @@ static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgEx *** } *** ``` ***/ -static int exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) +static int +exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) { /** Verify expression tree. **/ ASSERTMAGIC(tree, MGK_EXPRESSION); @@ -460,35 +462,36 @@ static int exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree *** -1 on failure, *** 1 if the expression is NULL. ***/ -static int exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) +static int +exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) { - /** Check for null values. **/ - if (numeric_expr == NULL || numeric_expr->Flags & EXPR_F_NULL) return 1; - - /** Check for null destination. **/ - if (result_ptr == NULL) - { - mssError(1, "EXP", "Null location provided to store numeric result."); - return -1; - } - - /** Get the numeric value. **/ - double n; - switch(numeric_expr->DataType) - { - case DATA_T_INTEGER: n = numeric_expr->Integer; break; - case DATA_T_DOUBLE: n = numeric_expr->Types.Double; break; - case DATA_T_MONEY: n = objDataToDouble(DATA_T_MONEY, &(numeric_expr->Types.Money)); break; - default: - mssError(1, "EXP", - "%s (%d) is not a numeric type.", - objTypeToStr(numeric_expr->DataType), numeric_expr->DataType - ); + /** Check for null values. **/ + if (numeric_expr == NULL || numeric_expr->Flags & EXPR_F_NULL) return 1; + + /** Check for null destination. **/ + if (result_ptr == NULL) + { + mssError(1, "EXP", "Null location provided to store numeric result."); return -1; - } - - /** Store the result. **/ - *result_ptr = n; + } + + /** Get the numeric value. **/ + double n; + switch(numeric_expr->DataType) + { + case DATA_T_INTEGER: n = numeric_expr->Integer; break; + case DATA_T_DOUBLE: n = numeric_expr->Types.Double; break; + case DATA_T_MONEY: n = objDataToDouble(DATA_T_MONEY, &(numeric_expr->Types.Money)); break; + default: + mssError(1, "EXP", + "%s (%d) is not a numeric type.", + objTypeToStr(numeric_expr->DataType), numeric_expr->DataType + ); + return -1; + } + + /** Store the result. **/ + *result_ptr = n; return 0; } @@ -497,7 +500,8 @@ static int exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) *** *** @param tree The affected tree. ***/ -static void exp_fn_i_free_result_string(pExpression tree) +static void +exp_fn_i_free_result_string(pExpression tree) { /** If no string is allocated, no work is needed. **/ if (tree->Alloc == 0) return; @@ -507,6 +511,8 @@ static void exp_fn_i_free_result_string(pExpression tree) /** No string is allocated anymore. */ tree->Alloc = 0; + + return; } /*** Ensure that the allocated result string is long enough to store a given @@ -518,7 +524,8 @@ static void exp_fn_i_free_result_string(pExpression tree) *** @returns 0 if successful, or *** -1 if an error occurs. ***/ -static int exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) +static int +exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) { /** Free the previous string (if needed) so we can store a new one. **/ exp_fn_i_free_result_string(tree); @@ -1589,7 +1596,8 @@ int exp_fn_reverse(pExpression tree, pParamObjects objlist, pExpression i0, pExp } /** Leading zero trim. */ -int exp_fn_lztrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +int +exp_fn_lztrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { /** Expect one nullable string parameter. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -1630,7 +1638,8 @@ int exp_fn_lztrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpr /** Left trim spaces. **/ -int exp_fn_ltrim(pExpression tree) +int +exp_fn_ltrim(pExpression tree) { /** Expect one nullable string parameter. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -1672,7 +1681,8 @@ int exp_fn_ltrim(pExpression tree) /** Right trim spaces. **/ -int exp_fn_rtrim(pExpression tree) +int +exp_fn_rtrim(pExpression tree) { /** Expect one nullable string parameter. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -1723,7 +1733,8 @@ int exp_fn_rtrim(pExpression tree) /** Left and right trim spaces. **/ -int exp_fn_trim(pExpression tree) +int +exp_fn_trim(pExpression tree) { /** Left trim the expression. **/ exp_fn_ltrim(tree); @@ -3768,7 +3779,8 @@ int exp_fn_from_base64(pExpression tree, pParamObjects objlist, pExpression i0, return -1; } -static int exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) +static int +exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) { /** Verify function schema: expect arg_num numeric values. **/ ArgExpect expects[arg_num + 1]; @@ -3818,20 +3830,26 @@ static int exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) return 0; } -int exp_fn_power(pExpression tree) +int +exp_fn_power(pExpression tree) { return exp_fn_i_do_math(tree, pow, 2); } -int exp_fn_ln(pExpression tree) + +int +exp_fn_ln(pExpression tree) { return exp_fn_i_do_math(tree, log, 1); } -int exp_fn_log10(pExpression tree) + +int +exp_fn_log10(pExpression tree) { return exp_fn_i_do_math(tree, log10, 1); } -int exp_fn_log(pExpression tree) +int +exp_fn_log(pExpression tree) { /** Verify function schema: A number and an optional base. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -4528,17 +4546,16 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress } -int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) +int +exp_fn_metaphone(pExpression tree, pParamObjects obj_list) { - const char fn_name[] = "metaphone"; - /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -4586,7 +4603,8 @@ int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) *** @param fn_name Either `cos_compare()` or `lev_compare()`. *** @returns 0 for success, -1 for failure. ***/ -static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) +static int +exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) { /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -4654,25 +4672,25 @@ static int exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* tree->DataType = DATA_T_DOUBLE; return 0; } - - return -1; + + return -1; /* Unreachable. */ } - -int exp_fn_cos_compare(pExpression tree, pParamObjects obj_list) +int +exp_fn_cos_compare(pExpression tree, pParamObjects obj_list) { return exp_fn_compare(tree, obj_list, "cos_compare"); } -int exp_fn_lev_compare(pExpression tree, pParamObjects obj_list) + +int +exp_fn_lev_compare(pExpression tree, pParamObjects obj_list) { return exp_fn_compare(tree, obj_list, "lev_compare"); } - -int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) +int +exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) { - const char fn_name[] = "levenshtein"; - /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, @@ -4680,7 +4698,7 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -4701,7 +4719,7 @@ int exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); if (!check_neg(edit_dist)) { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", fn_name, str1, str2); + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); return -1; } diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index 4510e2116..5d36089a1 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -139,7 +139,8 @@ char* obj_default_null_fmt = "NULL"; *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -int objTypeFromStr(const char* str) +int +objTypeFromStr(const char* str) { /** All valid types are non-null strings, at least 2 characters long. **/ if (str == NULL || str[0] == '\0' || str[1] == '\0') return -1; @@ -197,7 +198,8 @@ int objTypeFromStr(const char* str) *** "(unknown)" if the type is unknown, or *** "invalid" if the type number cannot even be a valid type. ***/ -char* objTypeToStr(const int type) +char* +objTypeToStr(const int type) { /** Guard out of bounds reads. **/ if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index a30ba2d84..94a58cf5f 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -101,7 +101,8 @@ *** @param ... Variables matching format specifiers in the format. *** @returns Nothing, always succeeds. ***/ -void mssErrorf(int clr, char* module, const char* format, ...) +void +mssErrorf(int clr, char* module, const char* format, ...) { /** Prevent interlacing with stdout flushing at a weird time. **/ check(fflush(stdout)); /* Failure ignored. */ @@ -131,6 +132,8 @@ void mssErrorf(int clr, char* module, const char* format, ...) /** Not sure why you have to error check the error function... **/ if (ret != 0) fprintf(stderr, "FAIL %d: mssError(%d, \"%s\", \"%%s\", \"%s\")\n", ret, clr, module, buf); + + return; } @@ -144,7 +147,8 @@ void mssErrorf(int clr, char* module, const char* format, ...) *** *: Any other value prints a warning and does nothing. *** @returns The new array, or null if and only if the passed pXArray has 0 items. ***/ -static void** ci_xaToTrimmedArray(pXArray arr, int array_handling) +static void** +ci_xaToTrimmedArray(pXArray arr, int array_handling) { const size_t arr_size = arr->nItems * sizeof(void*); void** result = check_ptr(nmSysMalloc(arr_size)); @@ -208,7 +212,8 @@ ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[nClusteringAlgorithms] = }; /** Converts a clustering algorithm to its string name. **/ -char* ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) +char* +ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) { switch (clustering_algorithm) { @@ -221,6 +226,8 @@ char* ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) case ALGORITHM_DB_SCAN: return "db-scan"; default: return "Unknown algorithm"; } + + return; /** Unreachable. **/ } /** Enum representing a similarity measurement algorithm. **/ @@ -238,7 +245,8 @@ SimilarityMeasure ALL_SIMILARITY_MEASURES[nSimilarityMeasures] = }; /** Converts a similarity measure to its string name. **/ -char* ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) +char* +ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) { switch (similarity_measure) { @@ -247,6 +255,8 @@ char* ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) case SIMILARITY_LEVENSHTEIN: return "levenshtein"; default: return "Unknown similarity measure"; } + + return; /** Unreachable. **/ } /*** Enum representing the type of data targetted by the driver, @@ -690,6 +700,8 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt); static void ci_GiveHint(const char* hint) { fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); + + return; } @@ -703,7 +715,8 @@ static void ci_GiveHint(const char* hint) *** length on a null terminated array of values. *** @returns Whether a hint was given. ***/ -static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) +static bool +ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) { char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.25); if (guess == NULL) return false; /* No hint. */ @@ -727,7 +740,8 @@ static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_va *** is NOT REQUIRED for dynamic attributes in the cluster driver. I had to debug *** and rewrite this for ages and it uses several functions I don't 100% understand. ***/ -static int ci_ParseAttribute( +static int +ci_ParseAttribute( pStructInf inf, char* attr_name, int datatype, @@ -809,7 +823,8 @@ static int ci_ParseAttribute( *** evaluating parameter variables in the structure file. *** @returns The data algorithm, or ALGORITHM_NULL on failure. ***/ -static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) +static ClusterAlgorithm +ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) { /** Get the algorithm attribute. **/ char* algorithm; @@ -857,7 +872,8 @@ static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf inf, pParamObject *** evaluating parameter variables in the structure file. *** @returns The similarity measure, or SIMILARITY_NULL on failure. ***/ -static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) +static SimilarityMeasure +ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) { /** Get the similarity_measure attribute. **/ char* measure; @@ -903,7 +919,8 @@ static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects *** cache entry keys. *** @returns A new pSourceData struct on success, or NULL on failure. ***/ -static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) +static pSourceData +ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) { char* buf = NULL; pSourceData source_data = NULL; @@ -995,7 +1012,8 @@ static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, *** used to generate cache entry keys. *** @returns A new pClusterData struct on success, or NULL on failure. ***/ -static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) +static pClusterData +ci_ParseClusterData(pStructInf inf, pNodeData node_data) { int result; pClusterData cluster_data = NULL; @@ -1294,7 +1312,8 @@ static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data) *** the cluster pointed to by the source attribute. *** @returns A new pSearchData struct on success, or NULL on failure. ***/ -static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) +static pSearchData +ci_ParseSearchData(pStructInf inf, pNodeData node_data) { pSearchData search_data = NULL; char* key = NULL; @@ -1471,7 +1490,8 @@ static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data) *** @param parent The parent object struct. *** @returns A new pNodeData struct on success, or NULL on failure. ***/ -static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) +static pNodeData +ci_ParseNodeData(pStructInf inf, pObject parent) { int ret = -1; pNodeData node_data = NULL; @@ -1758,7 +1778,8 @@ static pNodeData ci_ParseNodeData(pStructInf inf, pObject parent) // LINK #functions /** @param source_data A pSourceData struct, freed by this function. **/ -static void ci_FreeSourceData(pSourceData source_data) +static void +ci_FreeSourceData(pSourceData source_data) { /** Guard segfault. **/ if (source_data == NULL) @@ -1820,6 +1841,8 @@ static void ci_FreeSourceData(pSourceData source_data) /** Free the source data struct. **/ nmFree(source_data, sizeof(SourceData)); source_data = NULL; + + return; } @@ -1829,7 +1852,8 @@ static void ci_FreeSourceData(pSourceData source_data) *** @param cluster_data The cluster data struct to free. *** @param recursive Whether to recursively free subclusters. ***/ -static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) +static void +ci_FreeClusterData(pClusterData cluster_data, bool recursive) { /** Guard segfault. **/ if (cluster_data == NULL) @@ -1883,12 +1907,15 @@ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) /** Free the cluster data struct. **/ nmFree(cluster_data, sizeof(ClusterData)); cluster_data = NULL; + + return; } // LINK #functions /** @param search_data A pSearchData struct, freed by this function. **/ -static void ci_FreeSearchData(pSearchData search_data) +static void +ci_FreeSearchData(pSearchData search_data) { /** Guard segfault. **/ if (search_data == NULL) @@ -1919,12 +1946,15 @@ static void ci_FreeSearchData(pSearchData search_data) /** Free the search data struct. **/ nmFree(search_data, sizeof(SearchData)); search_data = NULL; + + return; } // LINK #functions /** @param node_data A pNodeData struct, freed by this function. **/ -static void ci_FreeNodeData(pNodeData node_data) +static void +ci_FreeNodeData(pNodeData node_data) { /** Guard segfault. **/ if (node_data == NULL) @@ -1991,10 +2021,13 @@ static void ci_FreeNodeData(pNodeData node_data) /** Free the node data. **/ nmFree(node_data, sizeof(NodeData)); node_data = NULL; + + return; } /** Frees all data in caches for all cluster driver instances. **/ -static void ci_ClearCaches(void) +static void +ci_ClearCaches(void) { /*** Free caches in reverse of the order they are created in case *** cached data relies on its source during the freeing process. @@ -2002,6 +2035,8 @@ static void ci_ClearCaches(void) check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, ci_CacheFreeSearch, NULL)); /* Failure ignored. */ check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, ci_CacheFreeCluster, NULL)); /* Failure ignored. */ check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, ci_CacheFreeSourceData, NULL)); /* Failure ignored. */ + + return; } @@ -2019,7 +2054,8 @@ static void ci_ClearCaches(void) *** @param source_data The source data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -static unsigned int ci_SizeOfSourceData(pSourceData source_data) +static unsigned int +ci_SizeOfSourceData(pSourceData source_data) { /** Guard segfault. **/ if (source_data == NULL) @@ -2063,7 +2099,8 @@ static unsigned int ci_SizeOfSourceData(pSourceData source_data) *** @param recursive Whether to recursively free subclusters. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) +static unsigned int +ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) { /** Guard segfault. **/ if (cluster_data == NULL) @@ -2107,7 +2144,8 @@ static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursi *** @param search_data The search data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -static unsigned int ci_SizeOfSearchData(pSearchData search_data) +static unsigned int +ci_SizeOfSearchData(pSearchData search_data) { /** Guard segfault. **/ if (search_data == NULL) @@ -2140,7 +2178,8 @@ static unsigned int ci_SizeOfSearchData(pSearchData search_data) *** @returns 0 if successful, or *** -1 other value on failure. ***/ -static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) +static int +ci_ComputeSourceData(pSourceData source_data, pObjSession session) { bool successful = false; int ret; @@ -2467,7 +2506,8 @@ static int ci_ComputeSourceData(pSourceData source_data, pObjSession session) *** @returns 0 if successful, or *** -1 other value on failure. ***/ -static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) +static int +ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { cluster_data->Sims = NULL; cluster_data->Clusters = NULL; @@ -2637,7 +2677,8 @@ static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) *** @returns 0 if successful, or *** -1 other value on failure. ***/ -static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) +static int +ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { pXArray dups = NULL; @@ -2803,7 +2844,8 @@ static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -static int ci_GetParamType(void* inf_v, const char* attr_name) +static int +ci_GetParamType(void* inf_v, const char* attr_name) { pNodeData node_data = (pNodeData)inf_v; @@ -2846,7 +2888,8 @@ static int ci_GetParamType(void* inf_v, const char* attr_name) *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) +static int +ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { pNodeData node_data = (pNodeData)inf_v; @@ -2881,7 +2924,8 @@ static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData // LINK #functions /** Not implemented. **/ -static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) +static int +ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are imutable."); @@ -2908,7 +2952,8 @@ static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData *** @returns A pDriverData struct representing a driver instance, or *** NULL if an error occurs. ***/ -void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) +void* +clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { pNodeData node_data = NULL; pDriverData driver_data = NULL; @@ -3075,7 +3120,8 @@ void* clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_typ *** @param oxt The object system tree, similar to a kind of "scope" (unused). *** @returns 0, success. ***/ -int clusterClose(void* inf_v, pObjTrxTree* oxt) +int +clusterClose(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -3109,7 +3155,8 @@ int clusterClose(void* inf_v, pObjTrxTree* oxt) *** @returns The cluster query, or *** NULL if an error occurs. ***/ -void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) +void* +clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) { pClusterQuery cluster_query = NULL; pDriverData driver_data = inf_v; @@ -3149,7 +3196,8 @@ void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) *** pointing to a specific target index into the relevant data. *** OR NULL, indicating that all data has been fetched. ***/ -void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) +void* +clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { pClusterQuery cluster_query = (pClusterQuery)qy_v; pDriverData driver_data = cluster_query->DriverData; @@ -3281,7 +3329,8 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) *** @param oxt The object system tree, similar to a kind of "scope" (unused). *** @returns 0, success. ***/ -int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) +int +clusterQueryClose(void* qy_v, pObjTrxTree* oxt) { if (qy_v != NULL) nmFree(qy_v, sizeof(ClusterQuery)); @@ -3299,7 +3348,8 @@ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) +int +clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -3407,7 +3457,8 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ -int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) +int +clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -3743,7 +3794,8 @@ int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val *** @returns A presentation hints object, if successful, *** NULL if an error occurs. ***/ -pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) +pObjPresentationHints +clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; pObjPresentationHints hints = NULL; @@ -4047,7 +4099,8 @@ pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pOb *** @param oxt Unused. *** @returns The name of the first attribute. ***/ -char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) +char* +clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -4067,7 +4120,8 @@ char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) *** @param oxt Unused. *** @returns The name of the next attribute. ***/ -char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) +char* +clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -4083,6 +4137,8 @@ char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); return NULL; } + + return; /* Unreachable. */ } @@ -4094,7 +4150,8 @@ char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) *** @returns 0 if successful, *** -1 if the driver is an unimplemented type (should never happen). ***/ -int clusterInfo(void* inf_v, pObjectInfo info) +int +clusterInfo(void* inf_v, pObjectInfo info) { pDriverData driver_data = (pDriverData)inf_v; pNodeData node_data = (pNodeData)driver_data->NodeData; @@ -4180,7 +4237,8 @@ int clusterInfo(void* inf_v, pObjectInfo info) *** @param oxt Unused. *** @returns The name of the first method. ***/ -char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) +char* +clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -4200,7 +4258,8 @@ char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) *** @param oxt Unused. *** @returns The name of the next method. ***/ -char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) +char* +clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -4210,7 +4269,8 @@ char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions /** Intended for use in xhForEach(). **/ -static int ci_PrintEntry(pXHashEntry entry, void* arg) +static int +ci_PrintEntry(pXHashEntry entry, void* arg) { /** Extract entry. **/ char* key = entry->Key; @@ -4300,7 +4360,8 @@ static int ci_PrintEntry(pXHashEntry entry, void* arg) // LINK #functions /** Intended for use in xhClearKeySafe(). **/ -static void ci_CacheFreeSourceData(pXHashEntry entry, void* path) +static void +ci_CacheFreeSourceData(pXHashEntry entry, void* path) { /** Extract hash entry. **/ char* key = entry->Key; @@ -4312,12 +4373,15 @@ static void ci_CacheFreeSourceData(pXHashEntry entry, void* path) /** Free data. **/ ci_FreeSourceData(source_data); nmSysFree(key); + + return; } // LINK #functions /** Intended for use in xhClearKeySafe(). **/ -static void ci_CacheFreeCluster(pXHashEntry entry, void* path) +static void +ci_CacheFreeCluster(pXHashEntry entry, void* path) { /** Extract hash entry. **/ char* key = entry->Key; @@ -4329,12 +4393,15 @@ static void ci_CacheFreeCluster(pXHashEntry entry, void* path) /** Free data. **/ ci_FreeClusterData(cluster_data, false); nmSysFree(key); + + return; } // LINK #functions /** Intended for use in xhClearKeySafe(). **/ -static void ci_CacheFreeSearch(pXHashEntry entry, void* path) +static void +ci_CacheFreeSearch(pXHashEntry entry, void* path) { /** Extract hash entry. **/ char* key = entry->Key; @@ -4346,6 +4413,8 @@ static void ci_CacheFreeSearch(pXHashEntry entry, void* path) /** Free data. **/ ci_FreeSearchData(search_data); nmSysFree(key); + + return; } @@ -4357,7 +4426,8 @@ static void ci_CacheFreeSearch(pXHashEntry entry, void* path) *** @param param A possibly optional param passed to the method. *** @param oxt The object system tree, similar to a kind of "scope" (unused). ***/ -int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) +int +clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) { pDriverData driver_data = (pDriverData)inf_v; @@ -4496,64 +4566,81 @@ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrx // LINK #functions /** Not implemented. **/ -int clusterCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) +int +clusterCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterCreate() is not implemented."); return -ENOSYS; } + /** Not implemented. **/ -int clusterDelete(pObject obj, pObjTrxTree* oxt) +int +clusterDelete(pObject obj, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterDelete() is not implemented."); return -1; } + /** Not implemented. **/ -int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) +int +clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterDeleteObj() is not implemented."); return -1; } + /** Not implemented. **/ -int clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt) +int +clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterRead() not implemented."); fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); return -1; } + /** Not implemented. **/ -int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt) +int +clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterWrite() not implemented because clusters are imutable."); return -1; } + /** Not implemented. **/ -int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) +int +clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are imutable."); return -1; } + /** Not implemented. **/ -int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt) +int +clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are imutable."); return -1; } + /** Not implemented. **/ -void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) +void* +clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterOpenAttr() not implemented."); return NULL; } + /** Not implemented. **/ -int clusterCommit(void* inf_v, pObjTrxTree* oxt) +int +clusterCommit(void* inf_v, pObjTrxTree* oxt) { mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are imutable."); @@ -4570,7 +4657,8 @@ int clusterCommit(void* inf_v, pObjTrxTree* oxt) *** @returns 0 if successful, or *** -1 if an error occurs. ***/ -int clusterInitialize(void) +int +clusterInitialize(void) { /** Allocate the driver. **/ pObjDriver drv = (pObjDriver)check_ptr(nmMalloc(sizeof(ObjDriver))); @@ -4637,8 +4725,8 @@ int clusterInitialize(void) /** Success. **/ return 0; - /** Error cleanup. **/ err_free: + /** Error cleanup. **/ if (ClusterDriverCaches.SourceDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SourceDataCache)); /* Failure ignored. **/ if (ClusterDriverCaches.ClusterDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.ClusterDataCache)); /* Failure ignored. **/ if (ClusterDriverCaches.SearchDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SearchDataCache)); /* Failure ignored. **/ diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 3faf27c3c..b767eeb64 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -127,7 +127,8 @@ *** @param size The amount of memory being allocated. *** @returns The pointer, for chaining. ***/ -void* meta_check_allocation(void* ptr, const char* fname, const size_t size) +void* +meta_check_allocation(void* ptr, const char* fname, const size_t size) { if (ptr == NULL) { @@ -170,7 +171,8 @@ MetaString; *** @param init_str The initial size of the string. *** @returns The new MetaString. ***/ -MetaString* meta_new_string(const char* init_str) +MetaString* +meta_new_string(const char* init_str) { MetaString *s; char empty_string[] = ""; @@ -196,7 +198,8 @@ MetaString* meta_new_string(const char* init_str) *** *** @param s The MetaString. ***/ -void meta_destroy_string(MetaString* s) +void +meta_destroy_string(MetaString* s) { if (s == NULL) return; @@ -205,6 +208,8 @@ void meta_destroy_string(MetaString* s) META_FREE(s->str); META_FREE(s); + + return; } /*** Increases a MetaString's buffer size. @@ -212,20 +217,26 @@ void meta_destroy_string(MetaString* s) *** @param s The MetaString* being modified. *** @param chars_needed Minimum number of characters to increase buffer size. ***/ -void meta_increase_buffer(MetaString* s, const size_t chars_needed) +void +meta_increase_buffer(MetaString* s, const size_t chars_needed) { s->bufsize += chars_needed + 8u; s->str = SAFE_REALLOC(s->str, s->bufsize * sizeof(char)); + + return; } /*** Convert all characters of a MetaString to uppercase. *** *** @param s The MetaString being modified. ***/ -void meta_make_upper(MetaString* s) +void +meta_make_upper(MetaString* s) { for (char* i = s->str; i[0] != '\0'; i++) *i = (char)toupper(*i); + + return; } /*** @param s The MetaString being checked. @@ -233,7 +244,8 @@ void meta_make_upper(MetaString* s) *** @returns 1 if the location is out of bounds for the MetaString, *** 0 otherwise. ***/ -bool meta_is_out_of_bounds(MetaString* s, unsigned int pos) +bool +meta_is_out_of_bounds(MetaString* s, unsigned int pos) { return (s->length <= pos); } @@ -243,7 +255,8 @@ bool meta_is_out_of_bounds(MetaString* s, unsigned int pos) *** @param s The MetaString being checked. *** @param pos The character location to check within the MetaString. ***/ -bool meta_is_vowel(MetaString* s, unsigned int pos) +bool +meta_is_vowel(MetaString* s, unsigned int pos) { if (meta_is_out_of_bounds(s, pos)) return 0; @@ -259,7 +272,8 @@ bool meta_is_vowel(MetaString* s, unsigned int pos) *** @param s The MetaString to be searched. *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. ***/ -bool meta_is_slavo_germanic(MetaString* s) +bool +meta_is_slavo_germanic(MetaString* s) { return (strstr(s->str, "W") != NULL) || (strstr(s->str, "K") != NULL) @@ -272,7 +286,8 @@ bool meta_is_slavo_germanic(MetaString* s) *** @returns The character at the position in the MetaString, or *** '\0' if the position is not in the MetaString. ***/ -char meta_get_char_at(MetaString* s, unsigned int pos) +char +meta_get_char_at(MetaString* s, unsigned int pos) { return (meta_is_out_of_bounds(s, pos)) ? '\0' : ((char) *(s->str + pos)); } @@ -289,7 +304,8 @@ char meta_get_char_at(MetaString* s, unsigned int pos) *** @returns 1 if any of the character sequences appear after the start *** in the MetaString and 0 otherwise. ***/ -bool meta_is_str_at(MetaString* s, unsigned int start, ...) +bool +meta_is_str_at(MetaString* s, unsigned int start, ...) { va_list ap; @@ -319,7 +335,8 @@ bool meta_is_str_at(MetaString* s, unsigned int start, ...) *** @param s The MetaString being modified. *** @param new_str The string being added. ***/ -void meta_add_str(MetaString* s, const char* new_str) +void +meta_add_str(MetaString* s, const char* new_str) { if (new_str == NULL) return; @@ -330,6 +347,8 @@ void meta_add_str(MetaString* s, const char* new_str) strcat(s->str, new_str); s->length += add_length; + + return; } /*** Computes double metaphone. @@ -347,7 +366,8 @@ void meta_add_str(MetaString* s, const char* new_str) *** @param secondary_code A pointer to a buffer where the pointer to a string *** containing the produced secondary code will be stored. ***/ -void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) +void +meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) { size_t length; @@ -1283,6 +1303,8 @@ void meta_double_metaphone(const char* str, char** primary_code, char** secondar meta_destroy_string(original); meta_destroy_string(primary); meta_destroy_string(secondary); + + return; } #ifdef TESTING @@ -1308,7 +1330,8 @@ void meta_double_metaphone(const char* str, char** primary_code, char** secondar unsigned int num_tests_passed = 0u, num_tests_failed = 0u; -void test(const char* input, const char* expected_primary, const char* expected_secondary) +void +test(const char* input, const char* expected_primary, const char* expected_secondary) { char* codes[2]; @@ -1337,13 +1360,16 @@ void test(const char* input, const char* expected_primary, const char* expected_ ); num_tests_failed++; } + + return; } // Special thanks to the following websites for double checking the correct results: // 1: https://words.github.io/double-metaphone // 2: https://mainegenealogy.net/metaphone_converter.asp // 3: https://en.toolpage.org/tool/metaphone -void run_tests(void) +void +run_tests(void) { printf("\nRunning tests...\n"); @@ -1526,6 +1552,8 @@ void run_tests(void) printf(" > Failed: %u\n", num_tests_failed); printf(" > Skipped: %u\n", 0u); /* Implementation removed. */ printf(" > Passed: %u/%u\n", num_tests_passed, total_tests); + + return; } int main(void) From 6b83c67007663018c4bad1e3d9e1e25ffbdd6026 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 15 Dec 2025 15:01:02 -0700 Subject: [PATCH 038/361] Fix indentation mistakes (thanks Centrallix Indent extension). --- centrallix-lib/src/util.c | 4 ++-- centrallix/expression/exp_functions.c | 10 +++++----- centrallix/objectsystem/obj_datatypes.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 24 ++++++++++++------------ centrallix/utility/double_metaphone.c | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 6e35fb99b..9dbc804d7 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -191,11 +191,11 @@ fprint_mem(FILE* out) check(fclose(fp)); /* Failure ignored. */ return; } - check(fclose(fp)); /* Failure ignored. */ + check(fclose(fp)); /* Failure ignored. */ long page_size = sysconf(_SC_PAGESIZE); // in bytes long resident_bytes = resident * page_size; - + const size_t buf_siz = 16u; char buf[buf_siz]; snprint_bytes(buf, buf_siz, (unsigned int)resident_bytes); diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 70bbf480d..3ee97016c 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -130,7 +130,7 @@ typedef struct static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) { - /** The expectation struct cannot be NULL. **/ + /** The expectation struct cannot be NULL. **/ if (arg_expect == NULL) { mssErrorf(1, "EXP", @@ -3782,7 +3782,7 @@ int exp_fn_from_base64(pExpression tree, pParamObjects objlist, pExpression i0, static int exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) { - /** Verify function schema: expect arg_num numeric values. **/ + /** Verify function schema: expect arg_num numeric values. **/ ArgExpect expects[arg_num + 1]; for (int i = 0; i < arg_num; i++) expects[i] = (ArgExpect){(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}; @@ -3804,7 +3804,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) return 0; } } - + /** Maximum supported args. **/ if (arg_num > 4) { @@ -4549,7 +4549,7 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress int exp_fn_metaphone(pExpression tree, pParamObjects obj_list) { - /** Verify function schema. **/ + /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, @@ -4722,7 +4722,7 @@ exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); return -1; } - + /** Return the computed distance. **/ tree->Integer = edit_dist; tree->DataType = DATA_T_INTEGER; diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index 5d36089a1..388a75e60 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -202,7 +202,7 @@ char* objTypeToStr(const int type) { /** Guard out of bounds reads. **/ - if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) + if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) { /** Invalid type. **/ mssErrorf(1, "Cluster", "Invalid type %d.\n", type); diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 94a58cf5f..b525dd9a1 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1353,7 +1353,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Attempt to give a hint. **/ char* cluster_names[node_data->nClusterDatas]; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) - cluster_names[i] = node_data->ClusterDatas[i]->Name; + cluster_names[i] = node_data->ClusterDatas[i]->Name; ci_TryHint(source_cluster_name, cluster_names, node_data->nClusterDatas); /** Fail. **/ @@ -1656,7 +1656,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) node_data->Params[i] = param; /** Check each provided param to see if the user provided value. **/ - for (unsigned int j = 0u; j < num_provided_params; j++) + for (unsigned int j = 0u; j < num_provided_params; j++) { pStruct provided_param = check_ptr(provided_params[j]); /* Failure ignored. */ @@ -2005,10 +2005,10 @@ ci_FreeNodeData(pNodeData node_data) /** Free data source, if one exists. **/ /*** Note: SourceData is freed last since other free functions may need to - *** access information from this structure when freeing data. - *** (For example, nVector which is used to determine the size of the - *** label struct in each cluster.) - ***/ + *** access information from this structure when freeing data. + *** (For example, nVector which is used to determine the size of the + *** label struct in each cluster.) + ***/ if (node_data->SourceData != NULL) { /*** This data is cached, so we should NOT free it! The caching system @@ -2436,7 +2436,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (key_xarray.nAlloc != 0) { for (unsigned int i = 0u; i < vector_xarray.nItems; i++) - { + { char* key = key_xarray.Items[i]; if (key != NULL) nmSysFree(key); else break; @@ -2790,7 +2790,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } default: - mssErrorf(1, "Cluster", + mssErrorf(1, "Cluster", "Unknown similarity meansure \"%s\".", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); @@ -4178,10 +4178,10 @@ clusterInfo(void* inf_v, pObjectInfo info) info->Flags |= OBJ_INFO_F_HAS_SUBOBJ; /* Data must not be empty. */ /*** Clusters always have one label per vector. - *** If we know how many vectors are in the dataset, - *** we know how many labels this cluster will have, - *** even if it hasn't been computed yet. - ***/ + *** If we know how many vectors are in the dataset, + *** we know how many labels this cluster will have, + *** even if it hasn't been computed yet. + ***/ if (node_data->SourceData->Vectors != NULL) { info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index b767eeb64..f37b0964c 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -587,7 +587,7 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** 'bellocchio' but not 'bacchus' **/ if ( meta_is_str_at(original, (current + 2), "I", "E", "H", "") - && !meta_is_str_at(original, (current + 2), "HU", "") + && !meta_is_str_at(original, (current + 2), "HU", "") ) { /** 'accident', 'accede' 'succeed' **/ From 66029f5859888050927f0e2c5e57e0771d2a633d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Jan 2026 10:21:58 -0700 Subject: [PATCH 039/361] Rename functions to use the proper prefix everywhere. Improve wording of cluster library description. Improve comments. --- centrallix-lib/include/clusters.h | 4 +- centrallix-lib/src/clusters.c | 75 +++++++++++++++++-------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 5aa3123e4..288f1c2c5 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -29,8 +29,8 @@ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description Clustering library used to cluster and search data with */ -/* cosine similarity and Levenshtein similarity (aka. edit */ -/* distance). Used by the "clustering driver". */ +/* cosine or Levenshtein (aka. edit distance) similarity */ +/* measures. Used by the "clustering driver". */ /* For more information on how to use this library, see */ /* string-similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 505c3c272..652d5571c 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -26,8 +26,8 @@ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description Clustering library used to cluster and search data with */ -/* cosine similarity and Levenshtein similarity (aka. edit */ -/* distance). Used by the "clustering driver". */ +/* cosine or Levenshtein (aka. edit distance) similarity */ +/* measures. Used by the "clustering driver". */ /* For more information on how to use this library, see */ /* string-similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ @@ -57,7 +57,7 @@ *** @returns The resulting hash. ***/ static unsigned int -hash_char_pair(const unsigned char c1, const unsigned char c2) +ca_hash_char_pair(const unsigned char c1, const unsigned char c2) { const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); @@ -71,7 +71,7 @@ hash_char_pair(const unsigned char c1, const unsigned char c2) *** @param c1 The first character in the character pair. *** @param c2 The second character in the character pair. *** @param hash The hash for the two characters, calculated by calling the - *** hash_char_pair() function (above). + *** ca_hash_char_pair() function (above). **/ typedef struct { @@ -90,7 +90,7 @@ typedef struct *** 0 if p1 and p2 have identical hashes. ***/ static int -charpair_cmp(const void *p1, const void *p2) +ca_char_pair_cmp(const void *p1, const void *p2) { const CharPair *a = p1, *b = p2; return a->hash - b->hash; @@ -149,11 +149,12 @@ ca_build_vector(const char* str) pVector sparse_vector = NULL; pVector trimmed_sparse_vector = NULL; + /** Allocate memory to store the characters. **/ unsigned int num_chars = 0u; chars = check_ptr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); if (chars == NULL) goto err_free; - /** Begin adding char pairs (in order). **/ + /** Store characters. **/ chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) { @@ -177,7 +178,8 @@ ca_build_vector(const char* str) } chars[num_chars++] = CA_BOUNDARY_CHAR; /* Ending boundary character. */ - /** Compute hash values for char pairs. **/ + + /** Compute character pair hashes. **/ char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); if (char_pairs == NULL) goto err_free; const unsigned int num_pairs = num_chars - 1u; @@ -189,21 +191,23 @@ ca_build_vector(const char* str) /** Hash the character pair into an index (dimension). **/ /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ - char_pairs[i].hash = hash_char_pair(chars[i], chars[i + 1]); + char_pairs[i].hash = ca_hash_char_pair(chars[i], chars[i + 1]); } /** Free unused memory. **/ nmSysFree(chars); chars = NULL; + /** Sort char_pairs by hash value. **/ - qsort(char_pairs, num_pairs, sizeof(CharPair), charpair_cmp); + qsort(char_pairs, num_pairs, sizeof(CharPair), ca_char_pair_cmp); + /** Allocate space for the sparse vector. **/ sparse_vector = check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); if (sparse_vector == NULL) goto err_free; - /** Build the sparse vector. **/ + /** Build the sparse vector from the character pairs. **/ unsigned int cur = 0u, dim = 0u; for (unsigned int i = 0u; i < num_pairs;) { @@ -236,6 +240,7 @@ ca_build_vector(const char* str) nmSysFree(char_pairs); char_pairs = NULL; + /** Trim extra space wasted by identical hashes. **/ trimmed_sparse_vector = check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); if (trimmed_sparse_vector == NULL) goto err_free; @@ -245,6 +250,7 @@ ca_build_vector(const char* str) return trimmed_sparse_vector; err_free: + /** Cleanup. **/ if (trimmed_sparse_vector != NULL) nmSysFree(trimmed_sparse_vector); if (sparse_vector != NULL) nmSysFree(sparse_vector); if (char_pairs != NULL) nmSysFree(char_pairs); @@ -264,7 +270,8 @@ ca_free_vector(pVector sparse_vector) return; } -/*** Compute the length of a sparsely allocated vector. +/*** Compute the actual number of ints stored in memory to store the given + *** sparsely allocated vector. *** *** @param vector The vector. *** @returns The computed length. @@ -288,7 +295,7 @@ ca_sparse_len(const pVector vector) return i; } -/*** Print the underlying implementation values sparsely allocated +/*** Print the underlying implementation-level values of a sparsely allocated *** vector (for debugging). *** *** @param vector The vector. @@ -311,7 +318,7 @@ ca_print_vector(const pVector vector) *** @returns The computed magnitude. ***/ static double -magnitude_sparse(const pVector vector) +ca_magnitude_sparse(const pVector vector) { unsigned int magnitude = 0u; @@ -335,7 +342,7 @@ magnitude_sparse(const pVector vector) *** @returns The computed magnitude. ***/ static double -magnitude_dense(const pCentroid centroid) +ca_magnitude_dense(const pCentroid centroid) { double magnitude = 0.0; @@ -353,7 +360,7 @@ magnitude_dense(const pCentroid centroid) *** @param param_value The location to save the param_value of the token. ***/ static void -parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) +ca_parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) { if (token < 0) { @@ -381,7 +388,7 @@ parse_vector_token(const int token, unsigned int* remaining, unsigned int* param *** 0 indicates completely different. ***/ static double -sparse_similarity(const pVector v1, const pVector v2) +ca_sparse_similarity(const pVector v1, const pVector v2) { /** Calculate dot product. **/ unsigned int vec1_remaining = 0u, vec2_remaining = 0u; @@ -389,8 +396,8 @@ sparse_similarity(const pVector v1, const pVector v2) while (dim < CA_NUM_DIMS) { unsigned int val1 = 0u, val2 = 0u; - if (vec1_remaining == 0u) parse_vector_token(v1[i1++], &vec1_remaining, &val1); - if (vec2_remaining == 0u) parse_vector_token(v2[i2++], &vec2_remaining, &val2); + if (vec1_remaining == 0u) ca_parse_vector_token(v1[i1++], &vec1_remaining, &val1); + if (vec2_remaining == 0u) ca_parse_vector_token(v2[i2++], &vec2_remaining, &val2); /*** Accumulate the dot_product. If either vector is 0 here, *** the total is 0 and this statement does nothing. @@ -404,11 +411,11 @@ sparse_similarity(const pVector v1, const pVector v2) dim += overlap; } - /** Optional optimization to speed up nonsimilar vectors. **/ + /** Optimization: Skip computing magnitudes for completely different vectors. **/ if (dot_product == 0u) return 0.0; /** Return the difference score. **/ - return (double)dot_product / (magnitude_sparse(v1) * magnitude_sparse(v2)); + return (double)dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_sparse(v2)); } /*** Calculate the difference on sparsely allocated vectors. Comparing @@ -420,7 +427,7 @@ sparse_similarity(const pVector v1, const pVector v2) *** 1 indicates completely different and *** 0 indicates identical. ***/ -#define sparse_dif(v1, v2) (1.0 - sparse_similarity(v1, v2)) +#define ca_sparse_dif(v1, v2) (1.0 - ca_sparse_similarity(v1, v2)) /*** Calculate the similarity between a sparsely allocated vector and a densely *** allocated centroid using a dot product. Comparing any string to an empty @@ -433,7 +440,7 @@ sparse_similarity(const pVector v1, const pVector v2) *** 0 indicates completely different. ***/ static double -sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) +ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) { double dot_product = 0.0; @@ -449,7 +456,7 @@ sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) } /** Return the difference score. **/ - return dot_product / (magnitude_sparse(v1) * magnitude_dense(c2)); + return dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_dense(c2)); } /*** Calculate the difference between a sparsely allocated vector and a densely @@ -462,7 +469,7 @@ sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) *** 1 indicates completely different and *** 0 indicates identical. ***/ -#define sparse_dif_to_centroid(v1, c2) (1.0 - sparse_similarity_to_centroid(v1, c2)) +#define ca_sparse_dif_to_centroid(v1, c2) (1.0 - ca_sparse_similarity_to_centroid(v1, c2)) /*** Computes Levenshtein distance between two strings. *** @@ -606,7 +613,7 @@ ca_cos_compare(void* v1, void* v2) if (!v1_empty && v2_empty) return 0.0; /** Apply rounding to avoid annoying floating point issues before returning. **/ - return round(sparse_similarity(vec1, vec2) * 1000000) / 1000000; + return round(ca_sparse_similarity(vec1, vec2) * 1000000.0) / 1000000.0; } /*** Compares two strings using their Levenshtein edit distance to compute a @@ -644,7 +651,7 @@ ca_lev_compare(void* str1, void* str2) const double normalized_similarity = 1.0 - (double)edit_dist / (double)max(len1, len2); /** Apply rounding to avoid annoying floating point issues before returning. **/ - return round(normalized_similarity * 1000000) / 1000000; + return round(normalized_similarity * 1000000.0) / 1000000.0; } /*** Check if two sparse vectors are identical. @@ -688,9 +695,9 @@ get_cluster_size( /** Allocate space to store clusters as averages are computed. **/ /*** We use nmMalloc() here because this function is usually called - *** repeatedly with the same number of clusters in the k-means loop. - *** Also, it is likely that k-means may be invoked multiple times with - *** the same k value, leading to additional caching benefits. + *** repeatedly with the same number of clusters at the end of the + *** loop in ca_kmeans(). Also, ca_kmeans() may be called multiple + *** times with the same k value, increasing this benefit. ***/ cluster_sums = check_ptr(nmMalloc(num_clusters * sizeof(double))); cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); @@ -706,7 +713,7 @@ get_cluster_size( for (unsigned int i = 0u; i < num_vectors; i++) { const unsigned int label = labels[i]; - cluster_sums[label] += sparse_dif_to_centroid(vectors[i], centroids[label]); + cluster_sums[label] += ca_sparse_dif_to_centroid(vectors[i], centroids[label]); cluster_counts[label]++; } @@ -848,7 +855,7 @@ ca_kmeans( /** Find nearest centroid. **/ for (unsigned int j = 0u; j < num_clusters; j++) { - const double dist = sparse_dif_to_centroid(vector, centroids[j]); + const double dist = ca_sparse_dif_to_centroid(vector, centroids[j]); if (dist < min_dist) { min_dist = dist; @@ -901,7 +908,7 @@ ca_kmeans( if (vector_sims != NULL) { for (unsigned int i = 0u; i < num_vectors; i++) - vector_sims[i] = sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); + vector_sims[i] = ca_sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); } /** Success. **/ @@ -1090,5 +1097,5 @@ ca_complete_search( } /** Scope cleanup. **/ -#undef sparse_dif -#undef sparse_dif_to_centroid +#undef ca_sparse_dif +#undef ca_sparse_dif_to_centroid From fce7a2c07ec2cbd7811162488e3d210a14604a07 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Jan 2026 10:23:26 -0700 Subject: [PATCH 040/361] Update magic.h to prepare for implementing magic on all cluster driver structs. --- centrallix-lib/include/magic.h | 42 +++++++++++++++++++++++---- centrallix-sysdoc/GCC_Dependencies.md | 2 +- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index 812483090..f7a0189f3 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -27,9 +27,39 @@ #ifdef DBMAGIC - -#define ASSERTMAGIC(x,y) ((!(x) || (((pMagicHdr)(x))->Magic == (y)))?0:(printf("LS-PANIC: Magic number assertion failed, unexpected %X != %X for %8.8lX\n",(x)?(((pMagicHdr)(x))->Magic):(0xEE1EE100),(y),(long)(x)),(*((int*)(8)) = *((int*)(0))))) -#define ASSERTNOTMAGIC(x,y) ((!(x) || (((pMagicHdr)(x))->Magic != (y)))?0:(printf("LS-PANIC: Magic number assertion failed, unexpected %X\n",(y)),(*((int*)(8)) = *((int*)(0))))) +#include + +#define ASSERTMAGIC(data, expect) \ + ({ \ + pMagicHdr _data = (pMagicHdr)(data); \ + Magic_t _expect = (expect); \ + Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + if (_data != NULL && _actual != _expect) \ + { \ + printf( \ + "%s:%d: Magic assertion failed, unexpected %u != %d for %8.8lX.\n", \ + __FILE__, __LINE__, _actual, _expect, (long)_data \ + ); \ + abort(); \ + } \ + 0; \ + }) + +#define ASSERTNOTMAGIC(data, expect) \ + ({ \ + pMagicHdr _data = (pMagicHdr)(data); \ + Magic_t _expect = (expect); \ + Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + if (_data != NULL && _actual == _expect) \ + { \ + printf( \ + "%s:%d: Magic assertion failed, unexpected %d.\n", \ + __FILE__, __LINE__, _expect \ + ); \ + abort(); \ + } \ + 0; \ + }) #else /* defined DBMAGIC */ @@ -38,9 +68,9 @@ #endif /* defined DBMAGIC */ -#define ISMAGIC(x,y) (((pMagicHdr)(x))->Magic == (y)) -#define ISNTMAGIC(x,y) (((pMagicHdr)(x))->Magic != (y)) -#define SETMAGIC(x,y) (((pMagicHdr)(x))->Magic = (y)) +#define ISMAGIC(data, expect) (((pMagicHdr)(data))->Magic == (expect)) +#define ISNTMAGIC(data, expect) (((pMagicHdr)(data))->Magic != (expect)) +#define SETMAGIC(data, expect) (((pMagicHdr)(data))->Magic = (expect)) typedef int Magic_t; diff --git a/centrallix-sysdoc/GCC_Dependencies.md b/centrallix-sysdoc/GCC_Dependencies.md index 1327ea090..5467ee13b 100644 --- a/centrallix-sysdoc/GCC_Dependencies.md +++ b/centrallix-sysdoc/GCC_Dependencies.md @@ -14,7 +14,7 @@ Date: Descember 4, 2025 This document tracks dependencies on the GCC toolchain in the centrallix codebase. As code is added which relies on GCC specific behavior, such additions should be noted here to make possible use of a different toolchain (e.g. LLVM) in the future less painful. ## List of Dependencies -- `util.h` Uses the `__typeof__` to avoid double-computation in macros. +- `util.h` & `magic.h`: Use `__typeof__` and `({ ... })` in macros to avoid double-computation. ## Notes `__FILE__` and `__LINE__` are not dependencies as they were added in C90. See [this page](https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html) for information about predefined macros. From b9defb8c5b1f0441f70d53ad34905ab9e5e1b286 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Jan 2026 10:33:58 -0700 Subject: [PATCH 041/361] Refactor some cluster driver code to make it cleaner. Add more hints to the cluster driver. Clean up and review some code. --- centrallix-lib/src/mtlexer.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 334 +++++++++++++++----------- 2 files changed, 200 insertions(+), 136 deletions(-) diff --git a/centrallix-lib/src/mtlexer.c b/centrallix-lib/src/mtlexer.c index 39a69cc15..6c5a46dba 100644 --- a/centrallix-lib/src/mtlexer.c +++ b/centrallix-lib/src/mtlexer.c @@ -909,7 +909,7 @@ mlxNextToken(pLxSession this) else { char buf[4]; - snprintf(buf, sizeof(buf), "%c", ch); // mssError() does not support %c. + snprintf(buf, sizeof(buf), "%c", ch); /* mssError() does not support %c. */ mssError(1, "MLX", "Unexpected character encountered: '%s'", buf); this->TokType = MLX_TOK_ERROR; break; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index b525dd9a1..fa9902fd2 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -70,8 +70,8 @@ ***/ /** Defaults for unspecified optional attributes. **/ -#define DEFAULT_MIN_IMPROVEMENT 0.0001 -#define DEFAULT_MAX_ITERATIONS 64u +#define CI_DEFAULT_MIN_IMPROVEMENT 0.0001 +#define CI_DEFAULT_MAX_ITERATIONS 64u /** ================ Stuff That Should Be Somewhere Else ================ **/ /** ANCHOR[id=temp] **/ @@ -227,7 +227,7 @@ ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) default: return "Unknown algorithm"; } - return; /** Unreachable. **/ + return NULL; /** Unreachable. **/ } /** Enum representing a similarity measurement algorithm. **/ @@ -256,7 +256,7 @@ ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) default: return "Unknown similarity measure"; } - return; /** Unreachable. **/ + return NULL; /** Unreachable. **/ } /*** Enum representing the type of data targetted by the driver, @@ -275,7 +275,7 @@ typedef unsigned char TargetType; /** Attribute name lists by TargetType. **/ #define END_OF_ARRAY NULL -char* const ATTR_ROOT[] = +const char* ATTR_ROOT[] = { "source", "attr_name", @@ -283,7 +283,7 @@ char* const ATTR_ROOT[] = "date_computed", END_OF_ARRAY, }; -char* const ATTR_CLUSTER[] = +const char* ATTR_CLUSTER[] = { "algorithm", "similarity_measure", @@ -294,21 +294,21 @@ char* const ATTR_CLUSTER[] = "date_computed", END_OF_ARRAY, }; -char* const ATTR_SEARCH[] = +const char* ATTR_SEARCH[] = { "source", "threshold", "similarity_measure", END_OF_ARRAY, }; -char* const ATTR_CLUSTER_ENTRY[] = +const char* ATTR_CLUSTER_ENTRY[] = { "items", "date_created", "date_computed", END_OF_ARRAY, }; -char* const ATTR_SEARCH_ENTRY[] = +const char* ATTR_SEARCH_ENTRY[] = { "key1", "key2", @@ -317,7 +317,7 @@ char* const ATTR_SEARCH_ENTRY[] = }; /** Method name list. **/ -char* const METHOD_NAMES[] = +const char* METHOD_NAMES[] = { "cache", "stat", @@ -622,11 +622,12 @@ struct // LINK #parsing static void ci_GiveHint(const char* hint); static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values); +static void ci_UnknownAttribute(const char* attr_name, int target_type); static int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path); -static pClusterData ci_ParseClusterData(pStructInf inf, pNodeData node_data); +static pClusterData ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data); static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data); static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj); @@ -728,6 +729,49 @@ ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) } +/*** Display an error message when an unknown attribute is requested, including + *** a hint about which attribute might be intended, if available. + *** + *** @param attr_name The name of the missing attribute. + *** @param target_type The target type, for determining the list of available + *** attributes in this context. + ***/ +static void +ci_UnknownAttribute(const char* attr_name, int target_type) + { + /** Display the error message. */ + mssErrorf(1, "Cluster", "Unknown attribute '%s'.", attr_name); + + /** Collect specific attributes based on target type. **/ + const char** specific_attrs = NULL; + switch (target_type) + { + case TARGET_NODE: specific_attrs = ATTR_ROOT; break; + case TARGET_CLUSTER: specific_attrs = ATTR_CLUSTER; break; + case TARGET_SEARCH: specific_attrs = ATTR_SEARCH; break; + case TARGET_CLUSTER_ENTRY: specific_attrs = ATTR_CLUSTER_ENTRY; break; + case TARGET_SEARCH_ENTRY: specific_attrs = ATTR_SEARCH_ENTRY; break; + default: + mssErrorf(0, "Cluster", + "Unknown target type %u detected while attempting to generate hint.", + target_type + ); + return; + } + + /** Count specific attributes. **/ + unsigned int n_specific_attrs = 0; + while (specific_attrs[n_specific_attrs] != NULL) n_specific_attrs++; + + /** Collect general attributes. */ + const char* general_attrs = (char*[]){"name", "annoation", "content_type", "inner_type", "outer_type", "internal_type", "date_computed", "date_created", "last_modification"}; + + /** Attempt to give hints. **/ + if (ci_TryHint(attr_name, specific_attrs, n_specific_attrs)); + else if (ci_TryHint(attr_name, general_attrs, 9)); + } + + // LINK #functions /*** Returns 0 for success and -1 on failure. Promises that mssError() will be *** invoked on failure, so the caller need not specify their own error message. @@ -847,12 +891,12 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) /** Attempt to give a hint. **/ char* all_names[nClusteringAlgorithms] = {NULL}; - for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) + for (unsigned int i = 1u; i < nClusteringAlgorithms; i++) all_names[i] = ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); if (ci_TryHint(algorithm, all_names, nClusteringAlgorithms)); else if (strcasecmp(algorithm, "sliding") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); - else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); - else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); else if (strcasecmp(algorithm, "nothing") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); /** Fail. **/ @@ -892,12 +936,12 @@ ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) /** Attempt to give a hint. **/ char* all_names[nSimilarityMeasures] = {NULL}; - for (unsigned int i = 0u; i < nSimilarityMeasures; i++) + for (unsigned int i = 1u; i < nSimilarityMeasures; i++) all_names[i] = ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); if (ci_TryHint(measure, all_names, nSimilarityMeasures)); - else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); - else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); - else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); + else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); else if (strcasecmp(measure, "edit-distance") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); /** Fail. **/ @@ -1013,7 +1057,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) *** @returns A new pClusterData struct on success, or NULL on failure. ***/ static pClusterData -ci_ParseClusterData(pStructInf inf, pNodeData node_data) +ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data) { int result; pClusterData cluster_data = NULL; @@ -1029,11 +1073,10 @@ ci_ParseClusterData(pStructInf inf, pNodeData node_data) if (cluster_data == NULL) goto err_free; memset(cluster_data, 0, sizeof(ClusterData)); - /** Basic Properties. **/ + /** Basic fields. **/ cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); if (cluster_data->Name == NULL) goto err_free; - cluster_data->SourceData = check_ptr(source_data); - if (cluster_data->SourceData == NULL) goto err_free; + cluster_data->SourceData = source_data; if (!check(objCurrentDate(&cluster_data->DateCreated))) goto err_free; /** Get algorithm. **/ @@ -1087,7 +1130,7 @@ ci_ParseClusterData(pStructInf inf, pNodeData node_data) /** Get min_improvement. **/ double improvement; result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); - if (result == 1) cluster_data->MinImprovement = DEFAULT_MIN_IMPROVEMENT; + if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; else if (result == 0) { if (improvement <= 0.0 || 1.0 <= improvement) @@ -1127,7 +1170,7 @@ ci_ParseClusterData(pStructInf inf, pNodeData node_data) } cluster_data->MaxIterations = (unsigned int)max_iterations; } - else cluster_data->MaxIterations = DEFAULT_MAX_ITERATIONS; + else cluster_data->MaxIterations = CI_DEFAULT_MAX_ITERATIONS; /** Search for sub-clusters. **/ if (!check(xaInit(&sub_clusters, 4u))) goto err_free; @@ -1186,11 +1229,12 @@ ci_ParseClusterData(pStructInf inf, pNodeData node_data) "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", name, group_type, inf->Name ); + ci_GiveHint("cluster/cluster"); continue; } /** Subcluster found. **/ - pClusterData sub_cluster = ci_ParseClusterData(sub_inf, node_data); + pClusterData sub_cluster = ci_ParseClusterData(sub_inf, param_list, source_data); if (sub_cluster == NULL) goto err_free; sub_cluster->Parent = cluster_data; if (!check_neg(xaAddItem(&sub_clusters, sub_cluster))) goto err_free; @@ -1318,6 +1362,10 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) pSearchData search_data = NULL; char* key = NULL; + /** Extract values. **/ + pParamObjects param_list = check_ptr(node_data->ParamList); + if (param_list == NULL) goto err_free; + /** Allocate space for search struct. **/ search_data = check_ptr(nmMalloc(sizeof(SearchData))); if (search_data == NULL) goto err_free; @@ -1328,9 +1376,9 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) if (search_data->Name == NULL) goto err_free; if (!check(objCurrentDate(&search_data->DateCreated))) goto err_free; - /** Get source cluster. **/ + /** Search for the source cluster. **/ char* source_cluster_name; - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), node_data->ParamList, true, true) != 0) return NULL; + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), param_list, true, true) != 0) return NULL; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { pClusterData cluster_data = node_data->ClusterDatas[i]; @@ -1361,7 +1409,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Get threshold attribute. **/ - if (ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), node_data->ParamList, true, true) != 0) goto err_free; + if (ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0) goto err_free; if (search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold) { mssErrorf(1, "Cluster", @@ -1372,7 +1420,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Get similarity measure. **/ - search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, node_data->ParamList); + search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); if (search_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free; /** Check for additional data to warn the user about. **/ @@ -1636,12 +1684,14 @@ ci_ParseNodeData(pStructInf inf, pObject parent) int num_provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf : 0; pStruct* provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf : NULL; - /** Iterate over each param in the structure file. **/ + /** Allocate space to store params. **/ node_data->nParams = param_infs.nItems; const size_t params_size = node_data->nParams * sizeof(pParam); node_data->Params = check_ptr(nmSysMalloc(params_size)); if (node_data->Params == NULL) goto err_free; memset(node_data->Params, 0, params_size); + + /** Iterate over each param in the structure file. **/ for (unsigned int i = 0u; i < node_data->nParams; i++) { pParam param = paramCreateFromInf(param_infs.Items[i]); @@ -1658,7 +1708,8 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Check each provided param to see if the user provided value. **/ for (unsigned int j = 0u; j < num_provided_params; j++) { - pStruct provided_param = check_ptr(provided_params[j]); /* Failure ignored. */ + pStruct provided_param = check_ptr(provided_params[j]); + if (provided_param == NULL) goto err_free; /** If this provided param value isn't for the param, ignore it. **/ if (strcmp(provided_param->Name, param->Name) != 0) continue; @@ -1700,7 +1751,8 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Iterate over provided parameters and warn the user if they specified a parameter that does not exist. **/ for (unsigned int i = 0u; i < num_provided_params; i++) { - pStruct provided_param = check_ptr(provided_params[i]); /* Failure ignored. */ + pStruct provided_param = check_ptr(provided_params[i]); + if (provided_param == NULL) goto err_free; char* provided_name = provided_param->Name; /** Look to see if this provided param actually exists for this driver instance. **/ @@ -1733,7 +1785,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) memset(node_data->ClusterDatas, 0, clusters_size); for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) { - node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data); + node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data->ParamList, node_data->SourceData); if (node_data->ClusterDatas[i] == NULL) goto err_free; } } @@ -2057,7 +2109,7 @@ ci_ClearCaches(void) static unsigned int ci_SizeOfSourceData(pSourceData source_data) { - /** Guard segfault. **/ + /** Guard segfaults. **/ if (source_data == NULL) { fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); @@ -2102,7 +2154,7 @@ ci_SizeOfSourceData(pSourceData source_data) static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) { - /** Guard segfault. **/ + /** Guard segfaults. **/ if (cluster_data == NULL) { fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); @@ -2147,7 +2199,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) static unsigned int ci_SizeOfSearchData(pSearchData search_data) { - /** Guard segfault. **/ + /** Guard segfaults. **/ if (search_data == NULL) { fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); @@ -2189,7 +2241,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) XArray data_xarray = {0}; XArray vector_xarray = {0}; - /** Guard segfault. **/ + /** Guard segfaults. **/ if (source_data == NULL) return -1; /** If the vectors are already computed, we're done. **/ @@ -2229,9 +2281,6 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Initialize an xarray to store the retrieved data. **/ - // memset(&key_xarray, 0, sizeof(XArray)); - // memset(&data_xarray, 0, sizeof(XArray)); - // memset(&vector_xarray, 0, sizeof(XArray)); if (!check(xaInit(&key_xarray, 64))) goto end_free; if (!check(xaInit(&data_xarray, 64))) goto end_free; if (!check(xaInit(&vector_xarray, 64))) goto end_free; @@ -2538,12 +2587,12 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate static memory for finding clusters. **/ const size_t clusters_size = cluster_data->nClusters * sizeof(Cluster); - cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); - if (cluster_data->Clusters == NULL) goto err_free; - memset(cluster_data->Clusters, 0, clusters_size); const size_t sims_size = source_data->nVectors * sizeof(double); + cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); + if (cluster_data->Clusters == NULL) goto err_free; if (cluster_data->Sims == NULL) goto err_free; + memset(cluster_data->Clusters, 0, clusters_size); memset(cluster_data->Sims, 0, sims_size); /** Execute clustering. **/ @@ -2551,15 +2600,23 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { case ALGORITHM_NONE: { - /** Put all the data into one cluster. **/ + /*** Put all the data into one cluster. Remember, in the + *** no clustering case, there is only one cluster (see + *** ci_ParseClusterData() above). + ***/ + + /** Initialize the cluster. **/ pCluster first_cluster = &cluster_data->Clusters[0]; first_cluster->Size = source_data->nVectors; first_cluster->Strings = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(char*))); if (first_cluster->Strings == NULL) goto err_free; first_cluster->Vectors = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(pVector))); if (first_cluster->Vectors == NULL) goto err_free; + + /** Copy data. **/ memcpy(first_cluster->Strings, source_data->Strings, source_data->nVectors * sizeof(char*)); memcpy(first_cluster->Vectors, source_data->Vectors, source_data->nVectors * sizeof(pVector)); + break; } @@ -2619,6 +2676,8 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (cluster->Strings == NULL) goto err_free; cluster->Vectors = check_ptr(nmSysMalloc(cluster->Size * sizeof(pVector))); if (cluster->Vectors == NULL) goto err_free; + + /** Add data to clusters. **/ for (unsigned int j = 0u; j < cluster->Size; j++) { const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[j]; @@ -2650,6 +2709,11 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { + /*** NOTE: The clusters here do not need to each be freed + *** individually because they are part of the dynamically + *** allocated Clusters array (freed after the loop). + *** Thus, this loop only frees each cluster's content. + ***/ pCluster cluster = &cluster_data->Clusters[i]; if (cluster->Strings != NULL) nmFree(cluster->Strings, cluster->Size * sizeof(char*)); else break; @@ -2701,93 +2765,20 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Record the date and time. **/ if (!check(objCurrentDate(&search_data->DateComputed))) goto err_free; - /** Execute the search using the specified source and comparison function. **/ - pXArray dups_temp = NULL; + /** Select the correct comparison function based on the similarity measure. **/ + const double (*similarity_function)(void *, void *); + char* similarity_function_name; switch (search_data->SimilarityMeasure) { case SIMILARITY_COSINE: - { - if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) - { - dups_temp = check_ptr(ca_sliding_search( - (void**)cluster_data->SourceData->Vectors, - cluster_data->SourceData->nVectors, - cluster_data->MaxIterations, /* Window size. */ - ca_cos_compare, - search_data->Threshold, - (void**)cluster_data->SourceData->Keys, - dups - )); - if (dups_temp == NULL) - { - mssErrorf(1, "Cluster", "Failed to compute sliding search with cosine similarity measure."); - goto err_free; - } - } - else - { - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - { - dups_temp = check_ptr(ca_complete_search( - (void**)cluster_data->Clusters[i].Vectors, - cluster_data->Clusters[i].Size, - ca_cos_compare, - search_data->Threshold, - (void**)cluster_data->SourceData->Keys, - dups - )); - if (dups_temp == NULL) - { - mssErrorf(1, "Cluster", "Failed to compute complete search with cosine similarity measure."); - goto err_free; - } - else dups = dups_temp; - } - } + similarity_function = ca_cos_compare; + similarity_function_name = "cosine"; break; - } - + case SIMILARITY_LEVENSHTEIN: - { - if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) - { - dups_temp = check_ptr(ca_sliding_search( - (void**)cluster_data->SourceData->Vectors, - cluster_data->SourceData->nVectors, - cluster_data->MaxIterations, /* Window size. */ - ca_lev_compare, - search_data->Threshold, - (void**)cluster_data->SourceData->Keys, - dups - )); - if (dups_temp == NULL) - { - mssErrorf(1, "Cluster", "Failed to compute sliding search with Levenstein similarity measure."); - goto err_free; - } - } - else - { - for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - { - dups_temp = check_ptr(ca_complete_search( - (void**)cluster_data->Clusters[i].Strings, - cluster_data->Clusters[i].Size, - ca_lev_compare, - search_data->Threshold, - (void**)cluster_data->SourceData->Keys, - dups - )); - if (dups_temp == NULL) - { - mssErrorf(1, "Cluster", "Failed to compute complete search with Levenstein similarity measure."); - goto err_free; - } - else dups = dups_temp; - } - } + similarity_function = ca_lev_compare; + similarity_function_name = "Levenstein"; break; - } default: mssErrorf(1, "Cluster", @@ -2796,9 +2787,66 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) ); goto err_free; } + + + /** Execute the search using the specified algorithm. **/ + pXArray dups_temp = NULL; + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) + { + /*** Note: We don't need to examine the clusters because nothing + *** was computed during the clustering phase. + ***/ + + /** Execute sliding search. **/ + dups_temp = check_ptr(ca_sliding_search( + (void**)source_data->Vectors, + source_data->nVectors, + cluster_data->MaxIterations, /* Window size. */ + similarity_function, + search_data->Threshold, + (void**)source_data->Keys, + dups + )); + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", + "Failed to compute sliding search with %s similarity measure.", + similarity_function_name + ); + goto err_free; + } + } + else + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + /** Extract the struct for the cluster. **/ + pCluster cluster = &cluster_data->Clusters[i]; + // ASSERTMAGIC(cluster, MGK_CL_CLUSTER); + + /** Execute complete search. **/ + dups_temp = check_ptr(ca_complete_search( + (void**)cluster->Vectors, + cluster->Size, + similarity_function, + search_data->Threshold, + (void**)source_data->Keys, + dups + )); + if (dups_temp == NULL) + { + mssErrorf(1, "Cluster", + "Failed to compute complete search with %s similarity measure.", + similarity_function_name + ); + goto err_free; + } + else dups = dups_temp; + } + } + if (dups_temp == NULL) goto err_free; else dups = dups_temp; - // fprintf(stderr, "Done searching, found %d dups.\n", dups->nItems); /** Store dups. **/ search_data->nDups = dups->nItems; @@ -2961,6 +3009,14 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Update statistics. **/ ClusterStatistics.OpenCalls++; + /** Guard segfaults. **/ + if (parent == NULL) + { + fprintf(stderr, "Warning: Call to clusterOpen(NULL, ...);\n"); + return; + } + ASSERTMAGIC(parent, MGK_OBJECT); + /** If CREAT and EXCL are specified, exclusively create it, failing if the file already exists. **/ pSnNode node_struct = NULL; bool can_create = (parent->Mode & O_CREAT) && (parent->SubPtr == parent->Pathname->nElements); @@ -3036,18 +3092,18 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** If the path does not go any deeper, we're done. **/ if (path_part == NULL) { - driver_data->TargetData = (void*)cluster; + driver_data->TargetData = (void*)cluster_data; break; } /** Need to go deeper: Search for the requested sub-cluster. **/ - for (unsigned int i = 0u; i < cluster->nSubClusters; i++) + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) { - pClusterData sub_cluster = cluster->SubClusters[i]; + pClusterData sub_cluster = cluster_data->SubClusters[i]; if (strcmp(sub_cluster->Name, path_part) != 0) continue; - /** Target found: Sub-cluster **/ - cluster = sub_cluster; + /** Target found: Sub-cluster_data **/ + cluster_data = sub_cluster; goto continue_descent; } @@ -3063,12 +3119,15 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Search searches. **/ for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) { - pSearchData search = node_data->SearchDatas[i]; - if (strcmp(search->Name, target_name) != 0) continue; + pSearchData search_data = node_data->SearchDatas[i]; + // ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + + /** Skip clusters with the wrong name. **/ + if (strcmp(search_data->Name, target_name) != 0) continue; /** Target found: Search **/ driver_data->TargetType = TARGET_SEARCH; - driver_data->TargetData = (void*)search; + driver_data->TargetData = (void*)search_data; /** Check for extra, invalid path parts. **/ char* extra_data = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); @@ -4138,7 +4197,7 @@ clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) return NULL; } - return; /* Unreachable. */ + return NULL; /* Unreachable. */ } @@ -4554,6 +4613,11 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree /** Unknown parameter. **/ mssErrorf(1, "Cluster", "Unknown command: \"%s\"", method_name); + /** Attempt to give hint. **/ + unsigned int n_methods = 0; + while (METHOD_NAMES[n_methods] != NULL) n_methods++; + if (ci_TryHint(method_name, METHOD_NAMES, n_methods)); + err: mssErrorf(0, "Cluster", "Failed execute command."); From 636814e10e60c5a69fa756c81d4a7bb50baea894 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Jan 2026 10:40:45 -0700 Subject: [PATCH 042/361] Add magic.h to all major cluster driver structs. Fix type warnings from const. Clean up code. --- centrallix-lib/include/magic.h | 8 + centrallix/osdrivers/objdrv_cluster.c | 480 ++++++++++++++++++-------- 2 files changed, 342 insertions(+), 146 deletions(-) diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index f7a0189f3..50ea697da 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -111,4 +111,12 @@ typedef struct #define MGK_SMREGION 0x1200345c /* smmalloc.h::SmRegion */ #define MGK_SMBLOCK 0x1200349a /* smmalloc_private.h::SmBlock */ +#define MGK_CL_SOURCE_DATA 0x12340c19 /* objdrv_cluster.c::SourceData */ +#define MGK_CL_CLUSTER 0x12340c28 /* objdrv_cluster.c::Cluster */ +#define MGK_CL_CLUSTER_DATA 0x12340c37 /* objdrv_cluster.c::ClusterData */ +#define MGK_CL_SEARCH_DATA 0x12340c46 /* objdrv_cluster.c::SearchData */ +#define MGK_CL_NODE_DATA 0x12340c55 /* objdrv_cluster.c::NodeData */ +#define MGK_CL_DRIVER_DATA 0x12340c64 /* objdrv_cluster.c::DriverData */ +#define MGK_CL_QUERY_DATA 0x12340c73 /* objdrv_cluster.c::QueryData */ + #endif /* not defined _MAGIC_H */ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index fa9902fd2..f22449ec6 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -41,6 +41,7 @@ #include #include "cxlib/clusters.h" +#include "cxlib/magic.h" #include "cxlib/mtsession.h" #include "cxlib/newmalloc.h" #include "cxlib/util.h" @@ -275,7 +276,7 @@ typedef unsigned char TargetType; /** Attribute name lists by TargetType. **/ #define END_OF_ARRAY NULL -const char* ATTR_ROOT[] = +char* ATTR_ROOT[] = { "source", "attr_name", @@ -283,7 +284,7 @@ const char* ATTR_ROOT[] = "date_computed", END_OF_ARRAY, }; -const char* ATTR_CLUSTER[] = +char* ATTR_CLUSTER[] = { "algorithm", "similarity_measure", @@ -294,21 +295,21 @@ const char* ATTR_CLUSTER[] = "date_computed", END_OF_ARRAY, }; -const char* ATTR_SEARCH[] = +char* ATTR_SEARCH[] = { "source", "threshold", "similarity_measure", END_OF_ARRAY, }; -const char* ATTR_CLUSTER_ENTRY[] = +char* ATTR_CLUSTER_ENTRY[] = { "items", "date_created", "date_computed", END_OF_ARRAY, }; -const char* ATTR_SEARCH_ENTRY[] = +char* ATTR_SEARCH_ENTRY[] = { "key1", "key2", @@ -317,7 +318,7 @@ const char* ATTR_SEARCH_ENTRY[] = }; /** Method name list. **/ -const char* METHOD_NAMES[] = +char* METHOD_NAMES[] = { "cache", "stat", @@ -331,7 +332,7 @@ const char* METHOD_NAMES[] = /*** Represents the data source which may have data already fetched. *** *** Memory Stats: - *** - Padding: 4 bytes + *** - Padding: 0 bytes *** - Total size: 80 bytes *** *** @skip --> Attribute Data. @@ -357,9 +358,13 @@ const char* METHOD_NAMES[] = *** @skip --> Time. *** @param DateCreated The date and time that this object was created and initialized. *** @param DateComputed The date and time that the computed attributes were computed. + *** + *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct _SOURCE { + Magic_t Magic; + unsigned int nVectors; char* Name; char* Key; char* SourcePath; @@ -368,7 +373,6 @@ typedef struct _SOURCE char** Keys; char** Strings; pVector* Vectors; - unsigned int nVectors; DateTime DateCreated; DateTime DateComputed; } @@ -378,18 +382,20 @@ typedef struct _SOURCE /*** Computed data for a single cluster. *** *** Memory Stats: - *** - Padding: 4 bytes + *** - Padding: 0 bytes *** - Total size: 24 bytes *** *** @param Size The number of items in the cluster. *** @param Strings The string values of each item. *** @param Vectors The cosine vectors for each item. + *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct { + Magic_t Magic; unsigned int Size; - char** Strings; - pVector* Vectors; + char** Strings; + pVector* Vectors; } Cluster, *pCluster; @@ -397,8 +403,8 @@ typedef struct /*** Data for each cluster. Only attribute data is checked for caching. *** *** Memory Stats: - *** - Padding: 2 bytes - *** - Total size: 96 bytes + *** - Padding: 6 bytes + *** - Total size: 104 bytes *** *** @skip --> Attribute Data. *** @param Name The cluster name, specified in the .cluster file. @@ -429,14 +435,17 @@ typedef struct *** @skip --> Time. *** @param DateCreated The date and time that this object was created and initialized. *** @param DateComputed The date and time that the computed attributes were computed. + *** + *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct _CLUSTER { + Magic_t Magic; + unsigned int nClusters; char* Name; char* Key; ClusterAlgorithm ClusterAlgorithm; SimilarityMeasure SimilarityMeasure; - unsigned int nClusters; double MinImprovement; unsigned int MaxIterations; unsigned int nSubClusters; @@ -449,13 +458,13 @@ typedef struct _CLUSTER DateTime DateComputed; } ClusterData, *pClusterData; - + /*** Data for each search. *** *** Memory Stats: - *** - Padding: 3 bytes - *** - Total size: 64 bytes + *** - Padding: 7 bytes + *** - Total size: 72 bytes *** *** @skip --> Attribute Data. *** @param Name The search name, specified in the .cluster file. @@ -473,9 +482,12 @@ typedef struct _CLUSTER *** @skip --> Time. *** @param DateCreated The date and time that this object was created and initialized. *** @param DateComputed The date and time that the computed attributes were computed. + *** + *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct _SEARCH { + Magic_t Magic; char* Name; char* Key; pClusterData SourceCluster; @@ -492,8 +504,8 @@ typedef struct _SEARCH /*** Node instance data. *** *** Memory Stats: - *** - Padding: 0 bytes - *** - Total size: 64 bytes + *** - Padding: 4 bytes + *** - Total size: 72 bytes *** *** @note When a .cluster file is openned, there will be only one node for that *** file. However, in the course of the query, many driver instance structs @@ -512,9 +524,11 @@ typedef struct _SEARCH *** @param nSearchDatas The parent object used to open this NodeData instance. *** @param OpenCount The number of open driver instances that are using the *** NodeData struct. When this reaches 0, the struct should be freed. + *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct _NODE { + Magic_t Magic; pObject Parent; pParam* Params; pParamObjects ParamList; @@ -531,8 +545,8 @@ typedef struct _NODE /*** Driver instance data. *** *** Memory Stats: - *** - Padding: 1 bytes - *** - Total size: 24 bytes + *** - Padding: 5 bytes + *** - Total size: 32 bytes *** *** This struct can be thought of like a "pointer" to specific data accessible *** through the stored pNodeData struct. This struct also communicates whether @@ -560,9 +574,11 @@ typedef struct _NODE *** ``` *** @param TargetAttrIndex An index into an attribute list (for GetNextAttr()). *** @param TargetMethodIndex An index into an method list (for GetNextMethod()). + *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct _DRIVER { + Magic_t Magic; pNodeData NodeData; void* TargetData; unsigned int TargetIndex; @@ -575,18 +591,20 @@ typedef struct _DRIVER /*** Query instance data. *** *** Memory Stats: - *** - Padding: 4 bytes + *** - Padding: 0 bytes *** - Total size: 16 bytes *** *** @param DriverData The associated driver instance being queried. *** @param RowIndex The selected row of the data targetted by the driver. + *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct { - pDriverData DriverData; + Magic_t Magic; unsigned int RowIndex; + pDriverData DriverData; } - ClusterQuery, *pClusterQuery; + ClusterQuery, *pQueryData; /** Global storage for caches. **/ @@ -622,7 +640,7 @@ struct // LINK #parsing static void ci_GiveHint(const char* hint); static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values); -static void ci_UnknownAttribute(const char* attr_name, int target_type); +static void ci_UnknownAttribute(char* attr_name, int target_type); static int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); @@ -737,13 +755,13 @@ ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) *** attributes in this context. ***/ static void -ci_UnknownAttribute(const char* attr_name, int target_type) +ci_UnknownAttribute(char* attr_name, const int target_type) { /** Display the error message. */ mssErrorf(1, "Cluster", "Unknown attribute '%s'.", attr_name); /** Collect specific attributes based on target type. **/ - const char** specific_attrs = NULL; + char** specific_attrs = NULL; switch (target_type) { case TARGET_NODE: specific_attrs = ATTR_ROOT; break; @@ -764,7 +782,7 @@ ci_UnknownAttribute(const char* attr_name, int target_type) while (specific_attrs[n_specific_attrs] != NULL) n_specific_attrs++; /** Collect general attributes. */ - const char* general_attrs = (char*[]){"name", "annoation", "content_type", "inner_type", "outer_type", "internal_type", "date_computed", "date_created", "last_modification"}; + char** general_attrs = (char*[]){"name", "annoation", "content_type", "inner_type", "outer_type", "internal_type", "date_computed", "date_created", "last_modification"}; /** Attempt to give hints. **/ if (ci_TryHint(attr_name, specific_attrs, n_specific_attrs)); @@ -968,11 +986,15 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) { char* buf = NULL; pSourceData source_data = NULL; - + + /** Magic checks. **/ + ASSERTMAGIC(inf, MGK_STRUCTINF); + /** Allocate SourceData. **/ source_data = check_ptr(nmMalloc(sizeof(SourceData))); if (source_data == NULL) goto err_free; memset(source_data, 0, sizeof(SourceData)); + SETMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Initialize obvious values for SourceData. **/ source_data->Name = check_ptr(nmSysStrdup(inf->Name)); @@ -1010,6 +1032,8 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, source_data->Key); if (source_maybe != NULL) { /* Cache hit. */ + ASSERTMAGIC(source_maybe, MGK_CL_SOURCE_DATA); + /** Free data we don't need. **/ nmSysFree(source_data->Key); ci_FreeSourceData(source_data); @@ -1064,14 +1088,15 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source XArray sub_clusters = {0}; char* key = NULL; - /** Extract values. **/ - pParamObjects param_list = node_data->ParamList; - pSourceData source_data = node_data->SourceData; + /** Verify source_data value. **/ + if (source_data == NULL) goto err_free; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Allocate space for data struct. **/ cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); if (cluster_data == NULL) goto err_free; memset(cluster_data, 0, sizeof(ClusterData)); + SETMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); /** Basic fields. **/ cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); @@ -1308,6 +1333,8 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, key); if (cluster_maybe != NULL) { /* Cache hit. */ + ASSERTMAGIC(cluster_maybe, MGK_CL_CLUSTER_DATA); + /** Free the parsed cluster that we no longer need. */ ci_FreeClusterData(cluster_data, false); nmSysFree(key); @@ -1370,6 +1397,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) search_data = check_ptr(nmMalloc(sizeof(SearchData))); if (search_data == NULL) goto err_free; memset(search_data, 0, sizeof(SearchData)); + SETMAGIC(search_data, MGK_CL_SEARCH_DATA); /** Get basic information. **/ search_data->Name = check_ptr(nmSysStrdup(inf->Name)); @@ -1382,6 +1410,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { pClusterData cluster_data = node_data->ClusterDatas[i]; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); if (strcmp(source_cluster_name, cluster_data->Name) == 0) { /** SourceCluster found. **/ @@ -1503,6 +1532,8 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); if (search_maybe != NULL) { /* Cache hit. */ + ASSERTMAGIC(search_maybe, MGK_CL_SEARCH_DATA); + /** Free the parsed search that we no longer need. **/ if (search_data != NULL) ci_FreeSearchData(search_data); if (key != NULL) nmSysFree(key); @@ -1547,6 +1578,10 @@ ci_ParseNodeData(pStructInf inf, pObject parent) XArray cluster_infs = {0}; XArray search_infs = {0}; + /** Magic. **/ + ASSERTMAGIC(inf, MGK_STRUCTINF); + ASSERTMAGIC(parent, MGK_OBJECT); + /** Get file path. **/ char* path = check_ptr(ci_file_path(parent)); if (path == NULL) goto err_free; @@ -1555,6 +1590,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) node_data = check_ptr(nmMalloc(sizeof(NodeData))); if (node_data == NULL) goto err_free; memset(node_data, 0, sizeof(NodeData)); + SETMAGIC(node_data, MGK_CL_NODE_DATA); node_data->Parent = parent; /** Set up param list. **/ @@ -1787,6 +1823,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) { node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data->ParamList, node_data->SourceData); if (node_data->ClusterDatas[i] == NULL) goto err_free; + ASSERTMAGIC(node_data->ClusterDatas[i], MGK_CL_CLUSTER_DATA); } } else node_data->ClusterDatas = NULL; @@ -1805,6 +1842,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) { node_data->SearchDatas[i] = ci_ParseSearchData(search_infs.Items[i], node_data); if (node_data->SearchDatas[i] == NULL) goto err_free; + ASSERTMAGIC(node_data->SearchDatas[i], MGK_CL_SEARCH_DATA); } } else node_data->SearchDatas = NULL; @@ -1839,6 +1877,7 @@ ci_FreeSourceData(pSourceData source_data) fprintf(stderr, "Warning: Call to ci_FreeSourceData(NULL);\n"); return; } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Free top level attributes, if they exist. **/ if (source_data->Name != NULL) @@ -1913,6 +1952,7 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) fprintf(stderr, "Warning: Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); return; } + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); /** Free attribute data. **/ if (cluster_data->Name != NULL) @@ -1975,6 +2015,7 @@ ci_FreeSearchData(pSearchData search_data) fprintf(stderr, "Warning: Call to ci_FreeSearchData(NULL);\n"); return; } + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); /** Free attribute data. **/ if (search_data->Name != NULL) @@ -2014,6 +2055,7 @@ ci_FreeNodeData(pNodeData node_data) fprintf(stderr, "Warning: Call to ci_FreeNodeData(NULL);\n"); return; } + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Free parsed params, if they exist. **/ if (node_data->Params != NULL) @@ -2115,6 +2157,7 @@ ci_SizeOfSourceData(pSourceData source_data) fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); return 0u; } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); unsigned int size = 0u; if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); @@ -2160,6 +2203,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); return 0u; } + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); unsigned int size = 0u; if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); @@ -2205,6 +2249,7 @@ ci_SizeOfSearchData(pSearchData search_data) fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); return 0u; } + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); unsigned int size = 0u; if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); @@ -2243,6 +2288,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Guard segfaults. **/ if (source_data == NULL) return -1; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** If the vectors are already computed, we're done. **/ if (source_data->Vectors != NULL) return 0; @@ -2290,6 +2336,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) { pObject entry = objQueryFetch(query, O_RDONLY); if (entry == NULL) break; /* Done. */ + ASSERTMAGIC(entry, MGK_OBJECT); /** Data value: Type checking. **/ const int data_datatype = objGetAttrType(entry, source_data->NameAttr); @@ -2563,6 +2610,8 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Guard segfaults. **/ if (cluster_data == NULL || node_data == NULL) return -1; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** If the clusters are already computed, we're done. **/ if (cluster_data->Clusters != NULL) return 0; @@ -2574,13 +2623,18 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) mssErrorf(1, "Cluster", "Failed to get source data for cluster computation."); goto err_free; } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** We need the SourceData vectors to compute clusters. **/ - if (ci_ComputeSourceData(source_data, node_data->ParamList->Session) != 0) + pObjSession session = check_ptr(node_data->ParamList->Session); + if (session == NULL) goto err_free; + ASSERTMAGIC(session, MGK_OBJSESSION); + if (ci_ComputeSourceData(source_data, session) != 0) { mssErrorf(0, "Cluster", "ClusterData computation failed due to missing SourceData."); goto err_free; } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Record the date and time. **/ if (!check(objCurrentDate(&cluster_data->DateComputed))) goto err_free; @@ -2607,6 +2661,9 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Initialize the cluster. **/ pCluster first_cluster = &cluster_data->Clusters[0]; + SETMAGIC(first_cluster, MGK_CL_CLUSTER); + + /** Allocate space. **/ first_cluster->Size = source_data->nVectors; first_cluster->Strings = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(char*))); if (first_cluster->Strings == NULL) goto err_free; @@ -2671,6 +2728,9 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { pXArray indexes_in_this_cluster = &indexes_in_cluster[i]; pCluster cluster = &cluster_data->Clusters[i]; + SETMAGIC(cluster, MGK_CL_CLUSTER); + + /** Allocate space. **/ cluster->Size = indexes_in_this_cluster->nItems; cluster->Strings = check_ptr(nmSysMalloc(cluster->Size * sizeof(char*))); if (cluster->Strings == NULL) goto err_free; @@ -2715,6 +2775,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) *** Thus, this loop only frees each cluster's content. ***/ pCluster cluster = &cluster_data->Clusters[i]; + ASSERTMAGIC(cluster, MGK_CL_CLUSTER); if (cluster->Strings != NULL) nmFree(cluster->Strings, cluster->Size * sizeof(char*)); else break; if (cluster->Vectors != NULL) nmFree(cluster->Vectors, cluster->Size * sizeof(pVector)); @@ -2746,11 +2807,17 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { pXArray dups = NULL; + /** Guard segfaults. **/ + if (search_data == NULL || node_data == NULL) return -1; + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + /** If the clusters are already computed, we're done. **/ if (search_data->Dups != NULL) return 0; /** We need the cluster data to be computed before we search it. **/ pClusterData cluster_data = check_ptr(search_data->SourceCluster); + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); if (cluster_data == NULL) { mssErrorf(1, "Cluster", "Failed to get cluster data for search computation."); @@ -2761,6 +2828,10 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) mssErrorf(0, "Cluster", "SearchData computation failed due to missing clusters."); goto err_free; } + + /** Extract source data. **/ + pSourceData source_data = cluster_data->SourceData; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Record the date and time. **/ if (!check(objCurrentDate(&search_data->DateComputed))) goto err_free; @@ -2895,8 +2966,9 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) static int ci_GetParamType(void* inf_v, const char* attr_name) { - pNodeData node_data = (pNodeData)inf_v; - + pNodeData node_data = (pNodeData)inf_v; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + /** Find the parameter. **/ for (unsigned int i = 0; i < node_data->nParams; i++) { @@ -2939,7 +3011,8 @@ ci_GetParamType(void* inf_v, const char* attr_name) static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - pNodeData node_data = (pNodeData)inf_v; + pNodeData node_data = (pNodeData)inf_v; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Find the parameter. **/ for (unsigned int i = 0; i < node_data->nParams; i++) @@ -3013,7 +3086,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb if (parent == NULL) { fprintf(stderr, "Warning: Call to clusterOpen(NULL, ...);\n"); - return; + return NULL; } ASSERTMAGIC(parent, MGK_OBJECT); @@ -3056,11 +3129,13 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb mssErrorf(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(parent)); goto err_free; } + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Allocate driver instance data. **/ driver_data = check_ptr(nmMalloc(sizeof(DriverData))); if (driver_data == NULL) goto err_free; memset(driver_data, 0, sizeof(DriverData)); + SETMAGIC(driver_data, MGK_CL_DRIVER_DATA); driver_data->NodeData = node_data; driver_data->NodeData->OpenCount++; @@ -3077,8 +3152,11 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Search clusters. **/ for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) { - pClusterData cluster = node_data->ClusterDatas[i]; - if (strcmp(cluster->Name, target_name) != 0) continue; + pClusterData cluster_data = node_data->ClusterDatas[i]; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + + /** Skip clusters with the wrong name. **/ + if (strcmp(cluster_data->Name, target_name) != 0) continue; /** Target found: Cluster **/ driver_data->TargetType = TARGET_CLUSTER; @@ -3182,8 +3260,9 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb int clusterClose(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; - + pDriverData driver_data = (pDriverData)inf_v; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + /** Update statistics. **/ ClusterStatistics.CloseCalls++; @@ -3191,7 +3270,8 @@ clusterClose(void* inf_v, pObjTrxTree* oxt) if (driver_data == NULL) return 0; /** Unlink the driver's node data. **/ - pNodeData node_data = driver_data->NodeData; + pNodeData node_data = check_ptr(driver_data->NodeData); /** Failure ignored. **/ + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); if (node_data != NULL && --node_data->OpenCount == 0) ci_FreeNodeData(driver_data->NodeData); @@ -3217,29 +3297,42 @@ clusterClose(void* inf_v, pObjTrxTree* oxt) void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) { - pClusterQuery cluster_query = NULL; - pDriverData driver_data = inf_v; + pQueryData query_data = NULL; + /** Get driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (driver_data == NULL) goto err_free; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + if (driver_data->TargetType != TARGET_SEARCH && driver_data->TargetType != TARGET_CLUSTER && driver_data->TargetType != TARGET_NODE) { /** Queries are not supported for this target type. **/ - return NULL; + goto err; } /** Update statistics. **/ ClusterStatistics.OpenQueryCalls++; /** Allocate memory for the query. **/ - cluster_query = check_ptr(nmMalloc(sizeof(ClusterQuery))); - if (cluster_query == NULL) return NULL; + query_data = check_ptr(nmMalloc(sizeof(ClusterQuery))); + if (query_data == NULL) goto err_free; /** Initialize the query. **/ - cluster_query->DriverData = (pDriverData)inf_v; - cluster_query->RowIndex = 0u; - - return cluster_query; + SETMAGIC(query_data, MGK_CL_QUERY_DATA); + query_data->DriverData = (pDriverData)inf_v; + query_data->RowIndex = 0u; + + return query_data; + + err_free: + /** Error cleanup. **/ + if (query_data != NULL) nmFree(query_data, sizeof(ClusterQuery)); + mssErrorf(0, "Cluster", "Failed to open query."); + + err: + return NULL; } @@ -3258,18 +3351,28 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { - pClusterQuery cluster_query = (pClusterQuery)qy_v; - pDriverData driver_data = cluster_query->DriverData; pDriverData result_data = NULL; + /** Unpack data into local variables. **/ + pQueryData query_data = check_ptr((pQueryData)qy_v); + if (query_data == NULL) goto err_free; + ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); + pDriverData driver_data = check_ptr(query_data->DriverData); + if (driver_data == NULL) goto err_free; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + pNodeData node_data = check_ptr(driver_data->NodeData); + if (node_data == NULL) goto err_free; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + /** Update statistics. **/ ClusterStatistics.FetchCalls++; /** Allocate result struct. **/ result_data = check_ptr(nmMalloc(sizeof(DriverData))); - if (result_data == NULL) goto err; + if (result_data == NULL) goto err_free; /** Default initialization. **/ + SETMAGIC(result_data, MGK_CL_DRIVER_DATA); result_data->NodeData = driver_data->NodeData; result_data->TargetData = driver_data->TargetData; result_data->TargetType = 0; /* Unset. */ @@ -3277,16 +3380,13 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) result_data->TargetAttrIndex = 0; /* Reset. */ result_data->TargetMethodIndex = 0; /* Reset. */ - /** Load node data. **/ - pNodeData node_data = driver_data->NodeData; - /** Ensure that the data being fetched exists and is computed. **/ const TargetType target_type = driver_data->TargetType; switch (target_type) { case TARGET_NODE: { - unsigned int index = cluster_query->RowIndex++; + unsigned int index = query_data->RowIndex++; /** Iterate over clusters. **/ const unsigned int n_cluster_datas = node_data->nClusterDatas; @@ -3311,25 +3411,26 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) else index -= n_search_datas; /** Iteration complete. **/ - goto done; + goto done_free; } case TARGET_CLUSTER: { /** Ensure the required data is computed. **/ pClusterData target = (pClusterData)driver_data->TargetData; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (ci_ComputeClusterData(target, node_data) != 0) { mssErrorf(0, "Cluster", "Failed to compute ClusterData for query."); - goto err; + goto err_free; } /** Stop iteration if the requested data does not exist. **/ - if (cluster_query->RowIndex >= target->nClusters) goto done; + if (query_data->RowIndex >= target->nClusters) goto done_free; /** Set the data being fetched. **/ result_data->TargetType = TARGET_CLUSTER_ENTRY; - result_data->TargetIndex = cluster_query->RowIndex++; + result_data->TargetIndex = query_data->RowIndex++; break; } @@ -3338,18 +3439,19 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { /** Ensure the required data is computed. **/ pSearchData target = (pSearchData)driver_data->TargetData; + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (ci_ComputeSearchData(target, node_data) != 0) { mssErrorf(0, "Cluster", "Failed to compute SearchData for query."); - goto err; + goto err_free; } /** Stop iteration if the requested data does not exist. **/ - if (cluster_query->RowIndex >= target->nDups) goto done; + if (query_data->RowIndex >= target->nDups) goto done_free; /** Set the data being fetched. **/ result_data->TargetType = TARGET_SEARCH_ENTRY; - result_data->TargetIndex = cluster_query->RowIndex++; + result_data->TargetIndex = query_data->RowIndex++; break; } @@ -3357,11 +3459,11 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_CLUSTER_ENTRY: case TARGET_SEARCH_ENTRY: mssErrorf(1, "Cluster", "Querying a query result is not allowed."); - goto err; + goto err_free; default: mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); - goto err; + goto err_free; } /** Add a link to the NodeData so that it isn't freed while we're using it. **/ @@ -3370,10 +3472,10 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) /** Success. **/ return result_data; - err: + err_free: mssErrorf(0, "Cluster", "Failed to fetch query result."); - done: + done_free: if (result_data != NULL) nmFree(result_data, sizeof(DriverData)); return NULL; } @@ -3390,8 +3492,16 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ***/ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) - { - if (qy_v != NULL) nmFree(qy_v, sizeof(ClusterQuery)); + { + /** No work needed to free NULL. **/ + if (qy_v == NULL) return 0; + + /** Cast the query data. **/ + pQueryData query_data = qy_v; + ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); + + /** Free the query data. **/ + nmFree(query_data, sizeof(ClusterQuery)); return 0; } @@ -3403,15 +3513,20 @@ clusterQueryClose(void* qy_v, pObjTrxTree* oxt) *** @param inf_v The driver instance. *** @param attr_name The name of the requested attribute. *** @param oxt The object system tree, similar to a kind of "scope" (unused). - *** @returns The datatype, see datatypes.h for a list of valid datatypes. + *** @returns The datatype, see datatypes.h for a list of valid datatypes, or + *** -1 if an error occurs. *** *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; - + /** Extract target type from driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (driver_data == NULL) goto err; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + const TargetType target_type = driver_data->TargetType; + /** Update statistics. **/ ClusterStatistics.GetTypeCalls++; @@ -3437,17 +3552,17 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "date_created") == 0 || strcmp(attr_name, "date_computed") == 0) { - return (driver_data->TargetType == TARGET_CLUSTER - || driver_data->TargetType == TARGET_CLUSTER_ENTRY - || driver_data->TargetType == TARGET_SEARCH - || driver_data->TargetType == TARGET_SEARCH_ENTRY) + return (target_type == TARGET_CLUSTER + || target_type == TARGET_CLUSTER_ENTRY + || target_type == TARGET_SEARCH + || target_type == TARGET_SEARCH_ENTRY) ? DATA_T_DATETIME /* Target has date attr. */ : DATA_T_UNAVAILABLE; /* Target does not have date attr. */ } /** Types for specific data targets. **/ handle_targets: - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: if (strcmp(attr_name, "source") == 0 @@ -3489,11 +3604,14 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) break; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); return DATA_T_UNAVAILABLE; } - - return DATA_T_UNAVAILABLE; + + return DATA_T_UNAVAILABLE; + + err: + return -1; } @@ -3519,7 +3637,11 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + /** Extract target type from driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (driver_data == NULL) goto err; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + const TargetType target_type = driver_data->TargetType; /** Update statistics. **/ ClusterStatistics.GetValCalls++; @@ -3528,7 +3650,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (attr_name == NULL) { fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); - return DATA_T_UNAVAILABLE; + goto err; } /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ @@ -3538,6 +3660,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Type check. **/ const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); + if (expected_datatype == DATA_T_UNAVAILABLE) goto unknown_attribute; if (datatype != expected_datatype) { mssErrorf(1, "Cluster", @@ -3551,24 +3674,39 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (strcmp(attr_name, "name") == 0) { ClusterStatistics.GetValCalls_name++; - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: - val->String = ((pSourceData)driver_data->TargetData)->Name; + { + pSourceData source_data = check_ptr(driver_data->TargetData); + if (source_data == NULL) goto err; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + val->String = source_data->Name; break; + } case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: - val->String = ((pClusterData)driver_data->TargetData)->Name; + { + pClusterData cluster_data = check_ptr(driver_data->TargetData); + if (cluster_data == NULL) goto err; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + val->String = cluster_data->Name; break; + } case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: - val->String = ((pSearchData)driver_data->TargetData)->Name; + { + pSearchData search_data = check_ptr(driver_data->TargetData); + if (search_data == NULL) goto err; + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + val->String = search_data->Name; break; + } default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); return -1; } @@ -3578,7 +3716,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Handle annotation. **/ if (strcmp(attr_name, "annotation") == 0) { - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: val->String = "Clustering driver."; break; case TARGET_CLUSTER: val->String = "Clustering driver: Cluster."; break; @@ -3587,7 +3725,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); return -1; } return 0; @@ -3607,7 +3745,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } if (strcmp(attr_name, "internal_type") == 0) { - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: val->String = "system/cluster"; break; case TARGET_CLUSTER: val->String = "cluster/cluster"; break; @@ -3615,7 +3753,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: val->String = "cluster/search"; break; case TARGET_SEARCH_ENTRY: val->String = "search/entry"; break; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); return -1; } @@ -3625,10 +3763,10 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Last modification is not implemented. **/ if (strcmp(attr_name, "last_modification") == 0) { - if (driver_data->TargetType == TARGET_CLUSTER - || driver_data->TargetType == TARGET_CLUSTER_ENTRY - || driver_data->TargetType == TARGET_SEARCH - || driver_data->TargetType == TARGET_SEARCH_ENTRY) + if (target_type == TARGET_CLUSTER + || target_type == TARGET_CLUSTER_ENTRY + || target_type == TARGET_SEARCH + || target_type == TARGET_SEARCH_ENTRY) goto date_computed; else return 1; /* null */ } @@ -3636,7 +3774,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Handle date_created. **/ if (strcmp(attr_name, "date_created") == 0) { - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: /** Attribute is not defined for this target type. **/ @@ -3644,13 +3782,23 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: - val->DateTime = &((pClusterData)driver_data->TargetData)->DateCreated; + { + pClusterData cluster_data = check_ptr(driver_data->TargetData); + if (cluster_data == NULL) goto err; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + val->DateTime = &cluster_data->DateCreated; return 0; + } case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: - val->DateTime = &((pSearchData)driver_data->TargetData)->DateCreated; + { + pSearchData search_data = check_ptr(driver_data->TargetData); + if (search_data == NULL) goto err; + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + val->DateTime = &search_data->DateCreated; return 0; + } } return -1; } @@ -3659,7 +3807,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (strcmp(attr_name, "date_computed") == 0) { date_computed: - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: /** Attribute is not defined for this target type. **/ @@ -3668,7 +3816,9 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: { - pClusterData target = (pClusterData)driver_data->TargetData; + pClusterData target = check_ptr((pClusterData)driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); pDateTime date_time = &target->DateComputed; if (date_time->Value == 0) return 1; /* null */ else val->DateTime = date_time; @@ -3678,7 +3828,9 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: { - pSearchData target = (pSearchData)driver_data->TargetData; + pSearchData target = check_ptr((pSearchData)driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); pDateTime date_time = &target->DateComputed; if (date_time->Value == 0) return 1; /* null */ else val->DateTime = date_time; @@ -3687,37 +3839,43 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } /** Default: Unknown type. **/ - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); return -1; } /** Handle attributes for specific data targets. **/ handle_targets: - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: + { + pSourceData source_data = check_ptr(driver_data->TargetData); + if (source_data == NULL) goto err; + ASSERTMAGIC(source_data, MGK_CL_SEARCH_DATA); + if (strcmp(attr_name, "source") == 0) { - /** TODO: THAT'S NOT A SOURCE DATA STRUCT!?!?!?!?!?!?!??!?!?!? */ - val->String = ((pSourceData)driver_data->TargetData)->SourcePath; - fprintf(stderr, "Got source: \"%s\"", val->String); + val->String = source_data->SourcePath; return 0; } if (strcmp(attr_name, "key_attr") == 0) { - val->String = ((pSourceData)driver_data->TargetData)->KeyAttr; + val->String = source_data->KeyAttr; return 0; } if (strcmp(attr_name, "name_attr") == 0) { - val->String = ((pSourceData)driver_data->TargetData)->NameAttr; + val->String = source_data->NameAttr; return 0; } break; + } case TARGET_CLUSTER: { - pClusterData target = (pClusterData)driver_data->TargetData; + pClusterData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (strcmp(attr_name, "algorithm") == 0) { @@ -3753,7 +3911,9 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: { - pSearchData target = (pSearchData)driver_data->TargetData; + pSearchData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (strcmp(attr_name, "source") == 0) { @@ -3774,11 +3934,18 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER_ENTRY: { - pClusterData target = (pClusterData)driver_data->TargetData; + pClusterData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; + ASSERTMAGIC(target_cluster, MGK_CL_CLUSTER); if (strcmp(attr_name, "items") == 0) { + /** Extract target strings (the result). **/ + char** target_strings = check_ptr(target_cluster->Strings); + if (target_strings == NULL) goto err; + /** Static variable to prevent leaking StringVec from previous calls. **/ static StringVec* vec = NULL; if (vec != NULL) nmFree(vec, sizeof(StringVec)); @@ -3787,7 +3954,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO val->StringVec = vec = check_ptr(nmMalloc(sizeof(StringVec))); if (val->StringVec == NULL) return -1; val->StringVec->nStrings = target_cluster->Size; - val->StringVec->Strings = target_cluster->Strings; + val->StringVec->Strings = target_strings; /** Success. **/ return 0; @@ -3797,8 +3964,11 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH_ENTRY: { - pSearchData target = (pSearchData)driver_data->TargetData; - pDup target_dup = target->Dups[driver_data->TargetIndex]; + pSearchData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); + pDup target_dup = check_ptr(target->Dups[driver_data->TargetIndex]); + if (target_dup == NULL) goto err; if (strcmp(attr_name, "sim") == 0) { @@ -3822,18 +3992,21 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); return -1; } - /** Unknown attribute. **/ + unknown_attribute: + ci_UnknownAttribute(attr_name, driver_data->TargetType); + + err:; char* name; clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); mssErrorf(1, "Cluster", - "Unknown attribute '%s' for cluster object %s (target type: %u, \"%s\").", - attr_name, driver_data->NodeData->SourceData->Name, driver_data->TargetType, name + "Failed to get attribute for cluster object %s (target type: %u, \"%s\").", + driver_data->NodeData->SourceData->Name, target_type, name ); - + return -1; } @@ -3856,10 +4029,15 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; pObjPresentationHints hints = NULL; pParamObjects tmp_list = NULL; + /** Extract target type from driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (driver_data == NULL) goto err_free; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + const TargetType target_type = driver_data->TargetType; + /** Malloc presentation hints struct. **/ hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); if (hints == NULL) goto err_free; @@ -3902,10 +4080,10 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "date_created") == 0 || strcmp(attr_name, "date_computed") == 0) { - if (driver_data->TargetType == TARGET_CLUSTER - || driver_data->TargetType == TARGET_CLUSTER_ENTRY - || driver_data->TargetType == TARGET_SEARCH - || driver_data->TargetType == TARGET_SEARCH_ENTRY) + if (target_type == TARGET_CLUSTER + || target_type == TARGET_CLUSTER_ENTRY + || target_type == TARGET_SEARCH + || target_type == TARGET_SEARCH_ENTRY) { hints->Length = 24; hints->VisualLength = 20; @@ -3916,7 +4094,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } /** Search by target type. **/ - switch (driver_data->TargetType) + switch (target_type) { case TARGET_NODE: if (strcmp(attr_name, "source") == 0) @@ -4032,7 +4210,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } /** End of overlapping region. **/ - if (driver_data->TargetType == TARGET_CLUSTER) break; + if (target_type == TARGET_CLUSTER) break; if (strcmp(attr_name, "source") == 0) { @@ -4057,8 +4235,10 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) case TARGET_CLUSTER_ENTRY: { - pClusterData target = (pClusterData)check_ptr(driver_data->TargetData); - if (target == NULL) goto err_free; + /** Unused. **/ + // pClusterData target = check_ptr(driver_data->TargetData); + // if (target == NULL) goto err_free; + // ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (strcmp(attr_name, "items") == 0) { @@ -4085,8 +4265,10 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) case TARGET_SEARCH_ENTRY: { - pSearchData target = (pSearchData)check_ptr(driver_data->TargetData); - if (target == NULL) goto err_free; + /** Unused. **/ + // pSearchData target = check_ptr(driver_data->TargetData); + // if (target == NULL) goto err_free; + // ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (strcmp(attr_name, "key1") == 0) { @@ -4118,16 +4300,15 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); goto err_free; } - /** Unknown attribute. **/ - unknown_attribute:; - mssErrorf(1, "Cluster", "Unknown attribute '%s'.", attr_name); + unknown_attribute: + ci_UnknownAttribute(attr_name, driver_data->TargetType); - /** Error cleanup. **/ err_free: + /** Error cleanup. **/ if (hints != NULL) nmFree(hints, sizeof(ObjPresentationHints)); hints = NULL; @@ -4161,8 +4342,9 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; - + pDriverData driver_data = (pDriverData)inf_v; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + driver_data->TargetAttrIndex = 0u; return clusterGetNextAttr(inf_v, oxt); @@ -4182,7 +4364,8 @@ clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = (pDriverData)inf_v; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const unsigned int i = driver_data->TargetAttrIndex++; switch (driver_data->TargetType) @@ -4212,8 +4395,10 @@ clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) int clusterInfo(void* inf_v, pObjectInfo info) { - pDriverData driver_data = (pDriverData)inf_v; - pNodeData node_data = (pNodeData)driver_data->NodeData; + pDriverData driver_data = (pDriverData)inf_v; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + pNodeData node_data = (pNodeData)driver_data->NodeData; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Reset flags buffer. **/ info->Flags = 0; @@ -4299,8 +4484,9 @@ clusterInfo(void* inf_v, pObjectInfo info) char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; - + pDriverData driver_data = (pDriverData)inf_v; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + driver_data->TargetMethodIndex = 0u; return clusterGetNextMethod(inf_v, oxt); @@ -4320,7 +4506,8 @@ clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = (pDriverData)inf_v; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); return METHOD_NAMES[driver_data->TargetMethodIndex++]; } @@ -4488,7 +4675,8 @@ ci_CacheFreeSearch(pXHashEntry entry, void* path) int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = (pDriverData)inf_v; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); /** Cache management method. **/ if (strcmp(method_name, "cache") == 0) From 495597e8567d21d8904636290c981ae9ea015a22 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Jan 2026 11:15:56 -0700 Subject: [PATCH 043/361] Fix a broken test by increasing the tolerance for reasonable deviations. --- centrallix/tests/test_cos_compare_00.to | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix/tests/test_cos_compare_00.to b/centrallix/tests/test_cos_compare_00.to index c5b0b1a5b..f6a635389 100644 --- a/centrallix/tests/test_cos_compare_00.to +++ b/centrallix/tests/test_cos_compare_00.to @@ -9,16 +9,16 @@ query select case4 = condition((cos_compare('hello there', 'hellow there') >= 0. # Tests on fabricated contact information. # All email addresses and phone numbers are imaginary and were fabricated for the purposes of this test -query select cynthia = condition((cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") >= 0.49) and (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") <= 0.54), "pass", "fail") +query select cynthia = condition((cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") >= 0.45) and (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") <= 0.59), "pass", "fail") -query select timothy = condition((cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.45) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.50), "pass", "fail") +query select timothy = condition((cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.40) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.55), "pass", "fail") -query select lance = condition((cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.425) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.475), "pass", "fail") +query select lance = condition((cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.4) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.5), "pass", "fail") query select gregory = condition((cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") >= 0.94) and (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") <= 0.99), "pass", "fail") -query select nathan = condition((cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >= 0.575) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.625), "pass", "fail") +query select nathan = condition((cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >= 0.6) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.7), "pass", "fail") query select identical = condition((cos_compare("This is an identical case", "This is an identical case") >= 0.975) and (cos_compare("This is an identical case", "This is an identical case") <= 1.00), "pass", "fail") -query select name = condition((cos_compare("Samuel", "Alex") >= 0.00) and (cos_compare("Samuel", "Alex") <= 0.025), "pass", "fail") +query select name = condition((cos_compare("Samuel", "Alex") >= 0.00) and (cos_compare("Samuel", "Alex") <= 0.05), "pass", "fail") From ab71333d202ec83687786d25fbe132c1058625f3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 28 Jan 2026 12:58:18 -0700 Subject: [PATCH 044/361] Compile tests with -lm to prevent sporadic linker errors. --- centrallix-lib/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 0daf7e568..91b670e83 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -59,7 +59,7 @@ LIBS = @LIBS@ ## PROFILE=@PROFILE@ COVERAGE=@COVERAGE@ -CFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PROFILE) $(COVERAGE) -g +CFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PROFILE) $(COVERAGE) -g -lm MTCFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PROFILE) $(COVERAGE) -g -O0 TCFLAGS=$(patsubst -DNDEBUG,,$(CFLAGS)) From 65e4458e1a8110d359cc311d514f3ad657bd03e2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 28 Jan 2026 13:00:10 -0700 Subject: [PATCH 045/361] Fix a critical driver bug causing levenshtein to be executed on the cosine vectors instead of the data strings. Clean up driver code to make it more readable. --- centrallix/osdrivers/objdrv_cluster.c | 105 +++++++++++++++++--------- 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index f22449ec6..62df6007e 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -260,6 +260,32 @@ ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) return NULL; /** Unreachable. **/ } +/*** Converts a similarity measure to the function pointer to a comparison + *** function. This function can be used to compute similarity between two + *** values, or it can be passed to `clusters.c` which uses it for similar + *** operations. + *** + *** @param similarity_measure The similarity measure to be converted. + *** @returns A similarity computation function. This function takes two void + *** pointers, representing the data to be compared, and a double that + *** represents how similar the data is. Or returns NULL if an error occurs, + *** calling mssError() to provide an error message. + ***/ +double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(void*, void*) + { + switch (similarity_measure) + { + case SIMILARITY_COSINE: return ca_cos_compare; + case SIMILARITY_LEVENSHTEIN: return ca_lev_compare; + default: + mssErrorf(1, "Cluster", + "Unknown similarity meansure \"%s\".", + ci_SimilarityMeasureToString(similarity_measure) + ); + return NULL; + } + } + /*** Enum representing the type of data targetted by the driver, *** set based on the path given when the driver is used to open *** a cluster file. @@ -2605,8 +2631,8 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { - cluster_data->Sims = NULL; - cluster_data->Clusters = NULL; + size_t clusters_size = -1; + size_t sims_size = -1; /** Guard segfaults. **/ if (cluster_data == NULL || node_data == NULL) return -1; @@ -2640,8 +2666,8 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (!check(objCurrentDate(&cluster_data->DateComputed))) goto err_free; /** Allocate static memory for finding clusters. **/ - const size_t clusters_size = cluster_data->nClusters * sizeof(Cluster); - const size_t sims_size = source_data->nVectors * sizeof(double); + clusters_size = cluster_data->nClusters * sizeof(Cluster); + sims_size = source_data->nVectors * sizeof(double); cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); if (cluster_data->Clusters == NULL) goto err_free; @@ -2770,8 +2796,8 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { /*** NOTE: The clusters here do not need to each be freed - *** individually because they are part of the dynamically - *** allocated Clusters array (freed after the loop). + *** individually because the structs themselves are stored + *** directly in the cluster_data->Clusters array. *** Thus, this loop only frees each cluster's content. ***/ pCluster cluster = &cluster_data->Clusters[i]; @@ -2836,29 +2862,8 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Record the date and time. **/ if (!check(objCurrentDate(&search_data->DateComputed))) goto err_free; - /** Select the correct comparison function based on the similarity measure. **/ - const double (*similarity_function)(void *, void *); - char* similarity_function_name; - switch (search_data->SimilarityMeasure) - { - case SIMILARITY_COSINE: - similarity_function = ca_cos_compare; - similarity_function_name = "cosine"; - break; - - case SIMILARITY_LEVENSHTEIN: - similarity_function = ca_lev_compare; - similarity_function_name = "Levenstein"; - break; - - default: - mssErrorf(1, "Cluster", - "Unknown similarity meansure \"%s\".", - ci_SimilarityMeasureToString(search_data->SimilarityMeasure) - ); - goto err_free; - } - + /** Get the comparison function based on the similarity measure. **/ + const double (*similarity_function)(void *, void *) = ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure); /** Execute the search using the specified algorithm. **/ pXArray dups_temp = NULL; @@ -2868,9 +2873,23 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) *** was computed during the clustering phase. ***/ + /** Get a pointer to the data that will be used for the search. **/ + void** data = NULL; + switch (search_data->SimilarityMeasure) + { + case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; + case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; + default: + mssErrorf(1, "Cluster", + "Unknown similarity meansure \"%s\".", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; + } + /** Execute sliding search. **/ dups_temp = check_ptr(ca_sliding_search( - (void**)source_data->Vectors, + data, source_data->nVectors, cluster_data->MaxIterations, /* Window size. */ similarity_function, @@ -2882,7 +2901,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { mssErrorf(1, "Cluster", "Failed to compute sliding search with %s similarity measure.", - similarity_function_name + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; } @@ -2893,11 +2912,25 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { /** Extract the struct for the cluster. **/ pCluster cluster = &cluster_data->Clusters[i]; - // ASSERTMAGIC(cluster, MGK_CL_CLUSTER); + ASSERTMAGIC(cluster, MGK_CL_CLUSTER); + + /** Get a pointer to the data that will be used for the search. **/ + void** data = NULL; + switch (search_data->SimilarityMeasure) + { + case SIMILARITY_COSINE: data = (void**)cluster->Vectors; break; + case SIMILARITY_LEVENSHTEIN: data = (void**)cluster->Strings; break; + default: + mssErrorf(1, "Cluster", + "Unknown similarity meansure \"%s\".", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; + } /** Execute complete search. **/ dups_temp = check_ptr(ca_complete_search( - (void**)cluster->Vectors, + data, cluster->Size, similarity_function, search_data->Threshold, @@ -2908,7 +2941,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { mssErrorf(1, "Cluster", "Failed to compute complete search with %s similarity measure.", - similarity_function_name + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; } @@ -3637,11 +3670,13 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { + TargetType target_type = -1; + /** Extract target type from driver data. **/ pDriverData driver_data = check_ptr(inf_v); if (driver_data == NULL) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); - const TargetType target_type = driver_data->TargetType; + target_type = driver_data->TargetType; /** Update statistics. **/ ClusterStatistics.GetValCalls++; From 8ba449eff3b7c5ba359f19b0cb497d5125f27593 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 28 Jan 2026 14:47:28 -0700 Subject: [PATCH 046/361] Fix a major bug where punctuation and whitespace was not properly ignored during cosine vector creation. Improve edge case handling in clusters.c. --- centrallix-lib/src/clusters.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 652d5571c..4e0910352 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -163,7 +163,7 @@ ca_build_vector(const char* str) unsigned char c = (unsigned char)maybe_char; /** Always consider boundary character in string. **/ - if (c != CA_BOUNDARY_CHAR) goto skip_checks; + if (c == CA_BOUNDARY_CHAR) goto skip_checks; /** Ignore insignificant characters: spaces and punctuation. **/ if (isspace(c)) continue; /* space, \n, \v, \f, \r */ @@ -303,6 +303,12 @@ ca_sparse_len(const pVector vector) void ca_print_vector(const pVector vector) { + if (vector == NULL) + { + printf("Vector: NULL"); + return; + } + const unsigned int len = ca_sparse_len(vector); printf("Vector: [%d", vector[0]); for (unsigned int i = 1u; i < len; i++) @@ -654,7 +660,8 @@ ca_lev_compare(void* str1, void* str2) return round(normalized_similarity * 1000000.0) / 1000000.0; } -/*** Check if two sparse vectors are identical. +/*** Check if two sparse vectors are identical. True if both vectors are + *** `NULL`, but false if one is and the other is not. *** *** @param v1 The first vector. *** @param v2 The second vector. @@ -664,8 +671,11 @@ ca_lev_compare(void* str1, void* str2) bool ca_eql(pVector v1, pVector v2) { - const unsigned int len = ca_sparse_len(v1); + /** Edge cases. **/ + if (v1 == v2) return true; + if (v1 == NULL || v2 == NULL) return false; + const unsigned int len = ca_sparse_len(v1); for (unsigned int i = 0u; i < len; i++) if (v1[i] != v2[i]) return false; From 2be1d2264a1d52c542479cb90c9b0f4b13a489ab Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 30 Jan 2026 15:15:36 -0700 Subject: [PATCH 047/361] Remove a broken link in clusters.h. Add CommentAnchorsExtension.md to document the use of the comment anchors VSCode extension. Add comment anchors support to string_similarity.md. Add a new known issue to string_similarity.md. --- centrallix-lib/include/clusters.h | 4 +- centrallix-lib/src/clusters.c | 5 +- centrallix-sysdoc/CommentAnchorsExtension.md | 96 ++++++++++++++++++++ centrallix-sysdoc/string_similarity.md | 43 +++++---- centrallix/osdrivers/objdrv_cluster.c | 17 +--- 5 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 centrallix-sysdoc/CommentAnchorsExtension.md diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 288f1c2c5..fd94d0be2 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -44,6 +44,9 @@ #include "cxlib/xarray.h" #endif +/** This file has additional documentation in string_similarity.md. **/ + + /*** This value defines the number of dimensions used for a sparse *** vector. The higher the number, the fewer collisions will be *** encountered when using these vectors for cosine comparisons. @@ -63,7 +66,6 @@ ***/ #define CA_NUM_DIMS 251 -/// LINK ../../centrallix-sysdoc/string_comparison.md#cosine_charsets /** The character used to create a pair with the first and last characters of a string. **/ #define CA_BOUNDARY_CHAR (unsigned char)('a' - 1) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 4e0910352..88b1bf318 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -32,8 +32,6 @@ /* string-similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ -/** This file has additional documentation in string_similarity.md. **/ - #include #include #include @@ -49,6 +47,9 @@ #include "util.h" #include "xarray.h" +/** This file has additional documentation in string_similarity.md. **/ + + /*** Gets the hash, representing a pair of ASCII characters, represented by unsigned ints. *** Thank you to professor John Delano for this hashing algorithm. *** diff --git a/centrallix-sysdoc/CommentAnchorsExtension.md b/centrallix-sysdoc/CommentAnchorsExtension.md new file mode 100644 index 000000000..397e5fa92 --- /dev/null +++ b/centrallix-sysdoc/CommentAnchorsExtension.md @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The Comment Anchors Extension +A couple of files in Centrallix use the [Comment Anchors VSCode extension](https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors) from Starlane Studios. This extension allows developers to use certain text in comments to create links to other files, or other locations within the same file. This extension is _optional_ and developing in Centrallix without it is fine. If you don't intend to use it, you can safely ignore `LINK`, `ANCHOR`, and similar comments intended for it, although please try not to break them. + + +## Table of Contents +- [The Comment Anchors Extension](#the-comment-anchors-extension) + - [Table of Contents](#table-of-contents) + - [Basic Features](#basic-features) + - [Links](#links) + - [Anchors](#anchors) + - [Quick Warning](#quick-warning) + - [Informing other developers](#informing-other-developers) + + +## Basic Features +Below is a quick summary of the primary features from the extension that are used in Centrallix. This is intended to help get developers up to speed easily without needing to sift through other features that aren't currently in use. + +_For more information, visit the [extension page](https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors)._ + + +### Links +Create a link by writing `LINK ` at the start of a line in a documentation comment. Control click the link to jump to the destination in the editor. A link might look like: +```c +/*** To read the license, click the link below: + *** LINK LICENSE + ***/ +``` +- Use `:line-number` to link to a specific line number. +- Use `#` to link to an [anchor](#anchors) with that id. +- Intra-file links don't need to specify a file (e.g. `LINK #example`). + +Note: Invalid or broken links to a location in a file will sometimes default to the first line of the file. + + +### Anchors +Create an anchor by writing `ANCHOR[id=]` at the start of a line in a documentation comment, like this: +```c +/*** ANCHOR[id=intro] + *** This file will teach you how to never write a memory error in C ever again... + ***/ +``` +Add `#ID` to the end of a link to target the anchor with that ID (useful for navigating long files, such as `objdrv_cluster.c`). + + +## Quick Warning +The comment anchors extension is a little less robust than I would like. Sometimes, placing characters before or after links or anchors on the same line will cause them to break. The extension is sometimes a little finicky. + + +## Informing other developers +When using the comment anchors extension, include something closely resembling the following snippet at the top of the file, just below the copywrite notice and include statements: + +**.c or .h file** +```c +/*** This file uses the optional Comment Anchors VSCode extension, documented + *** with CommentAnchorsExtension.md in centrallix-sysdoc. + ***/ +``` + +**.md file** +```md + +``` \ No newline at end of file diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index b6c96e8a2..310a5c2f2 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -30,12 +30,16 @@ + + -# String Similarity -The following sections discuss the approaches to calculating similarity between two strings using the `clusters.c` library. This library can be included using `#include "cxlib/clusters.h"` in the centrallix codebase (use `#include "clusters.h"` in other libaries in centrallix-lib). +# String Similarity +The following sections discuss the approaches to calculating similarity between two strings using the `clusters.c` library. This library can be included using `#include "cxlib/clusters.h"` in the centrallix codebase (use `#include "clusters.h"` in other libaries in centrallix-lib). -## Table of Contents +## Table of Contents - [String Comparison](#string-comparison) - [Table of Contents](#table-of-contents) - [Cosine Similarity](#cosine-similarity) @@ -54,12 +58,14 @@ The following sections discuss the approaches to calculating similarity between - [Future Implementation](#future-implementation) - [K-means Fuzzy Clustering](#k-means-fuzzy-clusterings) - [Implement Missing Algorithms](#implement-missing-algorithms) + - [Upgrade Other Duplicate Detection Systems](#upgrade-other-duplicate-detection-systems) + - [Fix Known Issues](#fix-known-issues) -## Cosine Similarity +## Cosine Similarity The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. Conceptually, it's like finding the _angle_ between two vectors. To get these vectors, we use the relative frequency of character pairs within each string. To reduce memory cost and speed up computation, we store them in a special sparsely allocated form, described below. -### Character Sets +### Character Sets Cosine compare currently uses the following character sets. These can be extended or modified later, if necessary. ```c const char ALLOW_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -77,35 +83,35 @@ const char BOUNDARY_CHAR = ('a' - 1); // aka. '`' - This character appears to have been selected to be one before the first character in `CHAR_SET` (thus convention dictates that it be written `'a' - 1` to indicate this), although it's unknown if that's the main or only reason. - If `clusters.h` is included, it can be accessed using the `CA_BOUNDARY_CHAR` macro. -### Character Pair Hashing +### Character Pair Hashing Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296` possible character pairs. If the number of characters in the `CHAR_SET` ever needed to be expanded - for example, to include all UTF-8 characters - this number would quickly explode exponentially to utterly infeasible proportions. Thus, a hashing algorithm is employed to hash each character pair down to a more reasonable number of dimensions (which can be accessed with the `CA_NUM_DIMS` macro). -### String Vectors +### String Vectors Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies to based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. -### Sparse Vectors +### Sparse Vectors As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. **Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. **Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. -### Computing Similarity +### Computing Similarity Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. -## Levenshtein Similarity +## Levenshtein Similarity The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using levenshtein edit distance. The levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. -## Clustering +## Clustering When searching for similar strings in a large amount of data (for example, `1,000,000` strings), comparing every string to every other string can be very computationally expensive. To speed up this process, it is helpful to _cluster_ similar strings together, then only compare strings within similar clusters. This sacrifices some accuracy to allow large amounts of data to be searched and compared in a feasible amount of time. -### K-means Clustering +### K-means Clustering When clustering data using the [k-means](https://en.wikipedia.org/wiki/K-means_clustering) algorithm, data is divided into a predefined number of clusters with the goal of maximizing the average similarity of data points within any given cluster. To quickly summarize the algorithm: 1. Randomly select `k` data points to be the initial centroids of each cluster. 2. For each data point, find the centroid it is most similar to, and assign it to that cluster. @@ -116,18 +122,18 @@ The implementation used in `clusters.c` also allows the programmer to specify a The `ca_kmeans()` function can be invoked using [the cosine comparison string vectors](#string-vectors) (see above) to cluster them into similar clusters. -### K-means++ Clustering +### K-means++ Clustering **Not yet implemented** This method is largely identical to k-means, except that [k-means++](https://en.wikipedia.org/wiki/K-means%2B%2B) assigns the initial centroids using an approximate algorithm designed to avoid some of the poor clustering possible with random assignment. -### K-medoids Clustering +### K-medoids Clustering **Not yet implemented** This method is also very similar to k-means, except that [k-medoids](https://en.wikipedia.org/wiki/K-medoids) places an additional requirement that all centroids be points in the data. This would theoretically allow for other similarity measures (such as Levenshtein edit distance) to be used for clustering instead of only cosine compare. -### DB-Scan +### DB-Scan **Proposed, not yet implemented or documented** -### Sliding Clusters +### Sliding Clusters A far more basic method of "clustering" is to simply sort all data alphabetically, then, instead of comparing each string to all other strings, it can be compared to only the next `n` strings. Of course, differences near the start of a string (for example, "fox" vs. "box") will cause those strings to sort far away from each other, leading them to be completely missed. Sorting using a similarity measure, such as `ca_cos_compare()` or `ca_lev_compare()` would resolve this issue. However, these comparison functions do not meet the transitivity requirement for sorting, which is that `(A < B) & (B < C) -> (A < C)`. For example, "car" is similar to "boxcar", which is also similar to "box". However, "car" and "box" are not similar at all. @@ -152,6 +158,7 @@ Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-m ### Upgrade Other Duplicate Detection Systems When a new record is entered, a quick scan is run to check if it might be a duplicate. There is also a button in the UI for a record that lets you run a duplicate check. These systems could also be upgraded using the new algorithms and strategies developed for general duplicate detection. -### Known Issues +### Fix Known Issues - The cluster driver often fails to open the structure file if it was modifed since the last time the path was openned. Opening a different path (including the root path, even though it does not support queries) fixes this issue. This is either a bug in the st_node caching or in the cluster driver's usage of stparse. -- The cluster does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to invalidate caches when data is changed, or use a fresh object system instance. +- The cluster does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to manually invalidate caches when data is changed, or use a fresh object system instance. +- It is not possible to open cluster or search entries, they can only be retrieved by querying. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 62df6007e..10abd473e 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -53,21 +53,8 @@ #include "st_node.h" #include "stparse.h" -/*** File notes: - *** This file uses comment anchors, provided by the Comment Anchors VSCode - *** extension from Starlane Studios. This allows developers with the extension - *** to control click the "LINK " comments to navigate to the coresponding - *** "ANCHOR[id=]" comment. (Note: Invalid or broken links will default to - *** the first line of the file.) - *** - *** For example, this link should take you to the function signatures: - *** LINK #functions - *** - *** Any developers without this extension can safely ignore these comments, - *** although please try not to break them. :) - *** - *** Comment Anchors VSCode Extension: - *** https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors +/*** This file uses the optional Comment Anchors VSCode extension, documented + *** with CommentAnchorsExtension.md in centrallix-sysdoc. ***/ /** Defaults for unspecified optional attributes. **/ From 814fcfabb871347de4de5db5b33dce9b4c76ef64 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 16 Mar 2026 11:44:41 -0600 Subject: [PATCH 048/361] Fix code that assumed DateTime->Value was seconds since the epoch (it is NOT). Remove DateTime cause for EXP_ARG_POSITIVE and EXP_ARG_NEGATIVE since it didn't work and wasn't used. Add handling for unset DateCreated to objdrv_cluster.c (should never happen). Clean up date handling code. --- centrallix/expression/exp_functions.c | 26 ++------------------------ centrallix/osdrivers/objdrv_cluster.c | 16 ++++++++-------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 24665821e..d0cb5039f 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -92,9 +92,9 @@ *** - `EXP_ARG_NON_EMPTY`: Expect string to be non-empty. Expect a *** stringvec or intvec to have elements (does not check them). *** - `EXP_ARG_POSITIVE`: Expect a positive or zero value for int, double, - *** money, or datetime. (Includes NON_NAN: NAN is not positive). + *** or money. (Includes NON_NAN: NAN is not positive). *** - `EXP_ARG_NEGATIVE`: Expect a negative or zero value for int, double, - *** money, or datetime. (Includes NON_NAN: NAN is not negative). + *** or money. (Includes NON_NAN: NAN is not negative). *** - `EXP_ARG_NON_NAN`: Expect a double to be a number, not NAN. *** *** @attention - Checks like `EXP_ARG_NON_EMPTY`, `EXP_ARG_NON_NAN`, etc. also @@ -283,28 +283,6 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e break; } - case DATA_T_DATETIME: - { - pDateTime value = &arg->Types.Date; - if (arg_expect->Flags & EXP_ARG_POSITIVE && value->Value < 0) - { - mssErrorf(1, "EXP", - "%s(...): Expects positive date offset but got %llu.", - fn_name, value->Value - ); - return -1; - } - if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->Value > 0) - { - mssErrorf(1, "EXP", - "%s(...): Expects negative date offset but got %llu.", - fn_name, value->Value - ); - return -1; - } - break; - } - case DATA_T_MONEY: { pMoneyType value = &arg->Types.Money; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 10abd473e..7fd30eeb4 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3808,7 +3808,8 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO pClusterData cluster_data = check_ptr(driver_data->TargetData); if (cluster_data == NULL) goto err; ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); - val->DateTime = &cluster_data->DateCreated; + if (cluster_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ + else val->DateTime = &cluster_data->DateCreated; return 0; } @@ -3818,7 +3819,8 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO pSearchData search_data = check_ptr(driver_data->TargetData); if (search_data == NULL) goto err; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); - val->DateTime = &search_data->DateCreated; + if (search_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ + else val->DateTime = &search_data->DateCreated; return 0; } } @@ -3841,9 +3843,8 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO pClusterData target = check_ptr((pClusterData)driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); - pDateTime date_time = &target->DateComputed; - if (date_time->Value == 0) return 1; /* null */ - else val->DateTime = date_time; + if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ + else val->DateTime = &target->DateComputed; return 0; } @@ -3853,9 +3854,8 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO pSearchData target = check_ptr((pSearchData)driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - pDateTime date_time = &target->DateComputed; - if (date_time->Value == 0) return 1; /* null */ - else val->DateTime = date_time; + if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ + else val->DateTime = &target->DateComputed; return 0; } } From 8917ae21cf0903b87209ab2a0db89f44de695e31 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 16 Mar 2026 11:55:17 -0600 Subject: [PATCH 049/361] Clean up code and comments. Update expression functions to use tree->Name instead of hard coding the function name. Merge exp_fn_cos_compare() and exp_fn_lev_compare() since exp_fn_compare() can use tree->Name to do both. Remove unused parameters from expression functions. Fix spelling errors. Clean up. --- centrallix/expression/exp_functions.c | 39 ++++++++++----------------- centrallix/osdrivers/objdrv_cluster.c | 16 +++++------ 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index d0cb5039f..80d7ab0d6 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1575,7 +1575,7 @@ int exp_fn_reverse(pExpression tree, pParamObjects objlist, pExpression i0, pExp /** Leading zero trim. */ int -exp_fn_lztrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +exp_fn_lztrim(pExpression tree) { /** Expect one nullable string parameter. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -4525,7 +4525,7 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress int -exp_fn_metaphone(pExpression tree, pParamObjects obj_list) +exp_fn_metaphone(pExpression tree) { /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -4574,15 +4574,16 @@ exp_fn_metaphone(pExpression tree, pParamObjects obj_list) /*** Computes cosine or Levenshtein similarity between two strings. These two *** tasks have a large amount of overlapping logic (mostly error checking), - *** so doing them with one function greatly reduces code duplocation. + *** so doing them with one function greatly reduces code duplication. + *** + *** Assumes that the function which caused this code to run was either + *** "cos_compare" or "lev_compare" (this value is read from `tree->Name`). *** *** @param tree The tree resulting from this function. - *** @param obj_list The evaluation "scope", including available variables. - *** @param fn_name Either `cos_compare()` or `lev_compare()`. *** @returns 0 for success, -1 for failure. ***/ static int -exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) +exp_fn_compare(pExpression tree) { /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -4591,7 +4592,7 @@ exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", fn_name); + mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -4608,7 +4609,7 @@ exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) char* str2 = check_ptr(maybe_str2->String); /** Handle either cos_compare() or lev_compare(). **/ - if (fn_name[0] == 'c') + if (tree->Name[0] == 'c') { /* cos_compare() */ int ret; @@ -4619,7 +4620,7 @@ exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) { mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to build vectors.", - fn_name, str1, str2 + tree->Name, str1, str2 ); ret = -1; } @@ -4655,19 +4656,7 @@ exp_fn_compare(pExpression tree, pParamObjects obj_list, const char* fn_name) } int -exp_fn_cos_compare(pExpression tree, pParamObjects obj_list) - { - return exp_fn_compare(tree, obj_list, "cos_compare"); - } - -int -exp_fn_lev_compare(pExpression tree, pParamObjects obj_list) - { - return exp_fn_compare(tree, obj_list, "lev_compare"); - } - -int -exp_fn_levenshtein(pExpression tree, pParamObjects obj_list) +exp_fn_levenshtein(pExpression tree) { /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ @@ -4893,15 +4882,15 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); xhAdd(&EXP.Functions, "metaphone", (char*)exp_fn_metaphone); xhAdd(&EXP.Functions, "levenshtein", (char*)exp_fn_levenshtein); - xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); - xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); + xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_compare); + xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_compare); xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); xhAdd(&EXP.Functions, "to_hex", (char*)exp_fn_to_hex); xhAdd(&EXP.Functions, "from_hex", (char*)exp_fn_from_hex); xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); - + /** Windowing **/ xhAdd(&EXP.Functions, "row_number", (char*)exp_fn_row_number); xhAdd(&EXP.Functions, "dense_rank", (char*)exp_fn_dense_rank); diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 7fd30eeb4..271412cdf 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -273,7 +273,7 @@ double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(v } } -/*** Enum representing the type of data targetted by the driver, +/*** Enum representing the type of data targeted by the driver, *** set based on the path given when the driver is used to open *** a cluster file. *** @@ -569,7 +569,7 @@ typedef struct _NODE *** is guaranteed to be computed. These three types can be returned from *** clusterOpen(), based on the provided path. *** - *** Alternatively, a cluster entry or search entry can be targetted by calling + *** Alternatively, a cluster entry or search entry can be targeted by calling *** fetch on a query pointing to a driver instance that targets a cluster or *** search (respectively). These two entry target types ensure that the data *** they indicate has been computed, so the GetAttrType() and GetAttrValue() @@ -578,12 +578,12 @@ typedef struct _NODE *** @param NodeData The associated node data struct. There can be many driver *** instances pointing to one NodeData at a time, but each driver instance *** always points to singular NodeData struct. - *** @param TargetType The type of data targetted (see above). + *** @param TargetType The type of data targeted (see above). *** @param TargetData If target type is: - *** ```csv + *** ```txt *** Node: A pointer to the SourceData struct. - *** Cluster or ClusterEntry: A pointer to the targetted cluster. - *** Search or SearchEntry: A pointer to the targetted search. + *** Cluster or ClusterEntry: A pointer to the targeted cluster. + *** Search or SearchEntry: A pointer to the targeted search. *** ``` *** @param TargetAttrIndex An index into an attribute list (for GetNextAttr()). *** @param TargetMethodIndex An index into an method list (for GetNextMethod()). @@ -608,7 +608,7 @@ typedef struct _DRIVER *** - Total size: 16 bytes *** *** @param DriverData The associated driver instance being queried. - *** @param RowIndex The selected row of the data targetted by the driver. + *** @param RowIndex The selected row of the data targeted by the driver. *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct @@ -3303,7 +3303,7 @@ clusterClose(void* inf_v, pObjTrxTree* oxt) // LINK #functions -/*** Opens a new query pointing to the first row of the data targetted by +/*** Opens a new query pointing to the first row of the data targeted by *** the driver instance struct. The query has an internal index counter *** that starts at the first row and increments as data is fetched. *** From 6eeeb2df21dd5dc2135f19c36d85c7ddc00a052d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 17 Mar 2026 17:39:53 -0600 Subject: [PATCH 050/361] Add seed attribute to cluster driver. --- centrallix-lib/include/clusters.h | 3 +- centrallix-lib/src/clusters.c | 8 ++++-- centrallix-os/cluster-schema.cluster | 1 + centrallix/osdrivers/objdrv_cluster.c | 41 +++++++++++++++++++++++---- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index fd94d0be2..3052e650e 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -107,7 +107,8 @@ int ca_kmeans( const unsigned int max_iter, const double min_improvement, unsigned int* labels, - double* vector_sims); + double* vector_sims, + bool auto_seed); /** Vector helper macros. **/ #define ca_is_empty(vector) (vector[0] == -CA_NUM_DIMS) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 88b1bf318..549cd79da 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -767,6 +767,9 @@ get_cluster_size( *** @param vector_sims An array of num_vectors elements, allocated by the *** caller, where index i stores the similarity of vector i to its assigned *** cluster. Passing NULL skips evaluation of these values. + *** @param auto_seed If true, the function will set its own seed. Otherwise, + *** it will use rand() without setting a seed. In the latter case, srand() + *** should be used by the caller prior to calling this function. *** *** @attention - Assumes: num_vectors is the length of vectors. *** @attention - Assumes: num_clusters is the length of labels. @@ -796,7 +799,8 @@ ca_kmeans( const unsigned int max_iter, const double min_improvement, unsigned int* labels, - double* vector_sims) + double* vector_sims, + bool auto_seed) { pCentroid* centroids = NULL; pCentroid* new_centroids = NULL; @@ -826,7 +830,7 @@ ca_kmeans( } /** Select random vectors to use as the initial centroids. **/ - srand(time(NULL)); + if (auto_seed) srand(time(NULL)); for (unsigned int i = 0u; i < num_clusters; i++) { /** Pick a random vector. **/ diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index 5e11cd7c2..74a2fc947 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -27,6 +27,7 @@ file_name "system/cluster" ?max_iterations : uint // default: 64 ?window_size : uint > 0 // required for algorithm = sliding_window. ?overlap_size : double && 0.0 <= x <= 1.0 // default: 0.0, only allowed for algorithm = k-means | k-means++ | k-medoids, not implemented + ?seed : uint > 0 // default: Dynamic seed. // Not implemented sub_cluster_name "cluster/cluster" diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 271412cdf..fc6ee9028 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -60,6 +60,7 @@ /** Defaults for unspecified optional attributes. **/ #define CI_DEFAULT_MIN_IMPROVEMENT 0.0001 #define CI_DEFAULT_MAX_ITERATIONS 64u +#define CI_NO_SEED 0u /** ================ Stuff That Should Be Somewhere Else ================ **/ /** ANCHOR[id=temp] **/ @@ -304,6 +305,7 @@ char* ATTR_CLUSTER[] = "num_clusters", "min_improvement", "max_iterations", + "seed", "date_created", "date_computed", END_OF_ARRAY, @@ -459,6 +461,8 @@ typedef struct _CLUSTER char* Key; ClusterAlgorithm ClusterAlgorithm; SimilarityMeasure SimilarityMeasure; + /** 2 bytes of auto-padding. **/ + unsigned int Seed; double MinImprovement; unsigned int MaxIterations; unsigned int nSubClusters; @@ -1209,7 +1213,22 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source cluster_data->MaxIterations = (unsigned int)max_iterations; } else cluster_data->MaxIterations = CI_DEFAULT_MAX_ITERATIONS; - + + /** Get seed. **/ + int seed; + result = ci_ParseAttribute(inf, "seed", DATA_T_INTEGER, POD(&seed), param_list, false, true); + if (result == -1) goto err_free; + if (result == 0) + { + if (seed < 1) + { + mssErrorf(1, "Cluster", "Invalid value for [seed : uint > 0]: %d", seed); + goto err_free; + } + cluster_data->Seed = (unsigned int)seed; + } + else cluster_data->Seed = CI_NO_SEED; + /** Search for sub-clusters. **/ if (!check(xaInit(&sub_clusters, 4u))) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) @@ -1232,6 +1251,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source "min_improvement", "max_iterations", "window_size", + "seed", }; const unsigned int nattrs = sizeof(attrs) / sizeof(char*); @@ -2711,8 +2731,12 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) const size_t lables_size = source_data->nVectors * sizeof(unsigned int); unsigned int* labels = check_ptr(nmSysMalloc(lables_size)); if (labels == NULL) goto err_free; - - /** Run kmeans. **/ + + /** Handle seed for ca_kmeans(). **/ + const bool auto_seed = (cluster_data->Seed == CI_NO_SEED); + if (!auto_seed) srand(cluster_data->Seed); + + /** Run ca_kmeans(). **/ const bool successful = check(ca_kmeans( source_data->Vectors, source_data->nVectors, @@ -2720,7 +2744,8 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) cluster_data->MaxIterations, cluster_data->MinImprovement, labels, - cluster_data->Sims + cluster_data->Sims, + auto_seed )); if (!successful) goto err_free; @@ -3596,7 +3621,8 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) || strcmp(attr_name, "similarity_measure") == 0) return DATA_T_STRING; if (strcmp(attr_name, "num_clusters") == 0 - || strcmp(attr_name, "max_iterations") == 0) + || strcmp(attr_name, "max_iterations") == 0 + || strcmp(attr_name, "seed") == 0) return DATA_T_INTEGER; if (strcmp(attr_name, "min_improvement") == 0) return DATA_T_DOUBLE; @@ -3928,6 +3954,11 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO val->Double = target->MinImprovement; return 0; } + if (strcmp(attr_name, "seed") == 0) + { + val->Integer = target->Seed; + return 0; + } break; } From c561728dd4556be2bac9dc7601d6ddb02b91fcee Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 17 Mar 2026 20:52:01 -0600 Subject: [PATCH 051/361] Improve support for IntVec and StringVec attribute types. Add support to mqGetAttrValue(). Add support to expEvalProperty(). Add support to the query command in testobj_do_cmd(). --- centrallix/expression/exp_evaluate.c | 19 +++++++++++++++++++ centrallix/multiquery/multiquery.c | 2 ++ centrallix/test_obj.c | 8 ++++++++ 3 files changed, 29 insertions(+) diff --git a/centrallix/expression/exp_evaluate.c b/centrallix/expression/exp_evaluate.c index 1984494a2..c35cdac8e 100644 --- a/centrallix/expression/exp_evaluate.c +++ b/centrallix/expression/exp_evaluate.c @@ -1460,6 +1460,25 @@ expEvalProperty(pExpression tree, pParamObjects objlist) memcpy(&(tree->Types.Date),vptr,sizeof(DateTime)); break; + /*** FIX: Shallow copy violates contract, no known drivers use + *** INTVEC yet so it's fine. + ***/ + case DATA_T_INTVEC: + v = getfn(obj, tree->Name, DATA_T_INTVEC, &vptr); + if (v != 0) break; + memcpy(&(tree->Types.StrVec), vptr, sizeof(IntVec)); + break; + + /*** FIX: Shallow copy violates contract, but objdrv_cluster.c is + *** the only one location that StringVec is used and I think it + *** won't cause issues. + ***/ + case DATA_T_STRINGVEC: + v = getfn(obj, tree->Name, DATA_T_STRINGVEC, &vptr); + if (v != 0) break; + memcpy(&(tree->Types.IntVec), vptr, sizeof(StringVec)); + break; + case DATA_T_MONEY: v = getfn(obj,tree->Name,DATA_T_MONEY,&vptr); if (v != 0) break; diff --git a/centrallix/multiquery/multiquery.c b/centrallix/multiquery/multiquery.c index 2a14e110e..f329d0ea3 100644 --- a/centrallix/multiquery/multiquery.c +++ b/centrallix/multiquery/multiquery.c @@ -4246,6 +4246,8 @@ mqGetAttrValue(void* inf_v, char* attrname, int datatype, void* value, pObjTrxTr case DATA_T_INTEGER: *(int*)value = exp->Integer; break; case DATA_T_STRING: *(char**)value = exp->String; break; case DATA_T_DOUBLE: *(double*)value = exp->Types.Double; break; + case DATA_T_INTVEC: *(pIntVec*)value = &(exp->Types.IntVec); break; + case DATA_T_STRINGVEC: *(pStringVec*)value = &(exp->Types.StrVec); break; case DATA_T_MONEY: *(pMoneyType*)value = &(exp->Types.Money); break; case DATA_T_DATETIME: *(pDateTime*)value = &(exp->Types.Date); break; case DATA_T_BINARY: diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index 97ef27e5c..130192dbe 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -912,6 +912,14 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) case DATA_T_DOUBLE: fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, &od, 0)); break; + + case DATA_T_STRINGVEC: + fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.StringVec, DATA_F_QUOTED & DATA_F_BRACKETS)); + break; + + case DATA_T_INTVEC: + fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.IntVec, DATA_F_QUOTED & DATA_F_BRACKETS)); + break; case DATA_T_STRING: case DATA_T_DATETIME: From ee9c410fe88e57036752e72bc4925ce6366d5a43 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 10:40:47 -0600 Subject: [PATCH 052/361] Rename NameAttr to DataAttr in SourceData struct to match KeyAttr. --- centrallix/osdrivers/objdrv_cluster.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index fc6ee9028..8e4092e16 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -356,7 +356,7 @@ char* METHOD_NAMES[] = *** @param SourcePath The path to the data source from which to retrieve data. *** @param KeyAttr The name of the attribute to use when getting keys from *** the SourcePath. - *** @param NameAttr The name of the attribute to use when getting data from + *** @param DataAttr The name of the attribute to use when getting data from *** the SourcePath. *** *** @skip --> Computed data. @@ -384,7 +384,7 @@ typedef struct _SOURCE char* Key; char* SourcePath; char* KeyAttr; - char* NameAttr; + char* DataAttr; char** Keys; char** Strings; pVector* Vectors; @@ -1030,19 +1030,19 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) /** Get the attribute name to use for querying data from the source. **/ if (ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; - source_data->NameAttr = check_ptr(nmSysStrdup(buf)); - if (source_data->NameAttr == NULL) goto err_free; + source_data->DataAttr = check_ptr(nmSysStrdup(buf)); + if (source_data->DataAttr == NULL) goto err_free; /** Create cache entry key. **/ const size_t len = strlen(path) + strlen(source_data->SourcePath) + strlen(source_data->KeyAttr) - + strlen(source_data->NameAttr) + 5lu; + + strlen(source_data->DataAttr) + 5lu; source_data->Key = check_ptr(nmSysMalloc(len * sizeof(char))); if (source_data->Key == NULL) goto err_free; snprintf(source_data->Key, len, "%s?%s->%s:%s", - path, source_data->SourcePath, source_data->KeyAttr, source_data->NameAttr + path, source_data->SourcePath, source_data->KeyAttr, source_data->DataAttr ); /** Check for a cached version. **/ @@ -1928,10 +1928,10 @@ ci_FreeSourceData(pSourceData source_data) nmSysFree(source_data->KeyAttr); source_data->KeyAttr = NULL; } - if (source_data->NameAttr != NULL) + if (source_data->DataAttr != NULL) { - nmSysFree(source_data->NameAttr); - source_data->NameAttr = NULL; + nmSysFree(source_data->DataAttr); + source_data->DataAttr = NULL; } /** Free fetched data, if it exists. **/ @@ -2196,7 +2196,7 @@ ci_SizeOfSourceData(pSourceData source_data) if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); if (source_data->KeyAttr != NULL) size += strlen(source_data->KeyAttr) * sizeof(char); - if (source_data->NameAttr != NULL) size += strlen(source_data->NameAttr) * sizeof(char); + if (source_data->DataAttr != NULL) size += strlen(source_data->DataAttr) * sizeof(char); if (source_data->Strings != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) @@ -2372,7 +2372,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) ASSERTMAGIC(entry, MGK_OBJECT); /** Data value: Type checking. **/ - const int data_datatype = objGetAttrType(entry, source_data->NameAttr); + const int data_datatype = objGetAttrType(entry, source_data->DataAttr); if (data_datatype == -1) { mssErrorf(0, "Cluster", @@ -2404,7 +2404,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Data value: Get value from database. **/ char* data; - ret = objGetAttrValue(entry, source_data->NameAttr, DATA_T_STRING, POD(&data)); + ret = objGetAttrValue(entry, source_data->DataAttr, DATA_T_STRING, POD(&data)); if (ret != 0) { mssErrorf(0, "Cluster", @@ -3913,7 +3913,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } if (strcmp(attr_name, "name_attr") == 0) { - val->String = source_data->NameAttr; + val->String = source_data->DataAttr; return 0; } break; From f8aa1006f442518690e3d50791bcfac930388ca2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 10:42:06 -0600 Subject: [PATCH 053/361] Fix clusterGetAttrValue() asserting MGK_CL_CLUSTER_DATA on SearchData targets. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 8e4092e16..ad1f20584 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3966,7 +3966,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { pSearchData target = check_ptr(driver_data->TargetData); if (target == NULL) goto err; - ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (strcmp(attr_name, "source") == 0) { From 7dfc69b8e3bf047188cdcee4f5c7b87b92dd9fc2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 10:42:50 -0600 Subject: [PATCH 054/361] Switch stats struct to use compile-time initialization (for consistency and performance). --- centrallix/osdrivers/objdrv_cluster.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ad1f20584..682a802e0 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -645,7 +645,7 @@ struct unsigned long long GetValCalls_key1; unsigned long long GetValCalls_key2; unsigned long long GetValCalls_sim; - } ClusterStatistics; + } ClusterStatistics = {0}; /** ================ Function Declarations ================ **/ @@ -4976,9 +4976,6 @@ clusterInitialize(void) if (!check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0))) goto err_free; if (!check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0))) goto err_free; - /** Initialize statistics. **/ - memset(&ClusterStatistics, 0, sizeof(ClusterStatistics)); - /** Setup the structure. **/ if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; if (!check(xaInit(&drv->RootContentTypes, 1))) goto err_free; From d1c2185c306dff4f54a9adeae9380c5f4fcc90c6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 10:51:23 -0600 Subject: [PATCH 055/361] Improve error messages in ci_ComputeSourceData(). Simplify error handling. Remove unnecessary statements. Improve comments. Clean up. --- centrallix/osdrivers/objdrv_cluster.c | 176 +++++++------------------- 1 file changed, 48 insertions(+), 128 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 682a802e0..f8dadbd72 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2333,13 +2333,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); if (obj == NULL) { - mssErrorf(0, "Cluster", - "Failed to open object driver:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n", - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath - ); + mssErrorf(0, "Cluster", "Failed to open object driver."); goto end_free; } @@ -2347,15 +2341,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); if (query == NULL) { - mssErrorf(0, "Cluster", - "Failed to open query:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name - ); + mssErrorf(0, "Cluster", "Failed to open query."); goto end_free; } @@ -2376,28 +2362,16 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (data_datatype == -1) { mssErrorf(0, "Cluster", - "Failed to get type for %uth entry:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name + "Failed to get type for data of %uth entry.", + vector_xarray.nItems ); goto end_free; } if (data_datatype != DATA_T_STRING) { mssErrorf(1, "Cluster", - "Type for %uth entry was not a string:\n" - " > Attribute: ['%s':'%s' : %s]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, objTypeToStr(data_datatype), - source_data->SourcePath, - obj->Driver->Name + "Type for data of %uth entry was %s instead of String:\n", + vector_xarray.nItems, objTypeToStr(data_datatype) ); goto end_free; } @@ -2408,45 +2382,30 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (ret != 0) { mssErrorf(0, "Cluster", - "Failed to value for %uth entry:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n" - " > Error code: %d\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name, - ret + "Failed to get attribute value for %uth data entry (error code: %d).", + vector_xarray.nItems, ret ); goto end_free; } /** Skip empty strings. **/ - if (strlen(data) == 0) - { - check(fflush(stdout)); /* Failure ignored. */ - continue; - } + if (strlen(data) == 0) continue; /** Convert the string to a vector. **/ pVector vector = ca_build_vector(data); if (vector == NULL) { mssErrorf(1, "Cluster", "Failed to build vectors for string \"%s\".", data); - successful = false; goto end_free; } if (ca_is_empty(vector)) { mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); - successful = false; goto end_free; } if (ca_has_no_pairs(vector)) { - /** Skip pVector with no pairs. **/ - check(fflush(stdout)); /* Failure ignored. */ + /** Skip pVector with only a single pair of boundary characters. **/ ca_free_vector(vector); continue; } @@ -2457,28 +2416,16 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (key_datatype == -1) { mssErrorf(0, "Cluster", - "Failed to get type for key on %uth entry:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name + "Failed to get type for key on %uth entry.", + vector_xarray.nItems ); goto end_free; } if (key_datatype != DATA_T_STRING) { mssErrorf(1, "Cluster", - "Type for key on %uth entry was not a string:\n" - " > Attribute: ['%s':'%s' : %s]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, objTypeToStr(key_datatype), - source_data->SourcePath, - obj->Driver->Name + "Type for key on %uth entry was %s instead of String:", + vector_xarray.nItems, objTypeToStr(key_datatype) ); goto end_free; } @@ -2489,16 +2436,8 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (ret != 0) { mssErrorf(0, "Cluster", - "Failed to value for key on %uth entry:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n" - " > Error code: %d\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name, - ret + "Failed to value for key on %uth entry (error code: %d).", + vector_xarray.nItems, ret ); goto end_free; } @@ -2513,54 +2452,56 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (!check_neg(xaAddItem(&vector_xarray, (void*)vector))) goto end_free; /** Clean up. **/ - ret = objClose(entry); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Failed to close object entry (error code %d).", ret); - // success = false; // Fall-through: Failure ignored. - } + check(objClose(entry)); /* Failure ignored. */ } source_data->nVectors = vector_xarray.nItems; if (source_data->nVectors == 0) { - mssErrorf(0, "Cluster", - "Data source path did not contain any valid data:\n" - " > Attribute: ['%s':'%s' : String]\n" - " > Source Path: %s\n" - " > Driver Used: %s\n", - vector_xarray.nItems, - source_data->KeyAttr, source_data->NameAttr, - source_data->SourcePath, - obj->Driver->Name - ); + mssErrorf(0, "Cluster", "Data source path did not contain any valid data:\n"); + goto end_free; } /** Trim and store keys. **/ source_data->Keys = (char**)check_ptr(ci_xaToTrimmedArray(&key_xarray, 1)); - if (source_data->Keys == NULL) goto err_free; + if (source_data->Keys == NULL) goto end_free; key_xarray.nAlloc = 0; /** Trim and store data strings. **/ source_data->Strings = (char**)check_ptr(ci_xaToTrimmedArray(&data_xarray, 1)); - if (source_data->Strings == NULL) goto err_free; + if (source_data->Strings == NULL) goto end_free; data_xarray.nAlloc = 0; /** Trim and store vectors. **/ source_data->Vectors = (int**)check_ptr(ci_xaToTrimmedArray(&vector_xarray, 1)); - if (source_data->Vectors == NULL) goto err_free; + if (source_data->Vectors == NULL) goto end_free; /* Should be unreachable. */ vector_xarray.nAlloc = 0; /** Success. **/ successful = true; - goto end_free; - - err_free: - if (source_data->Keys != NULL) nmSysFree(source_data->Keys); - if (source_data->Strings != NULL) nmSysFree(source_data->Strings); - if (source_data->Vectors != NULL) nmSysFree(source_data->Vectors); - + end_free: + /** Print an error if the function failed. **/ + if (!successful) + { + mssErrorf(0, "Cluster", + "SourceData computation failed:\n" + " > Key Attribute: ['%s' : String]\n" + " > Data Attribute: ['%s' : String]\n" + " > Source Path: \"%s\"\n" + " > Driver Used: %s", + source_data->KeyAttr, + source_data->DataAttr, + source_data->SourcePath, + (obj == NULL || obj->Driver == NULL) ? "Unavailable" : obj->Driver->Name + ); + + /** Free computed data. **/ + if (source_data->Keys != NULL) nmSysFree(source_data->Keys); + if (source_data->Strings != NULL) nmSysFree(source_data->Strings); + if (source_data->Vectors != NULL) nmSysFree(source_data->Vectors); + } + /** Clean up xarrays. **/ if (key_xarray.nAlloc != 0) { @@ -2593,31 +2534,10 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) check(xaDeInit(&vector_xarray)); /* Failure ignored. */ } - /** Clean up query. **/ - if (query != NULL) - { - ret = objQueryClose(query); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Failed to close query (error code %d).", ret); - // success = false; // Fall-through: Failure ignored. - } - } - - /** Clean up object. **/ - if (obj != NULL) - { - ret = objClose(obj); - if (ret != 0) - { - mssErrorf(0, "Cluster", "Failed to close object driver (error code %d).", ret); - // success = false; // Fall-through: Failure ignored. - } - } - - /** Print an error if the function failed. **/ - if (!successful) mssErrorf(0, "Cluster", "SourceData computation failed."); - + /** Clean up query & object structs. **/ + if (query != NULL) check(objQueryClose(query)); /* Failure ignored. */ + if (obj != NULL) check(objClose(obj)); /* Failure ignored. */ + /** Return the function status code. **/ return (successful) ? 0 : -1; } From fd289ff684f7c7e5d5671b0ccdcb5f092f63b4ba Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 10:58:40 -0600 Subject: [PATCH 056/361] Refactor clusters to store an array of indexes into the SourceData struct instead of data pointers. Rename dup to pair in variable names to include a more general set of use cases. Fix bug that tried to free clusters which had not been allocated in some error cases for ci_ComputeClusterData(). Clean up storage of detected pairs in ci_ComputeSearchData(). Update doc comments for ca_sliding_search() and ca_complete_search(). Fix and improve some comments. Fix spelling errors. Clean up. --- centrallix-lib/include/clusters.h | 27 +-- centrallix-lib/src/clusters.c | 98 +++++----- centrallix/osdrivers/objdrv_cluster.c | 255 +++++++++++++++----------- 3 files changed, 202 insertions(+), 178 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 3052e650e..4665bc557 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -74,21 +74,26 @@ typedef int* pVector; /* Sparse vector. */ typedef double* pCentroid; /* Dense centroid. */ #define pCentroidSize CA_NUM_DIMS * sizeof(double) -/** Duplocate information. **/ +/*** Information about detected matching pairs. + *** + *** @param i The index into the provided data for the first element of the pair. + *** @param j The index into the provided data for the second element of the pair. + *** @param similarity A number from 0 to 1, from a similarity function, showing + *** how similar the pairs are. + ***/ typedef struct { - void* key1; - void* key2; + unsigned int i, j; double similarity; } - Dup, *pDup; + Pair, *pPair; /** Registering all defined types for debugging. **/ #define ca_init() \ nmRegister(sizeof(pVector), "pVector"); \ nmRegister(sizeof(pCentroid), "pCentroid"); \ nmRegister(pCentroidSize, "Centroid"); \ - nmRegister(sizeof(Dup), "Dup") + nmRegister(sizeof(Pair), "Pair") /** Edit distance function. **/ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); @@ -99,7 +104,7 @@ unsigned int ca_sparse_len(const pVector vector); void ca_print_vector(const pVector vector); void ca_free_vector(pVector sparse_vector); -/** Kmeans function. **/ +/** k-means function. **/ int ca_kmeans( pVector* vectors, const unsigned int num_vectors, @@ -135,15 +140,13 @@ pXArray ca_sliding_search( const unsigned int num_data, const unsigned int window_size, const double (*similarity)(void*, void*), - const double dupe_threshold, - void** maybe_keys, - pXArray dups); + const double threshold, + pXArray maybe_pairs); pXArray ca_complete_search( void** data, const unsigned int num_data, const double (*similarity)(void*, void*), - const double dupe_threshold, - void** maybe_keys, - pXArray dups); + const double threshold, + pXArray maybe_pairs); #endif /* End of .h file. */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 549cd79da..83dd9a3dd 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -997,25 +997,19 @@ ca_most_similar( /*** Runs a sliding search over the provided data, comparing each element to - *** the following `window_size` elements, invoking the passed comparison - *** function just under `window_size * num_data` times. If any comparison - *** yields a similarity greater than the threshold, it is stored in the - *** xArray returned by this function. + *** the next `window_size` elements. If any comparison yields a similarity + *** greater than the threshold, it is stored in the returned xArray. *** *** @param data The data to be searched. - *** @param num_data The number of data items in data. + *** @param num_data The number of data items in `data`. *** @param window_size The size of the sliding window used for the search. *** @param similarity A function which takes two data items of the type of - *** the data param and returns their similarity. - *** @param threshold The minimum threshold required for a duplocate to be - *** included in the returned xArray. - *** @param maybe_keys A pointer to an array of keys, with one key per data. - *** These will be used to fill in the key1 and key2 attributes for each - *** struct. If this variable is null, these values are also left null. - *** @param maybe_dups A pointer to an xArray in which dups should be found. + *** the `data` param and returns their similarity. + *** @param threshold The minimum threshold required for each pair. + *** @param maybe_pairs A pointer to an xArray to which pairs should be added. *** Pass NULL to allocate a new one. - *** @returns An xArray holding all of the duplocates found, or NULL if an - *** error occurs. + *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this + *** will be that xArray, to allow for chaining. ***/ pXArray ca_sliding_search( @@ -1024,22 +1018,21 @@ ca_sliding_search( const unsigned int window_size, const double (*similarity)(void*, void*), const double threshold, - void** maybe_keys, - pXArray maybe_dups) + pXArray maybe_pairs) { - pXArray dups = maybe_dups; + pXArray pairs = maybe_pairs; - /** Allocate space for dups (if necessary). **/ - if (dups == NULL) + /** Allocate space for pairs (if it is not provided). **/ + if (pairs == NULL) { - /** Guess that we will need space for num_data * 2 dups. **/ + /** Guess that we will need space for num_data * 2 pairs. **/ const int guess_size = num_data * 2; - dups = check_ptr(xaNew(guess_size)); - if (dups == NULL) goto err; + pairs = check_ptr(xaNew(guess_size)); + if (pairs == NULL) goto err; } - const int num_starting_dups = dups->nItems; + const int num_starting_pairs = pairs->nItems; - /** Search for dups. **/ + /** Search for pairs. **/ for (unsigned int i = 0u; i < num_data; i++) { const unsigned int window_start = i + 1u; @@ -1050,54 +1043,46 @@ ca_sliding_search( if (isnan(sim) || sim < 0.0 || 1.0 < sim) { fprintf(stderr, "Invalid similarity %g %lf.\n", sim, sim); - goto err_free_dups; + goto err_free; } - if (sim > threshold) /* Dup found! */ + if (sim > threshold) /* Pair found! */ { - Dup* dup = (Dup*)check_ptr(nmMalloc(sizeof(Dup))); - if (dup == NULL) goto err_free_dups; - if (maybe_keys != NULL) - { - dup->key1 = maybe_keys[i]; - dup->key2 = maybe_keys[j]; - } - dup->similarity = sim; - if (!check_neg(xaAddItem(dups, (void*)dup))) goto err_free_dups; + pPair pair = (pPair)check_ptr(nmMalloc(sizeof(Pair))); + if (pair == NULL) goto err_free; + pair->i = i; + pair->j = j; + pair->similarity = sim; + if (!check_neg(xaAddItem(pairs, (void*)pair))) goto err_free; } } } /** Success. **/ - return dups; + return pairs; - err_free_dups: - /** Error cleanup: Free the dups that we added to the XArray. **/ - while (dups->nItems > num_starting_dups) - nmFree(dups->Items[--dups->nItems], sizeof(Dup)); - if (maybe_dups == NULL) check(xaDeInit(dups)); /* Failure ignored. */ + err_free: + /** Error cleanup: Free the pairs that we added to the XArray. **/ + while (pairs->nItems > num_starting_pairs) + nmFree(pairs->Items[--pairs->nItems], sizeof(Pair)); + if (maybe_pairs == NULL) check(xaDeInit(pairs)); /* Failure ignored. */ err: return NULL; } /*** Runs a complete search over the provided data, comparing each element to - *** each other element, invoking the passed comparison function `num_data^2` - *** times. If any comparison yields a similarity greater than the threshold, - *** it is stored in the xArray returned by this function. + *** each other element. If any comparison yields a similarity greater than the + *** threshold, it is stored in the returned xArray. *** *** @param data The data to be searched. - *** @param num_data The number of data items in data. + *** @param num_data The number of data items in `data`. *** @param similarity A function which takes two data items of the type of - *** the data param and returns their similarity. - *** @param threshold The minimum threshold required for a duplocate to be - *** included in the returned xArray. - *** @param maybe_keys A pointer to an array of keys, with one key per data. - *** These will be used to fill in the key1 and key2 attributes for each - *** struct. If this variable is null, these values are also left null. - *** @param maybe_dups A pointer to an xArray in which dups should be found. + *** the `data` param and returns their similarity. + *** @param threshold The minimum threshold required for each pair. + *** @param maybe_pairs A pointer to an xArray to which pairs should be added. *** Pass NULL to allocate a new one. - *** @returns An xArray holding all of the duplocates found. If maybe_dups is - *** not NULL, this will be that xArray, to allow for chaining. + *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this + *** will be that xArray, to allow for chaining. ***/ pXArray ca_complete_search( @@ -1105,10 +1090,9 @@ ca_complete_search( const unsigned int num_data, const double (*similarity)(void*, void*), const double threshold, - void** maybe_keys, - pXArray maybe_dups) + pXArray maybe_pairs) { - return ca_sliding_search(data, num_data, num_data, similarity, threshold, maybe_keys, maybe_dups); + return ca_sliding_search(data, num_data, num_data, similarity, threshold, maybe_pairs); } /** Scope cleanup. **/ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index f8dadbd72..be9a44c55 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -398,19 +398,18 @@ typedef struct _SOURCE *** *** Memory Stats: *** - Padding: 0 bytes - *** - Total size: 24 bytes + *** - Total size: 16 bytes *** *** @param Size The number of items in the cluster. - *** @param Strings The string values of each item. - *** @param Vectors The cosine vectors for each item. + *** @param Indexes Represents the data points in the cluster, as indexes into + *** the SourceData Keys, Strings, and Vectors fields. NULL if `Size == 0`. *** @param Magic A magic value for detecting corrupted memory. ***/ typedef struct { - Magic_t Magic; - unsigned int Size; - char** Strings; - pVector* Vectors; + Magic_t Magic; + unsigned int Size; + unsigned int* Indexes; } Cluster, *pCluster; @@ -492,9 +491,10 @@ typedef struct _CLUSTER *** included in the results of the search. *** *** @skip --> Computed data. - *** @param Dups An array holding the dups found by the search, or NULL if the - *** search has not been computed. - *** @param nDups The number of dups found. + *** @param Pairs An array holding the pair found by the search, or NULL if the + *** search has not been computed. The indexes stored in these pairs are + *** indexes into the SourceData data arrays. + *** @param nPairs The number of pairs found. *** *** @skip --> Time. *** @param DateCreated The date and time that this object was created and initialized. @@ -509,8 +509,8 @@ typedef struct _SEARCH char* Key; pClusterData SourceCluster; double Threshold; - pDup* Dups; - unsigned int nDups; + pPair* Pairs; + unsigned int nPairs; SimilarityMeasure SimilarityMeasure; DateTime DateCreated; DateTime DateComputed; @@ -2001,10 +2001,8 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) { pCluster cluster = &cluster_data->Clusters[i]; if (cluster == NULL) continue; - if (cluster->Strings != NULL) nmSysFree(cluster->Strings); - if (cluster->Vectors != NULL) nmSysFree(cluster->Vectors); - cluster->Strings = NULL; - cluster->Vectors = NULL; + if (cluster->Indexes != NULL) nmSysFree(cluster->Indexes); + cluster->Indexes = NULL; } nmSysFree(cluster_data->Clusters); nmSysFree(cluster_data->Sims); @@ -2058,15 +2056,15 @@ ci_FreeSearchData(pSearchData search_data) } /** Free computed data. **/ - if (search_data->Dups != NULL) + if (search_data->Pairs != NULL) { - for (unsigned int i = 0; i < search_data->nDups; i++) + for (unsigned int i = 0; i < search_data->nPairs; i++) { - nmFree(search_data->Dups[i], sizeof(Dup)); - search_data->Dups[i] = NULL; + nmFree(search_data->Pairs[i], sizeof(Pair)); + search_data->Pairs[i] = NULL; } - nmSysFree(search_data->Dups); - search_data->Dups = NULL; + nmSysFree(search_data->Pairs); + search_data->Pairs = NULL; } /** Free the search data struct. **/ @@ -2286,7 +2284,7 @@ ci_SizeOfSearchData(pSearchData search_data) unsigned int size = 0u; if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); - if (search_data->Dups != NULL) size += search_data->nDups * (sizeof(void*) + sizeof(Dup)); + if (search_data->Pairs != NULL) size += search_data->nPairs * (sizeof(void*) + sizeof(Pair)); size += sizeof(SearchData); return size; @@ -2297,8 +2295,8 @@ ci_SizeOfSearchData(pSearchData search_data) /** ANCHOR[id=computation] **/ // LINK #functions -/*** Ensures that the source_data->Data has been fetched from the data source - *** and that source_data->nVectors has been computed from the fetched data. +/*** Ensures that the `source_data->Data` has been fetched from the data source + *** and that `source_data->nVectors` has been computed from the fetched data. *** *** @attention - Promises that mssError() will be invoked on failure, so the *** caller is not required to specify their own error message. @@ -2607,25 +2605,19 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { case ALGORITHM_NONE: { - /*** Put all the data into one cluster. Remember, in the - *** no clustering case, there is only one cluster (see - *** ci_ParseClusterData() above). + /** Use a single cluster. **/ + /*** Note: There will only be a single cluster because `cluster_data->nClusters` + *** is set to 1 during parsing when the clustering algorithm is NONE. ***/ + pCluster only_cluster = &cluster_data->Clusters[0]; + SETMAGIC(only_cluster, MGK_CL_CLUSTER); - /** Initialize the cluster. **/ - pCluster first_cluster = &cluster_data->Clusters[0]; - SETMAGIC(first_cluster, MGK_CL_CLUSTER); - - /** Allocate space. **/ - first_cluster->Size = source_data->nVectors; - first_cluster->Strings = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(char*))); - if (first_cluster->Strings == NULL) goto err_free; - first_cluster->Vectors = check_ptr(nmSysMalloc(source_data->nVectors * sizeof(pVector))); - if (first_cluster->Vectors == NULL) goto err_free; - - /** Copy data. **/ - memcpy(first_cluster->Strings, source_data->Strings, source_data->nVectors * sizeof(char*)); - memcpy(first_cluster->Vectors, source_data->Vectors, source_data->nVectors * sizeof(pVector)); + /** Add all data points to that cluster. **/ + only_cluster->Size = source_data->nVectors; + only_cluster->Indexes = check_ptr(nmSysMalloc(only_cluster->Size * sizeof(int))); + if (only_cluster->Indexes == NULL) goto err_free; + for (unsigned int i = 0u; i < only_cluster->Size; i++) + only_cluster->Indexes[i] = i; break; } @@ -2671,7 +2663,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Convert the labels into clusters. **/ - /** Allocate space for clusters. **/ + /** Allocate temporary xArrays for tracking the indices stored in each cluster. **/ XArray indexes_in_cluster[cluster_data->nClusters]; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) if (!check(xaInit(&indexes_in_cluster[i], 8))) goto err_free; @@ -2681,26 +2673,36 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (!check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i))) goto err_free; nmSysFree(labels); /* Free unused data. */ - /** Iterate through each cluster, store it, and free the xArray. **/ + /** Store the indices for each cluster and free the temporary xArray. **/ for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { pXArray indexes_in_this_cluster = &indexes_in_cluster[i]; pCluster cluster = &cluster_data->Clusters[i]; SETMAGIC(cluster, MGK_CL_CLUSTER); - /** Allocate space. **/ + /** Store the data in the cluster. **/ cluster->Size = indexes_in_this_cluster->nItems; - cluster->Strings = check_ptr(nmSysMalloc(cluster->Size * sizeof(char*))); - if (cluster->Strings == NULL) goto err_free; - cluster->Vectors = check_ptr(nmSysMalloc(cluster->Size * sizeof(pVector))); - if (cluster->Vectors == NULL) goto err_free; - - /** Add data to clusters. **/ - for (unsigned int j = 0u; j < cluster->Size; j++) + if (cluster->Size == 0) { - const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[j]; - cluster->Strings[j] = source_data->Strings[index]; - cluster->Vectors[j] = source_data->Vectors[index]; + cluster->Indexes = NULL; + continue; + } + cluster->Indexes = check_ptr(nmSysMalloc(cluster->Size * sizeof(unsigned int*))); + if (cluster->Indexes == NULL) goto err_free; + for (unsigned int i = 0u; i < indexes_in_this_cluster->nItems; i++) + { + const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[i]; + if (index > __UINT32_MAX__) + { + mssErrorf(1, "Cluster", + "How did you try to cluster more than %u data points and ci_ComputeSearchData() " + "was the first thing to break?! Well... looks like it's time to update %s:%s to " + "handle a larger amount of data.", + __UINT32_MAX__, __FILE__, __LINE__ + ); + goto err_free; + } + cluster->Indexes[i] = (unsigned int)index; } check(xaDeInit(indexes_in_this_cluster)); /* Failure ignored. */ } @@ -2732,11 +2734,14 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) *** directly in the cluster_data->Clusters array. *** Thus, this loop only frees each cluster's content. ***/ - pCluster cluster = &cluster_data->Clusters[i]; + const pCluster cluster = &cluster_data->Clusters[i]; + + /** Skip the cluster if its data hasn't been set. **/ + if (cluster_data->Clusters[i].Magic == 0) continue; + + /** Free the data for the cluster. **/ ASSERTMAGIC(cluster, MGK_CL_CLUSTER); - if (cluster->Strings != NULL) nmFree(cluster->Strings, cluster->Size * sizeof(char*)); - else break; - if (cluster->Vectors != NULL) nmFree(cluster->Vectors, cluster->Size * sizeof(pVector)); + if (cluster->Indexes != NULL) nmSysFree(cluster->Indexes); else break; } nmFree(cluster_data->Clusters, clusters_size); @@ -2749,7 +2754,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) // LINK #functions -/*** Ensures that the search_data->Dups has been computed, running the a +/*** Ensures that the search_data->pairs has been computed, running the a *** search with the specified similarity measure if necessary. *** *** @attention - Promises that mssError() will be invoked on failure, so the @@ -2763,7 +2768,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { - pXArray dups = NULL; + pXArray pairs = NULL; /** Guard segfaults. **/ if (search_data == NULL || node_data == NULL) return -1; @@ -2771,7 +2776,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** If the clusters are already computed, we're done. **/ - if (search_data->Dups != NULL) return 0; + if (search_data->Pairs != NULL) return 0; /** We need the cluster data to be computed before we search it. **/ pClusterData cluster_data = check_ptr(search_data->SourceCluster); @@ -2798,7 +2803,6 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) const double (*similarity_function)(void *, void *) = ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure); /** Execute the search using the specified algorithm. **/ - pXArray dups_temp = NULL; if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) { /*** Note: We don't need to examine the clusters because nothing @@ -2820,16 +2824,15 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } /** Execute sliding search. **/ - dups_temp = check_ptr(ca_sliding_search( + pairs = check_ptr(ca_sliding_search( data, source_data->nVectors, cluster_data->MaxIterations, /* Window size. */ similarity_function, search_data->Threshold, - (void**)source_data->Keys, - dups + NULL )); - if (dups_temp == NULL) + if (pairs == NULL) { mssErrorf(1, "Cluster", "Failed to compute sliding search with %s similarity measure.", @@ -2840,18 +2843,24 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } else { + /** Initialize the pairs array with a size of double the amount of data. **/ + const int guess_size = search_data->SourceCluster->SourceData->nVectors * 2; + pairs = check_ptr(xaNew(guess_size)); + if (pairs == NULL) goto err_free; + + /** Iterate over each cluster. **/ for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { /** Extract the struct for the cluster. **/ pCluster cluster = &cluster_data->Clusters[i]; ASSERTMAGIC(cluster, MGK_CL_CLUSTER); - /** Get a pointer to the data that will be used for the search. **/ + /** Get a pointer to the data of the type needed for the search. **/ void** data = NULL; switch (search_data->SimilarityMeasure) { - case SIMILARITY_COSINE: data = (void**)cluster->Vectors; break; - case SIMILARITY_LEVENSHTEIN: data = (void**)cluster->Strings; break; + case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; + case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; default: mssErrorf(1, "Cluster", "Unknown similarity meansure \"%s\".", @@ -2860,38 +2869,63 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) goto err_free; } + /** Filter the data to only include values in the current cluster. **/ + void** filtered_data = data; + bool free_filtered_data = false; + if (cluster_data->nClusters > 1) + { + /** Allocate space. **/ + filtered_data = check_ptr(nmSysMalloc(cluster->Size * sizeof(void*))); + if (filtered_data == NULL) goto err_free; + free_filtered_data = true; + + /** Add filtered data. **/ + for (unsigned int i = 0u; i < cluster->Size; i++) + filtered_data[i] = data[cluster->Indexes[i]]; + } + /** Execute complete search. **/ - dups_temp = check_ptr(ca_complete_search( - data, + const pXArray cluster_pairs = check_ptr(ca_complete_search( + filtered_data, cluster->Size, similarity_function, search_data->Threshold, - (void**)source_data->Keys, - dups + NULL )); - if (dups_temp == NULL) + if (free_filtered_data) nmSysFree(filtered_data); + if (cluster_pairs == NULL) { mssErrorf(1, "Cluster", - "Failed to compute complete search with %s similarity measure.", + "Failed to compute ca_complete_search() with %s similarity measure.", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; } - else dups = dups_temp; + + /** Remap the pairs to point to index into the SourceData arrays instead of filtered_data. **/ + for (unsigned int i = 0u; i < cluster_pairs->nItems; i++) + { + const pPair pair = (pPair)cluster_pairs->Items[i]; + pair->i = cluster->Indexes[pair->i]; + pair->j = cluster->Indexes[pair->j]; + if (!check_neg(xaAddItem(pairs, pair))) goto err_free; + } + check(xaFree(cluster_pairs)); /* Failure ignored. */ } } - if (dups_temp == NULL) goto err_free; - else dups = dups_temp; - - /** Store dups. **/ - search_data->nDups = dups->nItems; - search_data->Dups = (dups->nItems == 0) - ? check_ptr(nmSysMalloc(0)) - : ci_xaToTrimmedArray(dups, 2); - if (search_data->Dups == NULL) + /** Store pairs. **/ + search_data->nPairs = pairs->nItems; + if (pairs->nItems == 0) + search_data->Pairs = check_ptr(nmSysMalloc(0)); + else + { + search_data->Pairs = (pPair*)check_ptr(ci_xaToTrimmedArray(pairs, 2)); /* Contract promises this won't fail. */ + pairs = NULL; /* Freed by ci_xaToTrimmedArray(). */ + } + if (search_data->Pairs == NULL) { - mssErrorf(1, "Cluster", "Failed to store dups after computing search data."); + mssErrorf(1, "Cluster", "Failed to store pair after computing search data."); goto err_free; } @@ -2899,15 +2933,15 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) return 0; err_free: - if (search_data->Dups != NULL) nmSysFree(search_data->Dups); - if (dups != NULL) + if (search_data->Pairs != NULL) nmSysFree(search_data->Pairs); + if (pairs != NULL) { - for (unsigned int i = 0u; i < dups->nItems; i++) + for (unsigned int i = 0u; i < pairs->nItems; i++) { - if (dups->Items[i] != NULL) nmFree(dups->Items[i], sizeof(Dup)); + if (pairs->Items[i] != NULL) nmFree(pairs->Items[i], sizeof(pairs)); else break; } - check(xaFree(dups)); /* Failure ignored. */ + check(xaFree(pairs)); /* Failure ignored. */ } mssErrorf(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); @@ -3412,7 +3446,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) } /** Stop iteration if the requested data does not exist. **/ - if (query_data->RowIndex >= target->nDups) goto done_free; + if (query_data->RowIndex >= target->nPairs) goto done_free; /** Set the data being fetched. **/ result_data->TargetType = TARGET_SEARCH_ENTRY; @@ -3915,19 +3949,22 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (strcmp(attr_name, "items") == 0) { - /** Extract target strings (the result). **/ - char** target_strings = check_ptr(target_cluster->Strings); - if (target_strings == NULL) goto err; - - /** Static variable to prevent leaking StringVec from previous calls. **/ + /** Static variable to allow us to free the StringVecs from previous calls. **/ static StringVec* vec = NULL; - if (vec != NULL) nmFree(vec, sizeof(StringVec)); + if (vec != NULL) + { + if (vec->Strings != NULL) nmSysFree(vec->Strings); + nmFree(vec, sizeof(StringVec)); + } /** Allocate and initialize the requested data. **/ - val->StringVec = vec = check_ptr(nmMalloc(sizeof(StringVec))); - if (val->StringVec == NULL) return -1; - val->StringVec->nStrings = target_cluster->Size; - val->StringVec->Strings = target_strings; + vec = val->StringVec = check_ptr(nmMalloc(sizeof(StringVec))); + if (vec == NULL) return -1; + memset(vec, 0, sizeof(StringVec)); + vec->nStrings = target_cluster->Size; + vec->Strings = check_ptr(nmSysMalloc(target_cluster->Size * sizeof(char*))); + for (unsigned int i = 0u; i < target_cluster->Size; i++) + vec->Strings[i] = target->SourceData->Strings[target_cluster->Indexes[i]]; /** Success. **/ return 0; @@ -3940,7 +3977,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO pSearchData target = check_ptr(driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - pDup target_dup = check_ptr(target->Dups[driver_data->TargetIndex]); + pPair target_dup = check_ptr(target->Pairs[driver_data->TargetIndex]); if (target_dup == NULL) goto err; if (strcmp(attr_name, "sim") == 0) @@ -3952,13 +3989,13 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (strcmp(attr_name, "key1") == 0) { ClusterStatistics.GetValCalls_key1++; - val->String = target_dup->key1; + val->String = target->SourceCluster->SourceData->Keys[target_dup->i]; return 0; } if (strcmp(attr_name, "key2") == 0) { ClusterStatistics.GetValCalls_key2++; - val->String = target_dup->key2; + val->String = target->SourceCluster->SourceData->Keys[target_dup->j]; return 0; } break; @@ -4410,9 +4447,9 @@ clusterInfo(void* inf_v, pObjectInfo info) { pSearchData search_data = (pSearchData)driver_data->TargetData; info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; - if (search_data->Dups != NULL) + if (search_data->Pairs != NULL) { - info->nSubobjects = search_data->nDups; + info->nSubobjects = search_data->nPairs; info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; } @@ -4549,7 +4586,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) bytes = ci_SizeOfSearchData(search_data); /** If less is specified, skip uncomputed source. **/ - if (*less_ptr > 0llu && search_data->Dups == NULL) goto no_print; + if (*less_ptr > 0llu && search_data->Pairs == NULL) goto no_print; /** Compute printing information. **/ type = "Search"; From dbc01f2a4f3cde1b0f3e191fbf8eadcb0c8d12c3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 11:06:52 -0600 Subject: [PATCH 057/361] Fix spelling errors and improve comments. Improve clarity of some comments. Fix spelling errors in comments and strings. Fix spacing errors. Rename nattrs to num_attrs. Improve the message for the drop_all method. Rename _CLUSTER internal struct name for ClusterData to _CD to avoid confusion with the Cluster struct. Fix incorrect number of pading bytes (I added the seed feild to the ClusterData struct and forgot to update the comment). Add comments showing where padding will be automatically added in structs. Update ci_ParseClusteringAlgorithm() to follow string comparison standards. Fix an error message for the max_iterations cluster data attribute which did not clarify that 0 was invalid. --- centrallix/osdrivers/objdrv_cluster.c | 107 ++++++++++++++------------ 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index be9a44c55..10a9401d7 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -151,7 +151,7 @@ ci_xaToTrimmedArray(pXArray arr, int array_handling) case 1: check(xaDeInit(arr)); arr->nAlloc = 0; break; /* Failure ignored. */ case 2: check(xaFree(arr)); break; /* Failure ignored. */ default: - /** Uh oh, there might be a memory leak... **/ + /** Uh oh, this might cause a memory leak... **/ fprintf(stderr, "Warning: ci_xaToTrimmedArray(%p, %d) - Unknown value (%d) for array_handling.\n", arr, array_handling, array_handling @@ -267,7 +267,7 @@ double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(v case SIMILARITY_LEVENSHTEIN: return ca_lev_compare; default: mssErrorf(1, "Cluster", - "Unknown similarity meansure \"%s\".", + "Unknown similarity measure \"%s\".", ci_SimilarityMeasureToString(similarity_measure) ); return NULL; @@ -417,7 +417,7 @@ typedef struct /*** Data for each cluster. Only attribute data is checked for caching. *** *** Memory Stats: - *** - Padding: 6 bytes + *** - Padding: 2 bytes *** - Total size: 104 bytes *** *** @skip --> Attribute Data. @@ -452,7 +452,7 @@ typedef struct *** *** @param Magic A magic value for detecting corrupted memory. ***/ -typedef struct _CLUSTER +typedef struct _CD { Magic_t Magic; unsigned int nClusters; @@ -465,8 +465,8 @@ typedef struct _CLUSTER double MinImprovement; unsigned int MaxIterations; unsigned int nSubClusters; - struct _CLUSTER** SubClusters; - struct _CLUSTER* Parent; + struct _CD** SubClusters; + struct _CD* Parent; pSourceData SourceData; Cluster* Clusters; double* Sims; @@ -505,6 +505,7 @@ typedef struct _CLUSTER typedef struct _SEARCH { Magic_t Magic; + /** 4 bytes of auto-padding. **/ char* Name; char* Key; pClusterData SourceCluster; @@ -512,6 +513,7 @@ typedef struct _SEARCH pPair* Pairs; unsigned int nPairs; SimilarityMeasure SimilarityMeasure; + /** 3 bytes of auto-padding. **/ DateTime DateCreated; DateTime DateComputed; } @@ -524,7 +526,7 @@ typedef struct _SEARCH *** - Padding: 4 bytes *** - Total size: 72 bytes *** - *** @note When a .cluster file is openned, there will be only one node for that + *** @note When a .cluster file is opened, there will be only one node for that *** file. However, in the course of the query, many driver instance structs *** may be created by functions like clusterQueryFetch(), and closed by the *** object system using clusterClose(). @@ -546,6 +548,7 @@ typedef struct _SEARCH typedef struct _NODE { Magic_t Magic; + /** 4 bytes of auto-padding. **/ pObject Parent; pParam* Params; pParamObjects ParamList; @@ -596,12 +599,14 @@ typedef struct _NODE typedef struct _DRIVER { Magic_t Magic; + /** 4 bytes of auto-padding. **/ pNodeData NodeData; void* TargetData; unsigned int TargetIndex; unsigned char TargetAttrIndex; unsigned char TargetMethodIndex; TargetType TargetType; + /** 1 bytes of auto-padding. **/ } DriverData, *pDriverData; @@ -799,7 +804,7 @@ ci_UnknownAttribute(char* attr_name, const int target_type) while (specific_attrs[n_specific_attrs] != NULL) n_specific_attrs++; /** Collect general attributes. */ - char** general_attrs = (char*[]){"name", "annoation", "content_type", "inner_type", "outer_type", "internal_type", "date_computed", "date_created", "last_modification"}; + char** general_attrs = (char*[]){"name", "annotation", "content_type", "inner_type", "outer_type", "internal_type", "date_computed", "date_created", "last_modification"}; /** Attempt to give hints. **/ if (ci_TryHint(attr_name, specific_attrs, n_specific_attrs)); @@ -817,7 +822,7 @@ ci_UnknownAttribute(char* attr_name, const int target_type) *** *** TODO: Greg - Review carefully. I think this code is the reason that runserver() *** is NOT REQUIRED for dynamic attributes in the cluster driver. I had to debug - *** and rewrite this for ages and it uses several functions I don't 100% understand. + *** and rewrite this for ages and it uses several functions I don't 100% understand. ***/ static int ci_ParseAttribute( @@ -914,12 +919,12 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) } /** Parse known clustering algorithms. **/ - if (!strcasecmp(algorithm, "none")) return ALGORITHM_NONE; - if (!strcasecmp(algorithm, "sliding-window")) return ALGORITHM_SLIDING_WINDOW; - if (!strcasecmp(algorithm, "k-means")) return ALGORITHM_KMEANS; - if (!strcasecmp(algorithm, "k-means++")) return ALGORITHM_KMEANS_PLUS_PLUS; - if (!strcasecmp(algorithm, "k-medoids")) return ALGORITHM_KMEDOIDS; - if (!strcasecmp(algorithm, "db-scan")) return ALGORITHM_DB_SCAN; + if (strcasecmp(algorithm, "none") == 0) return ALGORITHM_NONE; + if (strcasecmp(algorithm, "sliding-window") == 0) return ALGORITHM_SLIDING_WINDOW; + if (strcasecmp(algorithm, "k-means") == 0) return ALGORITHM_KMEANS; + if (strcasecmp(algorithm, "k-means++") == 0) return ALGORITHM_KMEANS_PLUS_PLUS; + if (strcasecmp(algorithm, "k-medoids") == 0) return ALGORITHM_KMEDOIDS; + if (strcasecmp(algorithm, "db-scan") == 0) return ALGORITHM_DB_SCAN; /** Unknown value for clustering algorithm. **/ mssErrorf(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); @@ -1207,13 +1212,13 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { if (max_iterations < 1) { - mssErrorf(1, "Cluster", "Invalid value for [max_iterations : uint]: %d", max_iterations); + mssErrorf(1, "Cluster", "Invalid value for [max_iterations : uint > 0]: %d", max_iterations); goto err_free; } cluster_data->MaxIterations = (unsigned int)max_iterations; } else cluster_data->MaxIterations = CI_DEFAULT_MAX_ITERATIONS; - + /** Get seed. **/ int seed; result = ci_ParseAttribute(inf, "seed", DATA_T_INTEGER, POD(&seed), param_list, false, true); @@ -1228,7 +1233,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source cluster_data->Seed = (unsigned int)seed; } else cluster_data->Seed = CI_NO_SEED; - + /** Search for sub-clusters. **/ if (!check(xaInit(&sub_clusters, 4u))) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) @@ -1253,11 +1258,11 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source "window_size", "seed", }; - const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + const unsigned int num_attrs = sizeof(attrs) / sizeof(char*); /** Ignore valid attribute names. **/ bool is_valid = false; - for (unsigned int i = 0u; i < nattrs; i++) + for (unsigned int i = 0u; i < num_attrs; i++) { if (strcmp(name, attrs[i]) == 0) { @@ -1269,7 +1274,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); - if (ci_TryHint(name, attrs, nattrs)); + if (ci_TryHint(name, attrs, num_attrs)); else if (strcasecmp(name, "k") == 0) ci_GiveHint("num_clusters"); else if (strcasecmp(name, "threshold") == 0) ci_GiveHint("min_improvement"); @@ -1504,11 +1509,11 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) "threshold", "similarity_measure", }; - const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + const unsigned int num_attrs = sizeof(attrs) / sizeof(char*); /** Ignore valid attribute names. **/ bool is_valid = false; - for (unsigned int i = 0u; i < nattrs; i++) + for (unsigned int i = 0u; i < num_attrs; i++) { if (strcmp(name, attrs[i]) == 0) { @@ -1520,7 +1525,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); - ci_TryHint(name, attrs, nattrs); + ci_TryHint(name, attrs, num_attrs); break; } @@ -1592,7 +1597,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) // LINK #functions /*** Allocates a new pNodeData struct from a parsed pStructInf. *** - *** @attention - Does not use caching directly, but uses subfunctions to + *** @attention - Does not use caching directly, but uses sub-functions to *** handle caching of substructures. *** @attention - Promises that mssError() will be invoked on failure, so the *** caller is not required to specify their own error message. @@ -1674,11 +1679,11 @@ ci_ParseNodeData(pStructInf inf, pObject parent) "key_attr", "data_attr", }; - const unsigned int nattrs = sizeof(attrs) / sizeof(char*); + const unsigned int num_attrs = sizeof(attrs) / sizeof(char*); /** Ignore valid attribute names. **/ bool is_valid = false; - for (unsigned int i = 0u; i < nattrs; i++) + for (unsigned int i = 0u; i < num_attrs; i++) { if (strcmp(name, attrs[i]) == 0) { @@ -1690,7 +1695,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in cluster node \"%s\".\n", name, inf->Name); - ci_TryHint(name, attrs, nattrs); + ci_TryHint(name, attrs, num_attrs); break; } @@ -2633,21 +2638,21 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) { mssErrorf(1, "Cluster", - "The similarity measure \"%s\" is not implemented.", + "The similarity measure \"%s\" is not implemented for 'k-means' clusters.", ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) ); goto err_free; } - /** Allocate lables. Note: kmeans does not require us to initialize them. **/ - const size_t lables_size = source_data->nVectors * sizeof(unsigned int); - unsigned int* labels = check_ptr(nmSysMalloc(lables_size)); + /** Allocate labels. Note: ca_kmeans() initializes labels for us. **/ + const size_t labels_size = source_data->nVectors * sizeof(unsigned int); + unsigned int* labels = check_ptr(nmSysMalloc(labels_size)); if (labels == NULL) goto err_free; - + /** Handle seed for ca_kmeans(). **/ const bool auto_seed = (cluster_data->Seed == CI_NO_SEED); if (!auto_seed) srand(cluster_data->Seed); - + /** Run ca_kmeans(). **/ const bool successful = check(ca_kmeans( source_data->Vectors, @@ -2817,7 +2822,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; default: mssErrorf(1, "Cluster", - "Unknown similarity meansure \"%s\".", + "Unknown similarity measure \"%s\".", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; @@ -2863,7 +2868,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; default: mssErrorf(1, "Cluster", - "Unknown similarity meansure \"%s\".", + "Unknown similarity measure \"%s\".", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; @@ -2956,8 +2961,8 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /*** Get the type of a parameter. Intended for expSetParamFunctions(). *** - *** @param inf_v Node data containing the list of paramenters. - *** @param attr_name The name of the requested paramenter. + *** @param inf_v Node data containing the list of parameters. + *** @param attr_name The name of the requested parameter. *** @returns The datatype, see datatypes.h for a list of valid datatypes. *** *** LINK ../../centrallix-lib/include/datatypes.h:72 @@ -2991,8 +2996,8 @@ ci_GetParamType(void* inf_v, const char* attr_name) *** This is intended behavior, for consistency with other Centrallix *** functions, so keep it in mind so you're not surprised. *** - *** @param inf_v Node data containing the list of paramenters. - *** @param attr_name The name of the requested paramenter. + *** @param inf_v Node data containing the list of parameters. + *** @param attr_name The name of the requested parameter. *** @param datatype The expected datatype of the parameter value. *** See datatypes.h for a list of valid datatypes. *** @param val A pointer to a location where a pointer to the requested @@ -3047,7 +3052,7 @@ ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are immutable."); return -1; } @@ -3060,11 +3065,11 @@ ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) /*** Opens a new cluster driver instance by parsing a `.cluster` file found *** at the path provided in parent. *** - *** @param parent The parent of the object to be openned, including useful + *** @param parent The parent of the object to be opened, including useful *** information such as the pathname, session, etc. *** @param mask Driver permission mask (unused). *** @param sys_type ? (unused) - *** @param usr_type The object system file type being openned. Should always + *** @param usr_type The object system file type being opened. Should always *** be "system/cluster" because this driver is only registered for that *** type of file. *** @param oxt The object system tree, similar to a kind of "scope" (unused). @@ -3113,7 +3118,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** If there still isn't a node, fail early. **/ if (node_struct == NULL) { - mssErrorf(0, "Cluster", "Failed to create node struct."); + mssErrorf(0, "Cluster", "Failed to create node struct from provided cluster file."); goto err_free; } @@ -4773,8 +4778,8 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree /** 'drop_all'. **/ if (strcmp(param->String, "drop_all") == 0) { - printf("\nDropping cache for all files:\n"); ci_ClearCaches(); + printf("Dropped cache for all cluster files.\n"); return 0; } @@ -4807,7 +4812,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree return 0; } - + /** Unknown parameter. **/ mssErrorf(1, "Cluster", "Unknown command: \"%s\"", method_name); @@ -4868,7 +4873,7 @@ clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjT int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterWrite() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterWrite() not implemented because clusters are immutable."); return -1; } @@ -4877,7 +4882,7 @@ clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxT int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are immutable."); return -1; } @@ -4886,7 +4891,7 @@ clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are immutable."); return -1; } @@ -4904,7 +4909,7 @@ clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) int clusterCommit(void* inf_v, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are imutable."); + mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are immutable."); return 0; } @@ -4912,7 +4917,7 @@ clusterCommit(void* inf_v, pObjTrxTree* oxt) // LINK #functions /*** Initialize the driver. This includes: - *** - Registering the driver with the objectsystem. + *** - Registering the driver with the object system. *** - Registering structs with newmalloc for debugging. *** - Initializing global data needed for the driver. *** From 4e9644058b8f9a30da654857eb141a873b0f5e18 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 11:17:25 -0600 Subject: [PATCH 058/361] Add a test case for the cluster driver. --- .../tests/test_objdrv_cluster_00.cluster | 32 +++++++ .../tests/test_objdrv_cluster_00.csv | 8 ++ .../tests/test_objdrv_cluster_00.spec | 14 +++ centrallix/tests/test_objdrv_cluster_00.cmp | 96 +++++++++++++++++++ centrallix/tests/test_objdrv_cluster_00.to | 29 ++++++ 5 files changed, 179 insertions(+) create mode 100644 centrallix-os/tests/test_objdrv_cluster_00.cluster create mode 100644 centrallix-os/tests/test_objdrv_cluster_00.csv create mode 100644 centrallix-os/tests/test_objdrv_cluster_00.spec create mode 100644 centrallix/tests/test_objdrv_cluster_00.cmp create mode 100644 centrallix/tests/test_objdrv_cluster_00.to diff --git a/centrallix-os/tests/test_objdrv_cluster_00.cluster b/centrallix-os/tests/test_objdrv_cluster_00.cluster new file mode 100644 index 000000000..f3af089f3 --- /dev/null +++ b/centrallix-os/tests/test_objdrv_cluster_00.cluster @@ -0,0 +1,32 @@ +$Version=2$ +test_objdrv_cluster_00 "system/cluster" + { + // Declare parameters. + algorithm "cluster/parameter" { type = string; } + k "cluster/parameter" { type = integer; } + threshold "cluster/parameter" { type = double; } + sim "cluster/parameter" { type = string; } + + // Declare data source. + source = "/tests/test_objdrv_cluster_00.csv/rows"; + key_attr = "key"; + data_attr = "data"; + + // A cluster for searching with clustering. + cluster "cluster/cluster" + { + algorithm = runserver(:parameters:algorithm); + similarity_measure = "cosine"; + num_clusters = runserver(:parameters:k); + min_improvement = 0.0001; + max_iterations = 16; + seed = 1; + } + + search "cluster/search" + { + source = cluster; + similarity_measure = runserver(:parameters:sim); + threshold = runserver(:parameters:threshold); + } + } diff --git a/centrallix-os/tests/test_objdrv_cluster_00.csv b/centrallix-os/tests/test_objdrv_cluster_00.csv new file mode 100644 index 000000000..2b1412fc4 --- /dev/null +++ b/centrallix-os/tests/test_objdrv_cluster_00.csv @@ -0,0 +1,8 @@ +key,data +"0: john","john" +"1: johnny","johnny" +"2: johns","johns" +"3: mathew","mathew" +"4: MatTheW","MatTheW" +"5: mathews","mathews" +"6: math","math" diff --git a/centrallix-os/tests/test_objdrv_cluster_00.spec b/centrallix-os/tests/test_objdrv_cluster_00.spec new file mode 100644 index 000000000..ea69334ab --- /dev/null +++ b/centrallix-os/tests/test_objdrv_cluster_00.spec @@ -0,0 +1,14 @@ +$Version=2$ +test_objdrv_cluster_00 "application/filespec" + { + // General parameters. + filetype = csv; + header_row = yes; + header_has_titles = no; + annotation = "test_objdrv_cluster_00"; + key_is_rowid = yes; + + // Column specifications. + key "filespec/column" { type=string; id=1; } + data "filespec/column" { type=string; id=2; } + } diff --git a/centrallix/tests/test_objdrv_cluster_00.cmp b/centrallix/tests/test_objdrv_cluster_00.cmp new file mode 100644 index 000000000..71b791587 --- /dev/null +++ b/centrallix/tests/test_objdrv_cluster_00.cmp @@ -0,0 +1,96 @@ +Attribute [algorithm]: string "k-means" +Attribute [similarity_measure]: string "cosine" +Attribute [num_clusters]: integer 2 +Attribute [source]: integer NULL +Attribute [threshold]: integer NULL +Attribute [algorithm]: integer NULL +Attribute [similarity_measure]: string "cosine" +Attribute [num_clusters]: integer NULL +Attribute [source]: string "cluster" +Attribute [threshold]: double 0.75 +Attribute [items_k1]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [items_k2]: stringve "john","johnny","johns" +Attribute [items_k2]: stringve "mathew","MatTheW","mathews","math" +Attribute [items_k3]: stringve "johnny" +Attribute [items_k3]: stringve "mathew","MatTheW","mathews","math" +Attribute [items_k3]: stringve "john","johns" +Attribute [items_k4]: stringve "johnny" +Attribute [items_k4]: stringve "MatTheW" +Attribute [items_k4]: stringve "john","johns" +Attribute [items_k4]: stringve "mathew","mathews","math" +Attribute [items_k1]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [items_k2]: stringve "john","johnny","johns" +Attribute [items_k2]: stringve "mathew","MatTheW","mathews","math" +Attribute [items_k3]: stringve "johnny" +Attribute [items_k3]: stringve "mathew","MatTheW","mathews","math" +Attribute [items_k3]: stringve "john","johns" +Attribute [items_k4]: stringve "johnny" +Attribute [items_k4]: stringve "MatTheW" +Attribute [items_k4]: stringve "john","johns" +Attribute [items_k4]: stringve "mathew","mathews","math" +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.757051 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "4: MatTheW" +Attribute [sim]: double 0.922627 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [key1]: string "4: MatTheW" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.874386 +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.757051 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "4: MatTheW" +Attribute [sim]: double 0.922627 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [key1]: string "4: MatTheW" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.874386 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "4: MatTheW" +Attribute [sim]: double 0.922627 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [key1]: string "4: MatTheW" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.874386 +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.757051 +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.757051 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.8 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.857143 +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.8 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.857143 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.857143 +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.8 +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.8 +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.857143 diff --git a/centrallix/tests/test_objdrv_cluster_00.to b/centrallix/tests/test_objdrv_cluster_00.to new file mode 100644 index 000000000..14dec4238 --- /dev/null +++ b/centrallix/tests/test_objdrv_cluster_00.to @@ -0,0 +1,29 @@ +##NAME Cluster Driver + +# Test parameters and initialization. +query select :algorithm, :similarity_measure, :num_clusters, :source, :threshold from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine ; + +# Test cluster. +query select items_k1=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select items_k2=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; +query select items_k3=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; +query select items_k4=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; + +# Drop caches and retest clustering. +exec /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster "cache" "drop_all" +query select items_k1=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select items_k2=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; +query select items_k3=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; +query select items_k4=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; + +# Test searching. +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/search ; +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/search ; +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/search ; +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/search ; + +# Test searching with Levenshtein. +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=levenshtein/search ; +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=levenshtein/search ; +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=levenshtein/search ; +query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=levenshtein/search ; From c7c6fcbc3506aff88bfb288e070e9edcbe7f528a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 11:35:17 -0600 Subject: [PATCH 059/361] Fix the keys field in the SourceData struct not being properly freed or counted. --- centrallix/osdrivers/objdrv_cluster.c | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 10a9401d7..9545abc39 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1918,6 +1918,7 @@ ci_FreeSourceData(pSourceData source_data) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Free top level attributes, if they exist. **/ + /** Note: The key field is handled by the caching system, so we shouldn't free it here. **/ if (source_data->Name != NULL) { nmSysFree(source_data->Name); @@ -1939,15 +1940,31 @@ ci_FreeSourceData(pSourceData source_data) source_data->DataAttr = NULL; } + /** Free fetched keys, if they exist. **/ + if (source_data->Keys != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + { + if (source_data->Keys[i] != NULL) + { + nmSysFree(source_data->Keys[i]); + source_data->Keys[i] = NULL; + } + } + nmSysFree(source_data->Keys); + source_data->Keys = NULL; + } + /** Free fetched data, if it exists. **/ if (source_data->Strings != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) { if (source_data->Strings[i] != NULL) + { nmSysFree(source_data->Strings[i]); - else continue; - source_data->Strings[i] = NULL; + source_data->Strings[i] = NULL; + } } nmSysFree(source_data->Strings); source_data->Strings = NULL; @@ -1959,9 +1976,10 @@ ci_FreeSourceData(pSourceData source_data) for (unsigned int i = 0u; i < source_data->nVectors; i++) { if (source_data->Vectors[i] != NULL) + { ca_free_vector(source_data->Vectors[i]); - else continue; - source_data->Vectors[i] = NULL; + source_data->Vectors[i] = NULL; + } } nmSysFree(source_data->Vectors); source_data->Vectors = NULL; @@ -2200,6 +2218,12 @@ ci_SizeOfSourceData(pSourceData source_data) if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); if (source_data->KeyAttr != NULL) size += strlen(source_data->KeyAttr) * sizeof(char); if (source_data->DataAttr != NULL) size += strlen(source_data->DataAttr) * sizeof(char); + if (source_data->Keys != NULL) + { + for (unsigned int i = 0u; i < source_data->nVectors; i++) + size += strlen(source_data->Keys[i]) * sizeof(char); + size += source_data->nVectors * sizeof(char*); + } if (source_data->Strings != NULL) { for (unsigned int i = 0u; i < source_data->nVectors; i++) From b4119687f614144e5aa7e2b8dcabb61d0ad18a7f Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 11:39:12 -0600 Subject: [PATCH 060/361] Update sizeof functions to return size_t instead of unsigned int. --- centrallix/osdrivers/objdrv_cluster.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 9545abc39..b09965ade 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -681,9 +681,9 @@ static void ci_ClearCaches(void); /** Deep Size Computation Functions. **/ // LINK #sizing -static unsigned int ci_SizeOfSourceData(pSourceData source_data); -static unsigned int ci_SizeOfClusterData(pClusterData cluster_data, bool recursive); -static unsigned int ci_SizeOfSearchData(pSearchData search_data); +static size_t ci_SizeOfSourceData(pSourceData source_data); +static size_t ci_SizeOfClusterData(pClusterData cluster_data, bool recursive); +static size_t ci_SizeOfSearchData(pSearchData search_data); /** Computation Functions. (Ensure data is computed.) **/ // LINK #computation @@ -2202,7 +2202,7 @@ ci_ClearCaches(void) *** @param source_data The source data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -static unsigned int +static size_t ci_SizeOfSourceData(pSourceData source_data) { /** Guard segfaults. **/ @@ -2213,7 +2213,7 @@ ci_SizeOfSourceData(pSourceData source_data) } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); - unsigned int size = 0u; + size_t size = 0u; if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); if (source_data->KeyAttr != NULL) size += strlen(source_data->KeyAttr) * sizeof(char); @@ -2254,7 +2254,7 @@ ci_SizeOfSourceData(pSourceData source_data) *** @param recursive Whether to recursively free subclusters. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -static unsigned int +static size_t ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) { /** Guard segfaults. **/ @@ -2265,7 +2265,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); - unsigned int size = 0u; + size_t size = 0u; if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); if (cluster_data->Clusters != NULL) { @@ -2300,7 +2300,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) *** @param search_data The search data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. ***/ -static unsigned int +static size_t ci_SizeOfSearchData(pSearchData search_data) { /** Guard segfaults. **/ @@ -4574,7 +4574,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) /** Handle type. **/ char* type; char* name; - unsigned int bytes; + size_t bytes; switch (*type_id_ptr) { case 1u: From 0fcf5e566799e80e7ee62484a29e1a7941f86989 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 11:43:05 -0600 Subject: [PATCH 061/361] Update generated HTML by running make in centrallix-doc/Widgets. --- centrallix-doc/Widgets/onepage.html | 2 +- centrallix-doc/Widgets/table.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-doc/Widgets/onepage.html b/centrallix-doc/Widgets/onepage.html index 9a449b6a5..d2e733884 100644 --- a/centrallix-doc/Widgets/onepage.html +++ b/centrallix-doc/Widgets/onepage.html @@ -5976,7 +5976,7 @@

Child Properties:

type string -The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. +The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. width diff --git a/centrallix-doc/Widgets/table.html b/centrallix-doc/Widgets/table.html index d0538544b..a3361828d 100644 --- a/centrallix-doc/Widgets/table.html +++ b/centrallix-doc/Widgets/table.html @@ -329,7 +329,7 @@

Child Properties:

type string -The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. +The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. width From f47c678175d15b7b6bd7b69d784b77bb381a711c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 12:09:14 -0600 Subject: [PATCH 062/361] Update copyright notices to use correct dates. Fix a spelling errors in a copyright notices. --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/include/glyph.h | 2 +- centrallix-lib/include/magic.h | 2 +- centrallix-lib/include/util.h | 2 +- centrallix-lib/include/xhash.h | 2 +- centrallix-lib/src/clusters.c | 2 +- centrallix-lib/src/mtask.c | 4 ++-- centrallix-lib/src/mtlexer.c | 2 +- centrallix-lib/src/util.c | 2 +- centrallix-lib/src/xhash.c | 2 +- centrallix-sysdoc/CommentAnchorsExtension.md | 2 +- centrallix-sysdoc/Libraries/mtask.md | 4 ++-- centrallix-sysdoc/Libraries/mtsession.md | 4 ++-- centrallix-sysdoc/Libraries/newmalloc.md | 4 ++-- centrallix-sysdoc/Libraries/xarray.md | 4 ++-- centrallix-sysdoc/Libraries/xhash.md | 4 ++-- centrallix-sysdoc/Libraries/xstring.md | 4 ++-- centrallix-sysdoc/OSDriver_Authoring.md | 4 ++-- centrallix-sysdoc/string_similarity.md | 2 +- centrallix/Makefile.in | 2 +- centrallix/centrallix.c | 2 +- centrallix/etc/types.cfg | 2 +- centrallix/expression/exp_compiler.c | 2 +- centrallix/expression/exp_evaluate.c | 2 +- centrallix/expression/exp_functions.c | 2 +- centrallix/include/cxss/policy.h | 2 +- centrallix/include/double_metaphone.h | 2 +- centrallix/include/expression.h | 2 +- centrallix/include/stparse.h | 2 +- centrallix/multiquery/multiquery.c | 2 +- centrallix/objectsystem/obj_datatypes.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 2 +- centrallix/test_obj.c | 2 +- centrallix/utility/double_metaphone.c | 2 +- 34 files changed, 42 insertions(+), 42 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 4665bc557..cc3087f33 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix-lib/include/glyph.h b/centrallix-lib/include/glyph.h index 4636d6b47..6a5b0a74e 100644 --- a/centrallix-lib/include/glyph.h +++ b/centrallix-lib/include/glyph.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index 50ea697da..d6cb37ac8 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index efe914d8a..6cbdeb29a 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-lib/include/xhash.h b/centrallix-lib/include/xhash.h index 65b900570..53cc8d892 100644 --- a/centrallix-lib/include/xhash.h +++ b/centrallix-lib/include/xhash.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 83dd9a3dd..f6d85b328 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix-lib/src/mtask.c b/centrallix-lib/src/mtask.c index 401fbf15b..8af29c3c8 100644 --- a/centrallix-lib/src/mtask.c +++ b/centrallix-lib/src/mtask.c @@ -41,7 +41,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -54,7 +54,7 @@ /* Description: */ /* */ /* The MTASK Multithreading Tasking Module provides non-preemptive */ -/* threading services for Centrallix. It has been shown to be useable */ +/* threading services for Centrallix. It has been shown to be usable */ /* on a variety of platforms, although the values for MT_TASKSEP may */ /* sometimes need to be adjusted. This module does NOT provide for */ /* kernel threads or for preemptive threading. */ diff --git a/centrallix-lib/src/mtlexer.c b/centrallix-lib/src/mtlexer.c index 6c5a46dba..15d67d177 100644 --- a/centrallix-lib/src/mtlexer.c +++ b/centrallix-lib/src/mtlexer.c @@ -19,7 +19,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 9dbc804d7..c5064d3ae 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index 7bf4242cc..58adc7e18 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -13,7 +13,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-sysdoc/CommentAnchorsExtension.md b/centrallix-sysdoc/CommentAnchorsExtension.md index 397e5fa92..756eb463a 100644 --- a/centrallix-sysdoc/CommentAnchorsExtension.md +++ b/centrallix-sysdoc/CommentAnchorsExtension.md @@ -2,7 +2,7 @@ - + diff --git a/centrallix-sysdoc/Libraries/mtask.md b/centrallix-sysdoc/Libraries/mtask.md index e3395ce0e..26982ff34 100644 --- a/centrallix-sysdoc/Libraries/mtask.md +++ b/centrallix-sysdoc/Libraries/mtask.md @@ -2,7 +2,7 @@ - + @@ -28,7 +28,7 @@ - + # Handling Network Connection diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md index a6c946e4d..84990ab32 100644 --- a/centrallix-sysdoc/Libraries/mtsession.md +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -2,7 +2,7 @@ - + @@ -27,7 +27,7 @@ - + # The MTSession Library diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index c77d90f12..cba631154 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -2,7 +2,7 @@ - + @@ -29,7 +29,7 @@ - + # Memory Management in Centrallix diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 48004cc1d..4e67be437 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -2,7 +2,7 @@ - + @@ -27,7 +27,7 @@ - + # The XArray Library diff --git a/centrallix-sysdoc/Libraries/xhash.md b/centrallix-sysdoc/Libraries/xhash.md index 5d4ca8028..daf16d186 100644 --- a/centrallix-sysdoc/Libraries/xhash.md +++ b/centrallix-sysdoc/Libraries/xhash.md @@ -2,7 +2,7 @@ - + @@ -27,7 +27,7 @@ - + # The XHash Library diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 4ecce289d..4924a0803 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -2,7 +2,7 @@ - + @@ -27,7 +27,7 @@ - + # The XString Library diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 4b90a117b..9cf4ee6db 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -2,7 +2,7 @@ - + @@ -28,7 +28,7 @@ - + # ObjectSystem Driver Interface diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 310a5c2f2..a4ae6fc33 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -2,7 +2,7 @@ - + diff --git a/centrallix/Makefile.in b/centrallix/Makefile.in index f50596c95..eb40ca74e 100644 --- a/centrallix/Makefile.in +++ b/centrallix/Makefile.in @@ -1,6 +1,6 @@ #### Centrallix Makefile #### -#### Copyright (C) 1998-2014 by LightSys Technology Services, Inc. +#### Copyright (C) 1998-2026 by LightSys Technology Services, Inc. #### #### FREE SOFTWARE, NO WARRANTY: This program is Free Software, licensed #### under version 2 of the GNU General Public License, or any later diff --git a/centrallix/centrallix.c b/centrallix/centrallix.c index 89871a9b3..27dc45d66 100644 --- a/centrallix/centrallix.c +++ b/centrallix/centrallix.c @@ -29,7 +29,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2002 LightSys Technology Services, Inc. */ +/* Copyright (C) 2002-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/etc/types.cfg b/centrallix/etc/types.cfg index 6cbac5ae6..53d874669 100644 --- a/centrallix/etc/types.cfg +++ b/centrallix/etc/types.cfg @@ -1,5 +1,5 @@ ## See LICENSE.txt for licensing information. -## Copyright (C) 1998-2024 LightSys Technology Services, Inc. +## Copyright (C) 1998-2026 LightSys Technology Services, Inc. ## ## types.cfg - file and object type information database. ## diff --git a/centrallix/expression/exp_compiler.c b/centrallix/expression/exp_compiler.c index 3fcd83efb..84753e5b8 100644 --- a/centrallix/expression/exp_compiler.c +++ b/centrallix/expression/exp_compiler.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/expression/exp_evaluate.c b/centrallix/expression/exp_evaluate.c index c35cdac8e..2413a0d00 100644 --- a/centrallix/expression/exp_evaluate.c +++ b/centrallix/expression/exp_evaluate.c @@ -16,7 +16,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 80d7ab0d6..205f511c5 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/include/cxss/policy.h b/centrallix/include/cxss/policy.h index 6f9ca7d83..19535b939 100644 --- a/centrallix/include/cxss/policy.h +++ b/centrallix/include/cxss/policy.h @@ -8,7 +8,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h index 757195862..f0641bd8a 100644 --- a/centrallix/include/double_metaphone.h +++ b/centrallix/include/double_metaphone.h @@ -71,7 +71,7 @@ /* Lawrence Philips which he published in the June, 2000 */ /* issue of C/C++ Users Journal. Double Metaphone is an */ /* improved version of the original Metaphone algorithm */ -/* written by Philips'. This implementaton was written by */ +/* written by Philips'. This implementation was written by */ /* Maurice Aubrey for C/C++ with bug fixes provided by */ /* Kevin Atkinson. It was revised by Israel Fuller to */ /* better align with the Centrallix coding style and */ diff --git a/centrallix/include/expression.h b/centrallix/include/expression.h index 8d506f72e..800a90fd5 100644 --- a/centrallix/include/expression.h +++ b/centrallix/include/expression.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/include/stparse.h b/centrallix/include/stparse.h index 2f7e4163d..a941b6a37 100644 --- a/centrallix/include/stparse.h +++ b/centrallix/include/stparse.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/multiquery/multiquery.c b/centrallix/multiquery/multiquery.c index f329d0ea3..fcc175a0c 100644 --- a/centrallix/multiquery/multiquery.c +++ b/centrallix/multiquery/multiquery.c @@ -20,7 +20,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index 388a75e60..c0c702abf 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -22,7 +22,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index b09965ade..0c0605af9 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2012 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index 130192dbe..a323e6d82 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -33,7 +33,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index f37b0964c..929620b7f 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -68,7 +68,7 @@ /* Lawrence Philips which he published in the June, 2000 */ /* issue of C/C++ Users Journal. Double Metaphone is an */ /* improved version of the original Metaphone algorithm */ -/* written by Philips'. This implementaton was written by */ +/* written by Philips'. This implementation was written by */ /* Maurice Aubrey for C/C++ with bug fixes provided by */ /* Kevin Atkinson. It was revised by Israel Fuller to */ /* better align with the Centrallix coding style and */ From e799efa8f7408dbae1e67742538ed03f6c9c4105 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 12:57:16 -0600 Subject: [PATCH 063/361] Fix typo that stated the signed int max was 2147483629. --- centrallix-lib/include/clusters.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index cc3087f33..27b25c2f0 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -53,7 +53,7 @@ *** This is also called the vector table size, if viewing the *** vector as a hash table of character pairs. *** - *** 2147483629 is the signed int max, and is also a prime number. + *** 2147483647 is the signed int max, and is also a prime number. *** Using this value ensures that the longest run of 0s will not *** cause an int underflow with the current encoding scheme. *** From b9a9d3641ee282f8b74070e1e06d67124a4069dc Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 13:01:26 -0600 Subject: [PATCH 064/361] Update macros. Refactor ca_init() into a function instead of a macro to make it more open to expansion. Rename pCentroidSize to CENTROID_SIZE. Fix missing parentheses for CA_BOUNDARY_CHAR and CENTROID_SIZE. --- centrallix-lib/include/clusters.h | 13 +++++-------- centrallix-lib/src/clusters.c | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 27b25c2f0..15f307ea9 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -67,12 +67,12 @@ #define CA_NUM_DIMS 251 /** The character used to create a pair with the first and last characters of a string. **/ -#define CA_BOUNDARY_CHAR (unsigned char)('a' - 1) +#define CA_BOUNDARY_CHAR ((unsigned char)('a' - 1)) /** Types. **/ typedef int* pVector; /* Sparse vector. */ typedef double* pCentroid; /* Dense centroid. */ -#define pCentroidSize CA_NUM_DIMS * sizeof(double) +#define CENTROID_SIZE (CA_NUM_DIMS * sizeof(double)) /*** Information about detected matching pairs. *** @@ -88,12 +88,6 @@ typedef struct } Pair, *pPair; -/** Registering all defined types for debugging. **/ -#define ca_init() \ - nmRegister(sizeof(pVector), "pVector"); \ - nmRegister(sizeof(pCentroid), "pCentroid"); \ - nmRegister(pCentroidSize, "Centroid"); \ - nmRegister(sizeof(Pair), "Pair") /** Edit distance function. **/ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); @@ -149,4 +143,7 @@ pXArray ca_complete_search( const double threshold, pXArray maybe_pairs); +/** Module management functions. **/ +void ca_init(void); + #endif /* End of .h file. */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index f6d85b328..5e22ef3b7 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -827,6 +827,10 @@ ca_kmeans( if (new_centroids[i] == NULL) goto end; memset(centroids[i], 0, pCentroidSize); memset(new_centroids[i], 0, pCentroidSize); + centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); + new_centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); + memset(centroids[i], 0, CENTROID_SIZE); + memset(new_centroids[i], 0, CENTROID_SIZE); } /** Select random vectors to use as the initial centroids. **/ @@ -935,7 +939,7 @@ ca_kmeans( { for (unsigned int i = 0u; i < num_clusters; i++) { - if (centroids[i] != NULL) nmFree(centroids[i], pCentroidSize); + if (centroids[i] != NULL) nmFree(centroids[i], CENTROID_SIZE); else break; } nmFree(centroids, num_clusters * sizeof(pCentroid)); @@ -944,7 +948,7 @@ ca_kmeans( { for (unsigned int i = 0u; i < num_clusters; i++) { - if (new_centroids[i] != NULL) nmFree(new_centroids[i], pCentroidSize); + if (new_centroids[i] != NULL) nmFree(new_centroids[i], CENTROID_SIZE); else break; } nmFree(new_centroids, num_clusters * sizeof(pCentroid)); @@ -1095,6 +1099,18 @@ ca_complete_search( return ca_sliding_search(data, num_data, num_data, similarity, threshold, maybe_pairs); } +/** Initialize the module. **/ +void +ca_init(void) + { + nmRegister(sizeof(pVector), "pVector"); + nmRegister(sizeof(pCentroid), "pCentroid"); + nmRegister(CENTROID_SIZE, "Centroid"); + nmRegister(sizeof(Pair), "Pair"); + + return; + } + /** Scope cleanup. **/ #undef ca_sparse_dif #undef ca_sparse_dif_to_centroid From c9241cf3cb2627f48d2e9824731b6f5df64efd64 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 13:05:05 -0600 Subject: [PATCH 065/361] Fix error handling. Fix calls to check_ptr() that forgot to handle the error. Add new calls to check_ptr(). Move ASSERTMAGIC() calls to be after null checks (style). Clean up. --- centrallix-lib/src/clusters.c | 13 +++---------- centrallix/expression/exp_functions.c | 18 +++++++++++++++--- centrallix/osdrivers/objdrv_cluster.c | 20 ++++++++++++++++++-- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 5e22ef3b7..6c0ea1434 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -712,8 +712,7 @@ get_cluster_size( ***/ cluster_sums = check_ptr(nmMalloc(num_clusters * sizeof(double))); cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); - if (cluster_sums == NULL) goto end; - if (cluster_counts == NULL) goto end; + if (cluster_sums == NULL || cluster_counts == NULL) goto end; for (unsigned int i = 0u; i < num_clusters; i++) { cluster_sums[i] = 0.0; @@ -815,20 +814,14 @@ ca_kmeans( const size_t centroids_size = num_clusters * sizeof(pCentroid); centroids = check_ptr(nmMalloc(centroids_size)); new_centroids = check_ptr(nmMalloc(centroids_size)); - if (centroids == NULL) goto end; - if (new_centroids == NULL) goto end; + if (centroids == NULL || new_centroids == NULL) goto end; memset(centroids, 0, centroids_size); memset(new_centroids, 0, centroids_size); for (unsigned int i = 0u; i < num_clusters; i++) { - centroids[i] = check_ptr(nmMalloc(pCentroidSize)); - new_centroids[i] = check_ptr(nmMalloc(pCentroidSize)); - if (centroids[i] == NULL) goto end; - if (new_centroids[i] == NULL) goto end; - memset(centroids[i], 0, pCentroidSize); - memset(new_centroids[i], 0, pCentroidSize); centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); new_centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); + if (centroids[i] == NULL || new_centroids[i] == NULL) goto end; memset(centroids[i], 0, CENTROID_SIZE); memset(new_centroids[i], 0, CENTROID_SIZE); } diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 205f511c5..349852e06 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1589,6 +1589,7 @@ exp_fn_lztrim(pExpression tree) /** Extract the arg string. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str == NULL) return -1; if (maybe_str->Flags & EXPR_F_NULL) { /** Propegate null values. **/ @@ -1596,7 +1597,8 @@ exp_fn_lztrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = maybe_str->String; + char* str = check_ptr(maybe_str->String); + if (str == NULL) return -1; /*** We don't need to allocate new memory or copy anything because we *** can simply point to the first character in the previous string @@ -1631,6 +1633,7 @@ exp_fn_ltrim(pExpression tree) /** Extract the arg string. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str == NULL) return -1; if (maybe_str->Flags & EXPR_F_NULL) { /** Propegate null values. **/ @@ -1638,7 +1641,8 @@ exp_fn_ltrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = maybe_str->String; + char* str = check_ptr(maybe_str->String); + if (str == NULL) return -1; /*** We don't need to allocate new memory or copy anything because we *** can simply point to the first character in the previous string @@ -1674,6 +1678,7 @@ exp_fn_rtrim(pExpression tree) /** Extract the arg string. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str == NULL) return -1; if (maybe_str->Flags & EXPR_F_NULL) { /** Propegate null values. **/ @@ -1681,7 +1686,8 @@ exp_fn_rtrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = maybe_str->String; + char* str = check_ptr(maybe_str->String); + if (str == NULL) return -1; /** Trim spaces from the end of the string. **/ /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ @@ -4543,6 +4549,7 @@ exp_fn_metaphone(pExpression tree) /** Extract string param. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (maybe_str == NULL) return -1; if (maybe_str->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; @@ -4550,6 +4557,7 @@ exp_fn_metaphone(pExpression tree) return 0; } const char* str = check_ptr(maybe_str->String); + if (str == NULL) return -1; const size_t str_len = strlen(str); if (str_len == 0u) { @@ -4599,6 +4607,7 @@ exp_fn_compare(pExpression tree) /** Extract strings. **/ pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (maybe_str1 == NULL || maybe_str2) return -1; if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; @@ -4607,6 +4616,7 @@ exp_fn_compare(pExpression tree) } char* str1 = check_ptr(maybe_str1->String); char* str2 = check_ptr(maybe_str2->String); + if (str1 == NULL || str2) return -1; /** Handle either cos_compare() or lev_compare(). **/ if (tree->Name[0] == 'c') @@ -4672,6 +4682,7 @@ exp_fn_levenshtein(pExpression tree) /** Extract strings. **/ pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (maybe_str1 == NULL || maybe_str2 == NULL) return -1; if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; @@ -4680,6 +4691,7 @@ exp_fn_levenshtein(pExpression tree) } char* str1 = check_ptr(maybe_str1->String); char* str2 = check_ptr(maybe_str2->String); + if (str1 == NULL || str2 == NULL) return -1; /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 0c0605af9..58ca80ffb 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1239,6 +1239,11 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); + if (sub_inf == NULL) + { + mssErrorf(1, "Cluster", "Failed to get %uth subinf.", i); + goto err_free; + } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; @@ -1494,6 +1499,11 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); + if (sub_inf == NULL) + { + mssErrorf(1, "Cluster", "Failed to get %uth subinf.", i); + goto err_free; + } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; @@ -1664,6 +1674,11 @@ ci_ParseNodeData(pStructInf inf, pObject parent) for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); + if (sub_inf == NULL) + { + mssErrorf(1, "Cluster", "Failed to get %uth subinf.", i); + goto err_free; + } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; @@ -2809,12 +2824,12 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** We need the cluster data to be computed before we search it. **/ pClusterData cluster_data = check_ptr(search_data->SourceCluster); - ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); if (cluster_data == NULL) { mssErrorf(1, "Cluster", "Failed to get cluster data for search computation."); goto err_free; } + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); if (ci_ComputeClusterData(cluster_data, node_data) != 0) { mssErrorf(0, "Cluster", "SearchData computation failed due to missing clusters."); @@ -3988,10 +4003,11 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Allocate and initialize the requested data. **/ vec = val->StringVec = check_ptr(nmMalloc(sizeof(StringVec))); - if (vec == NULL) return -1; + if (vec == NULL) goto err; memset(vec, 0, sizeof(StringVec)); vec->nStrings = target_cluster->Size; vec->Strings = check_ptr(nmSysMalloc(target_cluster->Size * sizeof(char*))); + if (vec->Strings == NULL) goto err; for (unsigned int i = 0u; i < target_cluster->Size; i++) vec->Strings[i] = target->SourceData->Strings[target_cluster->Indexes[i]]; From 5fd3d96cfa99aa8ea7ae14707ce8074feeb42388 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 13:05:46 -0600 Subject: [PATCH 066/361] Reimplement some code in ca_build_vector() so we don't need to use a goto, improving clarity. --- centrallix-lib/src/clusters.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 6c0ea1434..a66d9486b 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -163,14 +163,11 @@ ca_build_vector(const char* str) if (maybe_char < 0) fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); unsigned char c = (unsigned char)maybe_char; - /** Always consider boundary character in string. **/ - if (c == CA_BOUNDARY_CHAR) goto skip_checks; + /** Ignore insignificant characters. **/ + /** isspace(): space, \n, \v, \f, \r **/ + /** ispunct(): !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ **/ + if (c != CA_BOUNDARY_CHAR && (isspace(c) || ispunct(c))) continue; - /** Ignore insignificant characters: spaces and punctuation. **/ - if (isspace(c)) continue; /* space, \n, \v, \f, \r */ - if (ispunct(c)) continue; /* !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ */ - - skip_checks: /** Shift numbers to the end of the lowercase letters. **/ if ('0' <= c && c <= '9') c += 75u; From 3e57fa612c1ef81467c096173ba32ee1802a4c27 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 13:09:44 -0600 Subject: [PATCH 067/361] Fix spelling mistakes and clean up comments. Remove untested assertions in doc comments. Fix spelling mistakes. --- centrallix-lib/src/clusters.c | 20 ++++++++------------ centrallix-sysdoc/OSDriver_Authoring.md | 10 +++++----- centrallix-sysdoc/string_similarity.md | 10 +++++----- centrallix/expression/exp_functions.c | 12 ++++++------ 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index a66d9486b..3f9aff7d1 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -126,7 +126,7 @@ ca_char_pair_cmp(const void *p1, const void *p2) *** input to get_char_pair_hash(). *** *** After hashing each character pair, we add some number from 1 to 13 to the - *** coresponding dimension. However, for most names, this results in a lot of + *** corresponding dimension. However, for most names, this results in a lot of *** zeros and a FEW positive numbers. Thus, after creating the dense vector, *** we convert it to a sparse vector in which a negative number replaces a run *** of that many zeros. Consider the following example: @@ -382,8 +382,7 @@ ca_parse_vector_token(const int token, unsigned int* remaining, unsigned int* pa return; } -/*** Calculate the similarity on sparsely allocated vectors. Comparing - *** any string to an empty string should always return 0.5 (untested). +/*** Calculate the similarity on sparsely allocated vectors. *** *** @param v1 Sparse vector #1. *** @param v2 Sparse vector #2. @@ -422,8 +421,7 @@ ca_sparse_similarity(const pVector v1, const pVector v2) return (double)dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_sparse(v2)); } -/*** Calculate the difference on sparsely allocated vectors. Comparing - *** any string to an empty string should always return 0.5 (untested). +/*** Calculate the difference on sparsely allocated vectors. *** *** @param v1 Sparse vector #1. *** @param v2 Sparse vector #2. @@ -434,8 +432,7 @@ ca_sparse_similarity(const pVector v1, const pVector v2) #define ca_sparse_dif(v1, v2) (1.0 - ca_sparse_similarity(v1, v2)) /*** Calculate the similarity between a sparsely allocated vector and a densely - *** allocated centroid using a dot product. Comparing any string to an empty - *** string should always return 0.5 (untested). + *** allocated centroid using a dot product. *** *** @param v1 Sparse vector #1. *** @param c1 Dense centroid #2. @@ -464,8 +461,7 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) } /*** Calculate the difference between a sparsely allocated vector and a densely - *** allocated centroid. Comparing any string to an empty string should always - *** return 0.5 (untested). + *** allocated centroid. *** *** @param v1 Sparse vector #1. *** @param c1 Dense centroid #2. @@ -519,7 +515,7 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const lev_matrix[0][0] = 0u; /*** Base case #1: - *** Any source prefixe can be transformed into an empty string by + *** Any source prefix can be transformed into an empty string by *** dropping each character. ***/ for (unsigned int i = 1u; i <= str1_len; i++) @@ -541,7 +537,7 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const if (str1[i - 1] == str2[j - 1]) lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; - /*** We need to make a change, so use the oppereration with the + /*** We need to make a change, so use the operation with the *** lowest cost out of delete, insert, replace, or swap. ***/ else @@ -591,7 +587,7 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const return result; } -/*** Compares two strings using their cosie similarity, returning a value +/*** Compares two strings using their cosine similarity, returning a value *** between `0.0` (completely different) and `1.0` (identical). If either *** OR BOTH strings are NULL, this function returns `0.0`. *** diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 9cf4ee6db..9c9719f7f 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -363,7 +363,7 @@ The `Open()` routine is called with five parameters: - `parent->SubPtr : short`: The number of components in the path that are a part of the path to the driver's node object, including the `.` for the top level directory and the driver's node object. For example, in the above path of `/data/file.csv`, the path would be internally represented as `./ data/ file.csv`, so SubPtr is 3. - - For example, use `obj_internal_PathPart(parent->Pathname, parent->SubPtr - 1, 1)` to get the name of the file being openned, and use `obj_internal_PathPart(parent->Pathname, 0, parent->SubPtr)` to get the path. + - For example, use `obj_internal_PathPart(parent->Pathname, parent->SubPtr - 1, 1)` to get the name of the file being opened, and use `obj_internal_PathPart(parent->Pathname, 0, parent->SubPtr)` to get the path. - `parent->SubCnt : short`: _The driver should set this value_ to show the number of components it controls. This includes the driver's node object, so `SubCnt` will always be at least 1. For example, when opening `/data/file.csv/rows/1`, the CSV driver will read the `SubPtr` of 3 (see above), representing `./ data/ file.csv`. It will then set a `SubCnt` of 3, representing that it controls `file.csv /rows /1`. (The driver only sets `SubCnt`; `SubPtr` is provided.) @@ -409,7 +409,7 @@ The `OpenChild()` function is called with two parameters: |------------|--------------|---------------------------------------------------------------------------| | inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | | obj | pObject | An object? | -| child_name | char* | The value for the name attribute of the child object to be openned. | +| child_name | char* | The value for the name attribute of the child object to be opened. | | mask | int | The permission mask to be given to the object (if created).* | | sys_type | pContentType | Indicates the content type of the node object as determined by the OSML.* | | usr_type | char* | The object type requested by the user.* | @@ -419,7 +419,7 @@ The `OpenChild()` function is called with two parameters: *See [`Open()`](#function-open) above for more info. -The `OpenChild()` function should a pointer to the node object for the newly openned child on success or `NULL` on failure. +The `OpenChild()` function should a pointer to the node object for the newly opened child on success or `NULL` on failure. --- ### Function: Close() @@ -1082,9 +1082,9 @@ The expression (EXP) module is used for compiling, evaluating, reverse-evaluatin Expressions can be stand-alone expression trees, or they can take parameter objects. A parameter object is an open object (from `objOpen()`) whose values (attributes) are referenced within the expression string. By using such parameter objects, one expression can be compiled and then evaluated for many different objects with diverse attribute values. -Expression evaluation results in the top-level expression tree node having the final value of the expression, which may be `NULL`, and may be an integer, string, datetime, money, or double data type. For example, the final value of `:myobject:oneattribute == 'yes'` is the integer 1, `true`, if the attribute's value is indeed `'yes'` (and the integer 0, `false`, otherwise). +Expression evaluation results in the top-level expression tree node having the final value of the expression, which may be `NULL`, and may be an integer, string, datetime, money, or double data type. For example, the final value of `:my_object:oneattribute == 'yes'` is the integer 1, `true`, if the attribute's value is indeed `'yes'` (and the integer 0, `false`, otherwise). -Expression reverse-evaluation takes a given final value and attempts to assign values to the parameter object attributes based on the structure of the expression tree. It is akin to 'solving for X' in algebraic work, but isn't nearly that 'smart'. For example, with the previous expression, if the final value was set to 1 (`true`), then an `objSetAttrValue()` function would be called to set myobject's `oneattribute` to `yes`. Trying this with a final value of 0 (`false`) would result in no assignment to the attribute, since there would be no way of determining the proper value for that attribute (anything other than `yes` would work). +Expression reverse-evaluation takes a given final value and attempts to assign values to the parameter object attributes based on the structure of the expression tree. It is akin to 'solving for X' in algebraic work, but isn't nearly that 'smart'. For example, with the previous expression, if the final value was set to 1 (`true`), then an `objSetAttrValue()` function would be called to set my_object's `oneattribute` to `yes`. Trying this with a final value of 0 (`false`) would result in no assignment to the attribute, since there would be no way of determining the proper value for that attribute (anything other than `yes` would work). Reverse evaluation is typically very useful in updateable joins and views. diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index a4ae6fc33..2685edf98 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -56,7 +56,7 @@ The following sections discuss the approaches to calculating similarity between - [DBScan Clustering](#db-scan) - [Sliding Clusters](#sliding-clusters) - [Future Implementation](#future-implementation) - - [K-means Fuzzy Clustering](#k-means-fuzzy-clusterings) + - [K-means Fuzzy Clustering](#k-means-fuzzy-clustering) - [Implement Missing Algorithms](#implement-missing-algorithms) - [Upgrade Other Duplicate Detection Systems](#upgrade-other-duplicate-detection-systems) - [Fix Known Issues](#fix-known-issues) @@ -102,10 +102,10 @@ As noted above, the vast majority of elements in a vector generated by a typical Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. -## Levenshtein Similarity -The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using levenshtein edit distance. +## Levenshtein Similarity +The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using Levenshtein edit distance. -The levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. +The Levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. ## Clustering @@ -159,6 +159,6 @@ Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-m When a new record is entered, a quick scan is run to check if it might be a duplicate. There is also a button in the UI for a record that lets you run a duplicate check. These systems could also be upgraded using the new algorithms and strategies developed for general duplicate detection. ### Fix Known Issues -- The cluster driver often fails to open the structure file if it was modifed since the last time the path was openned. Opening a different path (including the root path, even though it does not support queries) fixes this issue. This is either a bug in the st_node caching or in the cluster driver's usage of stparse. +- The cluster driver often fails to open the structure file if it was modified since the last time the path was opened. Opening a different path (including the root path, even though it does not support queries) fixes this issue. This is either a bug in the st_node caching or in the cluster driver's usage of stparse. - The cluster does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to manually invalidate caches when data is changed, or use a fresh object system instance. - It is not possible to open cluster or search entries, they can only be retrieved by querying. diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 349852e06..1904d696e 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1592,7 +1592,7 @@ exp_fn_lztrim(pExpression tree) if (maybe_str == NULL) return -1; if (maybe_str->Flags & EXPR_F_NULL) { - /** Propegate null values. **/ + /** Propagate null values. **/ tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_STRING; return 0; @@ -1636,7 +1636,7 @@ exp_fn_ltrim(pExpression tree) if (maybe_str == NULL) return -1; if (maybe_str->Flags & EXPR_F_NULL) { - /** Propegate null values. **/ + /** Propagate null values. **/ tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_STRING; return 0; @@ -1649,7 +1649,7 @@ exp_fn_ltrim(pExpression tree) *** that isn't trimmed. ***/ - /** Iterate until we find the a charater that isn't a space. **/ + /** Iterate until we find the a character that isn't a space. **/ /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ while (*str == ' ') str++; @@ -1681,7 +1681,7 @@ exp_fn_rtrim(pExpression tree) if (maybe_str == NULL) return -1; if (maybe_str->Flags & EXPR_F_NULL) { - /** Propegate null values. **/ + /** Propagate null values. **/ tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_STRING; return 0; @@ -1729,7 +1729,7 @@ exp_fn_trim(pExpression tree) arg1->String = tree->String; tree->Alloc = 0; - /** Right trim the expression, which will use the overriden string above. **/ + /** Right trim the expression, which will use the overridden string above. **/ exp_fn_rtrim(tree); /** Restore the arg1 tree. **/ @@ -4652,7 +4652,7 @@ exp_fn_compare(pExpression tree) double lev_sim = check_double(ca_lev_compare(str1, str2)); if (isnan(lev_sim)) { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute levenstein edit distance."); + mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute Levenshtein edit distance."); return -1; } From 32ef9e0ce6193e0237a9a614cc589fbdeb6366cf Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 13:39:48 -0600 Subject: [PATCH 068/361] Use ca_parse_vector_token() to abstract sparse vector parsing logic. Move ca_parse_vector_token() earlier in the file to avoid needing a forward declaration. Rewrite ca_sparse_len(). Rewrite ca_magnitude_sparse(). Rewrite ca_sparse_similarity_to_centroid(). --- centrallix-lib/src/clusters.c | 89 ++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 3f9aff7d1..cdec3a646 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -268,6 +268,32 @@ ca_free_vector(pVector sparse_vector) return; } +/*** Parse a token from a sparsely allocated vector and write the param_value and + *** number of remaining values to the passed locations. + *** + *** @param token The sparse vector token being parsed. + *** @param remaining The location to save the remaining number of characters. + *** @param param_value The location to save the param_value of the token. + ***/ +static void +ca_parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) + { + if (token < 0) + { + /** This run contains -token zeros. **/ + *remaining = (unsigned)(-token); + *param_value = 0u; + } + else + { + /** This run contains one param_value. **/ + *remaining = 1u; + *param_value = (unsigned)(token); + } + + return; + } + /*** Compute the actual number of ints stored in memory to store the given *** sparsely allocated vector. *** @@ -279,15 +305,14 @@ ca_sparse_len(const pVector vector) { unsigned int i = 0u; - for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) + for (unsigned int dim = 0u; dim < CA_NUM_DIMS; i++) { - const int val = vector[i++]; - - /** Negative val represents -val 0s in the array, so skip that many values. **/ - if (val < 0) dim += (unsigned)(-val); + /** Parse the vector token. **/ + unsigned int dims_consumed = 0u, val = 0u; + ca_parse_vector_token(vector[i++], &dims_consumed, &val); - /** We have a param_value, but we don't need to do anything with it. **/ - else dim++; + /** Move ahead the requested number of dimensions. **/ + dim += dims_consumed; } return i; @@ -328,13 +353,15 @@ ca_magnitude_sparse(const pVector vector) for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) { - const int val = vector[i++]; + /** Parse the vector token. **/ + unsigned int dims_consumed = 0u, val = 0u; + ca_parse_vector_token(vector[i++], &dims_consumed, &val); - /** Negative val represents -val 0s in the array, so skip that many values. **/ - if (val < 0) dim += (unsigned)(-val); + /** Increase magnitude. **/ + magnitude += val * val; - /** We have a param_value, so square it and add it to the magnitude. **/ - else { magnitude += (unsigned)(val * val); dim++; } + /** Move ahead the requested number of dimensions. **/ + dim += dims_consumed; } return sqrt((double)magnitude); @@ -356,32 +383,6 @@ ca_magnitude_dense(const pCentroid centroid) return sqrt(magnitude); } -/*** Parse a token from a sparsely allocated vector and write the param_value and - *** number of remaining values to the passed locations. - *** - *** @param token The sparse vector token being parsed. - *** @param remaining The location to save the remaining number of characters. - *** @param param_value The location to save the param_value of the token. - ***/ -static void -ca_parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) - { - if (token < 0) - { - /** This run contains -token zeros. **/ - *remaining = (unsigned)(-token); - *param_value = 0u; - } - else - { - /** This run contains one param_value. **/ - *remaining = 1u; - *param_value = (unsigned)(token); - } - - return; - } - /*** Calculate the similarity on sparsely allocated vectors. *** *** @param v1 Sparse vector #1. @@ -447,13 +448,15 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) { - const int val = v1[i++]; + /** Parse the vector token. **/ + unsigned int dims_consumed = 0u, val = 0u; + ca_parse_vector_token(v1[i++], &dims_consumed, &val); - /** Negative val represents -val 0s in the array, so skip that many values. **/ - if (val < 0) dim += (unsigned)(-val); + /** Increase dot product (skipped for zero-values). **/ + if (val > 0u) dot_product += (double)val * c2[dim]; - /** We have a param_value, so square it and add it to the magnitude. **/ - else dot_product += (double)val * c2[dim++]; + /** Move ahead the requested number of dimensions. **/ + dim += dims_consumed; } /** Return the difference score. **/ From 00bdc0e98f80cc8b129917497c133e9e00fd83df Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 13:46:47 -0600 Subject: [PATCH 069/361] Rename params for ca_parse_vector_token() to improve readability. --- centrallix-lib/src/clusters.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index cdec3a646..fd32e572c 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -268,27 +268,28 @@ ca_free_vector(pVector sparse_vector) return; } -/*** Parse a token from a sparsely allocated vector and write the param_value and - *** number of remaining values to the passed locations. +/*** Parse a token from a sparsely allocated vector and write its value to + *** `token_value`. The number of dimensions consumed in the process is written + *** `dims_consumed`. *** *** @param token The sparse vector token being parsed. - *** @param remaining The location to save the remaining number of characters. - *** @param param_value The location to save the param_value of the token. + *** @param dims_consumed The location to save the dims_consumed number of characters. + *** @param token_value The location to save the token_value of the token. ***/ static void -ca_parse_vector_token(const int token, unsigned int* remaining, unsigned int* param_value) +ca_parse_vector_token(const int token, unsigned int* dims_consumed, unsigned int* token_value) { if (token < 0) { /** This run contains -token zeros. **/ - *remaining = (unsigned)(-token); - *param_value = 0u; + *dims_consumed = (unsigned)(-token); + *token_value = 0u; } else { - /** This run contains one param_value. **/ - *remaining = 1u; - *param_value = (unsigned)(token); + /** This run contains one token_value. **/ + *dims_consumed = 1u; + *token_value = (unsigned)(token); } return; From 118e22b2748deafa2e41685ff7b2e90f65a28d2f Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 13:49:05 -0600 Subject: [PATCH 070/361] Fix an incorrect doc comment. --- centrallix-lib/src/clusters.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index fd32e572c..0ece1ed35 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -479,8 +479,8 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) *** *** @param str1 The first string. *** @param str2 The second string. - *** @param length1 The length of the first string. - *** @param length1 The length of the first string. + *** @param str1_length The length of the first string. + *** @param str2_length The length of the second string. *** @returns The edit distance between the two strings, or a negative value on error. *** *** @attention - `Tip`: Pass 0 for the length of either string to infer it From 19cd50b455bad0fdbcfdbd03924aa286a3309b67 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 14:02:40 -0600 Subject: [PATCH 071/361] Fix vector function bugs. Fix ca_cos_compare() bug that didn't return 0.0 when either vector was NULL. Fix ca_eql() bug that could allow reading off the end of the second vector. Fix confusing loop check in ca_most_similar(). Add missing @returns to ca_kmeans() doc comment. --- centrallix-lib/src/clusters.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 0ece1ed35..d83596288 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -606,6 +606,8 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const double ca_cos_compare(void* v1, void* v2) { + /** Input validation checks. **/ + if (v1 == NULL || v2 == NULL) return 0.0; if (v1 == v2) return 1.0; /** Input validation checks. **/ @@ -672,8 +674,9 @@ ca_eql(pVector v1, pVector v2) /** Edge cases. **/ if (v1 == v2) return true; if (v1 == NULL || v2 == NULL) return false; - + const unsigned int len = ca_sparse_len(v1); + if (len != ca_sparse_len(v2)) return false; for (unsigned int i = 0u; i < len; i++) if (v1[i] != v2[i]) return false; @@ -766,6 +769,7 @@ get_cluster_size( *** @param auto_seed If true, the function will set its own seed. Otherwise, *** it will use rand() without setting a seed. In the latter case, srand() *** should be used by the caller prior to calling this function. + *** @returns 0 if successful, or -1 if an error occurs. *** *** @attention - Assumes: num_vectors is the length of vectors. *** @attention - Assumes: num_clusters is the length of labels. @@ -975,7 +979,7 @@ ca_most_similar( double best_sim = -INFINITY; /** Iterate over all data options to find the one with the highest similarity. **/ - for (unsigned int i = 0u; (num_data == 0u) ? (data[i] != NULL) : (i < num_data); i++) + for (unsigned int i = 0u; (num_data == 0u && data[i] != NULL) || (i < num_data); i++) { const double sim = check_double(similarity(target, data[i])); if (isnan(sim)) continue; /* Skip failed comparison. */ From ebb958571e8a686ccdaa422a85c3f55b5e7dffa8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 18 Mar 2026 14:09:11 -0600 Subject: [PATCH 072/361] Implement improved workaround for mssError() not supporting %c. --- centrallix-lib/src/mtlexer.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/src/mtlexer.c b/centrallix-lib/src/mtlexer.c index 15d67d177..7be3b6a81 100644 --- a/centrallix-lib/src/mtlexer.c +++ b/centrallix-lib/src/mtlexer.c @@ -908,9 +908,8 @@ mlxNextToken(pLxSession this) } else { - char buf[4]; - snprintf(buf, sizeof(buf), "%c", ch); /* mssError() does not support %c. */ - mssError(1, "MLX", "Unexpected character encountered: '%s'", buf); + /** mssError does not support %c, so we need to use this workaround. **/ + mssError(1, "MLX", "Unexpected character encountered: '%s'", (char[]){ch, '\0'}); this->TokType = MLX_TOK_ERROR; break; } From 6544e4e95a6edfc593615c7be9d414a9bab91118 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 09:25:37 -0600 Subject: [PATCH 073/361] Update doc comments. Review doc comments in clusters.c and update them to match the standards for the rest of the project. Rename internal functions in clusters.c to follow the style for internal functions. Clean up random whitespace characters. --- centrallix-lib/include/clusters.h | 7 +- centrallix-lib/src/clusters.c | 225 +++++++++++----------- centrallix/osdrivers/objdrv_cluster.c | 266 +++++++++++++------------- 3 files changed, 252 insertions(+), 246 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 15f307ea9..f76272dbc 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -66,7 +66,10 @@ ***/ #define CA_NUM_DIMS 251 -/** The character used to create a pair with the first and last characters of a string. **/ +/*** The character used to create a pair with the first and last characters + *** of a string. Currently set to 96, the character just before 'a' (97) + *** in the ASCII table. + ***/ #define CA_BOUNDARY_CHAR ((unsigned char)('a' - 1)) /** Types. **/ @@ -88,7 +91,7 @@ typedef struct } Pair, *pPair; - + /** Edit distance function. **/ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index d83596288..4e399b87f 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -50,15 +50,15 @@ /** This file has additional documentation in string_similarity.md. **/ -/*** Gets the hash, representing a pair of ASCII characters, represented by unsigned ints. - *** Thank you to professor John Delano for this hashing algorithm. +/*** Gets the hash for a pair of ASCII characters, represented by unsigned ints. + *** Thank you professor John Delano for this hashing algorithm. *** *** @param c1 The first character in the pair. *** @param c2 The second character in the pair. *** @returns The resulting hash. ***/ static unsigned int -ca_hash_char_pair(const unsigned char c1, const unsigned char c2) +ca_i_hash_char_pair(const unsigned char c1, const unsigned char c2) { const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); @@ -72,7 +72,7 @@ ca_hash_char_pair(const unsigned char c1, const unsigned char c2) *** @param c1 The first character in the character pair. *** @param c2 The second character in the character pair. *** @param hash The hash for the two characters, calculated by calling the - *** ca_hash_char_pair() function (above). + *** ca_i_hash_char_pair() function (above). **/ typedef struct { @@ -81,7 +81,7 @@ typedef struct } CharPair, *pCharPair; -/*** Internal function to compare two character pairs to allow us to sort them +/*** Internal function to compare two character pairs, allowing us to sort them *** by hash (ascending). *** *** @param p1 The first pCharPair. @@ -91,53 +91,55 @@ typedef struct *** 0 if p1 and p2 have identical hashes. ***/ static int -ca_char_pair_cmp(const void *p1, const void *p2) +ca_i_char_pair_cmp(const void *p1, const void *p2) { const CharPair *a = p1, *b = p2; return a->hash - b->hash; } -/*** Builds a vector using a string. +/*** Builds a sparse vector for cosine compare using the provided string. *** *** Vectors are based on the frequencies of character pairs in the string. - *** Space characters and punctuation characters (see code for list) are ignored, - *** and all characters are converted to lowercase. Character 96, which is just - *** before 'a' in the ASCII table (and maps to '`') is used to make pairs on the - *** start and end of strings. The only supported characters for the passed char* - *** are spaces, punctuation, uppercase and lowercase letters, and numbers. + *** Insignificant characters like spaces or punctuation are ignored (see + *** code for more detail) and all characters are converted to lowercase. + *** The boundary character (see `CA_BOUNDARY_CHAR`) is used to make pairs + *** on the start and end of strings. *** - *** This results in the following modified ASCII table: + *** The only supported characters for the passed `char*` are whitespace, + *** punctuation, uppercase and lowercase letters, and numbers. Passing + *** other characters results in undefined behavior. + *** + *** The function uses the following modified ASCII table: *** ```csv *** #, char, #, char, #, char - *** 97, a, 109, m, 121, y - *** 98, b, 110, n, 122, z - *** 99, c, 111, o, 123, 0 - *** 100, d, 112, p, 124, 1 - *** 101, e, 113, q, 125, 2 - *** 102, f, 114, r, 126, 3 - *** 103, g, 115, s, 127, 4 - *** 104, h, 116, t, 128, 5 - *** 105, i, 117, u, 129, 6 - *** 106, j, 118, v, 130, 7 - *** 107, k, 119, w, 131, 8 - *** 108, l, 120, x, 132, 9 + *** 97, 'a', 109, 'm', 121, 'y' + *** 98, 'b', 110, 'n', 122, 'z' + *** 99, 'c', 111, 'o', 123, '0' + *** 100, 'd', 112, 'p', 124, '1' + *** 101, 'e', 113, 'q', 125, '2' + *** 102, 'f', 114, 'r', 126, '3' + *** 103, 'g', 115, 's', 127, '4' + *** 104, 'h', 116, 't', 128, '5' + *** 105, 'i', 117, 'u', 129, '6' + *** 106, 'j', 118, 'v', 130, '7' + *** 107, 'k', 119, 'w', 131, '8' + *** 108, 'l', 120, 'x', 132, '9' *** ``` - *** Thus, any number from 96 (the start/end character) to 132 ('9') is a valid - *** input to get_char_pair_hash(). *** *** After hashing each character pair, we add some number from 1 to 13 to the - *** corresponding dimension. However, for most names, this results in a lot of - *** zeros and a FEW positive numbers. Thus, after creating the dense vector, - *** we convert it to a sparse vector in which a negative number replaces a run - *** of that many zeros. Consider the following example: - *** - *** Dense pVector: `[1,0,0,0,3,0]` + *** corresponding dimension based on the characters to improve performance in + *** cases where collisions occur. However, for most names, this process gives + *** a lot of zeros and a FEW positive numbers. Thus, we represent this vector + *** using a sparse abstraction where an entry of `-n` represents a run of `n` + *** consecutive zeros. Consider the following example: *** - *** Sparse pVector: `[1,-3,3,-1]` + *** Dense Vector: `[1,0,0,0,3,0]` + *** Sparse Representation: `[1,-3,3,-1]` *** *** Using these sparse vectors greatly reduces the required memory and gives - *** approximately an x5 boost to performance when traversing vectors, at the - *** cost of more algorithmically complex code. + *** approximately an x5 boost to performance on a test dataset with short + *** strings that rarely approached 32 characters (using 251 dimensions). The + *** only cost is more algorithmically complex code. *** *** @param str The string to be divided into pairs and hashed to make the vector. *** @returns The sparse vector built using the hashed character pairs. @@ -189,7 +191,7 @@ ca_build_vector(const char* str) /** Hash the character pair into an index (dimension). **/ /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ - char_pairs[i].hash = ca_hash_char_pair(chars[i], chars[i + 1]); + char_pairs[i].hash = ca_i_hash_char_pair(chars[i], chars[i + 1]); } /** Free unused memory. **/ @@ -198,7 +200,7 @@ ca_build_vector(const char* str) /** Sort char_pairs by hash value. **/ - qsort(char_pairs, num_pairs, sizeof(CharPair), ca_char_pair_cmp); + qsort(char_pairs, num_pairs, sizeof(CharPair), ca_i_char_pair_cmp); /** Allocate space for the sparse vector. **/ @@ -269,12 +271,14 @@ ca_free_vector(pVector sparse_vector) } /*** Parse a token from a sparsely allocated vector and write its value to - *** `token_value`. The number of dimensions consumed in the process is written - *** `dims_consumed`. + *** `token_value`. The number of dimensions consumed in the process is + *** written `dims_consumed`. *** - *** @param token The sparse vector token being parsed. - *** @param dims_consumed The location to save the dims_consumed number of characters. - *** @param token_value The location to save the token_value of the token. + *** @param token The sparse vector token to be parsed. + *** @param dims_consumed The location to store the number of dimensions + *** consumed, aka. how many indexes we would need to advance if this + *** were a densely allocated vector. + *** @param token_value The location to save the `token_value`. ***/ static void ca_parse_vector_token(const int token, unsigned int* dims_consumed, unsigned int* token_value) @@ -295,7 +299,7 @@ ca_parse_vector_token(const int token, unsigned int* dims_consumed, unsigned int return; } -/*** Compute the actual number of ints stored in memory to store the given +/*** Compute the actual number of ints stored in memory to hold the given *** sparsely allocated vector. *** *** @param vector The vector. @@ -306,6 +310,12 @@ ca_sparse_len(const pVector vector) { unsigned int i = 0u; + /*** TODO: Israel - Add code here to use nmSysGetSize(), if it is + *** available, once the newmalloc branch is merged. This would + *** allow us to know the size immediately without needing to + *** traverse the array. + ***/ + for (unsigned int dim = 0u; dim < CA_NUM_DIMS; i++) { /** Parse the vector token. **/ @@ -322,7 +332,7 @@ ca_sparse_len(const pVector vector) /*** Print the underlying implementation-level values of a sparsely allocated *** vector (for debugging). *** - *** @param vector The vector. + *** @param vector The vector to print. ***/ void ca_print_vector(const pVector vector) @@ -378,13 +388,14 @@ ca_magnitude_dense(const pCentroid centroid) { double magnitude = 0.0; - for (int i = 0; i < CA_NUM_DIMS; i++) + for (unsigned int i = 0u; i < CA_NUM_DIMS; i++) magnitude += centroid[i] * centroid[i]; return sqrt(magnitude); } -/*** Calculate the similarity on sparsely allocated vectors. +/*** Calculate the similarity between two sparsely allocated vectors by taking + *** their dot product. *** *** @param v1 Sparse vector #1. *** @param v2 Sparse vector #2. @@ -418,12 +429,13 @@ ca_sparse_similarity(const pVector v1, const pVector v2) /** Optimization: Skip computing magnitudes for completely different vectors. **/ if (dot_product == 0u) return 0.0; - + /** Return the difference score. **/ return (double)dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_sparse(v2)); } -/*** Calculate the difference on sparsely allocated vectors. +/*** Calculate the difference between two sparsely allocated vectors by + *** subtracting their dot product from 1.0. *** *** @param v1 Sparse vector #1. *** @param v2 Sparse vector #2. @@ -434,7 +446,7 @@ ca_sparse_similarity(const pVector v1, const pVector v2) #define ca_sparse_dif(v1, v2) (1.0 - ca_sparse_similarity(v1, v2)) /*** Calculate the similarity between a sparsely allocated vector and a densely - *** allocated centroid using a dot product. + *** allocated centroid by taking their dot product. *** *** @param v1 Sparse vector #1. *** @param c1 Dense centroid #2. @@ -465,7 +477,7 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) } /*** Calculate the difference between a sparsely allocated vector and a densely - *** allocated centroid. + *** allocated centroid by subtracting their dot product from 1.0. *** *** @param v1 Sparse vector #1. *** @param c1 Dense centroid #2. @@ -484,11 +496,8 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) *** @returns The edit distance between the two strings, or a negative value on error. *** *** @attention - `Tip`: Pass 0 for the length of either string to infer it - *** using the null terminating character. Conversely, character arrays - *** with no null terminator are allowed if an explicit length is specified. - *** - *** @attention - `Complexity`: O(nm), where n and m are the lengths of str1 - *** and str2 (respectively). + *** using the null terminating character. Conversely, character arrays + *** with no null terminator are allowed if an explicit length is passed. ***/ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) @@ -592,12 +601,16 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const } /*** Compares two strings using their cosine similarity, returning a value - *** between `0.0` (completely different) and `1.0` (identical). If either + *** between `0.0` (completely different) and `1.0` (identical). If either *** OR BOTH strings are NULL, this function returns `0.0`. *** + *** @attention - Note: Punctuation, whitespace, etc. are ignored due to how + *** cosine vector hashing is implemented. + *** *** @attention - This function takes `void*` instead of `pVector` so that it *** can be used as the similarity function in the ca_search() function *** family without needing a messy typecast to avoid the compiler warning. + *** However, behavior is undefined if `v1` and `v2` do are not `pVector`s. *** *** @param v1 A `pVector` to the first string to compare. *** @param v2 A `pVector` to the second string to compare. @@ -624,16 +637,23 @@ ca_cos_compare(void* v1, void* v2) /*** Compares two strings using their Levenshtein edit distance to compute a *** similarity between `0.0` (completely different) and `1.0` (identical). - *** If both strings are empty, this function returns `1.0` (identical). If + *** If both strings are empty, this function returns `1.0` (identical). If *** either OR BOTH strings are NULL, this function returns `0.0`. *** + *** @attention - Note: Unlike `ca_cos_compare()`, punctuation, whitespace, + *** etc. are NOT ignored. In fact, this functions supports strings that + *** contain ANY valid ASCII characters other than the NULL-terminator, + *** which is used to terminate the string. + *** *** @attention - This function takes `void*` instead of `char*` so that it *** can be used as the similarity function in the ca_search() function *** family without needing a messy typecast to avoid the compiler warning. + *** However, behavior is undefined if `v1` and `v2` do are not `char*`s. *** *** @param str1 A `char*` to the first string to compare. *** @param str2 A `char*` to the second string to compare. - *** @returns The levenshtein similarity between the two strings, or NAN on failure. + *** @returns The Levenshtein similarity between the two strings, + *** or NAN on failure. ***/ double ca_lev_compare(void* str1, void* str2) @@ -660,8 +680,9 @@ ca_lev_compare(void* str1, void* str2) return round(normalized_similarity * 1000000.0) / 1000000.0; } -/*** Check if two sparse vectors are identical. True if both vectors are - *** `NULL`, but false if one is and the other is not. +/*** Check if two sparse vectors are identical, typically used for debugging + *** or testing. True if both vectors are `NULL`, but false if one is and the + *** other is not. *** *** @param v1 The first vector. *** @param v2 The second vector. @@ -750,46 +771,37 @@ get_cluster_size( return result; } -/*** Executes the k-means clustering algorithm. Selects NUM_CLUSTERS random - *** vectors as initial centroids. Then points are assigned to the nearest - *** centroid, after which centroids are moved to the center of their points. +/*** Executes the k-means clustering algorithm. Selects `num_clusters` random + *** vectors as initial centroids, using `rand()` (to set a seed, call srand() + *** pass false for `auto_seed`). Each iteration, points are assigned to the + *** nearest centroid using cosine similarity on the provided sparse vectors. + *** After this, centroids are moved to the center of their points. This + *** process repeats until the `min_improvement` threshold is not met, or + *** `max_iter` is reached (whichever happens first). *** - *** @param vectors The vectors to cluster. - *** @param num_vectors The number of vectors to cluster. - *** @param num_clusters The number of clusters to generate. - *** @param max_iter The max number of iterations. - *** @param min_improvement The minimum amount of improvement that must be met - *** each clustering iteration. If there is less improvement, the algorithm - *** will stop. Pass any value less than -1 to fully disable this feature. - *** @param labels Stores the final cluster identities of the vectors after - *** clustering is completed. Each value will be `0 <= n < num_clusters`. + *** @attention - `num_vectors` must be the length of `vectors`. + *** @attention - `num_clusters` must be the length of `labels`. + *** + *** @param vectors The sparse cosine similarity vectors representing the data + *** to cluster. + *** @param num_vectors The number of vectors (in `vectors`) to cluster. + *** @param num_clusters The number of clusters to generate (also called k). + *** @param max_iter A hard cutoff for the max number of iterations, applied + *** even if the `min_improvement` threshold is still met on each iteration. + *** @param min_improvement The minimum improvement threshold that must be met + *** each clustering iteration. If this is not met, the iterations stop. + *** Pass -1 to disable this and iterate for as long as the centroids keep + *** changing (or until `max_iter` is reached). + *** @param labels Initialized by this function to stores the final cluster + *** identities of the vectors after clustering is completed. Each value + *** will be `0 <= n < num_clusters`. This buffer can be uninitialized. *** @param vector_sims An array of num_vectors elements, allocated by the *** caller, where index i stores the similarity of vector i to its assigned *** cluster. Passing NULL skips evaluation of these values. - *** @param auto_seed If true, the function will set its own seed. Otherwise, - *** it will use rand() without setting a seed. In the latter case, srand() - *** should be used by the caller prior to calling this function. + *** @param auto_seed If true, the function will set its own seed. Otherwise, + *** it will use rand() without setting a seed, so the caller should use + *** srand() to set a seed before calling. *** @returns 0 if successful, or -1 if an error occurs. - *** - *** @attention - Assumes: num_vectors is the length of vectors. - *** @attention - Assumes: num_clusters is the length of labels. - *** - *** @attention - Issue: At larger numbers of clustering iterations, some - *** clusters have a size of negative infinity. In this implementation, - *** the bug is mitigated by setting a small number of max iterations, - *** such as 16 instead of 100. - *** @attention - Issue: Clusters do not appear to improve much after the first - *** iteration, which puts the efficacy of the algorithm into question. This - *** may be due to the uneven density of a typical dataset. However, the - *** clusters still offer useful information. - *** - *** Complexity: - *** - *** - `O(kd + k + i*(k + n*(k+d) + kd))` - *** - *** - `O(kd + k + ik + ink + ind + ikd)` - *** - *** - `O(nk + nd)` ***/ int ca_kmeans( @@ -910,7 +922,7 @@ ca_kmeans( /** Is there enough improvement? **/ if (min_improvement < -1) continue; /** Skip check if it will never end the loop. **/ - const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); + const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); if (isnan(average_cluster_size)) goto end; const double improvement = old_average_cluster_size - average_cluster_size; if (improvement < min_improvement) break; @@ -956,13 +968,12 @@ ca_kmeans( *** it if the similarity meets the threshold. *** *** @param target The target data to compare to the rest of the data. - *** @param data The rest of the data, compared against the target to - *** find the data that is the most similar. + *** @param data The rest of the data, compared against the target. *** @param num_data The number of elements in data. Specify 0 to detect *** length on a null terminated array of data. - *** @param similarity A function which takes two data items of the type - *** of the data param and returns their similarity. - *** @param threshold The minimum similarity threshold. If the most similar + *** @param similarity A function that takes two data items (aka. pointers + *** in the array pointed to by `data`) and returns their similarity. + *** @param threshold The minimum similarity threshold. If the most similar *** data does not meet this threshold, the function returns NULL. *** @returns A pointer to the most similar piece of data found in the data *** array, or NULL if the most similar data did not meet the threshold. @@ -995,7 +1006,7 @@ ca_most_similar( /*** Runs a sliding search over the provided data, comparing each element to - *** the next `window_size` elements. If any comparison yields a similarity + *** the next `window_size` elements. If any comparison yields a similarity *** greater than the threshold, it is stored in the returned xArray. *** *** @param data The data to be searched. @@ -1006,7 +1017,7 @@ ca_most_similar( *** @param threshold The minimum threshold required for each pair. *** @param maybe_pairs A pointer to an xArray to which pairs should be added. *** Pass NULL to allocate a new one. - *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this + *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this *** will be that xArray, to allow for chaining. ***/ pXArray @@ -1023,7 +1034,7 @@ ca_sliding_search( /** Allocate space for pairs (if it is not provided). **/ if (pairs == NULL) { - /** Guess that we will need space for num_data * 2 pairs. **/ + /** Guess that we will need space for two pairs per data point. **/ const int guess_size = num_data * 2; pairs = check_ptr(xaNew(guess_size)); if (pairs == NULL) goto err; @@ -1069,7 +1080,7 @@ ca_sliding_search( } /*** Runs a complete search over the provided data, comparing each element to - *** each other element. If any comparison yields a similarity greater than the + *** each other element. If any comparison yields a similarity greater than the *** threshold, it is stored in the returned xArray. *** *** @param data The data to be searched. @@ -1079,7 +1090,7 @@ ca_sliding_search( *** @param threshold The minimum threshold required for each pair. *** @param maybe_pairs A pointer to an xArray to which pairs should be added. *** Pass NULL to allocate a new one. - *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this + *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this *** will be that xArray, to allow for chaining. ***/ pXArray diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 58ca80ffb..28b89de20 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -70,16 +70,15 @@ *** specifiers to mssError() without realizing that it didn't support them. *** Eventually, I got fed up enough having to write errors to a sting buffer *** and passing that buffer to mssError(), so I wrote this wrapper that does - *** it for me. Adding this behavior to mssError() would be better, though. + *** it for me. It'd be better to add this to mssError(), though. ***/ -/*** Displays error text to the user. Does not print a stack trace. Does not - *** exit the program, allowing for the calling function to fail, generating - *** an error cascade which may be useful to the user since a stack trace is - *** not readily available. +/*** Displays error text to the user (but no stack trace). Does not exit the + *** program, allowing the calling function to fail, generating a cascade of + *** error messages which may provide useful info. *** - *** @param clr Whether to clear the current error stack. As a rule of thumb, + *** @param clr Whether to clear the current error stack. As a rule of thumb, *** if you are the first one to detect the error, clear the stack so that - *** other unrelated messages are not shown. If you are detecting an error + *** other unrelated messages are not shown. If you are detecting an error *** from another function that may also call an mssError() function, do *** not clear the stack. *** @param module The name or abbreviation of the module in which this @@ -133,8 +132,8 @@ mssErrorf(int clr, char* module, const char* format, ...) *** @param cleanup 0: No clean up. *** 1: DeInit arr. *** 2: Free arr. - *** *: Any other value prints a warning and does nothing. - *** @returns The new array, or null if and only if the passed pXArray has 0 items. + *** *: Any other value prints a warning and does no clean up. + *** @returns The new array, or NULL if and only if `arr` is empty. ***/ static void** ci_xaToTrimmedArray(pXArray arr, int array_handling) @@ -178,7 +177,7 @@ ci_xaToTrimmedArray(pXArray arr, int array_handling) /** ================ Enum Declarations ================ **/ /** ANCHOR[id=enums] **/ -/** Enum representing a clustering algorithm. **/ +/** Enum type representing a clustering algorithm. **/ typedef unsigned char ClusterAlgorithm; #define ALGORITHM_NULL (ClusterAlgorithm)0u #define ALGORITHM_NONE (ClusterAlgorithm)1u @@ -219,7 +218,7 @@ ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) return NULL; /** Unreachable. **/ } -/** Enum representing a similarity measurement algorithm. **/ +/** Enum type representing a similarity measurement algorithm. **/ typedef unsigned char SimilarityMeasure; #define SIMILARITY_NULL (SimilarityMeasure)0u #define SIMILARITY_COSINE (SimilarityMeasure)1u @@ -233,7 +232,11 @@ SimilarityMeasure ALL_SIMILARITY_MEASURES[nSimilarityMeasures] = SIMILARITY_LEVENSHTEIN, }; -/** Converts a similarity measure to its string name. **/ +/*** Converts a similarity measure to its string name. + *** + *** @param similarity_measure The similarity measure to convert. + *** @returns The corresponding name. + ***/ char* ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) { @@ -248,16 +251,17 @@ ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) return NULL; /** Unreachable. **/ } -/*** Converts a similarity measure to the function pointer to a comparison - *** function. This function can be used to compute similarity between two - *** values, or it can be passed to `clusters.c` which uses it for similar - *** operations. +/*** Converts a similarity measure to a function pointer that points to the + *** corresponding comparison function. This function can be called directly + *** or passed to functions from `clusters.c`. *** *** @param similarity_measure The similarity measure to be converted. - *** @returns A similarity computation function. This function takes two void - *** pointers, representing the data to be compared, and a double that - *** represents how similar the data is. Or returns NULL if an error occurs, - *** calling mssError() to provide an error message. + *** @returns A similarity computation function. This function takes two void + *** pointers, representing the data to be compared, and returns a double + *** representing how similar the data is from 0.0 (no similarity) to 1.0 + *** (identical), or NAN if an error occurs. This function may return NULL + *** if an error occurs, in which case it always calls mssError() to give + *** an error message. ***/ double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(void*, void*) { @@ -267,19 +271,18 @@ double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(v case SIMILARITY_LEVENSHTEIN: return ca_lev_compare; default: mssErrorf(1, "Cluster", - "Unknown similarity measure \"%s\".", - ci_SimilarityMeasureToString(similarity_measure) + "Unknown similarity measure \"%s\" (%d).", + ci_SimilarityMeasureToString(similarity_measure), similarity_measure ); return NULL; } } -/*** Enum representing the type of data targeted by the driver, - *** set based on the path given when the driver is used to open - *** a cluster file. +/*** Enum representing the type of data targeted by the driver, set based on + *** the path given when the driver is used to open a cluster file. *** - *** `0u` is reserved for a possible `NULL` value in the future. - *** However, there is currently no allowed `NULL` TargetType. + *** `0u` is reserved for a possible NULL value in the future. However, NULL + *** values for TargetType are not currently allowed. ***/ typedef unsigned char TargetType; #define TARGET_NODE (TargetType)1u @@ -352,26 +355,24 @@ char* METHOD_NAMES[] = *** *** @skip --> Attribute Data. *** @param Name The source name, specified in the .cluster file. - *** @param Key The key associated with this object in the SourceDataCache. + *** @param Key The key associated with this object in the SourceDataCache. *** @param SourcePath The path to the data source from which to retrieve data. *** @param KeyAttr The name of the attribute to use when getting keys from *** the SourcePath. *** @param DataAttr The name of the attribute to use when getting data from *** the SourcePath. *** - *** @skip --> Computed data. + *** @skip --> Fetched/Computed Data. *** @param Strings The keys for each data string strings received from the *** database, allowing them to be lined up again when queried. *** @param Strings The data strings to be clustered and searched, or NULL if *** they have not been fetched from the source. *** @param Vectors The cosine comparison vectors from the fetched data, or - *** NULL if they haven't been computed. Note that vectors are no longer - *** needed once all clusters and searches have been computed, so they are - *** automatically freed in that case to save memory. + *** NULL if they haven't been computed. *** @param nVectors The number of vectors and data strings. *** *** @skip --> Time. - *** @param DateCreated The date and time that this object was created and initialized. + *** @param DateCreated The date and time that this object was created/initialized. *** @param DateComputed The date and time that the computed attributes were computed. *** *** @param Magic A magic value for detecting corrupted memory. @@ -427,19 +428,17 @@ typedef struct *** @param SimilarityMeasure The similarity measure used to compare items. *** @param nClusters The number of clusters. 1 if algorithm = none. *** @param MinImprovement The minimum amount of improvement that must be met - *** each clustering iteration. If there is less improvement, the algorithm - *** will stop. The "max" in a .cluster file is represented by -inf. - *** @param MaxIterations The maximum number of iterations that a clustering - *** algorithm can run for. Note: Sliding window uses this attribute to store - *** the window_size. + *** each clustering iteration. -inf represents "max" in the .cluster file. + *** @param MaxIterations The maximum number of iterations to run clustering. + *** Note: Sliding window uses this attribute to store the window_size. *** - *** @skip --> Relationship Data. + *** @skip --> Relational Data. *** @param nSubClusters The number of subclusters of this cluster. *** @param SubClusters A pClusterData array, NULL if nSubClusters == 0. *** @param Parent This cluster's parent. NULL if it is not a subcluster. *** @param SourceData Pointer to the source data that this cluster uses. *** - *** @skip --> Computed data. + *** @skip --> Computed Data. *** @param Clusters An array of length num_clusters, NULL if the clusters *** have not yet been computed. *** @param Sims An array of num_vectors elements, where index i stores the @@ -447,7 +446,7 @@ typedef struct *** if the clusters have not yet been computed. *** *** @skip --> Time. - *** @param DateCreated The date and time that this object was created and initialized. + *** @param DateCreated The date and time that this object was created/initialized. *** @param DateComputed The date and time that the computed attributes were computed. *** *** @param Magic A magic value for detecting corrupted memory. @@ -477,7 +476,7 @@ typedef struct _CD /*** Data for each search. - *** + *** *** Memory Stats: *** - Padding: 7 bytes *** - Total size: 72 bytes @@ -491,13 +490,13 @@ typedef struct _CD *** included in the results of the search. *** *** @skip --> Computed data. - *** @param Pairs An array holding the pair found by the search, or NULL if the - *** search has not been computed. The indexes stored in these pairs are - *** indexes into the SourceData data arrays. + *** @param Pairs An array holding the pairs found by the search, or NULL if + *** the search has not been computed. The indexes stored in these pairs + *** are indexes into the SourceData data arrays. *** @param nPairs The number of pairs found. *** *** @skip --> Time. - *** @param DateCreated The date and time that this object was created and initialized. + *** @param DateCreated The date and time that this object was created/initialized. *** @param DateComputed The date and time that the computed attributes were computed. *** *** @param Magic A magic value for detecting corrupted memory. @@ -526,10 +525,10 @@ typedef struct _SEARCH *** - Padding: 4 bytes *** - Total size: 72 bytes *** - *** @note When a .cluster file is opened, there will be only one node for that - *** file. However, in the course of the query, many driver instance structs - *** may be created by functions like clusterQueryFetch(), and closed by the - *** object system using clusterClose(). + *** @note When a .cluster file is opened, there will be only one node for + *** that file. However, in the course of the query, many driver instance + *** structs using this one node may be created thanks to functions such as + ***`clusterQueryFetch()`, and closed by with functions like `clusterClose()`. *** *** @param SourceData Data from the provided source. *** @param Params A pParam array storing the params in the .cluster file. @@ -569,20 +568,21 @@ typedef struct _NODE *** - Total size: 32 bytes *** *** This struct can be thought of like a "pointer" to specific data accessible - *** through the stored pNodeData struct. This struct also communicates whether + *** through the stored pNodeData struct. This struct also communicates whether *** that data is guaranteed to have been computed. *** *** For example, if target type is the root, a cluster, or a search, no data - *** is guaranteed to be computed. These three types can be returned from + *** is guaranteed to be computed. These three types can be returned from *** clusterOpen(), based on the provided path. *** *** Alternatively, a cluster entry or search entry can be targeted by calling *** fetch on a query pointing to a driver instance that targets a cluster or - *** search (respectively). These two entry target types ensure that the data - *** they indicate has been computed, so the GetAttrType() and GetAttrValue() - *** functions do not need to check this repeatedly each time they are called. + *** search (respectively). These two entry target types ensure that the data + *** they indicate has been computed. This makes the `clusterGetAttrType()` + *** and `clusterGetAttrValue()` functions faster and simpler because they do + *** not need to check that the data is computed every time they are called. *** - *** @param NodeData The associated node data struct. There can be many driver + *** @param NodeData The associated node data struct. While many driver struct *** instances pointing to one NodeData at a time, but each driver instance *** always points to singular NodeData struct. *** @param TargetType The type of data targeted (see above). @@ -747,7 +747,7 @@ static void ci_GiveHint(const char* hint) /*** Given the user a hint when they specify an invalid string for an attribute - *** where we know the list of valid strings. The hint is only displayed if + *** where we know the list of valid strings. The hint is only displayed if *** their string is close enough to a valid string. *** *** @param value The value the user gave. @@ -813,16 +813,17 @@ ci_UnknownAttribute(char* attr_name, const int target_type) // LINK #functions -/*** Returns 0 for success and -1 on failure. Promises that mssError() will be - *** invoked on failure, so the caller need not specify their own error message. - *** Returns 1 if attribute is available, printing an error if the attribute was - *** marked as required. +/*** @returns 0 if a value is found, + *** -1 if an error occurs, or + *** 1 if attribute is null, calling mssError() for required attributes. *** *** @attention - Promises that a failure invokes mssError() at least once. *** - *** TODO: Greg - Review carefully. I think this code is the reason that runserver() - *** is NOT REQUIRED for dynamic attributes in the cluster driver. I had to debug - *** and rewrite this for ages and it uses several functions I don't 100% understand. + *** TODO: Greg - Review carefully. I think this code is the reason that + *** runserver() is sometimes not required for dynamic attributes in the + *** cluster driver, which I'm pretty sure is incorrect. + *** TODO: Israel - After Greg's review, update this doc comment to properly + *** describe the function parameters. ***/ static int ci_ParseAttribute( @@ -897,8 +898,7 @@ ci_ParseAttribute( // LINK #functions -/*** Parses a ClusteringAlgorithm from the algorithm attribute in the pStructInf - *** representing some structure with that attribute in a parsed structure file. +/*** Parses a ClusteringAlgorithm from the algorithm attribute in the pStructInf. *** *** @attention - Promises that a failure invokes mssError() at least once. *** @@ -946,8 +946,7 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) // LINK #functions /*** Parses a SimilarityMeasure from the similarity_measure attribute in the given - *** pStructInf parameter, which represents some structure with that attribute - *** in a parsed structure file. + *** pStructInf parameter. *** *** @attention - Promises that a failure invokes mssError() at least once. *** @@ -2208,11 +2207,10 @@ ci_ClearCaches(void) // LINK #functions /*** Returns the deep size of a SourceData struct, including the size of all - *** allocated substructures. As far as I can tell, this is probably only - *** useful for cache management and debugging. + *** allocated substructures. *** - *** Note that Key is ignored because it is a pointer to data managed by the - *** caching systems, so it is not technically part of the struct. + *** Note: The Key field points to data managed by the caching systems, so it + *** is ignored. *** *** @param source_data The source data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. @@ -2259,11 +2257,10 @@ ci_SizeOfSourceData(pSourceData source_data) // LINK #functions /*** Returns the deep size of a ClusterData struct, including the size of all - *** allocated substructures. As far as I can tell, this is probably only - *** useful for cache management and debugging. + *** allocated substructures. *** - *** Note that Key is ignored because it is a pointer to data managed by the - *** caching systems, so it is not technically part of the struct. + *** Note: The Key field points to data managed by the caching systems, so it + *** is ignored. *** *** @param cluster_data The cluster data struct to be queried. *** @param recursive Whether to recursively free subclusters. @@ -2306,11 +2303,10 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) // LINK #functions /*** Returns the deep size of a SearchData struct, including the size of all - *** allocated substructures. As far as I can tell, this is probably only - *** useful for cache management and debugging. + *** allocated substructures. *** - *** Note that Key is ignored because it is a pointer to data managed by the - *** caching systems, so it is not technically part of the struct. + *** Note: The Key field points to data managed by the caching systems, so it + *** is ignored. *** *** @param search_data The search data struct to be queried. *** @returns The size in bytes of the struct and all internal allocated data. @@ -2339,13 +2335,12 @@ ci_SizeOfSearchData(pSearchData search_data) /** ANCHOR[id=computation] **/ // LINK #functions -/*** Ensures that the `source_data->Data` has been fetched from the data source - *** and that `source_data->nVectors` has been computed from the fetched data. +/*** Ensures that the fetched/computed attributes for `source_data` have been + *** computed, fetching from the data source and computing vectors if needed. *** - *** @attention - Promises that mssError() will be invoked on failure, so the - *** caller is not required to specify their own error message. + *** @attention - Promises that mssError() will be invoked on failure. *** - *** @param source_data The pSourceData affected by the computation. + *** @param source_data The pSourceData who's attributes should be computed. *** @param session The current session, used to open the data source. *** @returns 0 if successful, or *** -1 other value on failure. @@ -2586,13 +2581,12 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) // LINK #functions -/*** Ensures that the cluster_data->Labels has been computed, running the - *** specified clustering algorithm if necessary. +/*** Ensures that the computed attributes for `cluster_data` have been + *** computed, running the specified clustering algorithm if necessary. *** - *** @attention - Promises that mssError() will be invoked on failure, so the - *** caller is not required to specify their own error message. + *** @attention - Promises that mssError() will be invoked on failure. *** - *** @param cluster_data The pClusterData affected by the computation. + *** @param cluster_data The pClusterData who's attributes should be computed. *** @param node_data The current pNodeData, used to get vectors to cluster. *** @returns 0 if successful, or *** -1 other value on failure. @@ -2798,13 +2792,12 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) // LINK #functions -/*** Ensures that the search_data->pairs has been computed, running the a - *** search with the specified similarity measure if necessary. +/*** Ensures that the computed attributes for `search_data` are computed, + *** running the a search with the specified similarity measure if necessary. *** - *** @attention - Promises that mssError() will be invoked on failure, so the - *** caller is not required to specify their own error message. + *** @attention - Promises that mssError() will be invoked on failure. *** - *** @param cluster_data The pClusterData affected by the computation. + *** @param cluster_data The pSearchData who's attributes should be computed. *** @param node_data The current pNodeData, used to get vectors to cluster. *** @returns 0 if successful, or *** -1 other value on failure. @@ -2998,7 +2991,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** ANCHOR[id=params] **/ // LINK #functions -/*** Get the type of a parameter. Intended for expSetParamFunctions(). +/*** Get the type of a parameter. Intended for use in `expSetParamFunctions()`. *** *** @param inf_v Node data containing the list of parameters. *** @param attr_name The name of the requested parameter. @@ -3028,12 +3021,11 @@ ci_GetParamType(void* inf_v, const char* attr_name) // LINK #functions -/*** Get the value of a parameter. Intended for `expSetParamFunctions()`. +/*** Get the value of a parameter. Intended for use in `expSetParamFunctions()`. *** *** @attention - Warning: If the retrieved value is `NULL`, the pObjectData - *** val is not updated, and the function returns 1, indicating `NULL`. - *** This is intended behavior, for consistency with other Centrallix - *** functions, so keep it in mind so you're not surprised. + *** val is not updated, and the function returns 1, indicating `NULL`, + *** similar to other Centrallix functions. *** *** @param inf_v Node data containing the list of parameters. *** @param attr_name The name of the requested parameter. @@ -3111,7 +3103,7 @@ ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) *** @param usr_type The object system file type being opened. Should always *** be "system/cluster" because this driver is only registered for that *** type of file. - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). *** *** @returns A pDriverData struct representing a driver instance, or *** NULL if an error occurs. @@ -3293,11 +3285,11 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb // LINK #functions /*** Close a cluster driver instance object, releasing any necessary memory - *** and closing any necessary underlying resources. However, most of that + *** and closing any necessary underlying resources. However, most of that *** data will be cached and won't be freed unless the cache is dropped. *** *** @param inf_v The affected driver instance. - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns 0, success. ***/ int @@ -3327,13 +3319,13 @@ clusterClose(void* inf_v, pObjTrxTree* oxt) // LINK #functions /*** Opens a new query pointing to the first row of the data targeted by - *** the driver instance struct. The query has an internal index counter + *** the driver instance struct. The query has an internal index counter *** that starts at the first row and increments as data is fetched. *** *** @param inf_v The driver instance to be queried. *** @param query The query to use on this struct. This is assumed to be *** handled elsewhere, so we don't read it here (unused). - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns The cluster query, or *** NULL if an error occurs. ***/ @@ -3372,7 +3364,7 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) err_free: /** Error cleanup. **/ if (query_data != NULL) nmFree(query_data, sizeof(ClusterQuery)); - mssErrorf(0, "Cluster", "Failed to open query."); + mssErrorf(0, "Cluster", "Failed to open query."); err: return NULL; @@ -3380,7 +3372,7 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) // LINK #functions -/*** Get the next entry as an open driver instance object. +/*** Get the next entry of a query as an open driver instance object. *** *** @param qy_v A query instance, storing an internal index which is *** incremented once that data has been fetched. @@ -3388,8 +3380,8 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) *** @param mode Unused. *** @param oxt Unused. *** @returns pDriverData that is either a cluster entry or search entry, - *** pointing to a specific target index into the relevant data. - *** OR NULL, indicating that all data has been fetched. + *** pointing to a specific target index into the relevant data, + *** OR NULL if all data has been fetched or an error occurs. ***/ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) @@ -3526,11 +3518,11 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) // LINK #functions /*** Close a cluster query instance, releasing any necessary memory and - *** closing any necessary underlying resources. This does not close the + *** closing any necessary underlying resources. This does not close the *** underlying driver instance, which must be closed with clusterClose(). *** *** @param qy_v The affected query instance. - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns 0, success. ***/ int @@ -3555,7 +3547,7 @@ clusterQueryClose(void* qy_v, pObjTrxTree* oxt) *** *** @param inf_v The driver instance. *** @param attr_name The name of the requested attribute. - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns The datatype, see datatypes.h for a list of valid datatypes, or *** -1 if an error occurs. *** @@ -3609,8 +3601,8 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { case TARGET_NODE: if (strcmp(attr_name, "source") == 0 - || strcmp(attr_name, "data_attr") == 0 - || strcmp(attr_name, "key_attr") == 0) + || strcmp(attr_name, "key_attr") == 0 + || strcmp(attr_name, "data_attr") == 0) return DATA_T_STRING; break; @@ -3665,14 +3657,14 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) *** @param inf_v The driver instance to be read. *** @param attr_name The name of the requested attribute. *** @param datatype The expected datatype of the attribute value. - *** See datatypes.h for a list of valid datatypes. - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** See `datatypes.h` for a list of valid datatypes. *** @param val A pointer to a location where a pointer to the requested - *** data should be stored. Typically, the caller creates a local variable + *** data should be stored. Typically, the caller creates a local variable *** to store this pointer, then passes a pointer to that local variable *** so that they will have a pointer to the data. *** This buffer will not be modified unless the data is successfully - *** found. If a value other than 0 is returned, the buffer is not updated. + *** found. If a value other than 0 is returned, the buffer is not updated. + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns 0 if successful, *** -1 if an error occurs. *** @@ -3909,7 +3901,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO val->String = source_data->KeyAttr; return 0; } - if (strcmp(attr_name, "name_attr") == 0) + if (strcmp(attr_name, "data_attr") == 0) { val->String = source_data->DataAttr; return 0; @@ -4077,7 +4069,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO *** *** @param inf_v The driver instance to be read. *** @param attr_name The name of the requested attribute. - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns A presentation hints object, if successful, *** NULL if an error occurs. ***/ @@ -4386,12 +4378,12 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) // LINK #functions /*** Returns the name of the first attribute that one can get from - *** this driver instance (using GetAttrType() and GetAttrValue()). - *** Resets the internal variable (TargetAttrIndex) used to maintain - *** iteration state for clusterGetNextAttr(). + *** this driver instance (using `GetAttrType()` and `GetAttrValue()`). + *** Resets the internal variable (`TargetAttrIndex`) used to maintain + *** iteration state for `clusterGetNextAttr()`. *** *** @param inf_v The driver instance to be read. - *** @param oxt Unused. + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns The name of the first attribute. ***/ char* @@ -4413,7 +4405,7 @@ clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) *** the state of this iteration over repeated calls. *** *** @param inf_v The driver instance to be read. - *** @param oxt Unused. + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns The name of the next attribute. ***/ char* @@ -4528,12 +4520,12 @@ clusterInfo(void* inf_v, pObjectInfo info) // LINK #functions /*** Returns the name of the first method that one can execute from - *** this driver instance (using clusterExecuteMethod()). Resets the - *** internal variable (TargetMethodIndex) used to maintain iteration - *** state for clusterGetNextMethod(). + *** this driver instance (using `clusterExecuteMethod()`). Resets the + *** internal variable (`TargetMethodIndex`) used to maintain iteration + *** state for `clusterGetNextMethod()`. *** *** @param inf_v The driver instance to be read. - *** @param oxt Unused. + *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns The name of the first method. ***/ char* @@ -4550,8 +4542,8 @@ clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions /*** Returns the name of the next method that one can get from - *** this driver instance (using GetAttrType() and GetAttrValue()). - *** Uses an internal variable (TargetMethodIndex) used to maintain + *** this driver instance (using `GetAttrType()` and `GetAttrValue()`). + *** Uses an internal variable (`TargetMethodIndex`) used to maintain *** the state of this iteration over repeated calls. *** *** @param inf_v The driver instance to be read. @@ -4569,7 +4561,7 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions -/** Intended for use in xhForEach(). **/ +/** Intended for use in `xhForEach()`. **/ static int ci_PrintEntry(pXHashEntry entry, void* arg) { @@ -4660,7 +4652,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) // LINK #functions -/** Intended for use in xhClearKeySafe(). **/ +/** Intended for use in `xhClearKeySafe()`. **/ static void ci_CacheFreeSourceData(pXHashEntry entry, void* path) { @@ -4680,7 +4672,7 @@ ci_CacheFreeSourceData(pXHashEntry entry, void* path) // LINK #functions -/** Intended for use in xhClearKeySafe(). **/ +/** Intended for use in `xhClearKeySafe()`. **/ static void ci_CacheFreeCluster(pXHashEntry entry, void* path) { @@ -4700,7 +4692,7 @@ ci_CacheFreeCluster(pXHashEntry entry, void* path) // LINK #functions -/** Intended for use in xhClearKeySafe(). **/ +/** Intended for use in `xhClearKeySafe()`. **/ static void ci_CacheFreeSearch(pXHashEntry entry, void* path) { @@ -4725,7 +4717,7 @@ ci_CacheFreeSearch(pXHashEntry entry, void* path) *** @param inf_v The affected driver instance. *** @param method_name The name of the method. *** @param param A possibly optional param passed to the method. - *** @param oxt The object system tree, similar to a kind of "scope" (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). ***/ int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) @@ -4956,7 +4948,7 @@ clusterCommit(void* inf_v, pObjTrxTree* oxt) // LINK #functions -/*** Initialize the driver. This includes: +/*** Initialize the driver, including: *** - Registering the driver with the object system. *** - Registering structs with newmalloc for debugging. *** - Initializing global data needed for the driver. From 9de3d40272d1805db956b7e1a39d769ca94fd3c0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 09:26:22 -0600 Subject: [PATCH 074/361] Update attribute lists. Fix style on an attribute list that caused a line to be way too long. --- centrallix/osdrivers/objdrv_cluster.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 28b89de20..67024fe82 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -296,9 +296,8 @@ typedef unsigned char TargetType; char* ATTR_ROOT[] = { "source", - "attr_name", - "date_created", - "date_computed", + "key_attr", + "data_attr", END_OF_ARRAY, }; char* ATTR_CLUSTER[] = @@ -318,13 +317,13 @@ char* ATTR_SEARCH[] = "source", "threshold", "similarity_measure", + "date_created", + "date_computed", END_OF_ARRAY, }; char* ATTR_CLUSTER_ENTRY[] = { "items", - "date_created", - "date_computed", END_OF_ARRAY, }; char* ATTR_SEARCH_ENTRY[] = @@ -804,7 +803,10 @@ ci_UnknownAttribute(char* attr_name, const int target_type) while (specific_attrs[n_specific_attrs] != NULL) n_specific_attrs++; /** Collect general attributes. */ - char** general_attrs = (char*[]){"name", "annotation", "content_type", "inner_type", "outer_type", "internal_type", "date_computed", "date_created", "last_modification"}; + char** general_attrs = (char*[]){ + "name", "annotation", "content_type", "inner_type", "outer_type", + "internal_type", "date_computed", "date_created", "last_modification", + }; /** Attempt to give hints. **/ if (ci_TryHint(attr_name, specific_attrs, n_specific_attrs)); From 9cac9b81a147ea6eda4b53defb18998588cce321 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 09:38:18 -0600 Subject: [PATCH 075/361] Add `README.md` to explain the new datasets directory. --- centrallix-os/datasets/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 centrallix-os/datasets/README.md diff --git a/centrallix-os/datasets/README.md b/centrallix-os/datasets/README.md new file mode 100644 index 000000000..eae70ddff --- /dev/null +++ b/centrallix-os/datasets/README.md @@ -0,0 +1,6 @@ +# Datasets Folder + +The datasets folder is intended for storing datasets used in testing that must not be committed to GitHub. Datasets in this folder may contain sensitive data, including production data or patterns from production data. This data _must_ be treated with care, even if it is obfuscated. Thus, this directory should be "git-ignored". If you ever see files in this directory listed as changes when you run `git status` or use similar commands or tools, imedately remove these changes and investigate. Data in this directory must _never_ be committed or pushed! + +## Modifying this README.md +If you need to modify this readme, you will need to pass `-f` when adding it because the directory is part of the `.gitignore` (e.g. `git add -f README.md`). Obviously, be careful _not to `git add` any other files_ in this directory. From 98d8a117096932aff1a1f9d2db20f22b610fd553 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 09:40:00 -0600 Subject: [PATCH 076/361] Update `README.md` with knowledge learned from testing updates to files in the .gitignore. --- centrallix-os/datasets/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-os/datasets/README.md b/centrallix-os/datasets/README.md index eae70ddff..7633d84a4 100644 --- a/centrallix-os/datasets/README.md +++ b/centrallix-os/datasets/README.md @@ -3,4 +3,4 @@ The datasets folder is intended for storing datasets used in testing that must not be committed to GitHub. Datasets in this folder may contain sensitive data, including production data or patterns from production data. This data _must_ be treated with care, even if it is obfuscated. Thus, this directory should be "git-ignored". If you ever see files in this directory listed as changes when you run `git status` or use similar commands or tools, imedately remove these changes and investigate. Data in this directory must _never_ be committed or pushed! ## Modifying this README.md -If you need to modify this readme, you will need to pass `-f` when adding it because the directory is part of the `.gitignore` (e.g. `git add -f README.md`). Obviously, be careful _not to `git add` any other files_ in this directory. +If you need to modify this readme file, you may need to pass `-f` when adding it because the directory is part of the `.gitignore` (e.g. `git add -f README.md`). Obviously, be careful _not to `git add` any other files_ in this directory. From a0936d684b3960c4506904395ee7a5f2017afe8d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 11:27:23 -0600 Subject: [PATCH 077/361] Improve OSDriver_Authoring.md with Noah's comments. Fix various issues mentioned by Noah. Fix broken links. Fix spelling and grammar errors. --- centrallix-sysdoc/OSDriver_Authoring.md | 122 +++++++++++++----------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 9c9719f7f..40983d630 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -86,7 +86,7 @@ - [st_node: snReadNode()](#st_node-snreadnode) - [st_node: snNewNode()](#st_node-snnewnode) - [st_node: snWriteNode()](#st_node-snwritenode) - - [st_node: snDelete()](#st_node-sndeletenode) + - [st_node: snDelete()](#st_node-sndelete) - [st_node: snGetSerial()](#st_node-sngetserial) - [st_node: snGetLastModification()](#st_node-sngetlastmodification) - [Module: stparse](#module-stparse) @@ -100,8 +100,8 @@ - [stparse: stAddValue()](#stparse-staddvalue) - [stparse: stFreeInf()](#stparse-stfreeinf) - [stparse: Using Fields Directly](#stparse-using-fields-directly) - - [IV Module: Expression](#viii-module-expression) - - [expCompileExpression())](#expallocexpression) + - [IV Module: Expression](#iv-module-expression) + - [expAllocExpression())](#expallocexpression) - [expFreeExpression()](#expfreeexpression) - [expCompileExpression()](#expcompileexpression) - [expCompileExpressionFromLxs()](#expcompileexpressionfromlxs) @@ -117,12 +117,12 @@ - [expRemoveParamFromList()](#expremoveparamfromlist) - [expSetParamFunctions()](#expsetparamfunctions) - [expReverseEvalTree()](#expreverseevaltree) - - [V Path Handling Functions](#x-path-handling-functions) + - [V Path Handling Functions](#v-path-handling-functions) - [obj_internal_PathPart()](#obj_internal_pathpart) - [obj_internal_AddToPath()](#obj_internal_addtopath) - [obj_internal_CopyPath](#obj_internal_copypath) - [obj_internal_FreePathStruct()](#obj_internal_freepathstruct) - - [VI Parsing Data](#xii-parsing-data) + - [VI Parsing Data](#vi-parsing-data) - [mlxOpenSession()](#mlxopensession) - [mlxStringSession()](#mlxstringsession) - [mlxCloseSession()](#mlxclosesession) @@ -137,21 +137,21 @@ - [mlxSetReservedWords()](#mlxsetreservedwords) - [mlxNoteError()](#mlxnoteerror) - [mlxNotePosition()](#mlxnoteposition) - - [VII Driver Testing](#xiii-driver-testing) - - [Object opening, closing, creation, and deletion](#aobject-opening-closing-creation-and-deletion) - - [Object attribute enumeration, getting, and setting.](#bobject-attribute-enumeration-getting-and-setting) - - [Object querying (for subobjects)](#cobject-querying-for-subobjects) + - [VII Driver Testing](#vii-driver-testing) + - [A. Opening, closing, creating, and deleting](#a-opening-closing-creating-and-deleting) + - [B. Attributes](#b-attributes) + - [C. Querying Subobjects](#c-querying-subobjects) ## I Introduction An objectsystem driver's purpose is to provide access to a particular type of local or network data/resource. Specific information about the resource to be accessed (such as credentials for a database, queries for selecting data, the auth token for an API, etc.) is stored in a file that is opened by the relevant driver. For example, the query driver (defined in `objdrv_query.c`) opens `.qy` files, which store one or more ObjectSQL queries used to fetch data. -When the object system starts up, each driver registers one or more type names that it supports (e.g. `"system/query"` for the query driver). When a file is opened, the object system uses the file's type name to select which driver to use. It finds this type name with one of two strategies. If the file has an extension (e.g. `example.qy`), that extension can be mapped to a type name using `types.cfg` (e.g. `.qy` maps to `"system/query"`). Althernatively, the file may reside in a directory containing a `.type` file which explicitly specifies the type name for all files in that directory without recognizable extensions. +When the object system starts up, each driver registers one or more type names that it supports (e.g. `"system/query"` for the query driver). When a file is opened, the object system uses the file's type name to select which driver to use. It finds this type name with one of two strategies. If the file has an extension (e.g. `example.qy`), that extension can be mapped to a type name using `types.cfg` (e.g. `.qy` maps to `"system/query"`). Alternatively, the file may reside in a directory containing a `.type` file which explicitly specifies the type name for all files in that directory without recognizable extensions. Once a file is opened, the driver should organize provided data into a tree-structured hierarchy, which becomes part of the path used by Centrallix's ObjectSystem. For example, when opening `example.qy` in the ObjectSystem, the driver makes `/rows` and `/columns` available, allowing for paths such as `/apps/data/example.qy/rows`. The root of a driver's tree (`example.qy`) is called the driver's "node" object, and most paths traverse the node objects of multiple drivers. The root of the entire tree is a special driver called the root node which is used to begin traversal. Within its tree, a driver author is free to define any manner of hierarchical structures for representing available data. However, the structure should fit the basic ObjectSystem model of a hierarchy of objects, each having attributes, and optionally some methods and/or content. -A driver can be opened multiple times, leading one driver to have multiple "node" objects, also called instances. Typically, each "node" object relates to a particular instance of a resource. For example, say you are designing a driver to access MySQL databases. You could design the driver file to describe a MySQL instance. Thus, the node object for this driver could have children for each database in that instance (e.g. `Kardia_DB`, `mysql`, and even the system databases used by MySQL to manage the database internals). Another design would be for each driver file to describe one MySQL database. Thus, you could make a `Kardia_DB` file to access that database, and the children of that node object would be each table in the database. A third design option would be for each driver file to describe a MySQL table. Thus, you make a `p_partner` file to access members of the partner table, a `p_contact_info` file to access contact info for parterners, etc. with each node object having children for the rows in the table. This last option would require the developer to create a _lot_ of files (and would probably also make joins hard to implement), so in this case, it's probably not the best. Ultimately, though, these design choices are up to the driver author. +A driver can be opened multiple times, leading one driver to have multiple "node" objects, also called instances. Typically, each "node" object relates to a particular instance of a resource. For example, say you are designing a driver to access MySQL databases. You could design the driver file to describe a MySQL instance. Thus, the node object for this driver could have children for each database in that instance (e.g. `Kardia_DB`, `mysql`, and even the system databases used by MySQL to manage the database internals). Another design would be for each driver file to describe one MySQL database. Thus, you could make a `Kardia_DB` file to access that database, and the children of that node object would be each table in the database. A third design option would be for each driver file to describe a MySQL table. Thus, you make a `p_partner` file to access members of the partner table, a `p_contact_info` file to access contact info for partners, etc. with each node object having children for the rows in the table. This last option would require the developer to create a _lot_ of files (and would probably also make joins hard to implement), so in this case, it's probably not the best. Ultimately, though, these design choices are up to the driver author. an instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. @@ -160,7 +160,7 @@ The structure of the subtree beneath the node object is entirely up to the drive - Content, which can be read similar to reading a file. - Query data, allowing the object to be queried for information. -Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradime, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traversal of its child objects, although queries are not required to be implemented this way. +Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradigm, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traversal of its child objects, although queries are not required to be implemented this way. Below is an example of the Sybase driver's node object and its subtrees of child objects (defined in `objdrv_sybase.c`): @@ -197,7 +197,7 @@ Kardia_DB (type = "application/mysql") (... and so forth) -In this case, the `OMSS_DB` file becomes the driver's node object. This file would contain the information necessary to access the database, such as server name, database name, max connections to pool, and so forth. +In this case, the `Kardia_DB` file becomes the driver's node object. This file on disk contains the information necessary to access the database (such as server name, database name, max connections to pool, etc.). OS Drivers support several primary areas of functionality: - Opening and closing objects. @@ -207,7 +207,7 @@ OS Drivers support several primary areas of functionality: - Executing object methods (optional). - Querying data attributes (optional). -Using the example above, we can query from the database using a statement like `select :title from /OMSS_DB/JNetHelp/rows`, which will open a sybase driver instance, then open a query and repeatedly fetch rows, getting the `title` attribute from each row. +Using the example above, we can query from the database using a statement like `select :title from /Kardia_DB/p_partner/rows`, which will open a mysql driver instance, then open a query and repeatedly fetch rows, getting the `title` attribute from each row. @@ -307,7 +307,7 @@ For example: ```c if (xaInit(&(drv->RootContentTypes), 2) != 0) goto error_handling; if (xaAddItem(&(drv->RootContentTypes), "application/sybase") < 0) goto error_handling; -if (xaAddItem(&(drv->RootContentTypes), ""system/query"") < 0) goto error_handling; +if (xaAddItem(&(drv->RootContentTypes), "system/query") < 0) goto error_handling; ``` - 📖 **Note**: To make a specific file extension (like `.qy`) open in a driver, edit `types.cfg` to map that file extension to an available root content type supported by the driver (such as `"system/query"`). @@ -342,13 +342,13 @@ The `Open()` function opens a given file to create a new driver instance. This p 4. Perform other opening operations (such as reading database table information, etc., when a db table's row is being accessed). 5. Return a pointer to the node instance as a void pointer. This pointer will be passed as `void* inf_v` to the driver in subsequent calls involving this object (except the Query functions, discussed below). -- 📖 **Note - Transactions**: If the os driver specified the `OBJDRV_C_TRANS` capability, it must respect the current state of the user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). This is handled using the transaction tree parameter (`oxt : pObjTrxTree*`). +- 📖 **Note - Transactions**: If the os driver specified the `OBJDRV_C_TRANS` capability, it must respect the current state of the user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is pre-allocated; all the driver needs to do is fill it in). This is handled using the transaction tree parameter (`oxt : pObjTrxTree*`). #### Accessing the Node Object If `O_CREAT` and `O_EXCL` are both specified in `parent->Mode`, the driver should **only** create a new file and fail if the file already exists (refusing to open and read it). Otherwise, the driver should read an existing file, or create one if it does not exist and `O_CREAT` is specified, failing if no file can be read or created. #### Parsing Path Contents -The task of parsing the provided path into the subtree beneath its node object is one of the more complex operations for a driver. For example, the path to a driver's node object might be `/datasources/Kardia_DB` and the user opens an object called `/datasources/Kardia_DB/p_partner/rows/1`. In this case, the OS driver must parse the meaning of the subtree path `p_partner/rows/1`, storing the data targetted by the user into the driver instance to allow later method calls to access the correct data. +The task of parsing the provided path into the subtree beneath its node object is one of the more complex operations for a driver. For example, the path to a driver's node object might be `/datasources/Kardia_DB` and the user opens an object called `/datasources/Kardia_DB/p_partner/rows/1`. In this case, the OS driver must parse the meaning of the subtree path `p_partner/rows/1`, storing the data targeted by the user into the driver instance to allow later method calls to access the correct data. #### Parameters The `Open()` routine is called with five parameters: @@ -361,7 +361,7 @@ The `Open()` routine is called with five parameters: - `parent->Pathname->OpenCtl : pStruct[]`: Parameters for the open() operation, as defined by the driver author. These are specified in the path in a similar way to URLs (`example.qy?param1=value¶m2=other_value`). Drivers typically only use `parent->Pathname->OpenCtl[parent->SubPtr]` (see SubPtr below) to retrieve their own parameters, ignoring parameters passed to other drivers in the path. - - `parent->SubPtr : short`: The number of components in the path that are a part of the path to the driver's node object, including the `.` for the top level directory and the driver's node object. For example, in the above path of `/data/file.csv`, the path would be internally represented as `./ data/ file.csv`, so SubPtr is 3. + - `parent->SubPtr : short`: The number of components in the path that are a part of the path to the driver's node object, including the `.` for the top level directory and the driver's node object. For example, in the path of `/data/file.csv`, the path would be internally represented as `./ data/ file.csv`, so SubPtr is 3. - For example, use `obj_internal_PathPart(parent->Pathname, parent->SubPtr - 1, 1)` to get the name of the file being opened, and use `obj_internal_PathPart(parent->Pathname, 0, parent->SubPtr)` to get the path. @@ -403,23 +403,23 @@ void* xxxOpenChild(void* inf_v, pObject obj, char* child_name, int mask, pConten ``` Opens a single child object of the provided object by name. Conceptually, this is similar to querying the object for all children where the name attribute equals the passed `child_name` parameter and fetching only the first result. This function is used to open children of a driver that do not map well into the driver's node object tree. For example, the query file driver uses this function to allow the caller to open a temporary collection declared in that query file. -The `OpenChild()` function is called with two parameters: +The `OpenChild()` function is called with seven parameters: | Param | Type | Description | |------------|--------------|---------------------------------------------------------------------------| | inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | -| obj | pObject | An object? | -| child_name | char* | The value for the name attribute of the child object to be opened. | +| obj | pObject | The child object provided by the object system. | +| child_name | char* | The value for the name attribute of the child object to be opened. | | mask | int | The permission mask to be given to the object (if created).* | | sys_type | pContentType | Indicates the content type of the node object as determined by the OSML.* | | usr_type | char* | The object type requested by the user.* | | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | - - *See [`Open()`](#function-open) above for more info. -The `OpenChild()` function should a pointer to the node object for the newly opened child on success or `NULL` on failure. +The `OpenChild()` function should return a pointer to the node object for the newly opened child on success or `NULL` on failure. + +- 📖 **Note**: The object system stores the value returned from this function into `obj->Data`. --- ### Function: Close() @@ -448,7 +448,7 @@ The `Close()` function should return 0 on success or -1 on failure. ```c int xxxCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); ``` -The `Create()` function is used to create a new object, and uses the same parameters and return value as `Open()` (documented in detail above). This often means adding a new file to the file system to represent the object. Many drivers do not implement this and recommend that driver end-users create files using a standard text editor or programatically using more general means, such as general structure file generation. If implemented, this function frequently requires very similar path parsing functionality to `Open()`. +The `Create()` function is used to create a new object, and uses the same parameters and return value as `Open()` (documented in detail above). This often means adding a new file to the file system to represent the object. Many drivers do not implement this and recommend that driver end-users create files using a standard text editor or programmatically using more general means, such as general structure file generation. If implemented, this function frequently requires very similar path parsing functionality to `Open()`. - 📖 **Note**: For many drivers, the `Create()` function calls the driver's `Open()` function with `O_CREAT`, then calls its `Close()` function, although some drivers may manage this differently. @@ -489,7 +489,7 @@ The parameters passed are intentionally similar to the `fdRead()` function in `m | buffer | char* | The buffer where read data should be stored. | | max_cnt | int | The maximum number of bytes to read into the buffer. | | offset | int | An optional seek offset. | -| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 5th argument (`arg`). | +| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 4th argument (`arg`). | | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | - 📖 **Note**: Not all objects can be seekable and some of the objects handled by the driver may have limited seek functionality, even if others do not. @@ -532,7 +532,7 @@ The `OBJ_QY_F_FULLQUERY` flag indicates that the driver will handle the full `wh The `OBJ_QY_F_FULLSORT` flag indicates that the driver will handle all sorting for the data specified in `query->SortBy[]`. -If the driver can easily handle sorting/selection (as when querying an database), it should set these flags. Otherwise, it should let the OSML handle the ORDER BY and WHERE conditions to avoid unnecessary work for the driver author. +If the driver can easily handle sorting/selection (as when querying a database), it should set these flags. Otherwise, it should let the OSML handle the ORDER BY and WHERE conditions to avoid unnecessary work for the driver author. The `OpenQuery()` function returns a `void*` for the query instance struct, which will be passed to the other query functions (`QueryDelete()`, `QueryFetch()`, and `QueryClose()`). This structure normally points to the driver instance struct to allow easy access to queried data. `OpenQuery()` returns `NULL` if the object does not support queries or if an error occurs, in which case `mssError()` should be called before returning. @@ -667,14 +667,16 @@ The `GetAttrValue()` function takes four parameters: The value pointer points to a union struct which can hold one of several types of data in the same memory location. Which type of data is expected depends on the value of the `datatype` parameter. | Field | Datatype | Description | ----------- | ------------------ | ----------- -| `Integer` | `DATA_T_INTEGER` | An int where the value should be written. -| `String` | `DATA_T_STRING` | A `char*` where a pointer to the string should be written. -| `Double` | `DATA_T_DOUBLE` | A double where the double should be written. -| `DateTime` | `DATA_T_DATETIME` | A `pDateTime` where a pointer to the `DateTime` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. -| `IntVec` | `DATA_T_INTVEC` | A `pIntVec` where a pointer to the `IntVec` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. -| `StringVec` | `DATA_T_STRINGVEC` | A `pStringVec` where a pointer to the `StringVec` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. -| `Money` | `DATA_T_MONEY` | A `pMoneyType` where a pointer to the `MoneyType` struct (see [`datatypes.h`](../centrallix/include/datatypes.h)) should be written. -| `Generic` | ? | A `void*` to somewhere where something should be written should be written (usually implementation dependant). +| `Integer` | `DATA_T_INTEGER` | Store an `int` in the `val->Integer` field. +| `String` | `DATA_T_STRING` | Store a `char*` in the `val->String` field. +| `Double` | `DATA_T_DOUBLE` | Store a `double` in the `val->Double` field. +| `DateTime` | `DATA_T_DATETIME` | Store a `pDateTime`* in the `val->DateTime` field. +| `IntVec` | `DATA_T_INTVEC` | Store a `pIntVec`* in the `val->IntVec` field. +| `StringVec` | `DATA_T_STRINGVEC` | Store a `pStringVec`* in the `val->StringVec` field. +| `Money` | `DATA_T_MONEY` | Store a `pMoneyType`* in the `val->Money` field. +| `Generic` | ? | Store a `void*` in the `val->Generic` field (target data is usually implementation dependant). + +\*_See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more info about this datatype._ In this way, `int`s and `double`s can be returned by value while other types are returned by reference. Items returned by reference must be guaranteed to be valid until either the object is closed, or another `GetAttrValue()` or `SetAttrValue()` call is made on the same driver (which ever happens first). @@ -688,14 +690,14 @@ This function should return 0 on success, 1 if the value is `NULL` or undefined printf("Object name: \"%s\"\n", name); ``` -- 📖 **Note**: In legacy code, a typecasted `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix/include/datatypes.h) for more information). +- 📖 **Note**: In legacy code, a type cast `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix/include/datatypes.h) for more information). ### Function: SetAttrValue() ```c int xxxSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); ``` -The `SetAttrValue()` function is the same as `GetAttrValue()`, however it sets the value by reading it from the `val` parameter instead of getting the value by writing it to the `val` parameter. The return value is also identical, and `mssError()` should be invoked on failure, or if setting attributes programatically is not implemented. +The `SetAttrValue()` function is the same as `GetAttrValue()`, however it sets the value by reading it from the `val` parameter instead of getting the value by writing it to the `val` parameter. The return value is also identical, and `mssError()` should be invoked on failure, or if setting attributes programmatically is not implemented. ### Function: GetFirstAttr() & GetNextAttr() @@ -802,10 +804,10 @@ The return value, `hints : ObjPresentationHints`, contains the following useful - ⚠️ **Warning**: Behavior is undefined if: - The data is longer than length. -The `hints->Style` field can be set with several useful flags. To specify that a flag is not set (e.g. to specify explicitly that a field does allow `NULL`s), set the coresponding bit in the `hints->StyleMask` field while leaving the the bit in the `hints->Style` field set to 0. +The `hints->Style` field can be set with several useful flags. To specify that a flag is not set (e.g. to specify explicitly that a field does allow `NULL`s), set the corresponding bit in the `hints->StyleMask` field while leaving the the bit in the `hints->Style` field set to 0. The following macros are provided for setting style flags: -- `OBJ_PH_STYLE_BITMASK`: The items in `hints->EnumList` or `hints->EnumQuery` are bitmasked. +- `OBJ_PH_STYLE_BITMASK`: The items in `hints->EnumList` or `hints->EnumQuery` are bit masked. - `OBJ_PH_STYLE_LIST`: List-style presentation should be used for the values of an enum attribute. - `OBJ_PH_STYLE_BUTTONS`: Radio buttons or check boxes should be used for the presentation of enum attribute values. - `OBJ_PH_STYLE_NOTNULL`: The attribute does not allow `NULL` values. @@ -814,7 +816,7 @@ The following macros are provided for setting style flags: - `OBJ_PH_STYLE_READONLY`: The user is not allowed to modify this attribute. - `OBJ_PH_STYLE_HIDDEN`: This attribute should be hidden and not presented to the user. - `OBJ_PH_STYLE_PASSWORD`: Values in this attribute should be hidden, such as for passwords. -- `OBJ_PH_STYLE_MULTILINE`: String values should allow multiline editting. +- `OBJ_PH_STYLE_MULTILINE`: String values should allow multiline editing. - `OBJ_PH_STYLE_HIGHLIGHT`: This attribute should be highlighted when presented to the user. - `OBJ_PH_STYLE_LOWERCASE`: This attribute only allows lowercase characters. - `OBJ_PH_STYLE_UPPERCASE`: This attribute only allows uppercase characters. @@ -822,7 +824,7 @@ The following macros are provided for setting style flags: - `OBJ_PH_STYLE_SEPWINDOW`: Prefer separate windows for grouped fields. - `OBJ_PH_STYLE_ALWAYSDEF`: Always reset the default value when this attribute is modified. - `OBJ_PH_STYLE_CREATEONLY`: This attribute is writeable only when created, after that it is read only. -- `OBJ_PH_STYLE_MULTISEL`: This enum attribute can accept more than one value from the list of valid values. Think of using checkboxes instead of radio buttons (although the flag does requirement this UI decision). +- `OBJ_PH_STYLE_MULTISEL`: This enum attribute can accept more than one value from the list of valid values. Think of using checkboxes instead of radio buttons (although the flag does not make any UI requirements). - `OBJ_PH_STYLE_KEY`: This attribute is a primary key. - `OBJ_PH_STYLE_APPLYCHG`: Presentation hints should be applied on DataChange instead of on DataModify. @@ -838,7 +840,7 @@ The `Info()` function allows the caller to request extra information about a spe | inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | info | pObjectInfo | A driver info struct allocated by the caller which the driver sets with information. -The `pObjectInfo` struct has two fields: `Flags` and `nSubobjects`. This function should set `info->Flags` to 0 (to ensure no uninitialized noise gets into the data), then & it with all of the following flags that apply to that object. +The `pObjectInfo` struct has two fields: `Flags` and `nSubobjects`. This function should set `info->Flags` to 0 (to ensure no uninitialized noise gets into the data), then bitwise-or (`|`) it with all the following flags that apply to that object: - `OBJ_INFO_F_CAN_HAVE_SUBOBJ` / `OBJ_INFO_F_CANT_HAVE_SUBOBJ`: Indicates that the object can or cannot have subobjects. - `OBJ_INFO_F_HAS_SUBOBJ` / `OBJ_INFO_F_NO_SUBOBJ`: Indicates that the object has or does not have subobjects. - `OBJ_INFO_F_SUBOBJ_CNT_KNOWN`: Indicates that we know the number of subobjects. If set, the count should be stored in `info->nSubobjects`. @@ -893,11 +895,11 @@ A driver will commonly configure itself by reading text content from its node ob Although using the structure file format may be complex, it allows significant flexibility, as well as greater consistency across drivers. The use of this shared syntax across different drivers makes learning to use a new driver far easier than it would be if they all used unique, custom syntax for specifying properties. In the structure file syntax, data is structured in hierarchies where each sub-object can have named attributes as well as sub-objects. Centrallix has many examples of this, including any `.qy`, `.app`, `.cmp`, or `.cluster` file. -Structure files are accessed via the st_node (SN) and stparse (SP) modules. The st_node module loads and saves the structure file heirarchies as a whole. It also manages caching to reduce disk activity and eliminate repeated parsing of the same file. The stparse module provides access to the individual attributes and groups of attributes within a node structure file. +Structure files are accessed via the st_node (SN) and stparse (SP) modules. The st_node module loads and saves the structure file hierarchies as a whole. It also manages caching to reduce disk activity and eliminate repeated parsing of the same file. The stparse module provides access to the individual attributes and groups of attributes within a node structure file. -For example, if two sessions open two files, `/test1.rpt` and `/test2.rpt` the st_node module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be re-parsed. The file will be re-parsed if its timestamp changes. +For example, if two sessions open two files, `/test1.rpt` and `/test2.rpt` the st_node module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be reparsed. The file will be reparsed if its timestamp changes. -If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then st_node prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the st_node module to re-read the structure file defining the node object. Otherwise, the st_node module keeps track of the timestamp, and if it changes, the node object is re-read and re-parsed. +If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then st_node prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the st_node module to re-read the structure file defining the node object. Otherwise, the st_node module keeps track of the timestamp, and if it changes, the node object is re-read and reparsed. ### Module: st_node To obtain node object data, the driver should first open the node object with the st_node module. To use this module, include the file `st_node.h`, which provides the following functions (read `st_node.c` for more functions and additional information): @@ -1042,15 +1044,13 @@ This function adds a node of type `ST_T_SUBGROUP` to either an `ST_T_SUBGROUP` o ```c int stAddValue(pStructInf inf, char* strval, int intval); ``` -This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If `strval` is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. - - +This function adds a value to an attribute. If there is already one value, it creates an `EXPR_N_LIST`, and if there's already a list it simply adds the new value to the list. Thus, this function can be called multiple times on an attribute to add a list of multiple values. If `strval` is not null, a string value is added, otherwise an integer value is added using `intval`. The string is NOT copied, but is simply pointed-to. This function returns the number of values stored on the attribute, including the new one. No errors can occur, so it never returns -1. ### stparse: stFreeInf() ```c int stFreeInf(pStructInf this); ``` -This function is used to free a `StructInf` tree node. This also recursively frees sub-tree nodes, so these should be disconnected before calling if they are still needed. To do this, remove them from the SubInf array by appropriately adjusting the nSubInf counter and setting the SubInf array position to `NULL`. This function also disconnects the tree node from its parent, if any, so if the parent is already `free()`'d, prevent this behavior by setting the node's Parent pointer to `NULL` before calling this function. Any strings marked allocated with the StrAlloc flags will also be `free()`'d by this function, so update that flag if necessary. +This function is used to free a `StructInf` tree node. This also recursively frees sub-tree nodes, so these should be disconnected before calling if they are still needed. To do this, remove them from the SubInf array by appropriately adjusting the nSubInf counter and setting the SubInf array position to `NULL`. This function also disconnects the tree node from its parent, if any, so if the parent is already `free()`'d, prevent this behavior by setting the node's Parent pointer to `NULL` before calling this function. Any strings marked allocated with the StrAlloc flags will also be `free()`'d by this function, so update that flag if necessary. This function returns 0. ### stparse: Using Fields Directly @@ -1221,12 +1221,18 @@ static int ci_GetParamValue(void* v, char* attr_name, int datatype, pObjData val static int ci_SetParamValue(void* v, char* attr_name, int datatype, pObjData val); ``` +The functions use the following parameters: - `v : void*` is the object provided in `expAddParamToList()` (or a similar function). - `attr_name : char*` is the string name for the requested attribute. - `datatype : int` is the data type for the requested attribute. - `val : pObjectData` is either a buffer in which to store the requested data (`ci_GetParamValue()`) or a buffer containing data that will be copied to the parameter `ci_SetParamValue()`. -All three of these functions return 0 for success, 1 if the attribute is `NULL`, or -1 if an error occurs. The `expSetParamFunctions()` function returns 0 if the functions were set successfully, or -1 if an error occurs. +The functions return the following values: +- The `ci_GetParamType()` function returns the datatype on success (e.g. `DATA_T_INTEGER`), or -1 if an error occurs. +- The `ci_GetParamValue()` function returns 0 for success, 1 if the attribute is `NULL`, or -1 if an error occurs. +- The `ci_SetParamValue()` function returns 0 for success (even if the value was set to `NULL`), or -1 if an error occurs. + +The `expSetParamFunctions()` function returns 0 if the functions were set successfully, or -1 if an error occurs. ### expReverseEvalTree() ```c @@ -1295,7 +1301,7 @@ This function frees a pathname structure. ## VI Parsing Data -The mtlexer (MLX) module is a lexical analyzer library provided by Centrallix for parsing many types of data. It can parse data from either a `pFile` descriptor or from a string value. This lexical analyzer is also used by the [expression compiler](#viii-module-expression). In simple terms, it's a very fancy string tokenizer. +The mtlexer (MLX) module is a lexical analyzer library provided by Centrallix for parsing many types of data. It can parse data from either a `pFile` descriptor or from a string value. This lexical analyzer is also used by the [expression compiler](#iv-module-expression). In simple terms, it's a very fancy string tokenizer. ### mlxOpenSession() ```c @@ -1307,7 +1313,7 @@ This function opens a lexer session, using a file descripter as its source. Som | ----------------- | ------------ | `MLX_F_ICASEK` | Automatically convert all keywords (non-quoted strings) to lowercase. | `MLX_F_ICASER` | Automatically convert all reserved words to lowercase. This flag is highly recommended, and in some cases, required. -| `MLX_F_ICASE` | Same as MLX_F_ICASER | MLX_F_ICASEK. +| `MLX_F_ICASE` | Same as `MLX_F_ICASER` \| `MLX_F_ICASEK`. | `MLX_F_POUNDCOMM` | Respect # comment at the start of the line (`#comment`). | `MLX_F_CCOMM` | Respect c-style comments (`/*comment*/`). | `MLX_F_CPPCOMM` | Respect c-plus-plus comments (`//comment`). @@ -1384,7 +1390,7 @@ Returns the type of the next token in the token stream. Valid token types are: ```c char* mlxStringVal(pLxSession this, int* alloc); ``` -This function gets the string value of the current token. If `alloc` is `NULL`, only the first 255 bytes of the string will be returned, and the rest will be discarded. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always allocate memory for the string, whether long or short. +This function gets the string value of the current token. If `alloc` is `NULL`, an internal buffer is returned (which the caller _should not free_). This may also cause an error if such a buffer is not available. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always_ allocate memory for the string, whether long or short. This routine works no matter what the token type, and returns a string representation of the token if not `MLX_TOK_STRING`. @@ -1392,6 +1398,8 @@ This routine MAY NOT be called twice for the same token. - ⚠️ **Warning**: This function should not be called when `MLX_F_ALLOWNUL` is being used because it may return a null character, giving the caller no way to know whether it is the null-terminator or it simply existed in the input data stream. In this case, `mlxCopyToken()` should be used instead, as it gives a definitive answer on the token length. (`mlxStringVal()` can still be used on keywords, though, since they never contain a null, by definition). +- ⚠️ **Warning**: This documentation might be slightly out of date from what the function currently does. Double check `mtlexer.c`, if needed. + ### mlxIntVal() ```c int mlxIntVal(pLxSession this); @@ -1426,7 +1434,7 @@ This function sets the options (`MLX_F_xxx`) for an active lexer session. The o ```c int mlxUnsetOptions(pLxSession this, int options); ``` -Clears options set by [`mlxSetOptions()`](#mlxsetoptions). This function returns 0 if successful, or -1 if an error occurs. +This function clears the same set of flags used by [`mlxSetOptions()`](#mlxsetoptions). This function returns 0 if successful, or -1 if an error occurs. ### mlxSetReservedWords() ```c @@ -1461,7 +1469,7 @@ MLX: Error at line ## ## VII Driver Testing -This section contains a list of things that can be done to test an objectsystem driver and ensure that it preforms all basic operations correctly, using the [test_obj command line interface](http://www.centrallix.net/docs/docs.php). +This section contains a list of things that can be done to test an objectsystem driver and ensure that it preforms all basic operations correctly, using the [test_obj command line interface](https://www.centrallix.net/docs/docs.php?t=1.3.1%20test_obj%20Command-Line). It is strongly recommended to test for invalid reads, writes, frees, and memory leaks during each of these by watching memory utilization using nmDeltas() during repetitive operations (e.g., nmDeltas(), open, close, nmDeltas(), open, close, and then nmDeltas() again). @@ -1543,11 +1551,11 @@ The term "**MAY**" refers to optional, but permissible, behavior. 4. Objects returned by `xxxQueryFetch()` MUST also be able to be passed to `xxxOpenQuery()` to check for the existence of further subobjects, though the `xxxOpenQuery()` call is permitted to fail as in (C)(1) above. -5. Any name returned by `xxxGetAttrValue(name)` on a queried subobject MUST be usable to open the same object using `xxxOpen()`. +5. Any name returned by `xxxGetAttrValue(name)` on a queried sub-object MUST be usable to open the same object using `xxxOpen()`. 6. Drivers which connect to resources which are able to perform sorting and/or selection (filtering) of records or objects SHOULD use the [`OBJ_QY_F_FULLSORT`](#function-openquery) and [`OBJ_QY_F_FULLQUERY`](#function-openquery) flags. Further, they SHOULD pass on the sorting and filtering expressions to the remote resource so that resource can optimize sorting and/or filtering as needed. -7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then `OBJ_QY_F_FULLSORT` and/or `OBJ_QY_F_FULL`- QUERY MUST NOT be used. However the remote resource MAY still provide partial sorting and/or selection of data. +7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then `OBJ_QY_F_FULLSORT` and/or `OBJ_QY_F_FULL`- QUERY MUST NOT be used. However, the remote resource MAY still provide partial sorting and/or selection of data. 8. Drivers SHOULD NOT use `OBJ_QY_F_FULLSORT` and `OBJ_QY_F_FULLQUERY` if there is no advantage to letting the resource perform these operations (usually, however, if the resource provides such functionality, there is advantage to letting the resource perform those operations. However, the coding burden to provide the filtering and sorting expressions to the resource, and in the correct format for the resource, may be not worth the work). From f8572ad35e153533154a41050caf1b74df224576 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 12:53:22 -0600 Subject: [PATCH 078/361] Rewrite mssError() with lessons learned from mssErrorf(). Refactor mssError() to call vsnprintf() instead of using an incomplete custom implementation. Modify mssError() to return void instead of an int that literally nobody (other than me) ever uses. Modify mssError() to ensure an error message is always displayed somewhere if at all possible. Add additional error checking to mssError(). Add a newline at the start of new error stacks to improve readability of errors. Add code to flush stdout as soon as an error occurs to minimize interlacing. Add doc comment to mssError. Improve code readability of mssError(). Remove mssErrorf(). Remove code that hacks around mssError()'s previous limitations. Update all calls to mssErrorf() to use mssError(). Fix a spelling typo in an error message for the cluster driver. Clean up. --- centrallix-lib/include/mtsession.h | 2 +- centrallix-lib/src/mtlexer.c | 3 +- centrallix-lib/src/mtsession.c | 157 +++++++------- centrallix/expression/exp_functions.c | 68 +++--- centrallix/objectsystem/obj_datatypes.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 274 +++++++++--------------- 6 files changed, 220 insertions(+), 286 deletions(-) diff --git a/centrallix-lib/include/mtsession.h b/centrallix-lib/include/mtsession.h index 29adaff59..7a65fe1b0 100644 --- a/centrallix-lib/include/mtsession.h +++ b/centrallix-lib/include/mtsession.h @@ -81,7 +81,7 @@ void* mssGetParam(char* paramname); /** Error handling functions **/ int mssLog(int level, char* msg); -int mssError(int clr, char* module, char* message, ...); +void mssError(int clr, char* module, char* message, ...); int mssErrorErrno(int clr, char* module, char* message, ...); int mssClearError(); int mssPrintError(pFile fd); diff --git a/centrallix-lib/src/mtlexer.c b/centrallix-lib/src/mtlexer.c index 7be3b6a81..ce5b41464 100644 --- a/centrallix-lib/src/mtlexer.c +++ b/centrallix-lib/src/mtlexer.c @@ -908,8 +908,7 @@ mlxNextToken(pLxSession this) } else { - /** mssError does not support %c, so we need to use this workaround. **/ - mssError(1, "MLX", "Unexpected character encountered: '%s'", (char[]){ch, '\0'}); + mssError(1, "MLX", "Unexpected character encountered: '%c'", ch); this->TokType = MLX_TOK_ERROR; break; } diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index 015a00193..4ab4c41fe 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -3,6 +3,7 @@ #include "cxlibconfig-internal.h" #endif #include +#include #include #include #include @@ -23,6 +24,7 @@ #include "xstring.h" #include "xhash.h" #include "strtcpy.h" +#include "util.h" /************************************************************************/ /* Centrallix Application Server System */ @@ -452,99 +454,93 @@ mssEndSession(pMtSession s) } -/*** mssError - Add an error message to the error stack, optionally - *** clearing the existing contents thereof. +/*** Displays error text to the user (but no stack trace). Does not exit the + *** program, allowing the calling function to fail, generating a cascade of + *** error messages which may provide useful info. + *** + *** @param clr Whether to clear the current error stack. As a rule of thumb, + *** if you are the first one to detect the error, clear the stack so that + *** other unrelated messages are not shown. If you are detecting an error + *** from another function that may also call an mssError() function, do + *** not clear the stack. + *** @param module The name or abbreviation of the module in which this + *** function is being called, to help developers narrow down the location + *** of the error. + *** @param format The format text for the error, which accepts any format + *** specifier that would be accepted by printf(). + *** @param ... Variables matching format specifiers in the format. ***/ -int +void mssError(int clr, char* module, char* message, ...) { - va_list vl; - char* msg; - pMtSession s; - XString xs; - char* ptr; - char* cur_pos; - char* str; - int i; - char nbuf[16]; - char ch; - - /** Build the real error msg. **/ - xsInit(&xs); - cur_pos = message; - va_start(vl, message); - while((ptr = strchr(cur_pos, '%'))) - { - xsConcatenate(&xs, cur_pos, ptr - cur_pos); - switch(ptr[1]) - { - case '\0': - xsConcatenate(&xs, "%", 1); - cur_pos = ptr+1; - break; - case '%': - xsConcatenate(&xs, "%", 1); - cur_pos = ptr+2; - break; - case 's': - str = va_arg(vl, char*); - xsConcatenate(&xs, str?str:"(NULL)", -1); - cur_pos = ptr + 2; - break; - case 'c': - ch = va_arg(vl, int); - xsConcatenate(&xs, &ch, 1); - cur_pos = ptr + 2; - break; - case 'd': - i = va_arg(vl, int); - sprintf(nbuf,"%d",i); - xsConcatenate(&xs, nbuf, -1); - cur_pos = ptr + 2; - break; - default: - cur_pos = ptr + 2; - break; - } - } - va_end(vl); - if (*cur_pos) xsConcatenate(&xs, cur_pos, -1); - + char err_msg[BUFSIZ]; + unsigned int i = 0u; + + /** Prevent issues from interlacing this function with prints to stdout. **/ + check(fflush(stdout)); /* Failure ignored. */ + + /** Write the module to the start of the error message. */ + i += snprintf(err_msg, sizeof(err_msg) - i, "%s: ", module); + + /** Process the message format with all the same rules as printf(). **/ + va_list args; + va_start(args, message); + i += vsnprintf(err_msg, sizeof(err_msg) - i, message, args); + va_end(args); + /** Get current session **/ - s = (pMtSession)thGetParam(NULL,"mss"); - if (!s || MSS.LogAllErrors) + pMtSession s = thGetParam(NULL, "mss"); + const bool log_error = (s == NULL || MSS.LogAllErrors); + + /** Use standard logging without a session context, if needed. **/ + if (log_error) { - /*printf("mssError: Error occurred outside of session context.\n");*/ - if (!strcmp(MSS.LogMethod,"syslog")) + /** Start new error stacks with a newline to make them distinct. **/ + if (clr) fprintf(stderr, "\n"); + + /** Use the requested logging method. **/ + if (strcmp(MSS.LogMethod, "syslog") == 0) { - if (!s) - syslog(LOG_ERR, "System: %s: %.256s\n", module, xs.String); + if (s == NULL) + syslog(LOG_ERR, "System: %.256s\n", err_msg); else - syslog(LOG_WARNING, "User '%s': %s: %.256s\n", s->UserName, module, xs.String); + syslog(LOG_WARNING, "User '%s': %.256s\n", s->UserName, err_msg); } - else if (!strcmp(MSS.LogMethod, "stdout")) + else if (strcmp(MSS.LogMethod, "stdout") == 0) { - printf("%s: %s: %s\n",MSS.AppName[0]?MSS.AppName:"error",module,xs.String); + printf("%s: %s\n", (MSS.AppName[0]) ? MSS.AppName : "error", err_msg); + } + else + { + /** Default to printing to stderr if no known logging method is given. **/ + fprintf(stderr, "Unknown log method MSS.LogMethod \"%s\".\n", MSS.LogMethod); + fprintf(stderr, "mssError: %s\n", err_msg); } - if (!s) return -1; } - - /** Need to clear? **/ - if (clr) mssClearError(); - - /** Allocate space and construct the error text. **/ - msg = (char*)nmSysMalloc(strlen(module)+strlen(xs.String)+3); - if (!msg) + + /** If a session is available, try to add the error to the error list. **/ + if (s != NULL) { - perror("mssError: Could not allocate error"); - printf("mssError: %s: %s\n",module,xs.String); - return -1; + /** Clear the error context, if requested. **/ + if (clr) check(mssClearError()); /* Failure ignored. */ + + /** Allocate space and construct the error text. **/ + const char* allocated_err_msg = check_ptr(nmSysStrdup(err_msg)); + if (allocated_err_msg == NULL) + { + fprintf(stderr, "Failed to store error message: %s\n", err_msg); + return; /* Give up. */ + } + + /** Store the error. **/ + if (!check_neg(xaAddItem(&(s->ErrList), (void*)allocated_err_msg))) + { + fprintf(stderr, "Failed to add error message to session error list: %s\n", err_msg); + return; /* Give up. */ + } } - sprintf(msg,"%s: %s",module,xs.String); - xaAddItem(&(s->ErrList),(void*)msg); - xsDeInit(&xs); - - return 0; + + return; } @@ -836,4 +832,3 @@ mssGetParam(char* paramname) return p->Value; } - diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 1904d696e..2858ee572 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -133,7 +133,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e /** The expectation struct cannot be NULL. **/ if (arg_expect == NULL) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expectation struct cannot be NULL", fn_name ); @@ -149,7 +149,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e { if (arg_expect->Flags & EXP_ARG_NOT_NULL) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects a non-null value, but got NULL : %s (%d).", fn_name, objTypeToStr(actual_datatype), actual_datatype ); @@ -166,7 +166,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e /** Type checks requested, but no valid types given: Likely a mistake. **/ if (arg_expect->Datatypes[0] == -1) { - mssErrorf(1, "EXP", "%s(...): Invalid Schema! Empty array of allowed datatypes.", fn_name); + mssError(1, "EXP", "%s(...): Invalid Schema! Empty array of allowed datatypes.", fn_name); fprintf(stderr, "Hint: To skip type checks, pass NULL for the array of data types.\n"); return -1; } @@ -203,7 +203,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e /** Print error. **/ int first_datatype = arg_expect->Datatypes[0]; - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects type %s (%d)%s but got type %s (%d).", fn_name, objTypeToStr(first_datatype), first_datatype, buf, objTypeToStr(actual_datatype), actual_datatype ); @@ -222,7 +222,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e int value = arg->Integer; if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects positive int but got %d.", fn_name, value ); @@ -230,7 +230,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e } if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects negative int but got %d.", fn_name, value ); @@ -244,7 +244,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e double value = arg->Types.Double; if (arg_expect->Flags & EXP_ARG_NON_NAN && isnan(value)) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects non-nan double but got %g.", fn_name, value ); @@ -252,7 +252,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e } if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects positive double but got %g.", fn_name, value ); @@ -260,7 +260,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e } if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects negative double but got %g.", fn_name, value ); @@ -274,7 +274,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e char* str = arg->String; if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str[0] == '\0') { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects string to contain characters, but got \"\".", fn_name ); @@ -288,7 +288,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e pMoneyType value = &arg->Types.Money; if (arg_expect->Flags & EXP_ARG_POSITIVE && value->WholePart < 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects positive money value but got $%d.%g.", fn_name, value->WholePart, (double)value->FractionPart / 100.0 ); @@ -296,7 +296,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e } if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->WholePart > 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects negative money value but got $%d.%d.", fn_name, value->WholePart, (double)value->FractionPart / 100.0 ); @@ -309,7 +309,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e pStringVec str_vec = &arg->Types.StrVec; if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str_vec->nStrings == 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects StringVec to contain strings, but got [].", fn_name ); @@ -323,7 +323,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e pIntVec int_vec = &arg->Types.IntVec; if (arg_expect->Flags & EXP_ARG_NON_EMPTY && int_vec->nIntegers == 0) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(...): Expects IntVec to contain strings, but got [].", fn_name ); @@ -362,7 +362,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e *** }, tree *** ) != 0) *** { - *** mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + *** mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); *** return -1; *** } *** ``` @@ -382,7 +382,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) else if (opt_args > 0) { /** Required argument follows optional argument (not allowed). **/ - mssErrorf(1, "EXP", "%s(?): Invalid Schema! Required argument #%u after optional argument.", tree->Name, i); + mssError(1, "EXP", "%s(?): Invalid Schema! Required argument #%u after optional argument.", tree->Name, i); return -1; } else @@ -396,7 +396,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) { if (actual_args != req_args) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(?): Expects %u argument%s, got %d argument%s.", tree->Name, req_args, (req_args == 1) ? "" : "s", actual_args, (actual_args == 1) ? "" : "s" ); @@ -407,7 +407,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) { if (actual_args < req_args || total_args < actual_args) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(?): Expects between %u and %u arguments, got %d argument%s.", tree->Name, req_args, total_args, actual_args, (actual_args == 1) ? "" : "s" ); @@ -420,7 +420,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) { if (exp_fn_i_verify_arg(tree->Name, tree->Children.Items[i], &arg_expects[i]) != 0) { - mssErrorf(0, "EXP", + mssError(0, "EXP", "%s(...): Error while reading arg #%d/%d.", tree->Name, i + 1, max(i + 1, req_args) ); @@ -1583,7 +1583,7 @@ exp_fn_lztrim(pExpression tree) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -1627,7 +1627,7 @@ exp_fn_ltrim(pExpression tree) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -1672,7 +1672,7 @@ exp_fn_rtrim(pExpression tree) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -3773,7 +3773,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) expects[arg_num] = EXP_ARG_END; if (exp_fn_i_verify_schema(expects, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -3792,7 +3792,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) /** Maximum supported args. **/ if (arg_num > 4) { - mssErrorf(1, "EXP", "%s(...): exp_fn_i_do_math() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", tree->Name, __FILE__, __LINE__); + mssError(1, "EXP", "%s(...): exp_fn_i_do_math() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", tree->Name, __FILE__, __LINE__); return -1; } @@ -3802,7 +3802,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) { if (!check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i])))) { - mssErrorf(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); + mssError(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); return -1; } } @@ -3842,7 +3842,7 @@ exp_fn_log(pExpression tree) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -3850,14 +3850,14 @@ exp_fn_log(pExpression tree) double number, base; if (!check(exp_fn_i_get_number(check_ptr(tree->Children.Items[0]), &number))) { - mssErrorf(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); + mssError(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); return -1; } if (tree->Children.nItems > 1) { if (!check(exp_fn_i_get_number(check_ptr(tree->Children.Items[1]), &base))) { - mssErrorf(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); + mssError(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); return -1; } } @@ -4539,7 +4539,7 @@ exp_fn_metaphone(pExpression tree) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -4600,7 +4600,7 @@ exp_fn_compare(pExpression tree) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -4628,7 +4628,7 @@ exp_fn_compare(pExpression tree) const pVector v2 = check_ptr(ca_build_vector(str2)); if (v1 == NULL || v2 == NULL) { - mssErrorf(1, "EXP", + mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to build vectors.", tree->Name, str1, str2 ); @@ -4652,7 +4652,7 @@ exp_fn_compare(pExpression tree) double lev_sim = check_double(ca_lev_compare(str1, str2)); if (isnan(lev_sim)) { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute Levenshtein edit distance."); + mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute Levenshtein edit distance."); return -1; } @@ -4675,7 +4675,7 @@ exp_fn_levenshtein(pExpression tree) EXP_ARG_END, }, tree) != 0) { - mssErrorf(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } @@ -4698,7 +4698,7 @@ exp_fn_levenshtein(pExpression tree) int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); if (!check_neg(edit_dist)) { - mssErrorf(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); + mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); return -1; } diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index c0c702abf..93da47fca 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -205,7 +205,7 @@ objTypeToStr(const int type) if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) { /** Invalid type. **/ - mssErrorf(1, "Cluster", "Invalid type %d.\n", type); + mssError(1, "Cluster", "Invalid type %d.\n", type); return "invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 67024fe82..9734f139a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -65,66 +65,6 @@ /** ================ Stuff That Should Be Somewhere Else ================ **/ /** ANCHOR[id=temp] **/ -/** TODO: Greg - I think this should be moved to mtsession. **/ -/*** I caused at least 10 bugs early in the project trying to pass format - *** specifiers to mssError() without realizing that it didn't support them. - *** Eventually, I got fed up enough having to write errors to a sting buffer - *** and passing that buffer to mssError(), so I wrote this wrapper that does - *** it for me. It'd be better to add this to mssError(), though. - ***/ -/*** Displays error text to the user (but no stack trace). Does not exit the - *** program, allowing the calling function to fail, generating a cascade of - *** error messages which may provide useful info. - *** - *** @param clr Whether to clear the current error stack. As a rule of thumb, - *** if you are the first one to detect the error, clear the stack so that - *** other unrelated messages are not shown. If you are detecting an error - *** from another function that may also call an mssError() function, do - *** not clear the stack. - *** @param module The name or abbreviation of the module in which this - *** function is being called, to help developers narrow down the location - *** of the error. - *** @param format The format text for the error, which accepts any format - *** specifier that would be accepted by printf(). - *** @param ... Variables matching format specifiers in the format. - *** @returns Nothing, always succeeds. - ***/ -void -mssErrorf(int clr, char* module, const char* format, ...) - { - /** Prevent interlacing with stdout flushing at a weird time. **/ - check(fflush(stdout)); /* Failure ignored. */ - - /** Insert convenient newline before error stack begins. **/ - if (clr == 1) fprintf(stderr, "\n"); - - /** Process the format with all the same rules as printf(). **/ - char buf[BUFSIZ]; - va_list args; - va_start(args, format); - const int num_chars = vsnprintf(buf, sizeof(buf), format, args); - va_end(args); - - /** Error check vsnprintf, just to be safe. **/ - if (num_chars < 0) - { - perror("vsnprintf() failed"); - fprintf(stderr, "FAIL: mssErrorf(%d, \"%s\", \"%s\", ...)\n", clr, module, format); - return; - } - if (num_chars > BUFSIZ) - fprintf(stderr, "Warning: Error truncated (length %d > buffer size %d).\n", num_chars, BUFSIZ); - - /** Print the error. **/ - const int ret = mssError(clr, module, "%s", buf); - - /** Not sure why you have to error check the error function... **/ - if (ret != 0) fprintf(stderr, "FAIL %d: mssError(%d, \"%s\", \"%%s\", \"%s\")\n", ret, clr, module, buf); - - return; - } - - /** TODO: Greg - I think this should be moved to xarray. **/ /*** Trims an xArray, returning a new array (with nmSysMalloc). *** @@ -270,7 +210,7 @@ double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(v case SIMILARITY_COSINE: return ca_cos_compare; case SIMILARITY_LEVENSHTEIN: return ca_lev_compare; default: - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Unknown similarity measure \"%s\" (%d).", ci_SimilarityMeasureToString(similarity_measure), similarity_measure ); @@ -779,7 +719,7 @@ static void ci_UnknownAttribute(char* attr_name, const int target_type) { /** Display the error message. */ - mssErrorf(1, "Cluster", "Unknown attribute '%s'.", attr_name); + mssError(1, "Cluster", "Unknown attribute '%s'.", attr_name); /** Collect specific attributes based on target type. **/ char** specific_attrs = NULL; @@ -791,7 +731,7 @@ ci_UnknownAttribute(char* attr_name, const int target_type) case TARGET_CLUSTER_ENTRY: specific_attrs = ATTR_CLUSTER_ENTRY; break; case TARGET_SEARCH_ENTRY: specific_attrs = ATTR_SEARCH_ENTRY; break; default: - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Unknown target type %u detected while attempting to generate hint.", target_type ); @@ -843,7 +783,7 @@ ci_ParseAttribute( pStructInf attr_info = stLookup(inf, attr_name); if (attr_info == NULL) { - if (required) mssErrorf(1, "Cluster", "'%s' must be specified for clustering.", attr_name); + if (required) mssError(1, "Cluster", "'%s' must be specified for clustering.", attr_name); return 1; } ASSERTMAGIC(attr_info, MGK_STRUCTINF); @@ -860,14 +800,14 @@ ci_ParseAttribute( ret = expEvalTree(exp, param_list); if (ret != 0) { - mssErrorf(0, "Cluster", "Expression evaluation failed (error code %d).", ret); + mssError(0, "Cluster", "Expression evaluation failed (error code %d).", ret); goto err; } /** Check for data type mismatch. **/ if (datatype != exp->DataType) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Expected ['%s' : %s], but got type %s.", attr_name, objTypeToStr(datatype), objTypeToStr(exp->DataType) ); @@ -878,7 +818,7 @@ ci_ParseAttribute( ret = expExpressionToPod(exp, datatype, data); if (ret != 0) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Failed to get ['%s' : %s] using expression \"%s\" (error code %d).", attr_name, objTypeToStr(datatype), exp->Name, ret ); @@ -889,7 +829,7 @@ ci_ParseAttribute( return 0; err: - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to parse attribute \"%s\" from group \"%s\"", attr_name, inf->Name ); @@ -916,7 +856,7 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) char* algorithm; if (ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0) { - mssErrorf(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); + mssError(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); return ALGORITHM_NULL; } @@ -929,7 +869,7 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) if (strcasecmp(algorithm, "db-scan") == 0) return ALGORITHM_DB_SCAN; /** Unknown value for clustering algorithm. **/ - mssErrorf(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); + mssError(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); /** Attempt to give a hint. **/ char* all_names[nClusteringAlgorithms] = {NULL}; @@ -964,7 +904,7 @@ ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) char* measure; if (ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0) { - mssErrorf(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); + mssError(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); return SIMILARITY_NULL; } @@ -973,7 +913,7 @@ ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) if (!strcasecmp(measure, "levenshtein")) return SIMILARITY_LEVENSHTEIN; /** Unknown similarity measure. **/ - mssErrorf(1, "Cluster", "Unknown \"similarity measure\": %s", measure); + mssError(1, "Cluster", "Unknown \"similarity measure\": %s", measure); /** Attempt to give a hint. **/ char* all_names[nSimilarityMeasures] = {NULL}; @@ -1080,7 +1020,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) ci_FreeSourceData(source_data); } - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to parse source data from group \"%s\" in file: %s", inf->Name, path ); @@ -1154,7 +1094,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source goto err_free; if (window_size < 1) { - mssErrorf(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); + mssError(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); goto err_free; } @@ -1169,7 +1109,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source goto err_free; if (num_clusters < 2) { - mssErrorf(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); + mssError(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); if (num_clusters == 1) fprintf(stderr, "HINT: Use algorithm=\"none\" to disable clustering.\n"); goto err_free; } @@ -1183,7 +1123,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { if (improvement <= 0.0 || 1.0 <= improvement) { - mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %g", improvement); + mssError(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %g", improvement); goto err_free; } @@ -1197,7 +1137,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source if (result != 0) goto err_free; if (strcasecmp(str, "none") != 0) { - mssErrorf(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %s", str); + mssError(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %s", str); goto err_free; } @@ -1213,7 +1153,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { if (max_iterations < 1) { - mssErrorf(1, "Cluster", "Invalid value for [max_iterations : uint > 0]: %d", max_iterations); + mssError(1, "Cluster", "Invalid value for [max_iterations : uint > 0]: %d", max_iterations); goto err_free; } cluster_data->MaxIterations = (unsigned int)max_iterations; @@ -1228,7 +1168,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { if (seed < 1) { - mssErrorf(1, "Cluster", "Invalid value for [seed : uint > 0]: %d", seed); + mssError(1, "Cluster", "Invalid value for [seed : uint > 0]: %d", seed); goto err_free; } cluster_data->Seed = (unsigned int)seed; @@ -1242,7 +1182,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source pStructInf sub_inf = check_ptr(inf->SubInf[i]); if (sub_inf == NULL) { - mssErrorf(1, "Cluster", "Failed to get %uth subinf.", i); + mssError(1, "Cluster", "Failed to get %uth subinf.", i); goto err_free; } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); @@ -1294,7 +1234,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source if (group_type == NULL) goto err_free; if (strcmp(group_type, "cluster/cluster") != 0) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", name, group_type, inf->Name ); @@ -1313,7 +1253,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source default: { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Warning: Unknown struct type %d in cluster \"%s\".", struct_type, inf->Name ); @@ -1408,7 +1348,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source if (cluster_data != NULL) ci_FreeClusterData(cluster_data, false); - mssErrorf(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); + mssError(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); return NULL; } @@ -1469,7 +1409,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) if (search_data->SourceCluster == NULL) { /** Print error. **/ - mssErrorf(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); + mssError(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); /** Attempt to give a hint. **/ char* cluster_names[node_data->nClusterDatas]; @@ -1485,7 +1425,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) if (ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0) goto err_free; if (search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Invalid value for [threshold : 0.0 < x < 1.0 | \"none\"]: %g", search_data->Threshold ); @@ -1502,7 +1442,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) pStructInf sub_inf = check_ptr(inf->SubInf[i]); if (sub_inf == NULL) { - mssErrorf(1, "Cluster", "Failed to get %uth subinf.", i); + mssError(1, "Cluster", "Failed to get %uth subinf.", i); goto err_free; } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); @@ -1555,7 +1495,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) default: { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Warning: Unknown struct type %d in search \"%s\".", struct_type, inf->Name ); @@ -1599,7 +1539,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) err_free: if (search_data != NULL) ci_FreeSearchData(search_data); - mssErrorf(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); + mssError(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); return NULL; } @@ -1650,7 +1590,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); if (ret != 0) { - mssErrorf(0, "Cluster", "Failed to add parameters to the param list scope (error code %d).", ret); + mssError(0, "Cluster", "Failed to add parameters to the param list scope (error code %d).", ret); goto err_free; } @@ -1664,7 +1604,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ); if (ret != 0) { - mssErrorf(0, "Cluster", "Failed to set param functions (error code %d).", ret); + mssError(0, "Cluster", "Failed to set param functions (error code %d).", ret); goto err_free; } @@ -1677,7 +1617,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) pStructInf sub_inf = check_ptr(inf->SubInf[i]); if (sub_inf == NULL) { - mssErrorf(1, "Cluster", "Failed to get %uth subinf.", i); + mssError(1, "Cluster", "Failed to get %uth subinf.", i); goto err_free; } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); @@ -1755,7 +1695,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) default: { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Warning: Unknown struct type %d in search \"%s\".", struct_type, inf->Name ); @@ -1787,7 +1727,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) pParam param = paramCreateFromInf(param_infs.Items[i]); if (param == NULL) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to create param from inf for param #%u: %s", i, ((pStructInf)param_infs.Items[i])->Name ); @@ -1808,7 +1748,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, node_data->ParamList->Session); if (ret != 0) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to set param value from struct info.\n" " > Param #%u: %s\n" " > Provided Param #%u: %n\n" @@ -1828,7 +1768,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ret = paramEvalHints(param, node_data->ParamList, node_data->ParamList->Session); if (ret != 0) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to evaluate parameter hints for parameter \"%s\" (error code %d).", param->Name, ret ); @@ -1911,7 +1851,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); /* Failure ignored. */ if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); /* Failure ignored. */ if (node_data != NULL) ci_FreeNodeData(node_data); - mssErrorf(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); + mssError(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); return NULL; } @@ -2372,7 +2312,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); if (obj == NULL) { - mssErrorf(0, "Cluster", "Failed to open object driver."); + mssError(0, "Cluster", "Failed to open object driver."); goto end_free; } @@ -2380,7 +2320,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); if (query == NULL) { - mssErrorf(0, "Cluster", "Failed to open query."); + mssError(0, "Cluster", "Failed to open query."); goto end_free; } @@ -2400,7 +2340,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) const int data_datatype = objGetAttrType(entry, source_data->DataAttr); if (data_datatype == -1) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to get type for data of %uth entry.", vector_xarray.nItems ); @@ -2408,7 +2348,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } if (data_datatype != DATA_T_STRING) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Type for data of %uth entry was %s instead of String:\n", vector_xarray.nItems, objTypeToStr(data_datatype) ); @@ -2420,7 +2360,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) ret = objGetAttrValue(entry, source_data->DataAttr, DATA_T_STRING, POD(&data)); if (ret != 0) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to get attribute value for %uth data entry (error code: %d).", vector_xarray.nItems, ret ); @@ -2434,12 +2374,12 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) pVector vector = ca_build_vector(data); if (vector == NULL) { - mssErrorf(1, "Cluster", "Failed to build vectors for string \"%s\".", data); + mssError(1, "Cluster", "Failed to build vectors for string \"%s\".", data); goto end_free; } if (ca_is_empty(vector)) { - mssErrorf(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); + mssError(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); goto end_free; } if (ca_has_no_pairs(vector)) @@ -2454,7 +2394,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) const int key_datatype = objGetAttrType(entry, source_data->KeyAttr); if (key_datatype == -1) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to get type for key on %uth entry.", vector_xarray.nItems ); @@ -2462,7 +2402,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } if (key_datatype != DATA_T_STRING) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Type for key on %uth entry was %s instead of String:", vector_xarray.nItems, objTypeToStr(key_datatype) ); @@ -2474,7 +2414,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); if (ret != 0) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to value for key on %uth entry (error code: %d).", vector_xarray.nItems, ret ); @@ -2497,7 +2437,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) source_data->nVectors = vector_xarray.nItems; if (source_data->nVectors == 0) { - mssErrorf(0, "Cluster", "Data source path did not contain any valid data:\n"); + mssError(0, "Cluster", "Data source path did not contain any valid data:\n"); goto end_free; } @@ -2523,7 +2463,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Print an error if the function failed. **/ if (!successful) { - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "SourceData computation failed:\n" " > Key Attribute: ['%s' : String]\n" " > Data Attribute: ['%s' : String]\n" @@ -2611,7 +2551,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) pSourceData source_data = check_ptr(node_data->SourceData); if (source_data == NULL) { - mssErrorf(1, "Cluster", "Failed to get source data for cluster computation."); + mssError(1, "Cluster", "Failed to get source data for cluster computation."); goto err_free; } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); @@ -2622,7 +2562,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) ASSERTMAGIC(session, MGK_OBJSESSION); if (ci_ComputeSourceData(source_data, session) != 0) { - mssErrorf(0, "Cluster", "ClusterData computation failed due to missing SourceData."); + mssError(0, "Cluster", "ClusterData computation failed due to missing SourceData."); goto err_free; } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); @@ -2672,7 +2612,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Check for unimplemented similarity measures. **/ if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "The similarity measure \"%s\" is not implemented for 'k-means' clusters.", ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) ); @@ -2734,7 +2674,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[i]; if (index > __UINT32_MAX__) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "How did you try to cluster more than %u data points and ci_ComputeSearchData() " "was the first thing to break?! Well... looks like it's time to update %s:%s to " "handle a larger amount of data.", @@ -2752,7 +2692,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) } default: - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Clustering algorithm \"%s\" is not implemented.", ci_ClusteringAlgorithmToString(cluster_data->ClusterAlgorithm) ); @@ -2787,7 +2727,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) nmFree(cluster_data->Clusters, clusters_size); } - mssErrorf(0, "Cluster", "ClusterData computation failed for \"%s\".", cluster_data->Name); + mssError(0, "Cluster", "ClusterData computation failed for \"%s\".", cluster_data->Name); return -1; } @@ -2821,13 +2761,13 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) pClusterData cluster_data = check_ptr(search_data->SourceCluster); if (cluster_data == NULL) { - mssErrorf(1, "Cluster", "Failed to get cluster data for search computation."); + mssError(1, "Cluster", "Failed to get cluster data for search computation."); goto err_free; } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); if (ci_ComputeClusterData(cluster_data, node_data) != 0) { - mssErrorf(0, "Cluster", "SearchData computation failed due to missing clusters."); + mssError(0, "Cluster", "SearchData computation failed due to missing clusters."); goto err_free; } @@ -2855,7 +2795,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; default: - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Unknown similarity measure \"%s\".", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); @@ -2873,7 +2813,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) )); if (pairs == NULL) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Failed to compute sliding search with %s similarity measure.", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); @@ -2901,7 +2841,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; default: - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Unknown similarity measure \"%s\".", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); @@ -2934,7 +2874,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (free_filtered_data) nmSysFree(filtered_data); if (cluster_pairs == NULL) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Failed to compute ca_complete_search() with %s similarity measure.", ci_SimilarityMeasureToString(search_data->SimilarityMeasure) ); @@ -2964,7 +2904,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } if (search_data->Pairs == NULL) { - mssErrorf(1, "Cluster", "Failed to store pair after computing search data."); + mssError(1, "Cluster", "Failed to store pair after computing search data."); goto err_free; } @@ -2983,7 +2923,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) check(xaFree(pairs)); /* Failure ignored. */ } - mssErrorf(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); + mssError(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); return -1; } @@ -3062,7 +3002,7 @@ ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) if (param->Value->Flags & DATA_TF_NULL) return 1; if (param->Value->DataType != datatype) { - mssErrorf(1, "Cluster", "Type mismatch accessing parameter '%s'.", param->Name); + mssError(1, "Cluster", "Type mismatch accessing parameter '%s'.", param->Name); return -1; } @@ -3072,7 +3012,7 @@ ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) } err: - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Failed to get parameter ['%s' : %s]", attr_name, objTypeToStr(datatype) ); @@ -3085,7 +3025,7 @@ ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - mssErrorf(1, "Cluster", "SetParamValue() is not implemented because clusters are immutable."); + mssError(1, "Cluster", "SetParamValue() is not implemented because clusters are immutable."); return -1; } @@ -3135,7 +3075,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb node_struct = snNewNode(parent->Prev, usr_type); if (node_struct == NULL) { - mssErrorf(0, "Cluster", "Failed to exclusively create new node struct."); + mssError(0, "Cluster", "Failed to exclusively create new node struct."); goto err_free; } } @@ -3151,7 +3091,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** If there still isn't a node, fail early. **/ if (node_struct == NULL) { - mssErrorf(0, "Cluster", "Failed to create node struct from provided cluster file."); + mssError(0, "Cluster", "Failed to create node struct from provided cluster file."); goto err_free; } @@ -3163,7 +3103,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb node_data = ci_ParseNodeData(node_struct->Data, parent); if (node_data == NULL) { - mssErrorf(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(parent)); + mssError(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(parent)); goto err_free; } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -3223,7 +3163,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb } /** Path names sub-cluster that does not exist. **/ - mssErrorf(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); + mssError(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); goto err_free; continue_descent:; @@ -3248,14 +3188,14 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb char* extra_data = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); if (extra_data != NULL) { - mssErrorf(1, "Cluster", "Unknown path part %s.", extra_data); + mssError(1, "Cluster", "Unknown path part %s.", extra_data); goto err_free; } return (void*)driver_data; /* Success. */ } /** We were unable to find the requested cluster or search. **/ - mssErrorf(1, "Cluster", "\"%s\" is not the name of a declared cluster or search.", target_name); + mssError(1, "Cluster", "\"%s\" is not the name of a declared cluster or search.", target_name); /** Attempt to give a hint. **/ { @@ -3273,7 +3213,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb if (node_data != NULL) ci_FreeNodeData(node_data); if (driver_data != NULL) nmFree(driver_data, sizeof(DriverData)); - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to open cluster file \"%s\" at: %s", ci_file_name(parent), ci_file_path(parent) ); @@ -3366,7 +3306,7 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) err_free: /** Error cleanup. **/ if (query_data != NULL) nmFree(query_data, sizeof(ClusterQuery)); - mssErrorf(0, "Cluster", "Failed to open query."); + mssError(0, "Cluster", "Failed to open query."); err: return NULL; @@ -3458,7 +3398,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (ci_ComputeClusterData(target, node_data) != 0) { - mssErrorf(0, "Cluster", "Failed to compute ClusterData for query."); + mssError(0, "Cluster", "Failed to compute ClusterData for query."); goto err_free; } @@ -3479,7 +3419,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (ci_ComputeSearchData(target, node_data) != 0) { - mssErrorf(0, "Cluster", "Failed to compute SearchData for query."); + mssError(0, "Cluster", "Failed to compute SearchData for query."); goto err_free; } @@ -3495,11 +3435,11 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_CLUSTER_ENTRY: case TARGET_SEARCH_ENTRY: - mssErrorf(1, "Cluster", "Querying a query result is not allowed."); + mssError(1, "Cluster", "Querying a query result is not allowed."); goto err_free; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); goto err_free; } @@ -3510,7 +3450,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) return result_data; err_free: - mssErrorf(0, "Cluster", "Failed to fetch query result."); + mssError(0, "Cluster", "Failed to fetch query result."); done_free: if (result_data != NULL) nmFree(result_data, sizeof(DriverData)); @@ -3642,7 +3582,7 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) break; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); return DATA_T_UNAVAILABLE; } @@ -3703,7 +3643,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (expected_datatype == DATA_T_UNAVAILABLE) goto unknown_attribute; if (datatype != expected_datatype) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Type mismatch: Accessing attribute ['%s' : %s] as type %s.", attr_name, objTypeToStr(expected_datatype), objTypeToStr(datatype) ); @@ -3746,7 +3686,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); return -1; } @@ -3765,7 +3705,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); return -1; } return 0; @@ -3793,7 +3733,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: val->String = "cluster/search"; break; case TARGET_SEARCH_ENTRY: val->String = "search/entry"; break; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); return -1; } @@ -3879,7 +3819,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } /** Default: Unknown type. **/ - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); return -1; } @@ -4041,7 +3981,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); return -1; } @@ -4051,7 +3991,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO err:; char* name; clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Failed to get attribute for cluster object %s (target type: %u, \"%s\").", driver_data->NodeData->SourceData->Name, target_type, name ); @@ -4349,7 +4289,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } default: - mssErrorf(1, "Cluster", "Unknown target type %u.", target_type); + mssError(1, "Cluster", "Unknown target type %u.", target_type); goto err_free; } @@ -4366,7 +4306,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) char* internal_type = NULL; check(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); /* Failure ignored. */ check(clusterGetAttrValue(inf_v, "internal_type", DATA_T_STRING, POD(&internal_type), NULL)); /* Failure ignored. */ - mssErrorf(0, "Cluster", + mssError(0, "Cluster", "Failed to get presentation hints for object '%s' : \"%s\".", name, internal_type ); @@ -4425,7 +4365,7 @@ clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) case TARGET_CLUSTER_ENTRY: return ATTR_CLUSTER_ENTRY[i]; case TARGET_SEARCH_ENTRY: return ATTR_SEARCH_ENTRY[i]; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssError(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); return NULL; } @@ -4505,14 +4445,14 @@ clusterInfo(void* inf_v, pObjectInfo info) break; default: - mssErrorf(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + mssError(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); goto err; } return 0; err: - mssErrorf(0, "Cluster", "Failed execute get info."); + mssError(0, "Cluster", "Failed execute get info."); return -1; } @@ -4633,7 +4573,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) break; } default: - mssErrorf(0, "Cluster", "Unknown type_id %u.", *type_id_ptr); + mssError(0, "Cluster", "Unknown type_id %u.", *type_id_ptr); return -1; } @@ -4735,7 +4675,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree /** Second parameter is required. **/ if (param->String == NULL) { - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "[param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] is required for the cache method." ); goto err; @@ -4783,7 +4723,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree )); if (failed) { - mssErrorf(0, "Cluster", "Unexpected error occurred while showhing caches."); + mssError(0, "Cluster", "Unexpected error occurred while showing caches."); ret = -1; } @@ -4818,7 +4758,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree } /** Unknown parameter. **/ - mssErrorf(1, "Cluster", + mssError(1, "Cluster", "Expected [param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] for the cache method, but got: \"%s\"", param->String ); @@ -4848,7 +4788,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree } /** Unknown parameter. **/ - mssErrorf(1, "Cluster", "Unknown command: \"%s\"", method_name); + mssError(1, "Cluster", "Unknown command: \"%s\"", method_name); /** Attempt to give hint. **/ unsigned int n_methods = 0; @@ -4856,7 +4796,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (ci_TryHint(method_name, METHOD_NAMES, n_methods)); err: - mssErrorf(0, "Cluster", "Failed execute command."); + mssError(0, "Cluster", "Failed execute command."); return -1; } @@ -4870,7 +4810,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree int clusterCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterCreate() is not implemented."); + mssError(1, "Cluster", "clusterCreate() is not implemented."); return -ENOSYS; } @@ -4879,7 +4819,7 @@ clusterCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObj int clusterDelete(pObject obj, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterDelete() is not implemented."); + mssError(1, "Cluster", "clusterDelete() is not implemented."); return -1; } @@ -4888,7 +4828,7 @@ clusterDelete(pObject obj, pObjTrxTree* oxt) int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterDeleteObj() is not implemented."); + mssError(1, "Cluster", "clusterDeleteObj() is not implemented."); return -1; } @@ -4897,7 +4837,7 @@ clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) int clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterRead() not implemented."); + mssError(1, "Cluster", "clusterRead() not implemented."); fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); return -1; @@ -4907,7 +4847,7 @@ clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjT int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterWrite() not implemented because clusters are immutable."); + mssError(1, "Cluster", "clusterWrite() not implemented because clusters are immutable."); return -1; } @@ -4916,7 +4856,7 @@ clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxT int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are immutable."); + mssError(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are immutable."); return -1; } @@ -4925,7 +4865,7 @@ clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterAddAttr() not implemented because clusters are immutable."); + mssError(1, "Cluster", "clusterAddAttr() not implemented because clusters are immutable."); return -1; } @@ -4934,7 +4874,7 @@ clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterOpenAttr() not implemented."); + mssError(1, "Cluster", "clusterOpenAttr() not implemented."); return NULL; } @@ -4943,7 +4883,7 @@ clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) int clusterCommit(void* inf_v, pObjTrxTree* oxt) { - mssErrorf(1, "Cluster", "clusterCommit() not implemented because clusters are immutable."); + mssError(1, "Cluster", "clusterCommit() not implemented because clusters are immutable."); return 0; } @@ -5034,7 +4974,7 @@ clusterInitialize(void) nmFree(drv, sizeof(ObjDriver)); } - mssErrorf(1, "Cluster", "Failed to initialize cluster driver.\n"); + mssError(1, "Cluster", "Failed to initialize cluster driver.\n"); return -1; } From ddd99f66a624a6891dae2ace0b68721527c40116 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 14:06:57 -0600 Subject: [PATCH 079/361] Replace ci_xaToTrimmedArray() with xaToArray(). Add xaToArray() to xArray. Add xaTrim() to xArray. Remove ci_xaToTrimmedArray() from objdrv_cluster.c. Rewrite calls to ci_xaToTrimmedArray() to use xaToArray() instead. --- centrallix-lib/include/xarray.h | 2 + centrallix-lib/src/xarray.c | 38 +++++++++++++++++ centrallix/osdrivers/objdrv_cluster.c | 60 +++++++-------------------- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/centrallix-lib/include/xarray.h b/centrallix-lib/include/xarray.h index bcd107e79..9773767fa 100644 --- a/centrallix-lib/include/xarray.h +++ b/centrallix-lib/include/xarray.h @@ -50,6 +50,8 @@ int xaCount(pXArray this); int xaSetItem(pXArray this, int index, void* item); int xaInsertBefore(pXArray this, int index, void* item); int xaInsertAfter(pXArray this, int index, void* item); +int xaTrim(pXArray this); +void** xaToArray(pXArray this); #define CLD(x,y,z) ((x)((y)->Children.Items[(z)])) diff --git a/centrallix-lib/src/xarray.c b/centrallix-lib/src/xarray.c index 477e2fe24..3a7fa3429 100644 --- a/centrallix-lib/src/xarray.c +++ b/centrallix-lib/src/xarray.c @@ -6,6 +6,7 @@ #include #include #include "xarray.h" +#include "util.h" #include "newmalloc.h" /************************************************************************/ @@ -408,4 +409,41 @@ int xaInsertAfter(pXArray this, int index, void* item) return index+1; } +/*** Trims an xArray so that the allocated space matches the number of items + *** in the array. + *** + *** @param this The array to be trimmed. + *** @returns 0 if successful, or -1 if an error occurs. + ***/ +int +xaTrim(pXArray this) + { + /** Allocate the new internal items array. **/ + const size_t new_size = this->nItems * sizeof(void*); + void* new_items = check_ptr(nmSysRealloc(this->Items, new_size)); + if (new_items == NULL) return -1; + + /** Update the struct. **/ + this->Items = new_items; + this->nAlloc = this->nItems; + + return 0; + } +/*** Returns a new array with a shallow copy of the data of the xArray. This + *** new array is allocated with `nmSysMalloc()` and is the exact length that + *** is needed to store the items in the xArray. + *** + *** @param this The array to be read. + *** @returns The new array, or NULL if an error occurs. + ***/ +void** +xaToArray(pXArray this) + { + const size_t size = this->nItems * sizeof(void*); + void** result = check_ptr(nmSysMalloc(size)); + if (result == NULL) return NULL; + memcpy(result, this->Items, size); + + return result; + } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 9734f139a..806e95458 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -62,45 +62,6 @@ #define CI_DEFAULT_MAX_ITERATIONS 64u #define CI_NO_SEED 0u -/** ================ Stuff That Should Be Somewhere Else ================ **/ -/** ANCHOR[id=temp] **/ - -/** TODO: Greg - I think this should be moved to xarray. **/ -/*** Trims an xArray, returning a new array (with nmSysMalloc). - *** - *** @param arr The array to be trimmed. - *** @param cleanup 0: No clean up. - *** 1: DeInit arr. - *** 2: Free arr. - *** *: Any other value prints a warning and does no clean up. - *** @returns The new array, or NULL if and only if `arr` is empty. - ***/ -static void** -ci_xaToTrimmedArray(pXArray arr, int array_handling) - { - const size_t arr_size = arr->nItems * sizeof(void*); - void** result = check_ptr(nmSysMalloc(arr_size)); - if (result == NULL) return NULL; - memcpy(result, arr->Items, arr_size); - - /** Handle the array. **/ - switch (array_handling) - { - case 0: break; - case 1: check(xaDeInit(arr)); arr->nAlloc = 0; break; /* Failure ignored. */ - case 2: check(xaFree(arr)); break; /* Failure ignored. */ - default: - /** Uh oh, this might cause a memory leak... **/ - fprintf(stderr, - "Warning: ci_xaToTrimmedArray(%p, %d) - Unknown value (%d) for array_handling.\n", - arr, array_handling, array_handling - ); - break; - } - - return result; - } - /** I got tired of forgetting how to do these. **/ #define ci_file_name(obj) \ ({ \ @@ -1262,7 +1223,9 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source } } cluster_data->nSubClusters = sub_clusters.nItems; - cluster_data->SubClusters = (pClusterData*)ci_xaToTrimmedArray(&sub_clusters, 1); + cluster_data->SubClusters = check_ptr(xaToArray(&sub_clusters)); + if (cluster_data->SubClusters == NULL) goto err_free; + check(xaDeInit(&sub_clusters)); /* Failure ignored. */ sub_clusters.nAlloc = 0; /** Create the cache key. **/ @@ -2442,18 +2405,21 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Trim and store keys. **/ - source_data->Keys = (char**)check_ptr(ci_xaToTrimmedArray(&key_xarray, 1)); + source_data->Keys = check_ptr(xaToArray(&key_xarray)); if (source_data->Keys == NULL) goto end_free; + check(xaDeInit(&key_xarray)); /* Failure ignored. */ key_xarray.nAlloc = 0; /** Trim and store data strings. **/ - source_data->Strings = (char**)check_ptr(ci_xaToTrimmedArray(&data_xarray, 1)); + source_data->Strings = check_ptr(xaToArray(&data_xarray)); if (source_data->Strings == NULL) goto end_free; + check(xaDeInit(&data_xarray)); /* Failure ignored. */ data_xarray.nAlloc = 0; /** Trim and store vectors. **/ - source_data->Vectors = (int**)check_ptr(ci_xaToTrimmedArray(&vector_xarray, 1)); - if (source_data->Vectors == NULL) goto end_free; /* Should be unreachable. */ + source_data->Vectors = check_ptr(xaToArray(&vector_xarray)); + if (source_data->Vectors == NULL) goto end_free; + check(xaDeInit(&vector_xarray)); /* Failure ignored. */ vector_xarray.nAlloc = 0; /** Success. **/ @@ -2899,8 +2865,10 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) search_data->Pairs = check_ptr(nmSysMalloc(0)); else { - search_data->Pairs = (pPair*)check_ptr(ci_xaToTrimmedArray(pairs, 2)); /* Contract promises this won't fail. */ - pairs = NULL; /* Freed by ci_xaToTrimmedArray(). */ + search_data->Pairs = check_ptr(xaToArray(pairs)); + if (search_data->Pairs == NULL) goto err_free; + check(xaFree(&pairs)); /* Failure ignored. */ + pairs = NULL; } if (search_data->Pairs == NULL) { From d17c04351465c8fff914547d9d35c08651729c35 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 14:08:27 -0600 Subject: [PATCH 080/361] Fix a bug in exp_fn_compare() any correct use of the function to false fail with no error. Improve error handling in exp_fn_compare() to guarantee an error message. Improve codestyle. Clean up. --- centrallix/expression/exp_functions.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 2858ee572..5b92b10ce 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4601,13 +4601,13 @@ exp_fn_compare(pExpression tree) }, tree) != 0) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); - return -1; + goto err; } /** Extract strings. **/ pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); - if (maybe_str1 == NULL || maybe_str2) return -1; + if (maybe_str1 == NULL || maybe_str2 == NULL) goto err; if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; @@ -4616,7 +4616,7 @@ exp_fn_compare(pExpression tree) } char* str1 = check_ptr(maybe_str1->String); char* str2 = check_ptr(maybe_str2->String); - if (str1 == NULL || str2) return -1; + if (str1 == NULL || str2 == NULL) goto err; /** Handle either cos_compare() or lev_compare(). **/ if (tree->Name[0] == 'c') @@ -4645,15 +4645,19 @@ exp_fn_compare(pExpression tree) /** Clean up. **/ if (v1 != NULL) ca_free_vector(v1); if (v2 != NULL) ca_free_vector(v2); - return ret; + if (ret == -1) goto err; + else return 0; } else { /* lev_compare() */ - double lev_sim = check_double(ca_lev_compare(str1, str2)); + const double lev_sim = check_double(ca_lev_compare(str1, str2)); if (isnan(lev_sim)) { - mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute Levenshtein edit distance."); - return -1; + mssError(1, "EXP", + "%s(\"%s\", \"%s\"): Failed to compute Levenshtein edit distance.", + tree->Name, str1, str2 + ); + goto err; } /** Return the computed result. **/ @@ -4661,8 +4665,10 @@ exp_fn_compare(pExpression tree) tree->DataType = DATA_T_DOUBLE; return 0; } - - return -1; /* Unreachable. */ + + err: + mssError(0, "EXP", "%s(): Failed to compute Levenshtein edit distance.", tree->Name); + return -1; } int From 8d5364ebae5a4e4903dc41d3af262358af60031c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 14:08:47 -0600 Subject: [PATCH 081/361] Add detail to the description in the copyright notice for the cluster driver. --- centrallix/osdrivers/objdrv_cluster.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 806e95458..5bb17f1f5 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -25,7 +25,11 @@ /* Module: objdrv_cluster.c */ /* Author: Israel Fuller */ /* Creation: September 17, 2025 */ -/* Description: Cluster object driver. */ +/* Description: Object driver for Centrallix cluster objects. This */ +/* driver handles cluster configuration and access so */ +/* clustered resources can be opened, searched efficiently */ +/* with caching to prevent unnecessary re-computation, and */ +/* managed through the object system. */ /************************************************************************/ #include From 7e2979bbef73c661e6b5552dc8362b7f0393d961 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 14:18:55 -0600 Subject: [PATCH 082/361] Improve how enums are implemented in objdrv_cluster.c. Improve how the number of enum macros is calculated to make them more expandable. Rename macros for the number of enum types to match style. Add ALL_TARGET_TYPES and N_TARGET_TYPES. --- centrallix/osdrivers/objdrv_cluster.c | 42 +++++++++++++++++---------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 5bb17f1f5..cb60d0ee0 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -92,8 +92,7 @@ typedef unsigned char ClusterAlgorithm; #define ALGORITHM_KMEDOIDS (ClusterAlgorithm)5u #define ALGORITHM_DB_SCAN (ClusterAlgorithm)6u -#define nClusteringAlgorithms 7u -ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[nClusteringAlgorithms] = +ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[] = { ALGORITHM_NULL, ALGORITHM_NONE, @@ -103,6 +102,7 @@ ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[nClusteringAlgorithms] = ALGORITHM_KMEDOIDS, ALGORITHM_DB_SCAN, }; +#define N_CLUSTERING_ALGORITHMS ((unsigned int)(sizeof(ALL_CLUSTERING_ALGORITHMS) / sizeof(ClusterAlgorithm))) /** Converts a clustering algorithm to its string name. **/ char* @@ -129,13 +129,13 @@ typedef unsigned char SimilarityMeasure; #define SIMILARITY_COSINE (SimilarityMeasure)1u #define SIMILARITY_LEVENSHTEIN (SimilarityMeasure)2u -#define nSimilarityMeasures 3u -SimilarityMeasure ALL_SIMILARITY_MEASURES[nSimilarityMeasures] = +SimilarityMeasure ALL_SIMILARITY_MEASURES[] = { SIMILARITY_NULL, SIMILARITY_COSINE, SIMILARITY_LEVENSHTEIN, }; +#define N_SIMILARITY_MEASURES ((unsigned int)(sizeof(ALL_SIMILARITY_MEASURES) / sizeof(SimilarityMeasure))) /*** Converts a similarity measure to its string name. *** @@ -196,6 +196,16 @@ typedef unsigned char TargetType; #define TARGET_CLUSTER_ENTRY (TargetType)4u #define TARGET_SEARCH_ENTRY (TargetType)5u +TargetType ALL_TARGET_TYPES[] = + { + TARGET_NODE, + TARGET_CLUSTER, + TARGET_SEARCH, + TARGET_CLUSTER_ENTRY, + TARGET_SEARCH_ENTRY, + }; +#define N_TARGET_TYPES ((unsigned int)(sizeof(ALL_TARGET_TYPES) / sizeof(TargetType))) + /** Attribute name lists by TargetType. **/ #define END_OF_ARRAY NULL char* ATTR_ROOT[] = @@ -837,10 +847,10 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) mssError(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); /** Attempt to give a hint. **/ - char* all_names[nClusteringAlgorithms] = {NULL}; - for (unsigned int i = 1u; i < nClusteringAlgorithms; i++) + char* all_names[N_CLUSTERING_ALGORITHMS] = {NULL}; + for (unsigned int i = 1u; i < N_CLUSTERING_ALGORITHMS; i++) all_names[i] = ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); - if (ci_TryHint(algorithm, all_names, nClusteringAlgorithms)); + if (ci_TryHint(algorithm, all_names, N_CLUSTERING_ALGORITHMS)); else if (strcasecmp(algorithm, "sliding") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); @@ -881,10 +891,10 @@ ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) mssError(1, "Cluster", "Unknown \"similarity measure\": %s", measure); /** Attempt to give a hint. **/ - char* all_names[nSimilarityMeasures] = {NULL}; - for (unsigned int i = 1u; i < nSimilarityMeasures; i++) + char* all_names[N_SIMILARITY_MEASURES] = {NULL}; + for (unsigned int i = 1u; i < N_SIMILARITY_MEASURES; i++) all_names[i] = ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); - if (ci_TryHint(measure, all_names, nSimilarityMeasures)); + if (ci_TryHint(measure, all_names, N_SIMILARITY_MEASURES)); else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); @@ -4123,14 +4133,14 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "algorithm") == 0) { /** Enum values. **/ - check(xaInit(&(hints->EnumList), nClusteringAlgorithms)); /* Failure ignored. */ - for (unsigned int i = 0u; i < nClusteringAlgorithms; i++) + check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)); /* Failure ignored. */ + for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); /* Failure ignored. */ /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); char buf[8]; - snprintf(buf, sizeof(buf), "%d", nClusteringAlgorithms); + snprintf(buf, sizeof(buf), "%d", N_CLUSTERING_ALGORITHMS); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); /** Display flags. **/ @@ -4149,8 +4159,8 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "similarity_measure") == 0) { /** Enum values. **/ - check(xaInit(&(hints->EnumList), nSimilarityMeasures)); /* Failure ignored. */ - for (unsigned int i = 0u; i < nSimilarityMeasures; i++) + check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)); /* Failure ignored. */ + for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); /* Failure ignored. */ /** Display flags. **/ @@ -4160,7 +4170,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); char buf[8]; - snprintf(buf, sizeof(buf), "%d", nSimilarityMeasures); + snprintf(buf, sizeof(buf), "%d", N_SIMILARITY_MEASURES); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); /** Other hints. **/ From 59eda463c6fb7bc8b48dcbbb49d378f333b05a63 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 16:21:11 -0600 Subject: [PATCH 083/361] Re-add typecasts that I removed because I thought they were optional. --- centrallix/osdrivers/objdrv_cluster.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index cb60d0ee0..2b5c47910 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1237,7 +1237,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source } } cluster_data->nSubClusters = sub_clusters.nItems; - cluster_data->SubClusters = check_ptr(xaToArray(&sub_clusters)); + cluster_data->SubClusters = (ClusterData**)check_ptr(xaToArray(&sub_clusters)); if (cluster_data->SubClusters == NULL) goto err_free; check(xaDeInit(&sub_clusters)); /* Failure ignored. */ sub_clusters.nAlloc = 0; @@ -2419,19 +2419,19 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Trim and store keys. **/ - source_data->Keys = check_ptr(xaToArray(&key_xarray)); + source_data->Keys = (char**)check_ptr(xaToArray(&key_xarray)); if (source_data->Keys == NULL) goto end_free; check(xaDeInit(&key_xarray)); /* Failure ignored. */ key_xarray.nAlloc = 0; /** Trim and store data strings. **/ - source_data->Strings = check_ptr(xaToArray(&data_xarray)); + source_data->Strings = (char**)check_ptr(xaToArray(&data_xarray)); if (source_data->Strings == NULL) goto end_free; check(xaDeInit(&data_xarray)); /* Failure ignored. */ data_xarray.nAlloc = 0; /** Trim and store vectors. **/ - source_data->Vectors = check_ptr(xaToArray(&vector_xarray)); + source_data->Vectors = (int**)check_ptr(xaToArray(&vector_xarray)); if (source_data->Vectors == NULL) goto end_free; check(xaDeInit(&vector_xarray)); /* Failure ignored. */ vector_xarray.nAlloc = 0; @@ -2879,7 +2879,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) search_data->Pairs = check_ptr(nmSysMalloc(0)); else { - search_data->Pairs = check_ptr(xaToArray(pairs)); + search_data->Pairs = (pPair*)check_ptr(xaToArray(pairs)); if (search_data->Pairs == NULL) goto err_free; check(xaFree(&pairs)); /* Failure ignored. */ pairs = NULL; From cf26c0942c8b5d61b3eb7deb7a74833b2c886c3b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 16:21:18 -0600 Subject: [PATCH 084/361] Fix a memory error. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 2b5c47910..7bdf65a60 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2881,7 +2881,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { search_data->Pairs = (pPair*)check_ptr(xaToArray(pairs)); if (search_data->Pairs == NULL) goto err_free; - check(xaFree(&pairs)); /* Failure ignored. */ + check(xaFree(pairs)); /* Failure ignored. */ pairs = NULL; } if (search_data->Pairs == NULL) From f447c197ffa1f5337dfa667b10d77d049f532a8c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 16:27:35 -0600 Subject: [PATCH 085/361] Move file name and file path macros to obj.h to promote future reuse. Add objFileName() and objFilePath() to obj.h. Remove ci_file_name() and ci_file_path() from objdrv_cluster.c. Update callers of removed functions to use the new functions. --- centrallix/include/obj.h | 13 +++++++++++++ centrallix/osdrivers/objdrv_cluster.c | 22 +++++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index 1c55ed9e6..31f6e644f 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -631,6 +631,19 @@ typedef struct #define OBJ_MQ_F_NOUPDATE (1<<1) /* disallow any updates in this query */ #define OBJ_MQ_F_ONEROW (1<<2) /* only need first row from results */ +/** @returns The file name for the provided object. **/ +#define objFileName(obj) \ + ({ \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, _obj->SubPtr - 1, 1); \ + }) +/** @returns The file path to the provided object. **/ +#define objFilePath(obj) \ + ({ \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ + }) + /** objectsystem main functions **/ int objInitialize(); diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 7bdf65a60..ba1b152ae 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -66,18 +66,6 @@ #define CI_DEFAULT_MAX_ITERATIONS 64u #define CI_NO_SEED 0u -/** I got tired of forgetting how to do these. **/ -#define ci_file_name(obj) \ - ({ \ - __typeof__ (obj) _obj = (obj); \ - obj_internal_PathPart(_obj->Pathname, _obj->SubPtr - 1, 1); \ - }) -#define ci_file_path(obj) \ - ({ \ - __typeof__ (obj) _obj = (obj); \ - obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ - }) - /** ================ Enum Declarations ================ **/ /** ANCHOR[id=enums] **/ @@ -1549,7 +1537,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ASSERTMAGIC(parent, MGK_OBJECT); /** Get file path. **/ - char* path = check_ptr(ci_file_path(parent)); + char* path = check_ptr(objFilePath(parent)); if (path == NULL) goto err_free; /** Allocate node struct data. **/ @@ -1768,7 +1756,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) goto next_provided_param; /** This param doesn't exist, warn the user and attempt to give them a hint. **/ - fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, ci_file_name(parent)); + fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, objFileName(parent)); char** param_names = check_ptr(nmSysMalloc(node_data->nParams * sizeof(char*))); for (unsigned int j = 0u; j < node_data->nParams; j++) param_names[j] = node_data->Params[j]->Name; @@ -3085,7 +3073,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb node_data = ci_ParseNodeData(node_struct->Data, parent); if (node_data == NULL) { - mssError(0, "Cluster", "Failed to parse structure file \"%s\".", ci_file_name(parent)); + mssError(0, "Cluster", "Failed to parse structure file \"%s\".", objFileName(parent)); goto err_free; } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -3197,7 +3185,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb mssError(0, "Cluster", "Failed to open cluster file \"%s\" at: %s", - ci_file_name(parent), ci_file_path(parent) + objFileName(parent), objFilePath(parent) ); return NULL; @@ -4672,7 +4660,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (skip_uncomputed == 1ull || strcmp(param->String, "show") == 0) { show = true; - path = ci_file_path(driver_data->NodeData->Parent); + path = objFilePath(driver_data->NodeData->Parent); } if (strcmp(param->String, "show_all") == 0) show = true; From 9449178c6a17fb6267be81e7261d1ebde832efef Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 17:12:06 -0600 Subject: [PATCH 086/361] Refactor attribute name lists to improve reusability and extendability. Add DRIVER_ATTRIBUTE_NAMES global macro to obj.h. Add N_DRIVER_ATTRIBUTE_NAMES global macro to obj.h. Add macros that give the length of attribute arrays. Add macros that give the number of computed and input attributes in attribute arrays. Add requirement that input attributes must be listed before computed attributes to simplify access. Remove null termination from attribute arrays. Rename attribute arrays in objdrv_cluster.c. Update code that used attribute arrays to handle the changes and benefit from the new features. Update enums in objdrv_cluster.c to use an even more extendable (and more consistent) method for counting the number of valid values. --- centrallix/include/obj.h | 15 +++++ centrallix/osdrivers/objdrv_cluster.c | 81 ++++++++++++++------------- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index 31f6e644f..7caf87990 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -644,6 +644,21 @@ typedef struct obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ }) +/*** An array of the names of the general attributes that must be implemented + *** for every object system driver. See `OSDriver_Authoring.md` for more + *** information. + ***/ +#define DRIVER_ATTRIBUTE_NAMES \ + ((char*[]){ \ + "name", \ + "annotation", \ + "content_type", \ + "inner_type", \ + "outer_type", \ + "last_modification", \ + }) +#define N_DRIVER_ATTRIBUTE_NAMES ((unsigned int)(sizeof(DRIVER_ATTRIBUTE_NAMES) / sizeof(DRIVER_ATTRIBUTE_NAMES[0]))) + /** objectsystem main functions **/ int objInitialize(); diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ba1b152ae..ca9e37ee8 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -90,7 +90,7 @@ ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[] = ALGORITHM_KMEDOIDS, ALGORITHM_DB_SCAN, }; -#define N_CLUSTERING_ALGORITHMS ((unsigned int)(sizeof(ALL_CLUSTERING_ALGORITHMS) / sizeof(ClusterAlgorithm))) +#define N_CLUSTERING_ALGORITHMS ((unsigned int)(sizeof(ALL_CLUSTERING_ALGORITHMS) / sizeof(ALL_CLUSTERING_ALGORITHMS[0]))) /** Converts a clustering algorithm to its string name. **/ char* @@ -123,7 +123,7 @@ SimilarityMeasure ALL_SIMILARITY_MEASURES[] = SIMILARITY_COSINE, SIMILARITY_LEVENSHTEIN, }; -#define N_SIMILARITY_MEASURES ((unsigned int)(sizeof(ALL_SIMILARITY_MEASURES) / sizeof(SimilarityMeasure))) +#define N_SIMILARITY_MEASURES ((unsigned int)(sizeof(ALL_SIMILARITY_MEASURES) / sizeof(ALL_SIMILARITY_MEASURES[0]))) /*** Converts a similarity measure to its string name. *** @@ -192,18 +192,23 @@ TargetType ALL_TARGET_TYPES[] = TARGET_CLUSTER_ENTRY, TARGET_SEARCH_ENTRY, }; -#define N_TARGET_TYPES ((unsigned int)(sizeof(ALL_TARGET_TYPES) / sizeof(TargetType))) +#define N_TARGET_TYPES ((unsigned int)(sizeof(ALL_TARGET_TYPES) / sizeof(ALL_TARGET_TYPES[0]))) -/** Attribute name lists by TargetType. **/ -#define END_OF_ARRAY NULL -char* ATTR_ROOT[] = +/*** Attribute name lists by TargetType. + *** + *** Promises that input attributes are listed first, before computed attributes. + ***/ +char* ROOT_ATTRS[] = { "source", "key_attr", "data_attr", END_OF_ARRAY, }; -char* ATTR_CLUSTER[] = +#define N_ROOT_ATTRS ((unsigned int)(sizeof(ROOT_ATTRS) / sizeof(ROOT_ATTRS[0]))) +#define N_COMPUTED_ROOT_ATTRS 1u +#define N_INPUT_ROOT_ATTRS (N_ROOT_ATTRS - N_COMPUTED_ROOT_ATTRS) +char* CLUSTER_ATTRS[] = { "algorithm", "similarity_measure", @@ -213,37 +218,46 @@ char* ATTR_CLUSTER[] = "seed", "date_created", "date_computed", - END_OF_ARRAY, }; -char* ATTR_SEARCH[] = +#define N_CLUSTER_ATTRS ((unsigned int)(sizeof(CLUSTER_ATTRS) / sizeof(CLUSTER_ATTRS[0]))) +#define N_COMPUTED_CLUSTER_ATTRS 2u +#define N_INPUT_CLUSTER_ATTRS (N_CLUSTER_ATTRS - N_COMPUTED_CLUSTER_ATTRS) +char* SEARCH_ATTRS[] = { "source", "threshold", "similarity_measure", "date_created", "date_computed", - END_OF_ARRAY, }; -char* ATTR_CLUSTER_ENTRY[] = +#define N_SEARCH_ATTRS ((unsigned int)(sizeof(SEARCH_ATTRS) / sizeof(SEARCH_ATTRS[0]))) +#define N_COMPUTED_SEARCH_ATTRS 2u +#define N_INPUT_SEARCH_ATTRS (N_SEARCH_ATTRS - N_COMPUTED_SEARCH_ATTRS) +char* CLUSTER_ENTRY_ATTRS[] = { "items", END_OF_ARRAY, }; -char* ATTR_SEARCH_ENTRY[] = +#define N_CLUSTER_ENTRY_ATTRS ((unsigned int)(sizeof(CLUSTER_ENTRY_ATTRS) / sizeof(CLUSTER_ENTRY_ATTRS[0]))) +#define N_COMPUTED_CLUSTER_ENTRY_ATTRS 2u +#define N_INPUT_CLUSTER_ENTRY_ATTRS (N_CLUSTER_ENTRY_ATTRS - N_COMPUTED_CLUSTER_ENTRY_ATTRS) +char* SEARCH_ENTRY_ATTRS[] = { "key1", "key2", "sim", - END_OF_ARRAY, }; +#define N_SEARCH_ENTRY_ATTRS ((unsigned int)(sizeof(SEARCH_ENTRY_ATTRS) / sizeof(SEARCH_ENTRY_ATTRS[0]))) +#define N_COMPUTED_SEARCH_ENTRY_ATTRS 2u +#define N_INPUT_SEARCH_ENTRY_ATTRS (N_SEARCH_ENTRY_ATTRS - N_COMPUTED_SEARCH_ENTRY_ATTRS) /** Method name list. **/ char* METHOD_NAMES[] = { "cache", "stat", - END_OF_ARRAY, }; +#define METHOD_NAMES_COUNT ((unsigned int)(sizeof(METHOD_NAMES) / sizeof(METHOD_NAMES[0]))) /** ================ Struct Declarations ================ **/ @@ -681,18 +695,19 @@ ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) static void ci_UnknownAttribute(char* attr_name, const int target_type) { - /** Display the error message. */ + /** Display the error message. **/ mssError(1, "Cluster", "Unknown attribute '%s'.", attr_name); /** Collect specific attributes based on target type. **/ - char** specific_attrs = NULL; + char** my_attrs = NULL; + unsigned int n_my_attrs = 0u; switch (target_type) { - case TARGET_NODE: specific_attrs = ATTR_ROOT; break; - case TARGET_CLUSTER: specific_attrs = ATTR_CLUSTER; break; - case TARGET_SEARCH: specific_attrs = ATTR_SEARCH; break; - case TARGET_CLUSTER_ENTRY: specific_attrs = ATTR_CLUSTER_ENTRY; break; - case TARGET_SEARCH_ENTRY: specific_attrs = ATTR_SEARCH_ENTRY; break; + case TARGET_NODE: my_attrs = ROOT_ATTRS; n_my_attrs = N_INPUT_ROOT_ATTRS; break; + case TARGET_CLUSTER: my_attrs = CLUSTER_ATTRS; n_my_attrs = N_INPUT_CLUSTER_ATTRS; break; + case TARGET_SEARCH: my_attrs = SEARCH_ATTRS; n_my_attrs = N_INPUT_SEARCH_ATTRS; break; + case TARGET_CLUSTER_ENTRY: my_attrs = CLUSTER_ENTRY_ATTRS; n_my_attrs = N_INPUT_CLUSTER_ENTRY_ATTRS; break; + case TARGET_SEARCH_ENTRY: my_attrs = SEARCH_ENTRY_ATTRS; n_my_attrs = N_INPUT_SEARCH_ENTRY_ATTRS; break; default: mssError(0, "Cluster", "Unknown target type %u detected while attempting to generate hint.", @@ -701,19 +716,9 @@ ci_UnknownAttribute(char* attr_name, const int target_type) return; } - /** Count specific attributes. **/ - unsigned int n_specific_attrs = 0; - while (specific_attrs[n_specific_attrs] != NULL) n_specific_attrs++; - - /** Collect general attributes. */ - char** general_attrs = (char*[]){ - "name", "annotation", "content_type", "inner_type", "outer_type", - "internal_type", "date_computed", "date_created", "last_modification", - }; - /** Attempt to give hints. **/ - if (ci_TryHint(attr_name, specific_attrs, n_specific_attrs)); - else if (ci_TryHint(attr_name, general_attrs, 9)); + if (ci_TryHint(attr_name, my_attrs, n_my_attrs)); + else if (ci_TryHint(attr_name, DRIVER_ATTRIBUTE_NAMES, N_DRIVER_ATTRIBUTE_NAMES)); } @@ -4329,11 +4334,11 @@ clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) const unsigned int i = driver_data->TargetAttrIndex++; switch (driver_data->TargetType) { - case TARGET_NODE: return ATTR_ROOT[i]; - case TARGET_CLUSTER: return ATTR_CLUSTER[i]; - case TARGET_SEARCH: return ATTR_SEARCH[i]; - case TARGET_CLUSTER_ENTRY: return ATTR_CLUSTER_ENTRY[i]; - case TARGET_SEARCH_ENTRY: return ATTR_SEARCH_ENTRY[i]; + case TARGET_NODE: return (i < N_ROOT_ATTRS) ? ROOT_ATTRS[i] : NULL; + case TARGET_CLUSTER: return (i < N_CLUSTER_ATTRS) ? CLUSTER_ATTRS[i] : NULL; + case TARGET_SEARCH: return (i < N_SEARCH_ATTRS) ? SEARCH_ATTRS[i] : NULL; + case TARGET_CLUSTER_ENTRY: return (i < N_CLUSTER_ENTRY_ATTRS) ? CLUSTER_ENTRY_ATTRS[i] : NULL; + case TARGET_SEARCH_ENTRY: return (i < N_SEARCH_ENTRY_ATTRS) ? SEARCH_ENTRY_ATTRS[i] : NULL; default: mssError(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); return NULL; From fd84d7939ed82f253b8bee5e9300337fe840bde0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 17:21:33 -0600 Subject: [PATCH 087/361] Revisit attributes offered by the cluster driver. Update test_objdrv_cluster_00.to and cmp to handle the new attributes. Add the 'internal_type' attribute to the clusterGetType() function. Remove a use of the END_OF_ARRAY macro that I forgot to remove before, causing a compiler error. Clean up formatting and other mistakes. --- centrallix/osdrivers/objdrv_cluster.c | 12 +- centrallix/tests/test_objdrv_cluster_00.cmp | 172 ++++++++++---------- centrallix/tests/test_objdrv_cluster_00.to | 32 ++-- 3 files changed, 110 insertions(+), 106 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ca9e37ee8..f0bc461be 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -203,7 +203,7 @@ char* ROOT_ATTRS[] = "source", "key_attr", "data_attr", - END_OF_ARRAY, + "internal_type", }; #define N_ROOT_ATTRS ((unsigned int)(sizeof(ROOT_ATTRS) / sizeof(ROOT_ATTRS[0]))) #define N_COMPUTED_ROOT_ATTRS 1u @@ -236,7 +236,8 @@ char* SEARCH_ATTRS[] = char* CLUSTER_ENTRY_ATTRS[] = { "items", - END_OF_ARRAY, + "date_computed", + "date_created", }; #define N_CLUSTER_ENTRY_ATTRS ((unsigned int)(sizeof(CLUSTER_ENTRY_ATTRS) / sizeof(CLUSTER_ENTRY_ATTRS[0]))) #define N_COMPUTED_CLUSTER_ENTRY_ATTRS 2u @@ -246,6 +247,8 @@ char* SEARCH_ENTRY_ATTRS[] = "key1", "key2", "sim", + "date_computed", + "date_created", }; #define N_SEARCH_ENTRY_ATTRS ((unsigned int)(sizeof(SEARCH_ENTRY_ATTRS) / sizeof(SEARCH_ENTRY_ATTRS[0]))) #define N_COMPUTED_SEARCH_ENTRY_ATTRS 2u @@ -3495,9 +3498,10 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Types for general attributes. **/ if (strcmp(attr_name, "name") == 0 || strcmp(attr_name, "annotation") == 0 - || strcmp(attr_name,"content_type") == 0 + || strcmp(attr_name, "content_type") == 0 || strcmp(attr_name, "inner_type") == 0 - || strcmp(attr_name,"outer_type") == 0) + || strcmp(attr_name, "outer_type") == 0 + || strcmp(attr_name, "internal_type") == 0) return DATA_T_STRING; if (strcmp(attr_name, "last_modification") == 0) return DATA_T_DATETIME; diff --git a/centrallix/tests/test_objdrv_cluster_00.cmp b/centrallix/tests/test_objdrv_cluster_00.cmp index 71b791587..b8949bc68 100644 --- a/centrallix/tests/test_objdrv_cluster_00.cmp +++ b/centrallix/tests/test_objdrv_cluster_00.cmp @@ -8,89 +8,89 @@ Attribute [similarity_measure]: string "cosine" Attribute [num_clusters]: integer NULL Attribute [source]: string "cluster" Attribute [threshold]: double 0.75 -Attribute [items_k1]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" -Attribute [items_k2]: stringve "john","johnny","johns" -Attribute [items_k2]: stringve "mathew","MatTheW","mathews","math" -Attribute [items_k3]: stringve "johnny" -Attribute [items_k3]: stringve "mathew","MatTheW","mathews","math" -Attribute [items_k3]: stringve "john","johns" -Attribute [items_k4]: stringve "johnny" -Attribute [items_k4]: stringve "MatTheW" -Attribute [items_k4]: stringve "john","johns" -Attribute [items_k4]: stringve "mathew","mathews","math" -Attribute [items_k1]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" -Attribute [items_k2]: stringve "john","johnny","johns" -Attribute [items_k2]: stringve "mathew","MatTheW","mathews","math" -Attribute [items_k3]: stringve "johnny" -Attribute [items_k3]: stringve "mathew","MatTheW","mathews","math" -Attribute [items_k3]: stringve "john","johns" -Attribute [items_k4]: stringve "johnny" -Attribute [items_k4]: stringve "MatTheW" -Attribute [items_k4]: stringve "john","johns" -Attribute [items_k4]: stringve "mathew","mathews","math" -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.757051 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "4: MatTheW" -Attribute [sim]: double 0.922627 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.947714 -Attribute [key1]: string "4: MatTheW" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.874386 -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.757051 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "4: MatTheW" -Attribute [sim]: double 0.922627 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.947714 -Attribute [key1]: string "4: MatTheW" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.874386 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "4: MatTheW" -Attribute [sim]: double 0.922627 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.947714 -Attribute [key1]: string "4: MatTheW" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.874386 -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.757051 -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.757051 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.947714 -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.8 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.857143 -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.8 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.857143 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.857143 -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.8 -Attribute [key1]: string "0: john" -Attribute [key2]: string "2: johns" -Attribute [sim]: double 0.8 -Attribute [key1]: string "3: mathew" -Attribute [key2]: string "5: mathews" -Attribute [sim]: double 0.857143 +Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [k2_items]: stringve "john","johnny","johns" +Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "johnny" +Attribute [k3_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "john","johns" +Attribute [k4_items]: stringve "johnny" +Attribute [k4_items]: stringve "MatTheW" +Attribute [k4_items]: stringve "john","johns" +Attribute [k4_items]: stringve "mathew","mathews","math" +Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [k2_items]: stringve "john","johnny","johns" +Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "johnny" +Attribute [k3_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "john","johns" +Attribute [k4_items]: stringve "johnny" +Attribute [k4_items]: stringve "MatTheW" +Attribute [k4_items]: stringve "john","johns" +Attribute [k4_items]: stringve "mathew","mathews","math" +Attribute [k1_key1]: string "0: john" +Attribute [k1_key2]: string "2: johns" +Attribute [k1_sim]: double 0.757051 +Attribute [k1_key1]: string "3: mathew" +Attribute [k1_key2]: string "4: MatTheW" +Attribute [k1_sim]: double 0.922627 +Attribute [k1_key1]: string "3: mathew" +Attribute [k1_key2]: string "5: mathews" +Attribute [k1_sim]: double 0.947714 +Attribute [k1_key1]: string "4: MatTheW" +Attribute [k1_key2]: string "5: mathews" +Attribute [k1_sim]: double 0.874386 +Attribute [k2_key1]: string "0: john" +Attribute [k2_key2]: string "2: johns" +Attribute [k2_sim]: double 0.757051 +Attribute [k2_key1]: string "3: mathew" +Attribute [k2_key2]: string "4: MatTheW" +Attribute [k2_sim]: double 0.922627 +Attribute [k2_key1]: string "3: mathew" +Attribute [k2_key2]: string "5: mathews" +Attribute [k2_sim]: double 0.947714 +Attribute [k2_key1]: string "4: MatTheW" +Attribute [k2_key2]: string "5: mathews" +Attribute [k2_sim]: double 0.874386 +Attribute [k3_key1]: string "3: mathew" +Attribute [k3_key2]: string "4: MatTheW" +Attribute [k3_sim]: double 0.922627 +Attribute [k3_key1]: string "3: mathew" +Attribute [k3_key2]: string "5: mathews" +Attribute [k3_sim]: double 0.947714 +Attribute [k3_key1]: string "4: MatTheW" +Attribute [k3_key2]: string "5: mathews" +Attribute [k3_sim]: double 0.874386 +Attribute [k3_key1]: string "0: john" +Attribute [k3_key2]: string "2: johns" +Attribute [k3_sim]: double 0.757051 +Attribute [k4_key1]: string "0: john" +Attribute [k4_key2]: string "2: johns" +Attribute [k4_sim]: double 0.757051 +Attribute [k4_key1]: string "3: mathew" +Attribute [k4_key2]: string "5: mathews" +Attribute [k4_sim]: double 0.947714 +Attribute [k1_key1]: string "0: john" +Attribute [k1_key2]: string "2: johns" +Attribute [k1_sim]: double 0.8 +Attribute [k1_key1]: string "3: mathew" +Attribute [k1_key2]: string "5: mathews" +Attribute [k1_sim]: double 0.857143 +Attribute [k2_key1]: string "0: john" +Attribute [k2_key2]: string "2: johns" +Attribute [k2_sim]: double 0.8 +Attribute [k2_key1]: string "3: mathew" +Attribute [k2_key2]: string "5: mathews" +Attribute [k2_sim]: double 0.857143 +Attribute [k3_key1]: string "3: mathew" +Attribute [k3_key2]: string "5: mathews" +Attribute [k3_sim]: double 0.857143 +Attribute [k3_key1]: string "0: john" +Attribute [k3_key2]: string "2: johns" +Attribute [k3_sim]: double 0.8 +Attribute [k4_key1]: string "0: john" +Attribute [k4_key2]: string "2: johns" +Attribute [k4_sim]: double 0.8 +Attribute [k4_key1]: string "3: mathew" +Attribute [k4_key2]: string "5: mathews" +Attribute [k4_sim]: double 0.857143 diff --git a/centrallix/tests/test_objdrv_cluster_00.to b/centrallix/tests/test_objdrv_cluster_00.to index 14dec4238..fc583d04f 100644 --- a/centrallix/tests/test_objdrv_cluster_00.to +++ b/centrallix/tests/test_objdrv_cluster_00.to @@ -4,26 +4,26 @@ query select :algorithm, :similarity_measure, :num_clusters, :source, :threshold from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine ; # Test cluster. -query select items_k1=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; -query select items_k2=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; -query select items_k3=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; -query select items_k4=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; +query select k1_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select k2_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; +query select k3_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; +query select k4_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; # Drop caches and retest clustering. exec /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster "cache" "drop_all" -query select items_k1=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; -query select items_k2=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; -query select items_k3=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; -query select items_k4=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; +query select k1_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select k2_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; +query select k3_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; +query select k4_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; # Test searching. -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/search ; -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/search ; -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/search ; -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/search ; +query select k1_key1 = :key1, k1_key2 = :key2, k1_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/search ; +query select k2_key1 = :key1, k2_key2 = :key2, k2_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/search ; +query select k3_key1 = :key1, k3_key2 = :key2, k3_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/search ; +query select k4_key1 = :key1, k4_key2 = :key2, k4_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/search ; # Test searching with Levenshtein. -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=levenshtein/search ; -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=levenshtein/search ; -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=levenshtein/search ; -query select * from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=levenshtein/search ; +query select k1_key1 = :key1, k1_key2 = :key2, k1_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=levenshtein/search ; +query select k2_key1 = :key1, k2_key2 = :key2, k2_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=levenshtein/search ; +query select k3_key1 = :key1, k3_key2 = :key2, k3_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=levenshtein/search ; +query select k4_key1 = :key1, k4_key2 = :key2, k4_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=levenshtein/search ; From 00c07f18d02c9da6d8b075c8597399388a6d09f4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 19 Mar 2026 17:41:23 -0600 Subject: [PATCH 088/361] Improve the cluster driver testcase to test the default attribute lists. --- centrallix/tests/test_objdrv_cluster_00.cmp | 36 ++++++++++++++++++--- centrallix/tests/test_objdrv_cluster_00.to | 8 +++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/centrallix/tests/test_objdrv_cluster_00.cmp b/centrallix/tests/test_objdrv_cluster_00.cmp index b8949bc68..877d020df 100644 --- a/centrallix/tests/test_objdrv_cluster_00.cmp +++ b/centrallix/tests/test_objdrv_cluster_00.cmp @@ -1,13 +1,16 @@ Attribute [algorithm]: string "k-means" Attribute [similarity_measure]: string "cosine" Attribute [num_clusters]: integer 2 -Attribute [source]: integer NULL -Attribute [threshold]: integer NULL -Attribute [algorithm]: integer NULL -Attribute [similarity_measure]: string "cosine" -Attribute [num_clusters]: integer NULL +Attribute [min_improvement]: double 0.0001 +Attribute [max_iterations]: integer 16 +Attribute [seed]: integer 1 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' Attribute [source]: string "cluster" Attribute [threshold]: double 0.75 +Attribute [similarity_measure]: string "cosine" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" Attribute [k2_items]: stringve "john","johnny","johns" Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" @@ -94,3 +97,26 @@ Attribute [k4_sim]: double 0.8 Attribute [k4_key1]: string "3: mathew" Attribute [k4_key2]: string "5: mathews" Attribute [k4_sim]: double 0.857143 +Attribute [items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.757051 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "4: MatTheW" +Attribute [sim]: double 0.922627 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "4: MatTheW" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.874386 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' diff --git a/centrallix/tests/test_objdrv_cluster_00.to b/centrallix/tests/test_objdrv_cluster_00.to index fc583d04f..8c007e86b 100644 --- a/centrallix/tests/test_objdrv_cluster_00.to +++ b/centrallix/tests/test_objdrv_cluster_00.to @@ -1,7 +1,7 @@ ##NAME Cluster Driver -# Test parameters and initialization. -query select :algorithm, :similarity_measure, :num_clusters, :source, :threshold from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine ; +# Test parameters, initialization, and default attribute lists. +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine ; # Test cluster. query select k1_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; @@ -27,3 +27,7 @@ query select k1_key1 = :key1, k1_key2 = :key2, k1_sim = :sim from /tests/test_ob query select k2_key1 = :key1, k2_key2 = :key2, k2_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=levenshtein/search ; query select k3_key1 = :key1, k3_key2 = :key2, k3_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=levenshtein/search ; query select k4_key1 = :key1, k4_key2 = :key2, k4_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=levenshtein/search ; + +# Test the default attribute list for clusters +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/search ; From b540a38443e14b5f85f1a7a4010fb43dd729b8f6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 09:57:42 -0600 Subject: [PATCH 089/361] Improve driver with testing. Fix clusterGetAttrValue() asserting MGK_CL_SEARCH_DATA on a SourceData struct. Fix input attribute array being used for output attributes. Update driver warnings for unknown attributes to use the attribute lists instead of redefining them. Add presentation hints for internal type. Add more detail to error messages when presentation hints fail. --- centrallix/osdrivers/objdrv_cluster.c | 79 +++++++++++---------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index f0bc461be..5f4e0ab5a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -706,11 +706,11 @@ ci_UnknownAttribute(char* attr_name, const int target_type) unsigned int n_my_attrs = 0u; switch (target_type) { - case TARGET_NODE: my_attrs = ROOT_ATTRS; n_my_attrs = N_INPUT_ROOT_ATTRS; break; - case TARGET_CLUSTER: my_attrs = CLUSTER_ATTRS; n_my_attrs = N_INPUT_CLUSTER_ATTRS; break; - case TARGET_SEARCH: my_attrs = SEARCH_ATTRS; n_my_attrs = N_INPUT_SEARCH_ATTRS; break; - case TARGET_CLUSTER_ENTRY: my_attrs = CLUSTER_ENTRY_ATTRS; n_my_attrs = N_INPUT_CLUSTER_ENTRY_ATTRS; break; - case TARGET_SEARCH_ENTRY: my_attrs = SEARCH_ENTRY_ATTRS; n_my_attrs = N_INPUT_SEARCH_ENTRY_ATTRS; break; + case TARGET_NODE: my_attrs = ROOT_ATTRS; n_my_attrs = N_ROOT_ATTRS; break; + case TARGET_CLUSTER: my_attrs = CLUSTER_ATTRS; n_my_attrs = N_CLUSTER_ATTRS; break; + case TARGET_SEARCH: my_attrs = SEARCH_ATTRS; n_my_attrs = N_SEARCH_ATTRS; break; + case TARGET_CLUSTER_ENTRY: my_attrs = CLUSTER_ENTRY_ATTRS; n_my_attrs = N_CLUSTER_ENTRY_ATTRS; break; + case TARGET_SEARCH_ENTRY: my_attrs = SEARCH_ENTRY_ATTRS; n_my_attrs = N_SEARCH_ENTRY_ATTRS; break; default: mssError(0, "Cluster", "Unknown target type %u detected while attempting to generate hint.", @@ -1165,23 +1165,11 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { case ST_T_ATTRIB: { - /** Valid attribute names. **/ - char* attrs[] = { - "algorithm", - "similarity_measure", - "num_clusters", - "min_improvement", - "max_iterations", - "window_size", - "seed", - }; - const unsigned int num_attrs = sizeof(attrs) / sizeof(char*); - /** Ignore valid attribute names. **/ bool is_valid = false; - for (unsigned int i = 0u; i < num_attrs; i++) + for (unsigned int i = 0u; i < N_INPUT_CLUSTER_ATTRS; i++) { - if (strcmp(name, attrs[i]) == 0) + if (strcmp(name, CLUSTER_ATTRS[i]) == 0) { is_valid = true; break; @@ -1191,7 +1179,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); - if (ci_TryHint(name, attrs, num_attrs)); + if (ci_TryHint(name, CLUSTER_ATTRS, N_INPUT_CLUSTER_ATTRS)); else if (strcasecmp(name, "k") == 0) ci_GiveHint("num_clusters"); else if (strcasecmp(name, "threshold") == 0) ci_GiveHint("min_improvement"); @@ -1427,19 +1415,11 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) { case ST_T_ATTRIB: { - /** Valid attribute names. **/ - char* attrs[] = { - "source", - "threshold", - "similarity_measure", - }; - const unsigned int num_attrs = sizeof(attrs) / sizeof(char*); - /** Ignore valid attribute names. **/ bool is_valid = false; - for (unsigned int i = 0u; i < num_attrs; i++) + for (unsigned int i = 0u; i < N_INPUT_SEARCH_ATTRS; i++) { - if (strcmp(name, attrs[i]) == 0) + if (strcmp(name, SEARCH_ATTRS[i]) == 0) { is_valid = true; break; @@ -1449,7 +1429,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); - ci_TryHint(name, attrs, num_attrs); + ci_TryHint(name, SEARCH_ATTRS, N_INPUT_SEARCH_ATTRS); break; } @@ -1602,19 +1582,11 @@ ci_ParseNodeData(pStructInf inf, pObject parent) { case ST_T_ATTRIB: { - /** Valid attribute names. **/ - char* attrs[] = { - "source", - "key_attr", - "data_attr", - }; - const unsigned int num_attrs = sizeof(attrs) / sizeof(char*); - /** Ignore valid attribute names. **/ bool is_valid = false; - for (unsigned int i = 0u; i < num_attrs; i++) + for (unsigned int i = 0u; i < N_INPUT_ROOT_ATTRS; i++) { - if (strcmp(name, attrs[i]) == 0) + if (strcmp(name, ROOT_ATTRS[i]) == 0) { is_valid = true; break; @@ -1623,8 +1595,8 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (is_valid) continue; /* Next inf. */ /** Give the user a warning, and attempt to give them a hint. **/ - fprintf(stderr, "Warning: Unknown attribute '%s' in cluster node \"%s\".\n", name, inf->Name); - ci_TryHint(name, attrs, num_attrs); + fprintf(stderr, "Warning: Unknown attribute '%s' in cluster driver root node \"%s\".\n", name, inf->Name); + ci_TryHint(name, ROOT_ATTRS, N_INPUT_ROOT_ATTRS); break; } @@ -3810,7 +3782,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { pSourceData source_data = check_ptr(driver_data->TargetData); if (source_data == NULL) goto err; - ASSERTMAGIC(source_data, MGK_CL_SEARCH_DATA); + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); if (strcmp(attr_name, "source") == 0) { @@ -4043,6 +4015,21 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) hints->VisualLength = 30; goto end; } + if (strcmp(attr_name, "internal_type") == 0) + { + check(xaInit(&(hints->EnumList), 5)); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("search/entry")))); /* Failure ignored. */ + hints->Length = 16; + hints->VisualLength = 16; + hints->FriendlyName = check_ptr(nmSysStrdup("Internal Type")); /* Failure ignored. */ + hints->Style |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; + hints->StyleMask |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; + goto end; + } /** Handle date created and date computed. */ if (strcmp(attr_name, "date_created") == 0 @@ -4286,8 +4273,8 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) check(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); /* Failure ignored. */ check(clusterGetAttrValue(inf_v, "internal_type", DATA_T_STRING, POD(&internal_type), NULL)); /* Failure ignored. */ mssError(0, "Cluster", - "Failed to get presentation hints for object '%s' : \"%s\".", - name, internal_type + "Failed to get presentation hints for '%s' on object '%s' : \"%s\".", + attr_name, name, internal_type ); end: From e27308e4c668218aef725869255423e8343858ca Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 09:58:35 -0600 Subject: [PATCH 090/361] Update an example on OSDriver_Authoring.md because the sybase driver is weird and I don't want to use it as an example anymore. --- centrallix-sysdoc/OSDriver_Authoring.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 40983d630..ce117966b 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -254,11 +254,11 @@ _**Function is always required, but can always return NULL if queries are not su --- ### Abbreviation Prefix -Each OS Driver will have an abbreviation prefix, such as `qy` for the query driver or `sydb` for the sybase database driver. This prefix should be prepended to the start of every public function name within the OS driver for consistency and scope management (e.g. `qyInitialize()`, `sydbQueryFetch()`, etc.). Normally, a driver's abbreviation prefix is two to four characters, all lowercase and may be the same as a file extension the driver supports. However, this is not an absolute requirement (see the cluster driver in `objdrv_cluster.c` which supports `.cluster` files using an abbreviation prefix of `cluster`). +Each OS Driver will have an abbreviation prefix, such as `qy` for the query driver or `rpt` for the report driver. This prefix should be prepended to the start of every public function name within the OS driver for consistency and scope management (e.g. `qyInitialize()`, `rptOpen()`, etc.). Normally, a driver's abbreviation prefix is two to four characters, all lowercase and may be the same as a file extension the driver supports. However, this is not an absolute requirement (see the cluster driver in `objdrv_cluster.c` which supports `.cluster` files using an abbreviation prefix of `cluster`). This document uses `xxx` to refer to an unspecified abbreviation prefix. -- 📖 **Note**: Once an abbreviation prefix has been selected, the driver author should add it to the [Prefixes.md](Prefixes.md) file. +- 📖 **Note**: Once an abbreviation prefix has been selected, the driver author should add it to the [Prefixes.md](Prefixes.md) file. It is advised never to pick a prefix longer than 7 characters, as doing so will this file's table style. ### Internal Functions From bda9eac598977a32e0b8cbb9a11a4e4a1fdb00da Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 12:25:06 -0600 Subject: [PATCH 091/361] Improve structs. Rename nVectors to nDatas. Review and update doc comments on structs. Add a struct union to improve clarity of the ClusterData struct. --- centrallix/osdrivers/objdrv_cluster.c | 154 ++++++++++++++------------ 1 file changed, 86 insertions(+), 68 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 5f4e0ab5a..0c7b0c016 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -266,7 +266,8 @@ char* METHOD_NAMES[] = /** ================ Struct Declarations ================ **/ /** ANCHOR[id=structs] **/ -/*** Represents the data source which may have data already fetched. +/*** Represents the data source which may have data already fetched. Only + *** attribute data is checked for caching. *** *** Memory Stats: *** - Padding: 0 bytes @@ -277,29 +278,31 @@ char* METHOD_NAMES[] = *** @param Key The key associated with this object in the SourceDataCache. *** @param SourcePath The path to the data source from which to retrieve data. *** @param KeyAttr The name of the attribute to use when getting keys from - *** the SourcePath. + *** the driver represented by SourcePath. *** @param DataAttr The name of the attribute to use when getting data from - *** the SourcePath. + *** the driver represented by SourcePath. *** *** @skip --> Fetched/Computed Data. - *** @param Strings The keys for each data string strings received from the - *** database, allowing them to be lined up again when queried. + *** @param Keys The keys for each data string received from the database, used + *** when the results are queried, or NULL if the data has not been fetched. *** @param Strings The data strings to be clustered and searched, or NULL if *** they have not been fetched from the source. *** @param Vectors The cosine comparison vectors from the fetched data, or - *** NULL if they haven't been computed. - *** @param nVectors The number of vectors and data strings. + *** NULL if they haven't been computed yet. + *** @param nDatas The number of keys, data strings, and vectors that have been + *** fetched/computed, or 0 if no data has been fetched/computed yet. *** *** @skip --> Time. *** @param DateCreated The date and time that this object was created/initialized. - *** @param DateComputed The date and time that the computed attributes were computed. + *** @param DateComputed The date and time that the fetch/computed attributes + *** were fetched/computed. *** - *** @param Magic A magic value for detecting corrupted memory. + *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct _SOURCE { Magic_t Magic; - unsigned int nVectors; + unsigned int nDatas; char* Name; char* Key; char* SourcePath; @@ -321,9 +324,10 @@ typedef struct _SOURCE *** - Total size: 16 bytes *** *** @param Size The number of items in the cluster. - *** @param Indexes Represents the data points in the cluster, as indexes into - *** the SourceData Keys, Strings, and Vectors fields. NULL if `Size == 0`. - *** @param Magic A magic value for detecting corrupted memory. + *** @param Indexes The data points in the cluster, as indexes into SourceData. + *** Use these to access the Keys, Strings, or Vectors array fields. This + *** value is NULL if `Size == 0`. + *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct { @@ -334,7 +338,8 @@ typedef struct Cluster, *pCluster; -/*** Data for each cluster. Only attribute data is checked for caching. +/*** Data for each cluster object defined in the .cluster file. Only attribute + *** data is checked for caching. *** *** Memory Stats: *** - Padding: 2 bytes @@ -345,30 +350,33 @@ typedef struct *** @param Key The key associated with this object in the ClusterDataCache. *** @param ClusterAlgorithm The clustering algorithm to be used. *** @param SimilarityMeasure The similarity measure used to compare items. - *** @param nClusters The number of clusters. 1 if algorithm = none. + *** @param nClusters The number of clusters. 1 if `algorithm == none`. *** @param MinImprovement The minimum amount of improvement that must be met - *** each clustering iteration. -inf represents "max" in the .cluster file. + *** each clustering iteration. -inf represents the "max" value in the + *** .cluster file. *** @param MaxIterations The maximum number of iterations to run clustering. - *** Note: Sliding window uses this attribute to store the window_size. + *** @param WindowSize The size of the sliding window for sliding window + *** searches. Shares memory with MaxIterations because the sliding window + *** clustering algorithm does not have any concept of "iterations". *** - *** @skip --> Relational Data. - *** @param nSubClusters The number of subclusters of this cluster. - *** @param SubClusters A pClusterData array, NULL if nSubClusters == 0. - *** @param Parent This cluster's parent. NULL if it is not a subcluster. + *** @skip --> Relational Data. (Note: sub-clusters are not implemented.) + *** @param nSubClusters The number of sub-clusters for this cluster. + *** @param SubClusters A pClusterData array, NULL if `nSubClusters == 0`. + *** @param Parent This cluster's parent. NULL if it is not a sub-cluster. *** @param SourceData Pointer to the source data that this cluster uses. *** *** @skip --> Computed Data. - *** @param Clusters An array of length num_clusters, NULL if the clusters - *** have not yet been computed. - *** @param Sims An array of num_vectors elements, where index i stores the - *** similarity of vector i to its assigned cluster. This attribute is NULL - *** if the clusters have not yet been computed. + *** @param Clusters An array of length nClusters, NULL if the clusters have + *** been computed yet. + *** @param Sims An array of nClusters elements, where index i stores the + *** similarity of vector i to its assigned cluster, NULL if the clusters + *** have not been computed yet. *** *** @skip --> Time. *** @param DateCreated The date and time that this object was created/initialized. *** @param DateComputed The date and time that the computed attributes were computed. *** - *** @param Magic A magic value for detecting corrupted memory. + *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct _CD { @@ -381,7 +389,10 @@ typedef struct _CD /** 2 bytes of auto-padding. **/ unsigned int Seed; double MinImprovement; - unsigned int MaxIterations; + union { + unsigned int MaxIterations; + unsigned int WindowSize; + }; unsigned int nSubClusters; struct _CD** SubClusters; struct _CD* Parent; @@ -410,15 +421,17 @@ typedef struct _CD *** *** @skip --> Computed data. *** @param Pairs An array holding the pairs found by the search, or NULL if - *** the search has not been computed. The indexes stored in these pairs - *** are indexes into the SourceData data arrays. - *** @param nPairs The number of pairs found. + *** the search has not been computed yet. The indexes stored in these + *** pairs are indexes into the SourceData data arrays (access with + *** `Source->SourceData->[DATA_ARRAY]`). + *** @param nPairs The number of pairs found, or 0 if the search has not been + *** computed yet. *** *** @skip --> Time. *** @param DateCreated The date and time that this object was created/initialized. *** @param DateComputed The date and time that the computed attributes were computed. *** - *** @param Magic A magic value for detecting corrupted memory. + *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct _SEARCH { @@ -453,15 +466,15 @@ typedef struct _SEARCH *** @param Params A pParam array storing the params in the .cluster file. *** @param nParams The number of specified params. *** @param ParamList A "scope" for resolving parameter values during parsing. - *** @param ClusterDatas A pCluster array for the clusters in the .cluster file. - *** Will be NULL if `nClusters = 0`. + *** @param ClusterDatas A pCluster array for the clusters in the .cluster file, + *** NULL if `nClusters == 0`. *** @param nClusterDatas The number of specified clusters. *** @param SearchDatas A SearchData array for the searches in the .cluster file. *** @param nSearches The number of specified searches. *** @param nSearchDatas The parent object used to open this NodeData instance. *** @param OpenCount The number of open driver instances that are using the *** NodeData struct. When this reaches 0, the struct should be freed. - *** @param Magic A magic value for detecting corrupted memory. + *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct _NODE { @@ -486,9 +499,9 @@ typedef struct _NODE *** - Padding: 5 bytes *** - Total size: 32 bytes *** - *** This struct can be thought of like a "pointer" to specific data accessible - *** through the stored pNodeData struct. This struct also communicates whether - *** that data is guaranteed to have been computed. + *** Think of this struct like a "pointer" to specific data accessible through + *** the pNodeData field. This struct also tells us whether that data is + *** guaranteed to be computed already. *** *** For example, if target type is the root, a cluster, or a search, no data *** is guaranteed to be computed. These three types can be returned from @@ -501,9 +514,14 @@ typedef struct _NODE *** and `clusterGetAttrValue()` functions faster and simpler because they do *** not need to check that the data is computed every time they are called. *** - *** @param NodeData The associated node data struct. While many driver struct - *** instances pointing to one NodeData at a time, but each driver instance - *** always points to singular NodeData struct. + *** @attention - When allocating this struct, remember to increment the + *** `NodeData->OpenCount` value. When freeing this struct, remember to + *** decrement `NodeData->OpenCount`, freeing the NodeData struct as well + *** if it reaches 0, to prevent memory leaks. + *** + *** @param NodeData The associated node data struct. While many driver + *** struct instances pointing to one NodeData at a time, but each driver + *** instance always points to singular NodeData struct. *** @param TargetType The type of data targeted (see above). *** @param TargetData If target type is: *** ```txt @@ -513,7 +531,7 @@ typedef struct _NODE *** ``` *** @param TargetAttrIndex An index into an attribute list (for GetNextAttr()). *** @param TargetMethodIndex An index into an method list (for GetNextMethod()). - *** @param Magic A magic value for detecting corrupted memory. + *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct _DRIVER { @@ -537,7 +555,7 @@ typedef struct _DRIVER *** *** @param DriverData The associated driver instance being queried. *** @param RowIndex The selected row of the data targeted by the driver. - *** @param Magic A magic value for detecting corrupted memory. + *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct { @@ -548,7 +566,7 @@ typedef struct ClusterQuery, *pQueryData; -/** Global storage for caches. **/ +/** Global storage for driver caches. **/ struct { XHashTable SourceDataCache; @@ -1070,7 +1088,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source } /** Store value. **/ - cluster_data->MaxIterations = (unsigned int)window_size; + cluster_data->WindowSize = (unsigned int)window_size; goto parsing_done; } @@ -1251,7 +1269,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source cluster_data->Name, ALGORITHM_SLIDING_WINDOW, cluster_data->SimilarityMeasure, - cluster_data->MaxIterations + cluster_data->WindowSize ); break; } @@ -1844,7 +1862,7 @@ ci_FreeSourceData(pSourceData source_data) /** Free fetched keys, if they exist. **/ if (source_data->Keys != NULL) { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + for (unsigned int i = 0u; i < source_data->nDatas; i++) { if (source_data->Keys[i] != NULL) { @@ -1859,7 +1877,7 @@ ci_FreeSourceData(pSourceData source_data) /** Free fetched data, if it exists. **/ if (source_data->Strings != NULL) { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + for (unsigned int i = 0u; i < source_data->nDatas; i++) { if (source_data->Strings[i] != NULL) { @@ -1874,7 +1892,7 @@ ci_FreeSourceData(pSourceData source_data) /** Free computed vectors, if they exist. **/ if (source_data->Vectors != NULL) { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + for (unsigned int i = 0u; i < source_data->nDatas; i++) { if (source_data->Vectors[i] != NULL) { @@ -2120,21 +2138,21 @@ ci_SizeOfSourceData(pSourceData source_data) if (source_data->DataAttr != NULL) size += strlen(source_data->DataAttr) * sizeof(char); if (source_data->Keys != NULL) { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + for (unsigned int i = 0u; i < source_data->nDatas; i++) size += strlen(source_data->Keys[i]) * sizeof(char); - size += source_data->nVectors * sizeof(char*); + size += source_data->nDatas * sizeof(char*); } if (source_data->Strings != NULL) { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + for (unsigned int i = 0u; i < source_data->nDatas; i++) size += strlen(source_data->Strings[i]) * sizeof(char); - size += source_data->nVectors * sizeof(char*); + size += source_data->nDatas * sizeof(char*); } if (source_data->Vectors != NULL) { - for (unsigned int i = 0u; i < source_data->nVectors; i++) + for (unsigned int i = 0u; i < source_data->nDatas; i++) size += ca_sparse_len(source_data->Vectors[i]) * sizeof(int); - size += source_data->nVectors * sizeof(pVector); + size += source_data->nDatas * sizeof(pVector); } size += sizeof(SourceData); @@ -2168,10 +2186,10 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); if (cluster_data->Clusters != NULL) { - const unsigned int nVectors = cluster_data->SourceData->nVectors; + const unsigned int nDatas = cluster_data->SourceData->nDatas; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) size += cluster_data->Clusters[i].Size * (sizeof(char*) + sizeof(pVector)); - size += nVectors * (sizeof(Cluster) + sizeof(double)); + size += nDatas * (sizeof(Cluster) + sizeof(double)); } if (cluster_data->SubClusters != NULL) { @@ -2379,8 +2397,8 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) check(objClose(entry)); /* Failure ignored. */ } - source_data->nVectors = vector_xarray.nItems; - if (source_data->nVectors == 0) + source_data->nDatas = vector_xarray.nItems; + if (source_data->nDatas == 0) { mssError(0, "Cluster", "Data source path did not contain any valid data:\n"); goto end_free; @@ -2520,7 +2538,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate static memory for finding clusters. **/ clusters_size = cluster_data->nClusters * sizeof(Cluster); - sims_size = source_data->nVectors * sizeof(double); + sims_size = source_data->nDatas * sizeof(double); cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); if (cluster_data->Clusters == NULL) goto err_free; @@ -2541,7 +2559,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) SETMAGIC(only_cluster, MGK_CL_CLUSTER); /** Add all data points to that cluster. **/ - only_cluster->Size = source_data->nVectors; + only_cluster->Size = source_data->nDatas; only_cluster->Indexes = check_ptr(nmSysMalloc(only_cluster->Size * sizeof(int))); if (only_cluster->Indexes == NULL) goto err_free; for (unsigned int i = 0u; i < only_cluster->Size; i++) @@ -2568,7 +2586,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) } /** Allocate labels. Note: ca_kmeans() initializes labels for us. **/ - const size_t labels_size = source_data->nVectors * sizeof(unsigned int); + const size_t labels_size = source_data->nDatas * sizeof(unsigned int); unsigned int* labels = check_ptr(nmSysMalloc(labels_size)); if (labels == NULL) goto err_free; @@ -2579,7 +2597,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Run ca_kmeans(). **/ const bool successful = check(ca_kmeans( source_data->Vectors, - source_data->nVectors, + source_data->nDatas, cluster_data->nClusters, cluster_data->MaxIterations, cluster_data->MinImprovement, @@ -2597,7 +2615,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (!check(xaInit(&indexes_in_cluster[i], 8))) goto err_free; /** Iterate through each label and add the index of the specified cluster to the xArray. **/ - for (unsigned long long i = 0llu; i < source_data->nVectors; i++) + for (unsigned long long i = 0llu; i < source_data->nDatas; i++) if (!check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i))) goto err_free; nmSysFree(labels); /* Free unused data. */ @@ -2753,8 +2771,8 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Execute sliding search. **/ pairs = check_ptr(ca_sliding_search( data, - source_data->nVectors, - cluster_data->MaxIterations, /* Window size. */ + source_data->nDatas, + cluster_data->WindowSize, similarity_function, search_data->Threshold, NULL @@ -2771,7 +2789,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) else { /** Initialize the pairs array with a size of double the amount of data. **/ - const int guess_size = search_data->SourceCluster->SourceData->nVectors * 2; + const int guess_size = search_data->SourceCluster->SourceData->nDatas * 2; pairs = check_ptr(xaNew(guess_size)); if (pairs == NULL) goto err_free; @@ -4384,7 +4402,7 @@ clusterInfo(void* inf_v, pObjectInfo info) if (node_data->SourceData->Vectors != NULL) { info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; - info->nSubobjects = node_data->SourceData->nVectors; + info->nSubobjects = node_data->SourceData->nDatas; } break; From 62eebee2c71d41cbe8f4a564ccf9b7bade2f369a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 12:29:00 -0600 Subject: [PATCH 092/361] Modify min_improvement field in the driver schema to only accept doubles without reducing functionality. --- centrallix-lib/src/clusters.c | 4 ++-- centrallix-os/cluster-schema.cluster | 6 +++++- centrallix/osdrivers/objdrv_cluster.c | 26 ++++++-------------------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 4e399b87f..07dbd1b6d 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -790,7 +790,7 @@ get_cluster_size( *** even if the `min_improvement` threshold is still met on each iteration. *** @param min_improvement The minimum improvement threshold that must be met *** each clustering iteration. If this is not met, the iterations stop. - *** Pass -1 to disable this and iterate for as long as the centroids keep + *** Pass -1.0 to disable this and iterate for as long as the centroids keep *** changing (or until `max_iter` is reached). *** @param labels Initialized by this function to stores the final cluster *** identities of the vectors after clustering is completed. Each value @@ -921,7 +921,7 @@ ca_kmeans( } /** Is there enough improvement? **/ - if (min_improvement < -1) continue; /** Skip check if it will never end the loop. **/ + if (min_improvement <= -1.0) continue; /** Skip check if it will never end the loop. **/ const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); if (isnan(average_cluster_size)) goto end; const double improvement = old_average_cluster_size - average_cluster_size; diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index 74a2fc947..af69b23d5 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -23,12 +23,16 @@ file_name "system/cluster" | "k-means++" | "k-medoids" | "db-scan" // Not implemented similarity_measure : "cosine" | "levenshtein" // levenshtein not implemented. num_clusters : uint > 1 // (probably a parameter) - ?min_improvement : double && 0.0 < x < 1.0 | "none" // default: 0.0001 + ?min_improvement : double && -1.0 <= x <= 1.0 // default: 0.0001 ?max_iterations : uint // default: 64 ?window_size : uint > 0 // required for algorithm = sliding_window. ?overlap_size : double && 0.0 <= x <= 1.0 // default: 0.0, only allowed for algorithm = k-means | k-means++ | k-medoids, not implemented ?seed : uint > 0 // default: Dynamic seed. + // Note: Use min_improvement=-1.0; to effectively disable it. This will + // run as many iterations as possible until the clusters stop moving or + // `max_iterations` is reached. + // Not implemented sub_cluster_name "cluster/cluster" { diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 0c7b0c016..10e17166f 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1107,31 +1107,17 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get min_improvement. **/ double improvement; result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); - if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; + if (result == -1) goto err_free; + else if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; else if (result == 0) { - if (improvement <= 0.0 || 1.0 <= improvement) - { - mssError(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %g", improvement); - goto err_free; - } - - /** Successfully got value. **/ - cluster_data->MinImprovement = improvement; - } - else if (result == -1) - { - char* str; - result = ci_ParseAttribute(inf, "min_improvement", DATA_T_STRING, POD(&str), param_list, false, true); - if (result != 0) goto err_free; - if (strcasecmp(str, "none") != 0) + if (-1.0 <= improvement && improvement <= 1.0) + cluster_data->MinImprovement = improvement; + else { - mssError(1, "Cluster", "Invalid value for [min_improvement : 0.0 < x < 1.0 | \"none\"]: %s", str); + mssError(1, "Cluster", "Invalid value for [min_improvement : -1.0 <= x <= 1.0]: %g", improvement); goto err_free; } - - /** Successfully got none. **/ - cluster_data->MinImprovement = -INFINITY; } /** Get max_iterations. **/ From d6e9d989b52775e41f7269e266ee7fba462dee28 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 12:39:34 -0600 Subject: [PATCH 093/361] Fix a bug in the new mssError() implementation. --- centrallix-lib/src/mtsession.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index 4ab4c41fe..ad0842f56 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -480,12 +480,12 @@ mssError(int clr, char* module, char* message, ...) check(fflush(stdout)); /* Failure ignored. */ /** Write the module to the start of the error message. */ - i += snprintf(err_msg, sizeof(err_msg) - i, "%s: ", module); + i += snprintf(err_msg + i, sizeof(err_msg) - i, "%s: ", module); /** Process the message format with all the same rules as printf(). **/ va_list args; va_start(args, message); - i += vsnprintf(err_msg, sizeof(err_msg) - i, message, args); + i += vsnprintf(err_msg + i, sizeof(err_msg) - i, message, args); va_end(args); /** Get current session **/ From 71b3c60d173f7f3ad857c81dd6e8b999a2514ced Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 13:20:39 -0600 Subject: [PATCH 094/361] Clean up. Add code to prevent opening subclusters as this functionality isn't fully implemented yet. Add code to check unchecked errors. Remove unnecessary ASSERTMAGIC() calls. Simplify confusing free loop logic. Clean up comments and spacing. Fix spelling errors. --- centrallix/osdrivers/objdrv_cluster.c | 39 ++++++++++++++++----------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 10e17166f..ac2756654 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1237,7 +1237,8 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source case ALGORITHM_NONE: { const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 8lu; - key = nmSysMalloc(len * sizeof(char)); + key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (key == NULL) goto err_free; snprintf(key, len, "%s/%s?%u", source_data->Key, cluster_data->Name, @@ -1249,7 +1250,8 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source case ALGORITHM_SLIDING_WINDOW: { const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 16lu; - key = nmSysMalloc(len * sizeof(char)); + key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (key == NULL) goto err_free; snprintf(key, len, "%s/%s?%u&%u&%u", source_data->Key, cluster_data->Name, @@ -1263,7 +1265,8 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source default: { const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 32lu; - key = nmSysMalloc(len * sizeof(char)); + key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (key == NULL) goto err_free; snprintf(key, len, "%s/%s?%u&%u&%u&%g&%u", source_data->Key, cluster_data->Name, @@ -1326,8 +1329,6 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source *** caller is not required to specify their own error message. *** *** @param inf A parsed pStructInf for a search group in a structure file. - *** @param param_list The param objects that function as a kind of "scope" for - *** evaluating parameter variables in the structure file. *** @param node_data The pNodeData, used to get the param list and to look up *** the cluster pointed to by the source attribute. *** @returns A new pSearchData struct on success, or NULL on failure. @@ -1411,7 +1412,8 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) goto err_free; } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); - char* name = sub_inf->Name; + char* name = check_ptr(sub_inf->Name); + if (name == NULL) goto err_free; /** Handle various struct types. **/ const int struct_type = stStructType(sub_inf); @@ -1466,7 +1468,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; key = check_ptr(nmSysMalloc(len * sizeof(char))); if (key == NULL) goto err_free; - snprintf(key, len, "%s/%s?%g&%u", + snprintf(key, len, "%s/%s?%g&%u", source_key, search_data->Name, search_data->Threshold, @@ -1607,7 +1609,6 @@ ci_ParseNodeData(pStructInf inf, pObject parent) case ST_T_SUBGROUP: { - /** The spec does not specify any valid sub-groups for searches. **/ char* group_type = check_ptr(sub_inf->UsrType); if (group_type == NULL) goto err_free; if (strcmp(group_type, "cluster/parameter") == 0) @@ -1766,7 +1767,6 @@ ci_ParseNodeData(pStructInf inf, pObject parent) { node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data->ParamList, node_data->SourceData); if (node_data->ClusterDatas[i] == NULL) goto err_free; - ASSERTMAGIC(node_data->ClusterDatas[i], MGK_CL_CLUSTER_DATA); } } else node_data->ClusterDatas = NULL; @@ -1785,7 +1785,6 @@ ci_ParseNodeData(pStructInf inf, pObject parent) { node_data->SearchDatas[i] = ci_ParseSearchData(search_infs.Items[i], node_data); if (node_data->SearchDatas[i] == NULL) goto err_free; - ASSERTMAGIC(node_data->SearchDatas[i], MGK_CL_SEARCH_DATA); } } else node_data->SearchDatas = NULL; @@ -1928,9 +1927,11 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { pCluster cluster = &cluster_data->Clusters[i]; - if (cluster == NULL) continue; - if (cluster->Indexes != NULL) nmSysFree(cluster->Indexes); - cluster->Indexes = NULL; + if (cluster->Indexes != NULL) + { + nmSysFree(cluster->Indexes); + cluster->Indexes = NULL; + } } nmSysFree(cluster_data->Clusters); nmSysFree(cluster_data->Sims); @@ -1946,9 +1947,10 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) { if (cluster_data->SubClusters[i] != NULL) + { ci_FreeClusterData(cluster_data->SubClusters[i], recursive); - else continue; - cluster_data->SubClusters[i] = NULL; + cluster_data->SubClusters[i] = NULL; + } } } nmSysFree(cluster_data->SubClusters); @@ -3092,10 +3094,15 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Target found: Cluster **/ driver_data->TargetType = TARGET_CLUSTER; + /** Sub-clusters are not fully implemented yet, skip the cluster logic below. **/ + parent->SubCnt++; + driver_data->TargetData = (void*)cluster_data; + goto success; /* Done! */ + /** Check for sub-clusters in the path. **/ while (true) { - /** Decend one path part deeper into the path. **/ + /** Descend one path part deeper into the path. **/ const char* path_part = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); /** If the path does not go any deeper, we're done. **/ From fa048eb24a10fc1650b7c4300b0b478d22bddd44 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 13:25:18 -0600 Subject: [PATCH 095/361] Rename Key fields to CacheKey to reduce confusion with the Keys fetched in the SourceData struct. --- centrallix/osdrivers/objdrv_cluster.c | 80 +++++++++++++-------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ac2756654..70be01b05 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -275,7 +275,7 @@ char* METHOD_NAMES[] = *** *** @skip --> Attribute Data. *** @param Name The source name, specified in the .cluster file. - *** @param Key The key associated with this object in the SourceDataCache. + *** @param CacheKey The key associated with this object in the SourceDataCache. *** @param SourcePath The path to the data source from which to retrieve data. *** @param KeyAttr The name of the attribute to use when getting keys from *** the driver represented by SourcePath. @@ -304,7 +304,7 @@ typedef struct _SOURCE Magic_t Magic; unsigned int nDatas; char* Name; - char* Key; + char* CacheKey; char* SourcePath; char* KeyAttr; char* DataAttr; @@ -347,7 +347,7 @@ typedef struct *** *** @skip --> Attribute Data. *** @param Name The cluster name, specified in the .cluster file. - *** @param Key The key associated with this object in the ClusterDataCache. + *** @param CacheKey The key associated with this object in the ClusterDataCache. *** @param ClusterAlgorithm The clustering algorithm to be used. *** @param SimilarityMeasure The similarity measure used to compare items. *** @param nClusters The number of clusters. 1 if `algorithm == none`. @@ -383,7 +383,7 @@ typedef struct _CD Magic_t Magic; unsigned int nClusters; char* Name; - char* Key; + char* CacheKey; ClusterAlgorithm ClusterAlgorithm; SimilarityMeasure SimilarityMeasure; /** 2 bytes of auto-padding. **/ @@ -413,7 +413,7 @@ typedef struct _CD *** *** @skip --> Attribute Data. *** @param Name The search name, specified in the .cluster file. - *** @param Key The key associated with this object in the SearchDataCache. + *** @param CacheKey The key associated with this object in the SearchDataCache. *** @param Source The cluster from which this search is to be derived. *** @param SimilarityMeasure The similarity measure used to compare items. *** @param Threshold The minimum similarity threshold for elements to be @@ -438,7 +438,7 @@ typedef struct _SEARCH Magic_t Magic; /** 4 bytes of auto-padding. **/ char* Name; - char* Key; + char* CacheKey; pClusterData SourceCluster; double Threshold; pPair* Pairs; @@ -973,21 +973,21 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) + strlen(source_data->SourcePath) + strlen(source_data->KeyAttr) + strlen(source_data->DataAttr) + 5lu; - source_data->Key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (source_data->Key == NULL) goto err_free; - snprintf(source_data->Key, len, + source_data->CacheKey = check_ptr(nmSysMalloc(len * sizeof(char))); + if (source_data->CacheKey == NULL) goto err_free; + snprintf(source_data->CacheKey, len, "%s?%s->%s:%s", path, source_data->SourcePath, source_data->KeyAttr, source_data->DataAttr ); /** Check for a cached version. **/ - pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, source_data->Key); + pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey); if (source_maybe != NULL) { /* Cache hit. */ ASSERTMAGIC(source_maybe, MGK_CL_SOURCE_DATA); /** Free data we don't need. **/ - nmSysFree(source_data->Key); + nmSysFree(source_data->CacheKey); ci_FreeSourceData(source_data); /** Return the cached source data. **/ @@ -995,7 +995,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) } /** Cache miss: Add the new object to the cache for next time. **/ - if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->Key, (void*)source_data))) + if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey, (void*)source_data))) goto err_free; /** Success. **/ @@ -1005,7 +1005,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) err_free: if (source_data != NULL) { - if (source_data->Key != NULL) nmSysFree(source_data->Key); + if (source_data->CacheKey != NULL) nmSysFree(source_data->CacheKey); ci_FreeSourceData(source_data); } @@ -1038,7 +1038,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source int result; pClusterData cluster_data = NULL; XArray sub_clusters = {0}; - char* key = NULL; + char* cache_key = NULL; /** Verify source_data value. **/ if (source_data == NULL) goto err_free; @@ -1230,17 +1230,17 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source check(xaDeInit(&sub_clusters)); /* Failure ignored. */ sub_clusters.nAlloc = 0; - /** Create the cache key. **/ + /** Create the cache_key. **/ parsing_done:; switch (cluster_data->ClusterAlgorithm) { case ALGORITHM_NONE: { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 8lu; - key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (key == NULL) goto err_free; - snprintf(key, len, "%s/%s?%u", - source_data->Key, + const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 8lu; + cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (cache_key == NULL) goto err_free; + snprintf(cache_key, len, "%s/%s?%u", + source_data->CacheKey, cluster_data->Name, ALGORITHM_NONE ); @@ -1249,11 +1249,11 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source case ALGORITHM_SLIDING_WINDOW: { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 16lu; - key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (key == NULL) goto err_free; - snprintf(key, len, "%s/%s?%u&%u&%u", - source_data->Key, + const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 16lu; + cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (cache_key == NULL) goto err_free; + snprintf(cache_key, len, "%s/%s?%u&%u&%u", + source_data->CacheKey, cluster_data->Name, ALGORITHM_SLIDING_WINDOW, cluster_data->SimilarityMeasure, @@ -1264,11 +1264,11 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source default: { - const size_t len = strlen(source_data->Key) + strlen(cluster_data->Name) + 32lu; - key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (key == NULL) goto err_free; - snprintf(key, len, "%s/%s?%u&%u&%u&%g&%u", - source_data->Key, + const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 32lu; + cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (cache_key == NULL) goto err_free; + snprintf(cache_key, len, "%s/%s?%u&%u&%u&%g&%u", + source_data->CacheKey, cluster_data->Name, cluster_data->ClusterAlgorithm, cluster_data->SimilarityMeasure, @@ -1279,29 +1279,29 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source break; } } - cluster_data->Key = key; + cluster_data->CacheKey = cache_key; /** Check for a cached version. **/ - pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, key); + pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, cache_key); if (cluster_maybe != NULL) { /* Cache hit. */ ASSERTMAGIC(cluster_maybe, MGK_CL_CLUSTER_DATA); /** Free the parsed cluster that we no longer need. */ ci_FreeClusterData(cluster_data, false); - nmSysFree(key); + nmSysFree(cache_key); /** Return the cached cluster. **/ return cluster_maybe; } /** Cache miss. **/ - if (!check(xhAdd(&ClusterDriverCaches.ClusterDataCache, key, (void*)cluster_data))) goto err_free; + if (!check(xhAdd(&ClusterDriverCaches.ClusterDataCache, cache_key, (void*)cluster_data))) goto err_free; return cluster_data; /** Error cleanup. **/ err_free: - if (key != NULL) nmSysFree(key); + if (cache_key != NULL) nmSysFree(cache_key); if (sub_clusters.nAlloc != 0) { @@ -1464,7 +1464,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Create cache entry key. **/ - char* source_key = search_data->SourceCluster->Key; + char* source_key = search_data->SourceCluster->CacheKey; const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; key = check_ptr(nmSysMalloc(len * sizeof(char))); if (key == NULL) goto err_free; @@ -2102,7 +2102,7 @@ ci_ClearCaches(void) /*** Returns the deep size of a SourceData struct, including the size of all *** allocated substructures. *** - *** Note: The Key field points to data managed by the caching systems, so it + *** Note: The CacheKey field points to data managed by the caching systems, so it *** is ignored. *** *** @param source_data The source data struct to be queried. @@ -2152,7 +2152,7 @@ ci_SizeOfSourceData(pSourceData source_data) /*** Returns the deep size of a ClusterData struct, including the size of all *** allocated substructures. *** - *** Note: The Key field points to data managed by the caching systems, so it + *** Note: The CacheKey field points to data managed by the caching systems, so it *** is ignored. *** *** @param cluster_data The cluster data struct to be queried. @@ -2198,7 +2198,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) /*** Returns the deep size of a SearchData struct, including the size of all *** allocated substructures. *** - *** Note: The Key field points to data managed by the caching systems, so it + *** Note: The CacheKey field points to data managed by the caching systems, so it *** is ignored. *** *** @param search_data The search data struct to be queried. @@ -4680,7 +4680,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree printf("\nShowing cache for "); if (path != NULL) printf("\"%s\":\n", path); else printf("all files:\n"); - printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Cache Entry Key"); + printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Entry CacheKey"); failed |= !check(xhForEach( &ClusterDriverCaches.SourceDataCache, ci_PrintEntry, From 99f0a296b3e2ea15a6f5b9738d958858af783367 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 15:25:10 -0600 Subject: [PATCH 096/361] Clean up and updates. Fix a typo in ci_ComputeSourceData() that might have freed the wrong number of items from key_xarray.Items (although it couldn't in the current code). Update outdated sizeof logic in ci_SizeOfClusterData(). Improve handling of cluster_data->Sims in ci_FreeClusterData(). Reword error messages to avoid referencing ordinals incorrectly. Switch fetch loop to use clearer syntax. --- centrallix/osdrivers/objdrv_cluster.c | 33 ++++++++++++++------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 70be01b05..8ec671149 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1934,8 +1934,11 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) } } nmSysFree(cluster_data->Clusters); - nmSysFree(cluster_data->Sims); cluster_data->Clusters = NULL; + } + if (cluster_data->Sims != NULL) + { + nmSysFree(cluster_data->Sims); cluster_data->Sims = NULL; } @@ -2174,10 +2177,9 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); if (cluster_data->Clusters != NULL) { - const unsigned int nDatas = cluster_data->SourceData->nDatas; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - size += cluster_data->Clusters[i].Size * (sizeof(char*) + sizeof(pVector)); - size += nDatas * (sizeof(Cluster) + sizeof(double)); + size += cluster_data->Clusters[i].Size * (sizeof(int)); + size += cluster_data->nClusters * (sizeof(Cluster) + sizeof(double)); } if (cluster_data->SubClusters != NULL) { @@ -2186,7 +2188,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) size += ci_SizeOfClusterData(cluster_data->SubClusters[i], recursive); } - size += cluster_data->nSubClusters * sizeof(void*); + size += cluster_data->nSubClusters * sizeof(pClusterData); } size += sizeof(ClusterData); @@ -2217,7 +2219,7 @@ ci_SizeOfSearchData(pSearchData search_data) unsigned int size = 0u; if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); - if (search_data->Pairs != NULL) size += search_data->nPairs * (sizeof(void*) + sizeof(Pair)); + if (search_data->Pairs != NULL) size += search_data->nPairs * (sizeof(pPair) + sizeof(Pair)); size += sizeof(SearchData); return size; @@ -2281,10 +2283,9 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (!check(xaInit(&vector_xarray, 64))) goto end_free; /** Fetch data and build vectors. **/ - while (true) + pObject entry; + while ((entry = objQueryFetch(query, O_RDONLY)) != NULL) { - pObject entry = objQueryFetch(query, O_RDONLY); - if (entry == NULL) break; /* Done. */ ASSERTMAGIC(entry, MGK_OBJECT); /** Data value: Type checking. **/ @@ -2292,7 +2293,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (data_datatype == -1) { mssError(0, "Cluster", - "Failed to get type for data of %uth entry.", + "Failed to get type for data of entry #%u.", vector_xarray.nItems ); goto end_free; @@ -2300,7 +2301,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (data_datatype != DATA_T_STRING) { mssError(1, "Cluster", - "Type for data of %uth entry was %s instead of String:\n", + "Type for data of entry #%u was %s instead of String:\n", vector_xarray.nItems, objTypeToStr(data_datatype) ); goto end_free; @@ -2312,7 +2313,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (ret != 0) { mssError(0, "Cluster", - "Failed to get attribute value for %uth data entry (error code: %d).", + "Failed to get attribute value for data entry #%u (error code: %d).", vector_xarray.nItems, ret ); goto end_free; @@ -2346,7 +2347,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (key_datatype == -1) { mssError(0, "Cluster", - "Failed to get type for key on %uth entry.", + "Failed to get type of key on entry #%u.", vector_xarray.nItems ); goto end_free; @@ -2354,7 +2355,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (key_datatype != DATA_T_STRING) { mssError(1, "Cluster", - "Type for key on %uth entry was %s instead of String:", + "Type of key on entry #%u was %s instead of String:", vector_xarray.nItems, objTypeToStr(key_datatype) ); goto end_free; @@ -2366,7 +2367,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (ret != 0) { mssError(0, "Cluster", - "Failed to value for key on %uth entry (error code: %d).", + "Failed to value for key on entry #%u (error code: %d).", vector_xarray.nItems, ret ); goto end_free; @@ -2438,7 +2439,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Clean up xarrays. **/ if (key_xarray.nAlloc != 0) { - for (unsigned int i = 0u; i < vector_xarray.nItems; i++) + for (unsigned int i = 0u; i < key_xarray.nItems; i++) { char* key = key_xarray.Items[i]; if (key != NULL) nmSysFree(key); From 33d61500818313f7afa338fa2bc8cfd6bbe6c049 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 20 Mar 2026 17:15:32 -0600 Subject: [PATCH 097/361] Fix bugs and clean up. Add compiler directives to require that ca_has_no_pairs() be updated if CA_NUM_DIMS is changed. Add comments noting the assumptions and reasoning for ca_has_no_pairs(). Modify mssError() to NOT print an error if MSS.LogMethod is "" because some centrallix-lib tests spam errors to such an absurd degree that they may time out if we need to wait to print them all. Fix fprint_mem() misinterpreting the number of memory pages as the number of bytes. Fix incorrect value for N_UNITS and other mistakes in snprint_bytes(). Add some error checking to timer functions. Add doc comments to timer functions. Clarify dates in a copyright notice. Add additional comments. Clean up. --- centrallix-lib/include/clusters.h | 9 ++++ centrallix-lib/src/mtsession.c | 8 +-- centrallix-lib/src/util.c | 86 ++++++++++++++++++++++++++----- 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index f76272dbc..c813e2169 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -114,11 +114,20 @@ int ca_kmeans( /** Vector helper macros. **/ #define ca_is_empty(vector) (vector[0] == -CA_NUM_DIMS) +/*** Note: Given that CA_NUM_DIMS == 251, ca_build_vector("") will give the + *** vector we check for in the ca_has_no_pairs() macro, [-172, 11, -78], + *** which has a single pair of boundary characters. + *** If CA_NUM_DIMS is modified, this macro will need to be updated, hence the + *** compiler directive causing it to be undefined in this case, likely leading + *** to a lot of compiler or linker issues to remind the developer about this. + ***/ +#if CA_NUM_DIMS == 251 #define ca_has_no_pairs(vector) \ ({ \ __typeof__ (vector) _v = (vector); \ _v[0] == -172 && _v[1] == 11 && _v[2] == -78; \ }) +#endif /** Comparison functions (see ca_search()). **/ double ca_cos_compare(void* v1, void* v2); diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index ad0842f56..bd1650e6f 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -496,7 +496,7 @@ mssError(int clr, char* module, char* message, ...) if (log_error) { /** Start new error stacks with a newline to make them distinct. **/ - if (clr) fprintf(stderr, "\n"); + if (clr && MSS.LogMethod[0] != '\0') fprintf(stderr, "\n"); /** Use the requested logging method. **/ if (strcmp(MSS.LogMethod, "syslog") == 0) @@ -510,12 +510,6 @@ mssError(int clr, char* module, char* message, ...) { printf("%s: %s\n", (MSS.AppName[0]) ? MSS.AppName : "error", err_msg); } - else - { - /** Default to printing to stderr if no known logging method is given. **/ - fprintf(stderr, "Unknown log method MSS.LogMethod \"%s\".\n", MSS.LogMethod); - fprintf(stderr, "mssError: %s\n", err_msg); - } } /** If a session is available, try to add the error to the error list. **/ diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index c5064d3ae..9f1d590ab 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -10,7 +10,7 @@ /* */ /* Module: util.c, util.h */ /* Author: Micah Shennum and Israel Fuller */ -/* Date: May 26, 2011 */ +/* Date: May 26, 2011 and October 13, 2025 (respectively) */ /* Description: Collection of utilities including: */ /* - Utilities for parsing numbers. */ /* - The timer utility for benchmarking code. */ @@ -95,9 +95,9 @@ unsigned int strtoui(const char *nptr, char **endptr, int base){ *** Fun Fact: Windows uses kibibytes, but displays them as KB. ***/ #define USE_METRIC false -#define N_UNITS 6u -static char* units_cs[N_UNITS] = {"bytes", "KiB", "MiB", "GiB"}; -static char* units_metric[N_UNITS] = {"bytes", "KB", "MB", "GB"}; +static char* units_cs[] = {"bytes", "KiB", "MiB", "GiB"}; +static char* units_metric[] = {"bytes", "KB", "MB", "GB"}; +#define N_UNITS ((unsigned int)(sizeof(units_cs) / sizeof(units_cs[0]))) /*** Displays a size in bytes using the largest unit where the result would be *** at least 1.0. Note that units larger than GB and GiB are not supported @@ -119,7 +119,7 @@ snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) /** Search for the largest unit where the value would be at least 1. **/ const double size = (double)bytes; - for (unsigned char i = N_UNITS; i >= 1u; i--) + for (unsigned char i = N_UNITS - 1; i >= 1u; i--) { const double denominator = pow(unit_size, i); if (size >= denominator) @@ -140,14 +140,14 @@ snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) return buf; } -#undef nUints +#undef N_UNITS /*** Print a large number formatted with comas to a buffer. *** *** @param buf The buffer to print the number into. *** @param buf_size The maximum number of characters to add to the buffer. *** @param value The value to write into the buffer. - *** @returns `buf`, or `NULL` if `buf_size` is 0. + *** @returns `buf`, or NULL if `buf_size` is 0. */ char* snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) @@ -160,6 +160,9 @@ snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) return buf; } + /*** Write the number to the string in reverse order, adding commas as + *** they are needed. + ***/ char tmp[32]; unsigned int ti = 0; while (value > 0 && ti < sizeof(tmp) - 1) @@ -177,12 +180,14 @@ snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) return buf; } +/** Print summary the current memory in use to the file pointer. **/ void fprint_mem(FILE* out) { FILE* fp = fopen("/proc/self/statm", "r"); if (fp == NULL) { perror("fopen()"); return; } + /** Get page counts. **/ long size, resident, share, text, lib, data, dt; if (fscanf(fp, "%ld %ld %ld %ld %ld %ld %ld", &size, &resident, &share, &text, &lib, &data, &dt) != 7) @@ -193,19 +198,33 @@ fprint_mem(FILE* out) } check(fclose(fp)); /* Failure ignored. */ - long page_size = sysconf(_SC_PAGESIZE); // in bytes - long resident_bytes = resident * page_size; + /** Get page size. **/ + const long page_size = sysconf(_SC_PAGESIZE); /* in bytes */ + if (page_size == -1) + { + fprintf(stderr, "Failed to get page size.\n"); + return; + } - const size_t buf_siz = 16u; - char buf[buf_siz]; - snprint_bytes(buf, buf_siz, (unsigned int)resident_bytes); + /** Get the number of resident bytes used. **/ + const long resident_bytes = resident * page_size; + char buf[16]; + snprint_bytes(buf, sizeof(buf), (unsigned int)(resident_bytes)); + /** fprintf() out data. **/ fprintf(out, "Memory used: %ld bytes (%s)\n", resident_bytes, buf); - fprintf(out, "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", share, text, lib, data); + fprintf(out, + "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", + share * page_size, text * page_size, lib * page_size, data * page_size + ); return; } +/*** Get the current monotonic time in seconds. + *** + *** @returns The current monotonic time as a fractional number of seconds. + ***/ static double get_time(void) { @@ -216,6 +235,11 @@ get_time(void) return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9f; } +/*** Initialize a timer struct. + *** + *** @param timer The timer to initialize. + *** @returns `timer`, for chaining. + ***/ pTimer timer_init(pTimer timer) { @@ -226,12 +250,21 @@ timer_init(pTimer timer) return timer; } +/*** Allocate and initialize a new timer. + *** + *** @returns A newly allocated timer, or NULL if allocation fails. + ***/ pTimer timer_new(void) { - return timer_init(nmMalloc(sizeof(Timer))); + return timer_init(check_ptr(nmMalloc(sizeof(Timer)))); } +/*** Start timing. + *** + *** @param timer The timer to start. + *** @returns `timer`, for chaining. + ***/ pTimer timer_start(pTimer timer) { @@ -241,6 +274,11 @@ timer_start(pTimer timer) return timer; } +/*** Stop timing and add the elapsed time to the timer total. + *** + *** @param timer The timer to stop. + *** @returns `timer`, for chaining. + ***/ pTimer timer_stop(pTimer timer) { @@ -250,21 +288,41 @@ timer_stop(pTimer timer) return timer; } +/*** Get the total accumulated time for a timer. + *** + *** @param timer The timer to read. + *** @returns The total accumulated time in seconds, or NAN if `timer` + *** is NULL. + ***/ double timer_get(pTimer timer) { return (timer) ? timer->total : NAN; } +/*** Reset a timer to its initial state so that it can be reused to time + *** something else. + *** + *** @param timer The timer to reset. + *** @returns `timer`, for chaining. + ***/ pTimer timer_reset(pTimer timer) { return timer_init(timer); } +/*** De-initialize a timer before it is freed. + *** + *** @param timer The timer to de-initialize. + ***/ void timer_de_init(pTimer timer) {} +/*** De-initialize and free a timer allocated by timer_new(). + *** + *** @param timer The timer to free. + ***/ void timer_free(pTimer timer) { From b2e0f35216e7791cd7e23ef08c79be05232ffb8d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 23 Mar 2026 11:07:48 -0600 Subject: [PATCH 098/361] Address Greptile comments. Fix possible buffer overflows in strings used by double metaphone. Fix a memory leak in exp_fn_metaphone(). Fix a call to nmFree() that passed the wrong size. Add doc comment to strtcpy() and strtcat(). --- centrallix-lib/src/strtcpy.c | 35 ++++++++++++++++++--------- centrallix/expression/exp_functions.c | 2 ++ centrallix/osdrivers/objdrv_cluster.c | 2 +- centrallix/utility/double_metaphone.c | 16 ++++++++---- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/centrallix-lib/src/strtcpy.c b/centrallix-lib/src/strtcpy.c index 99357a7e3..05b855770 100644 --- a/centrallix-lib/src/strtcpy.c +++ b/centrallix-lib/src/strtcpy.c @@ -26,11 +26,18 @@ #define __builtin_expect(e,c) (e) #endif -/*** strtcat() - truncating string concatenation - *** - *** Appends to dst, being sure to not overflow the given dstlen size. - *** Returns number of bytes actually copied, including null terminator. - *** If truncated, returns -(bytes copied). +/*** Truncating string concatenation. + *** + *** Appends the source string to the destination string, truncating the data + *** if needed to avoid overflowing the destination buffer. + *** + *** @param dst A pointer to the destination buffer. + *** @param src A pointer to the source buffer to read from. + *** @param dstlen The total allocated memory of the destination buffer (not + *** the length of available space), used to avoid writing off the end of + *** the buffer. + *** @returns number of bytes actually copied, including null terminator. If + *** truncated, returns `-(bytes copied)`. ***/ int strtcat(char* dst, const char* src, size_t dstlen) @@ -50,10 +57,18 @@ strtcat(char* dst, const char* src, size_t dstlen) } -/*** strtcpy() - truncating string copy - *** - *** returns number of bytes actually copied (including null terminator) - *** if truncated, returns -(bytes copied), which is the same as -(dstlen). +/*** Truncating string copy. + *** + *** Truncates written data if the source data is too long to fit into the + *** allocated destination buffer. + *** + *** @param dst A pointer to the destination buffer. + *** @param src A pointer to the source buffer to read from. + *** @param dstlen The length of the destination buffer, used to avoid writing + *** off the end of the allocated memory. + *** @returns The number of bytes actually copied (including null terminator). + *** If the data is truncated, returns `-(bytes copied)`, which is the same + *** as `-(dstlen)`. ***/ int strtcpy(char* dst, const char* src, size_t dstlen) @@ -81,5 +96,3 @@ strtcpy(char* dst, const char* src, size_t dstlen) return origlen - dstlen; #endif } - - diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 5b92b10ce..fcc120b8d 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4574,6 +4574,8 @@ exp_fn_metaphone(pExpression tree) const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; if (!check(exp_fn_i_alloc_result_string(tree, length))) return -1; sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); + nmSysFree(primary); + nmSysFree(secondary); tree->DataType = DATA_T_STRING; return 0; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 8ec671149..8320fdee2 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2874,7 +2874,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { for (unsigned int i = 0u; i < pairs->nItems; i++) { - if (pairs->Items[i] != NULL) nmFree(pairs->Items[i], sizeof(pairs)); + if (pairs->Items[i] != NULL) nmFree(pairs->Items[i], sizeof(Pair)); else break; } check(xaFree(pairs)); /* Failure ignored. */ diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 929620b7f..9f05550ee 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -188,7 +188,7 @@ meta_new_string(const char* init_str) s->str = (char*)SAFE_MALLOC(s->bufsize * sizeof(char)); - strncpy(s->str, init_str, s->length + 1); + strtcpy(s->str, init_str, s->bufsize); s->free_str_on_destroy = 1; return s; @@ -308,6 +308,7 @@ bool meta_is_str_at(MetaString* s, unsigned int start, ...) { va_list ap; + bool found = false; /** Should never happen. **/ if (meta_is_out_of_bounds(s, start)) @@ -320,14 +321,17 @@ meta_is_str_at(MetaString* s, unsigned int start, ...) do { test = va_arg(ap, char*); - if (*test && (strncmp(pos, test, strlen(test)) == 0)) - return true; + if (test[0] != '\0' && (strncmp(pos, test, strlen(test)) == 0)) + { + found = true; + break; + } } while (test[0] != '\0'); va_end(ap); - return false; + return found; } /*** Adds a string to a MetaString, expanding the MetaString if needed. @@ -341,11 +345,13 @@ meta_add_str(MetaString* s, const char* new_str) if (new_str == NULL) return; + /** Increase the buffer to the required size. **/ const size_t add_length = strlen(new_str); if ((s->length + add_length) > (s->bufsize - 1)) meta_increase_buffer(s, add_length); - strcat(s->str, new_str); + /** Write the data to the buffer. **/ + strtcat(s->str, new_str, s->bufsize); s->length += add_length; return; From 326a475d9b25cca374266127731a6c5810f1020e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 23 Mar 2026 11:22:49 -0600 Subject: [PATCH 099/361] Update error handling in exp_fn_metaphone(). --- centrallix/expression/exp_functions.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index fcc120b8d..726cef05a 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4533,6 +4533,9 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress int exp_fn_metaphone(pExpression tree) { + int ret = -1; + bool free_strs = true; + /** Verify function schema. **/ if (exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, @@ -4540,7 +4543,7 @@ exp_fn_metaphone(pExpression tree) }, tree) != 0) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); - return -1; + goto end_free; } /** Allocate space to store metaphone pointers. **/ @@ -4549,20 +4552,21 @@ exp_fn_metaphone(pExpression tree) /** Extract string param. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); - if (maybe_str == NULL) return -1; + if (maybe_str == NULL) goto end_free; if (maybe_str->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_STRING; - return 0; + goto end_free; } const char* str = check_ptr(maybe_str->String); - if (str == NULL) return -1; + if (str == NULL) goto end_free; const size_t str_len = strlen(str); if (str_len == 0u) { primary = ""; secondary = ""; + free_strs = false; goto store_data; } @@ -4574,11 +4578,15 @@ exp_fn_metaphone(pExpression tree) const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; if (!check(exp_fn_i_alloc_result_string(tree, length))) return -1; sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); - nmSysFree(primary); - nmSysFree(secondary); tree->DataType = DATA_T_STRING; + ret = 0; - return 0; + end_free: + if (free_strs && primary != NULL) nmSysFree(primary); + if (free_strs && secondary != NULL) nmSysFree(secondary); + if (ret == -1) mssError(0, "EXP", "%s(): Failed to execute function.", tree->Name); + + return ret; } From 06f07b9868169a82c87108ca5edb715560880938 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 23 Mar 2026 14:36:43 -0600 Subject: [PATCH 100/361] Modify check function syntax. Modify check() and check_neg() to return the result value. Remove check_weak() because we couldn't think of any usecase for it. Update all code that called check() or check_neg() to align with the new syntax. Fix an error case in ci_ParseSearchData() that was not previously handled. --- centrallix-lib/include/util.h | 43 +++++-------- centrallix-lib/src/clusters.c | 6 +- centrallix-lib/src/mtsession.c | 2 +- centrallix/expression/exp_functions.c | 12 ++-- centrallix/osdrivers/objdrv_cluster.c | 88 ++++++++++++++------------- 5 files changed, 72 insertions(+), 79 deletions(-) diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 6cbdeb29a..37f4601d7 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -98,10 +98,11 @@ extern "C" { void print_err(int code, const char* function_name, const char* file_name, const int line_number); /*** Ensures that developer diagnostics are printed if the result of the - *** passed function call is not zero. Not intended for user errors. - *** - *** @param result The result of the function we're checking. - *** @returns Whether the passed function succeeded. + *** passed function call is not zero. Not intended for user errors. + *** + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. ***/ #define check(result) \ ({ \ @@ -109,14 +110,15 @@ void print_err(int code, const char* function_name, const char* file_name, const __typeof__ (result) _r = (result); \ const bool success = (_r == 0); \ if (!success) print_err(_r, #result, __FILE__, __LINE__); \ - success; \ + _r; \ }) /*** Ensures that developer diagnostics are printed if the result of the *** passed function call is negative. Not intended for user errors. *** - *** @param result The result of the function we're checking. - *** @returns Whether the passed function succeeded. + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. ***/ #define check_neg(result) \ ({ \ @@ -124,29 +126,15 @@ void print_err(int code, const char* function_name, const char* file_name, const __typeof__ (result) _r = (result); \ const bool success = (_r >= 0); \ if (!success) print_err(_r, #result, __FILE__, __LINE__); \ - success; \ - }) - -/*** Ensures that developer diagnostics are printed if the result of the - *** passed function call is -1. Not intended for user errors. - *** - *** @param result The result of the function we're checking. - *** @returns Whether the passed function succeeded. - ***/ -#define check_weak(result) \ - ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - const bool success = (_r != -1); \ - if (!success) print_err(_r, #result, __FILE__, __LINE__); \ - success; \ + _r; \ }) /*** Ensures that developer diagnostics are printed if the result of the *** passed function call is a NAN double. Not intended for user errors. *** - *** @param result The result of the function we're checking. - *** @returns result + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. ***/ #define check_double(result) \ ({ \ @@ -159,8 +147,9 @@ void print_err(int code, const char* function_name, const char* file_name, const /*** Ensures that developer diagnostics are printed if the result of the *** passed function call is a NULL pointer. Not intended for user errors. *** - *** @param result The result of the function we're checking. - *** @returns result + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. ***/ #define check_ptr(result) \ ({ \ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 07dbd1b6d..e6f7c66a3 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -670,8 +670,8 @@ ca_lev_compare(void* str1, void* str2) if (len1 == 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ - const int edit_dist = ca_edit_dist((const char*)str1, (const char*)str2, len1, len2); - if (!check_neg(edit_dist)) return NAN; + const int edit_dist = check_neg(ca_edit_dist((const char*)str1, (const char*)str2, len1, len2)); + if (edit_dist < 0) return NAN; /** Normalize edit distance into a similarity measure. **/ const double normalized_similarity = 1.0 - (double)edit_dist / (double)max(len1, len2); @@ -1061,7 +1061,7 @@ ca_sliding_search( pair->i = i; pair->j = j; pair->similarity = sim; - if (!check_neg(xaAddItem(pairs, (void*)pair))) goto err_free; + if (check_neg(xaAddItem(pairs, (void*)pair)) < 0) goto err_free; } } } diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index bd1650e6f..3e35dea16 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -527,7 +527,7 @@ mssError(int clr, char* module, char* message, ...) } /** Store the error. **/ - if (!check_neg(xaAddItem(&(s->ErrList), (void*)allocated_err_msg))) + if (check_neg(xaAddItem(&(s->ErrList), (void*)allocated_err_msg)) < 0) { fprintf(stderr, "Failed to add error message to session error list: %s\n", err_msg); return; /* Give up. */ diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 726cef05a..460db8670 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1703,7 +1703,7 @@ exp_fn_rtrim(pExpression tree) } /** We need to copy to remove spaces. **/ - if (!check(exp_fn_i_alloc_result_string(tree, n + 1))) return -1; + if (check(exp_fn_i_alloc_result_string(tree, n + 1)) != 0) return -1; memcpy(tree->String, str, n); tree->String[n] = '\0'; @@ -3800,7 +3800,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) double n[4]; for (int i = 0; i < arg_num; i++) { - if (!check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i])))) + if (check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i]))) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); return -1; @@ -3848,14 +3848,14 @@ exp_fn_log(pExpression tree) /** Extract args. **/ double number, base; - if (!check(exp_fn_i_get_number(check_ptr(tree->Children.Items[0]), &number))) + if (check(exp_fn_i_get_number(check_ptr(tree->Children.Items[0]), &number)) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); return -1; } if (tree->Children.nItems > 1) { - if (!check(exp_fn_i_get_number(check_ptr(tree->Children.Items[1]), &base))) + if (check(exp_fn_i_get_number(check_ptr(tree->Children.Items[1]), &base)) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); return -1; @@ -4576,7 +4576,7 @@ exp_fn_metaphone(pExpression tree) /** Store the results. **/ store_data:; const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; - if (!check(exp_fn_i_alloc_result_string(tree, length))) return -1; + if (check(exp_fn_i_alloc_result_string(tree, length)) != 0) return -1; sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); tree->DataType = DATA_T_STRING; ret = 0; @@ -4712,7 +4712,7 @@ exp_fn_levenshtein(pExpression tree) /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); - if (!check_neg(edit_dist)) + if (check_neg(edit_dist) < 0) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); return -1; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 8320fdee2..6666d0594 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -787,7 +787,7 @@ ci_ParseAttribute( /** Evaluate expression. **/ ret = expEvalTree(exp, param_list); - if (ret != 0) + if (ret != 0) { mssError(0, "Cluster", "Expression evaluation failed (error code %d).", ret); goto err; @@ -951,7 +951,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) /** Initialize obvious values for SourceData. **/ source_data->Name = check_ptr(nmSysStrdup(inf->Name)); if (source_data->Name == NULL) goto err_free; - if (!check(objCurrentDate(&source_data->DateCreated))) goto err_free; + if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; /** Get source. **/ if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; @@ -995,7 +995,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) } /** Cache miss: Add the new object to the cache for next time. **/ - if (!check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey, (void*)source_data))) + if (check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey, (void*)source_data)) != 0) goto err_free; /** Success. **/ @@ -1054,7 +1054,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); if (cluster_data->Name == NULL) goto err_free; cluster_data->SourceData = source_data; - if (!check(objCurrentDate(&cluster_data->DateCreated))) goto err_free; + if (check(objCurrentDate(&cluster_data->DateCreated)) != 0) goto err_free; /** Get algorithm. **/ cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); @@ -1151,7 +1151,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source else cluster_data->Seed = CI_NO_SEED; /** Search for sub-clusters. **/ - if (!check(xaInit(&sub_clusters, 4u))) goto err_free; + if (check(xaInit(&sub_clusters, 4u)) != 0) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); @@ -1209,7 +1209,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source pClusterData sub_cluster = ci_ParseClusterData(sub_inf, param_list, source_data); if (sub_cluster == NULL) goto err_free; sub_cluster->Parent = cluster_data; - if (!check_neg(xaAddItem(&sub_clusters, sub_cluster))) goto err_free; + if (check_neg(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; break; } @@ -1296,7 +1296,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source } /** Cache miss. **/ - if (!check(xhAdd(&ClusterDriverCaches.ClusterDataCache, cache_key, (void*)cluster_data))) goto err_free; + if (check(xhAdd(&ClusterDriverCaches.ClusterDataCache, cache_key, (void*)cluster_data)) != 0) goto err_free; return cluster_data; /** Error cleanup. **/ @@ -1352,7 +1352,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Get basic information. **/ search_data->Name = check_ptr(nmSysStrdup(inf->Name)); if (search_data->Name == NULL) goto err_free; - if (!check(objCurrentDate(&search_data->DateCreated))) goto err_free; + if (check(objCurrentDate(&search_data->DateCreated)) != 0) goto err_free; /** Search for the source cluster. **/ char* source_cluster_name; @@ -1491,7 +1491,10 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Cache miss. **/ - check(xhAdd(search_cache, key, (void*)search_data)); + if (check(xhAdd(search_cache, key, (void*)search_data)) != 0) + goto err_free; + + /** Done. **/ return search_data; /** Error cleanup. **/ @@ -1568,9 +1571,9 @@ ci_ParseNodeData(pStructInf inf, pObject parent) } /** Detect relevant groups. **/ - if (!check(xaInit(¶m_infs, 8))) goto err_free; - if (!check(xaInit(&cluster_infs, 8))) goto err_free; - if (!check(xaInit(&search_infs, 8))) goto err_free; + if (check(xaInit(¶m_infs, 8)) != 0) goto err_free; + if (check(xaInit(&cluster_infs, 8)) != 0) goto err_free; + if (check(xaInit(&search_infs, 8)) != 0) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); @@ -1613,17 +1616,17 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (group_type == NULL) goto err_free; if (strcmp(group_type, "cluster/parameter") == 0) { - if (!check_neg(xaAddItem(¶m_infs, sub_inf))) + if (check_neg(xaAddItem(¶m_infs, sub_inf) < 0)) goto err_free; } else if (strcmp(group_type, "cluster/cluster") == 0) { - if (!check_neg(xaAddItem(&cluster_infs, sub_inf))) + if (check_neg(xaAddItem(&cluster_infs, sub_inf) < 0)) goto err_free; } else if (strcmp(group_type, "cluster/search") == 0) { - if (!check_neg(xaAddItem(&search_infs, sub_inf))) + if (check_neg(xaAddItem(&search_infs, sub_inf) < 0)) goto err_free; } else @@ -2259,7 +2262,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (source_data->Vectors != NULL) return 0; /** Record the date and time. **/ - if (!check(objCurrentDate(&source_data->DateComputed))) goto end_free; + if (check(objCurrentDate(&source_data->DateComputed)) != 0) goto end_free; /** Open the source path specified by the .cluster file. **/ obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); @@ -2278,9 +2281,9 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Initialize an xarray to store the retrieved data. **/ - if (!check(xaInit(&key_xarray, 64))) goto end_free; - if (!check(xaInit(&data_xarray, 64))) goto end_free; - if (!check(xaInit(&vector_xarray, 64))) goto end_free; + if (check(xaInit(&key_xarray, 64)) != 0) goto end_free; + if (check(xaInit(&data_xarray, 64)) != 0) goto end_free; + if (check(xaInit(&vector_xarray, 64)) != 0) goto end_free; /** Fetch data and build vectors. **/ pObject entry; @@ -2378,9 +2381,9 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (key_dup == NULL) goto end_free; char* data_dup = check_ptr(nmSysStrdup(data)); if (data_dup == NULL) goto end_free; - if (!check_neg(xaAddItem(&key_xarray, (void*)key_dup))) goto end_free; - if (!check_neg(xaAddItem(&data_xarray, (void*)data_dup))) goto end_free; - if (!check_neg(xaAddItem(&vector_xarray, (void*)vector))) goto end_free; + if (check_neg(xaAddItem(&key_xarray, (void*)key_dup) < 0)) goto end_free; + if (check_neg(xaAddItem(&data_xarray, (void*)data_dup) < 0)) goto end_free; + if (check_neg(xaAddItem(&vector_xarray, (void*)vector) < 0)) goto end_free; /** Clean up. **/ check(objClose(entry)); /* Failure ignored. */ @@ -2523,7 +2526,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Record the date and time. **/ - if (!check(objCurrentDate(&cluster_data->DateComputed))) goto err_free; + if (check(objCurrentDate(&cluster_data->DateComputed)) != 0) goto err_free; /** Allocate static memory for finding clusters. **/ clusters_size = cluster_data->nClusters * sizeof(Cluster); @@ -2584,7 +2587,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (!auto_seed) srand(cluster_data->Seed); /** Run ca_kmeans(). **/ - const bool successful = check(ca_kmeans( + const bool successful = (check(ca_kmeans( source_data->Vectors, source_data->nDatas, cluster_data->nClusters, @@ -2593,7 +2596,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) labels, cluster_data->Sims, auto_seed - )); + )) == 0); if (!successful) goto err_free; /** Convert the labels into clusters. **/ @@ -2601,11 +2604,11 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate temporary xArrays for tracking the indices stored in each cluster. **/ XArray indexes_in_cluster[cluster_data->nClusters]; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - if (!check(xaInit(&indexes_in_cluster[i], 8))) goto err_free; + if (check(xaInit(&indexes_in_cluster[i], 8)) != 0) goto err_free; /** Iterate through each label and add the index of the specified cluster to the xArray. **/ for (unsigned long long i = 0llu; i < source_data->nDatas; i++) - if (!check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i))) goto err_free; + if (check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; nmSysFree(labels); /* Free unused data. */ /** Store the indices for each cluster and free the temporary xArray. **/ @@ -2731,7 +2734,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Record the date and time. **/ - if (!check(objCurrentDate(&search_data->DateComputed))) goto err_free; + if (check(objCurrentDate(&search_data->DateComputed)) != 0) goto err_free; /** Get the comparison function based on the similarity measure. **/ const double (*similarity_function)(void *, void *) = ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure); @@ -2842,7 +2845,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) const pPair pair = (pPair)cluster_pairs->Items[i]; pair->i = cluster->Indexes[pair->i]; pair->j = cluster->Indexes[pair->j]; - if (!check_neg(xaAddItem(pairs, pair))) goto err_free; + if (check_neg(xaAddItem(pairs, pair)) < 0) goto err_free; } check(xaFree(cluster_pairs)); /* Failure ignored. */ } @@ -2964,7 +2967,7 @@ ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) } /** Return param value. **/ - if (!check(objCopyData(&(param->Value->Data), val, datatype))) goto err; + if (check(objCopyData(&(param->Value->Data), val, datatype)) != 0) goto err; return 0; } @@ -4682,23 +4685,23 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (path != NULL) printf("\"%s\":\n", path); else printf("all files:\n"); printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Entry CacheKey"); - failed |= !check(xhForEach( + failed |= (check(xhForEach( &ClusterDriverCaches.SourceDataCache, ci_PrintEntry, (void*[]){&i, &source_bytes, (void*)&skip_uncomputed, path} - )); + )) != 0); i++; - failed |= !check(xhForEach( + failed |= (check(xhForEach( &ClusterDriverCaches.ClusterDataCache, ci_PrintEntry, (void*[]){&i, &cluster_bytes, (void*)&skip_uncomputed, path} - )); + )) != 0); i++; - failed |= !check(xhForEach( + failed |= (check(xhForEach( &ClusterDriverCaches.SearchDataCache, ci_PrintEntry, (void*[]){&i, &search_bytes, (void*)&skip_uncomputed, path} - )); + )) != 0); if (failed) { mssError(0, "Cluster", "Unexpected error occurred while showing caches."); @@ -4886,14 +4889,15 @@ clusterInitialize(void) /** Initialize caches. **/ // memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); - if (!check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0))) goto err_free; - if (!check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0))) goto err_free; - if (!check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0))) goto err_free; + if (check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0)) != 0) goto err_free; + if (check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0)) != 0) goto err_free; + if (check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0)) != 0) goto err_free; /** Setup the structure. **/ if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; - if (!check(xaInit(&drv->RootContentTypes, 1))) goto err_free; - if (!check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster"))) goto err_free; + if (check(xaInit(&drv->RootContentTypes, 1)) != 0) goto err_free; + if (check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster")) < 0) goto err_free; + drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ /** Setup the function references. **/ @@ -4926,7 +4930,7 @@ clusterInitialize(void) drv->GetQueryIdentityPath = NULL; /** Register the driver. **/ - if (!check(objRegisterDriver(drv))) goto err_free; + if (check(objRegisterDriver(drv)) != 0) goto err_free; /** Register structs used in this project with the newmalloc memory management system. **/ nmRegister(sizeof(SourceData), "ClusterSourceData"); From 0c8f5ccfc563dbc96354ab106be8e8d7ca7c3330 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 23 Mar 2026 17:14:44 -0600 Subject: [PATCH 101/361] Overhaul error checking in double_metaphone.c. Add error checking for memory allocations instead of exiting on failure. Update function signatures as needed. Remove built in test suite in double_metaphone.c because the new dependencies cause it to no longer work. Add pMetaString, replacing uses of MetaString*. Fix dead incorrect link to the original repository. --- centrallix/expression/exp_functions.c | 9 +- centrallix/include/double_metaphone.h | 2 +- centrallix/utility/double_metaphone.c | 767 ++++++++------------------ 3 files changed, 247 insertions(+), 531 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 460db8670..96d0d452d 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4571,7 +4571,12 @@ exp_fn_metaphone(pExpression tree) } /** Compute DoubleMetaphone. **/ - meta_double_metaphone(str, &primary, &secondary); + ret = meta_double_metaphone(str, &primary, &secondary); + if (ret != 0) + { + mssError(1, "EXP", "Double metaphone computation failed (error code %d).", ret); + goto end_free; + } /** Store the results. **/ store_data:; @@ -4584,7 +4589,7 @@ exp_fn_metaphone(pExpression tree) end_free: if (free_strs && primary != NULL) nmSysFree(primary); if (free_strs && secondary != NULL) nmSysFree(secondary); - if (ret == -1) mssError(0, "EXP", "%s(): Failed to execute function.", tree->Name); + if (ret != 0) mssError(0, "EXP", "%s(): Failed to execute function.", tree->Name); return ret; } diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h index f0641bd8a..5f07de7f6 100644 --- a/centrallix/include/double_metaphone.h +++ b/centrallix/include/double_metaphone.h @@ -78,6 +78,6 @@ /* standards so that it could be included here. */ /************************************************************************/ -void meta_double_metaphone(const char* str, char** primary_code, char** secondary_code); +int meta_double_metaphone(const char* str, char** primary_code, char** secondary_code); #endif /* End of .h file. */ diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 9f05550ee..71ea6c79f 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -94,7 +94,7 @@ *** *** If you have any questions, please feel free to reach out to me or Greg. *** - *** Original Source: https://github.com/gitpan/Text-meta_double_metaphone + *** Original Source: https://github.com/gitpan/Text-DoubleMetaphone ***/ #include @@ -104,58 +104,9 @@ #include #include -/*** If running in a testing environment, newmalloc is not - *** available, so we fall back to default C memory allocation. - ***/ -#ifndef TESTING #include "cxlib/newmalloc.h" -#define META_MALLOC(size) nmSysMalloc(size) -#define META_REALLOC(ptr, size) nmSysRealloc(ptr, size) -#define META_FREE(ptr) nmSysFree(ptr) -#else -#include -#define META_MALLOC(size) malloc(size) -#define META_REALLOC(ptr, size) realloc(ptr, size) -#define META_FREE(ptr) free(ptr) -#endif - -/*** Helper function to handle checking for failed memory allocation - *** Author: Israel Fuller. - *** - *** @param ptr Pointer to the memory that should be allocated. - *** @param fname The name of the function invoked to allocate memory. - *** @param size The amount of memory being allocated. - *** @returns The pointer, for chaining. - ***/ -void* -meta_check_allocation(void* ptr, const char* fname, const size_t size) - { - if (ptr == NULL) - { - /** Create the most descriptive error message we can. **/ - char error_buf[BUFSIZ]; - snprintf(error_buf, sizeof(error_buf), "exp_double_metaphone.c: Fail - %s(%lu)", fname, size); - perror(error_buf); - - // Throw error for easier locating in a debugger. - fprintf(stderr, "Program will now crash.\n"); - assert(0); - } - - return ptr; - } - -/** Malloc shortcut macros. **/ -#define SAFE_MALLOC(size) \ - ({ \ - const size_t sz = (size); \ - memset(meta_check_allocation(META_MALLOC(sz), "META_MALLOC", sz), 0, sz); \ - }) -#define SAFE_REALLOC(ptr, size) \ - ({ \ - const size_t sz = (size); \ - meta_check_allocation(META_REALLOC(ptr, sz), "META_REALLOC", sz); \ - }) +#include "cxlib/strtcpy.h" +#include "cxlib/util.h" typedef struct { @@ -164,20 +115,21 @@ typedef struct size_t bufsize; int free_str_on_destroy; } -MetaString; + MetaString, *pMetaString; /*** Allocates a new MetaString. *** *** @param init_str The initial size of the string. - *** @returns The new MetaString. + *** @returns The new MetaString, or NULL if an error occurs. ***/ -MetaString* +pMetaString meta_new_string(const char* init_str) { - MetaString *s; + pMetaString s; char empty_string[] = ""; - s = (MetaString*)SAFE_MALLOC(sizeof(MetaString)); + s = (pMetaString)check_ptr(nmSysMalloc(sizeof(MetaString))); + if (s == NULL) goto err_free; if (init_str == NULL) init_str = empty_string; @@ -186,12 +138,22 @@ meta_new_string(const char* init_str) /** Preallocate a bit more for potential growth. **/ s->bufsize = s->length + 7u; - s->str = (char*)SAFE_MALLOC(s->bufsize * sizeof(char)); + s->str = (char*)check_ptr(nmSysMalloc(s->bufsize * sizeof(char))); + if (s->str == NULL) goto err_free; strtcpy(s->str, init_str, s->bufsize); s->free_str_on_destroy = 1; - return s; + return s; + + err_free: + if (s != NULL) + { + if (s->str != NULL) nmSysFree(s->str); + nmSysFree(s); + } + + return NULL; } /*** Frees a MetaString. @@ -199,31 +161,33 @@ meta_new_string(const char* init_str) *** @param s The MetaString. ***/ void -meta_destroy_string(MetaString* s) +meta_destroy_string(pMetaString s) { if (s == NULL) return; if (s->free_str_on_destroy && s->str != NULL) - META_FREE(s->str); + nmSysFree(s->str); - META_FREE(s); + nmSysFree(s); return; } /*** Increases a MetaString's buffer size. *** - *** @param s The MetaString* being modified. + *** @param s The pMetaString being modified. *** @param chars_needed Minimum number of characters to increase buffer size. + *** @returns 0 if successful, or -1 if an error occurs. ***/ -void -meta_increase_buffer(MetaString* s, const size_t chars_needed) +int +meta_increase_buffer(pMetaString s, const size_t chars_needed) { s->bufsize += chars_needed + 8u; - s->str = SAFE_REALLOC(s->str, s->bufsize * sizeof(char)); + s->str = check_ptr(nmSysRealloc(s->str, s->bufsize * sizeof(char))); + if (s->str == NULL) return -1; - return; + return 0; } /*** Convert all characters of a MetaString to uppercase. @@ -231,7 +195,7 @@ meta_increase_buffer(MetaString* s, const size_t chars_needed) *** @param s The MetaString being modified. ***/ void -meta_make_upper(MetaString* s) +meta_make_upper(pMetaString s) { for (char* i = s->str; i[0] != '\0'; i++) *i = (char)toupper(*i); @@ -245,7 +209,7 @@ meta_make_upper(MetaString* s) *** 0 otherwise. ***/ bool -meta_is_out_of_bounds(MetaString* s, unsigned int pos) +meta_is_out_of_bounds(pMetaString s, unsigned int pos) { return (s->length <= pos); } @@ -256,7 +220,7 @@ meta_is_out_of_bounds(MetaString* s, unsigned int pos) *** @param pos The character location to check within the MetaString. ***/ bool -meta_is_vowel(MetaString* s, unsigned int pos) +meta_is_vowel(pMetaString s, unsigned int pos) { if (meta_is_out_of_bounds(s, pos)) return 0; @@ -273,7 +237,7 @@ meta_is_vowel(MetaString* s, unsigned int pos) *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. ***/ bool -meta_is_slavo_germanic(MetaString* s) +meta_is_slavo_germanic(pMetaString s) { return (strstr(s->str, "W") != NULL) || (strstr(s->str, "K") != NULL) @@ -287,7 +251,7 @@ meta_is_slavo_germanic(MetaString* s) *** '\0' if the position is not in the MetaString. ***/ char -meta_get_char_at(MetaString* s, unsigned int pos) +meta_get_char_at(pMetaString s, unsigned int pos) { return (meta_is_out_of_bounds(s, pos)) ? '\0' : ((char) *(s->str + pos)); } @@ -305,14 +269,14 @@ meta_get_char_at(MetaString* s, unsigned int pos) *** in the MetaString and 0 otherwise. ***/ bool -meta_is_str_at(MetaString* s, unsigned int start, ...) +meta_is_str_at(pMetaString s, unsigned int start, ...) { va_list ap; bool found = false; /** Should never happen. **/ if (meta_is_out_of_bounds(s, start)) - return 0; + return false; const char* pos = (s->str + start); va_start(ap, start); @@ -338,23 +302,25 @@ meta_is_str_at(MetaString* s, unsigned int start, ...) *** *** @param s The MetaString being modified. *** @param new_str The string being added. + *** @returns 0 if successful, or -1 if an error occurs. ***/ -void -meta_add_str(MetaString* s, const char* new_str) +int +meta_add_str(pMetaString s, const char* new_str) { if (new_str == NULL) - return; + return -1; /** Increase the buffer to the required size. **/ const size_t add_length = strlen(new_str); - if ((s->length + add_length) > (s->bufsize - 1)) - meta_increase_buffer(s, add_length); + const size_t new_length = s->length + add_length + 1; + if (new_length > s->bufsize && check(meta_increase_buffer(s, add_length)) != 0) + return -1; /** Write the data to the buffer. **/ strtcat(s->str, new_str, s->bufsize); s->length += add_length; - return; + return 0; } /*** Computes double metaphone. @@ -371,45 +337,50 @@ meta_add_str(MetaString* s, const char* new_str) *** containing the produced primary code will be stored. *** @param secondary_code A pointer to a buffer where the pointer to a string *** containing the produced secondary code will be stored. + *** @returns 0 if successful, or -1 if an error occurs. ***/ -void +int meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) { - size_t length; + int ret = -1; - if (primary_code == NULL) + /** Edge cases. **/ + if (str == NULL) { - fprintf(stderr, "Warning: Call to meta_double_metaphone() is missing a pointer to store primary code.\n"); - return; + fprintf(stderr, "Error: Missing input string.\n"); + goto end_free; } - - if (secondary_code == NULL) + const size_t length = strlen(str); + if (length == 0lu) { - fprintf(stderr, "Warning: Call to meta_double_metaphone() is missing a pointer to store secondary code.\n"); - return; + fprintf(stderr, "Error: Empty input string.\n"); + goto end_free; } - - if (str == NULL || (length = strlen(str)) == 0u) + if (primary_code == NULL) { - fprintf(stderr, "Warning: Call to meta_double_metaphone() with invalid string.\n"); - - /** Double Metaphone on an invalid string yields two empty strings. **/ - *primary_code = (char*)SAFE_MALLOC(sizeof(char)); - *secondary_code = (char*)SAFE_MALLOC(sizeof(char)); - return; + fprintf(stderr, "Error: Missing a pointer to store primary code.\n"); + goto end_free; } + if (secondary_code == NULL) + { + fprintf(stderr, "Error: Missing a pointer to store secondary code.\n"); + goto end_free; + } + + /** Declare iteration variables. **/ unsigned int current = 0; - unsigned int last = (unsigned int)(length - 1); + const unsigned int last = (unsigned int)(length - 1); /** Pad original so we can index beyond end. **/ - MetaString* original = meta_new_string(str); + pMetaString original = check_ptr(meta_new_string(str)); + if (original == NULL) goto end_free; meta_make_upper(original); - meta_add_str(original, " "); + if (check(meta_add_str(original, " ")) != 0) goto end_free; - MetaString* primary = meta_new_string(""); - MetaString* secondary = meta_new_string(""); - primary->free_str_on_destroy = 0; - secondary->free_str_on_destroy = 0; + /** Allocate the primary and secondary output strings. **/ + pMetaString primary = check_ptr(meta_new_string("")); + pMetaString secondary = check_ptr(meta_new_string("")); + if (primary == NULL || secondary == NULL) goto end_free; /** Skip these if they are at start of a word. **/ if (meta_is_str_at(original, 0, "GN", "KN", "PN", "WR", "PS", "")) @@ -419,8 +390,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod const char first_char = meta_get_char_at(original, 0); if (first_char == 'X') { - meta_add_str(primary, "S"); /* 'Z' maps to 'S' */ - meta_add_str(secondary, "S"); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; /* 'Z' maps to 'S' */ + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; current += 1; } @@ -444,8 +415,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (current == 0) { /** All init vowels now map to 'A'. **/ - meta_add_str(primary, "A"); - meta_add_str(secondary, "A"); + if (check(meta_add_str(primary, "A") != 0)) goto end_free; + if (check(meta_add_str(secondary, "A") != 0)) goto end_free; } current += 1; break; @@ -454,8 +425,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'B': { /** "-mb", e.g", "dumb", already skipped over... **/ - meta_add_str(primary, "P"); - meta_add_str(secondary, "P"); + if (check(meta_add_str(primary, "P") != 0)) goto end_free; + if (check(meta_add_str(secondary, "P") != 0)) goto end_free; current += (next_char == 'B') ? 2 : 1; break; @@ -475,8 +446,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ) ) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; current += 2; break; } @@ -484,8 +455,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Special case 'caesar' **/ if (current == 0 && meta_is_str_at(original, current, "CAESAR", "")) { - meta_add_str(primary, "S"); - meta_add_str(secondary, "S"); + if (check(meta_add_str(primary, "S") != 0)) goto end_free; + if (check(meta_add_str(secondary, "S") != 0)) goto end_free; current += 2; break; } @@ -493,8 +464,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Italian 'chianti' **/ if (meta_is_str_at(original, current, "CHIA", "")) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; current += 2; break; } @@ -504,8 +475,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Find 'michael' **/ if (current > 0 && meta_is_str_at(original, current, "CHAE", "")) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; current += 2; break; } @@ -517,8 +488,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && !meta_is_str_at(original, 0, "CHORE", "") ) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; current += 2; break; } @@ -536,8 +507,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ) ) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; } else { @@ -546,19 +517,19 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (meta_is_str_at(original, 0, "MC", "")) { /* e.g., "McHugh" */ - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; } else { - meta_add_str(primary, "X"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "X") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; } } else { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "X") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; } } current += 2; @@ -569,8 +540,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (meta_is_str_at(original, current, "CZ", "") && !meta_is_str_at(original, (current - 2), "WICZ", "")) { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "S") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; current += 2; break; } @@ -578,8 +549,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** e.g., 'focaccia' **/ if (meta_is_str_at(original, (current + 1), "CIA", "")) { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "X") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; current += 3; break; } @@ -602,22 +573,22 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod || meta_is_str_at(original, (current - 1), "UCCEE", "UCCES", "") ) { - meta_add_str(primary, "KS"); - meta_add_str(secondary, "KS"); + if (check(meta_add_str(primary, "KS")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KS")) != 0) goto end_free; /** 'bacci', 'bertucci', other italian **/ } else { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; } current += 3; break; } else { /** Pierce's rule **/ - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; current += 2; break; } @@ -625,8 +596,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (meta_is_str_at(original, current, "CK", "CG", "CQ", "")) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; current += 2; break; } @@ -636,21 +607,21 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /* Italian vs. English */ if (meta_is_str_at(original, current, "CIO", "CIE", "CIA", "")) { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; } else { - meta_add_str(primary, "S"); - meta_add_str(secondary, "S"); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; } current += 2; break; } /** else **/ - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; /** Name sent in 'mac caffrey', 'mac gregor **/ if (meta_is_str_at(original, (current + 1), " C", " Q", " G", "")) @@ -670,16 +641,16 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (meta_is_str_at(original, (current + 2), "I", "E", "Y", "")) { /** e.g. 'edge' **/ - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; current += 3; break; } else { /** e.g. 'edgar' **/ - meta_add_str(primary, "TK"); - meta_add_str(secondary, "TK"); + if (check(meta_add_str(primary, "TK")) != 0) goto end_free; + if (check(meta_add_str(secondary, "TK")) != 0) goto end_free; current += 2; break; } @@ -687,15 +658,15 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (meta_is_str_at(original, current, "DT", "DD", "")) { - meta_add_str(primary, "T"); - meta_add_str(secondary, "T"); + if (check(meta_add_str(primary, "T")) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; current += 2; break; } /** else **/ - meta_add_str(primary, "T"); - meta_add_str(secondary, "T"); + if (check(meta_add_str(primary, "T")) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; current += 1; break; } @@ -703,8 +674,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'F': { current += (next_char == 'F') ? 2 : 1; - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; break; } @@ -715,8 +686,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** 'Vghee' */ if (current > 0 && !meta_is_vowel(original, (current - 1))) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; current += 2; break; } @@ -728,13 +699,13 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { if (meta_get_char_at(original, (current + 2)) == 'I') { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; } else { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; } current += 2; break; @@ -762,13 +733,13 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && meta_is_str_at(original, (current - 3), "C", "G", "L", "R", "T", "") ) { - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; } else if (current > 0 && meta_get_char_at(original, (current - 1)) != 'I') { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; } current += 2; @@ -780,8 +751,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { if (current == 1 && !is_slavo_germanic && meta_is_vowel(original, 0)) { - meta_add_str(primary, "KN"); - meta_add_str(secondary, "N"); + if (check(meta_add_str(primary, "KN")) != 0) goto end_free; + if (check(meta_add_str(secondary, "N")) != 0) goto end_free; } else /** not e.g. 'cagney' **/ @@ -791,13 +762,13 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && !meta_is_str_at(original, (current + 2), "EY", "") ) { - meta_add_str(primary, "N"); - meta_add_str(secondary, "KN"); + if (check(meta_add_str(primary, "N")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KN")) != 0) goto end_free; } else { - meta_add_str(primary, "KN"); - meta_add_str(secondary, "KN"); + if (check(meta_add_str(primary, "KN")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KN")) != 0) goto end_free; } current += 2; break; @@ -809,8 +780,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && meta_is_str_at(original, (current + 1), "LI", "") ) { - meta_add_str(primary, "KL"); - meta_add_str(secondary, "L"); + if (check(meta_add_str(primary, "KL")) != 0) goto end_free; + if (check(meta_add_str(secondary, "L")) != 0) goto end_free; current += 2; break; } @@ -828,8 +799,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ) ) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "J"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; current += 2; break; } @@ -842,8 +813,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && !meta_is_str_at(original, (current - 1), "E", "I", "RGY", "OGY", "") ) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "J"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; current += 2; break; } @@ -858,21 +829,21 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") || meta_is_str_at(original, (current + 1), "ET", "")) { - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; } else { /** Always soft, if french ending. **/ if (meta_is_str_at(original, (current + 1), "IER ", "")) { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; } else { - meta_add_str(primary, "J"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; } } current += 2; @@ -880,8 +851,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod } current += (next_char == 'G') ? 2 : 1; - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; break; } @@ -893,8 +864,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && meta_is_vowel(original, current + 1) ) { - meta_add_str(primary, "H"); - meta_add_str(secondary, "H"); + if (check(meta_add_str(primary, "H")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; current += 2; } else /* also takes care of 'HH' */ @@ -915,13 +886,13 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod || (current == 0 && meta_get_char_at(original, current + 4) == ' ') ) { - meta_add_str(primary, "H"); - meta_add_str(secondary, "H"); + if (check(meta_add_str(primary, "H")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; } else { - meta_add_str(primary, "J"); - meta_add_str(secondary, "H"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; } current += 1; break; @@ -929,8 +900,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (current == 0 && !has_jose_next) { - meta_add_str(primary, "J"); /* Yankelovich/Jankelowicz */ - meta_add_str(secondary, "A"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; /* Yankelovich/Jankelowicz */ + if (check(meta_add_str(secondary, "A")) != 0) goto end_free; } else { @@ -941,15 +912,15 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && meta_is_vowel(original, (current - 1)) ) { - meta_add_str(primary, "J"); - meta_add_str(secondary, "H"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; } else { if (current == last) { - meta_add_str(primary, "J"); - meta_add_str(secondary, ""); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "")) != 0) goto end_free; } else { @@ -958,8 +929,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && !meta_is_str_at(original, (current - 1), "S", "K", "L", "") ) { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; } } } @@ -972,8 +943,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'K': { current += (next_char == 'K') ? 2 : 1; - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; break; } @@ -996,8 +967,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ) ) { - meta_add_str(primary, "L"); - meta_add_str(secondary, ""); + if (check(meta_add_str(primary, "L")) != 0) goto end_free; + if (check(meta_add_str(secondary, "")) != 0) goto end_free; current += 2; break; } @@ -1005,8 +976,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod } else current += 1; - meta_add_str(primary, "L"); - meta_add_str(secondary, "L"); + if (check(meta_add_str(primary, "L")) != 0) goto end_free; + if (check(meta_add_str(secondary, "L")) != 0) goto end_free; break; } @@ -1020,16 +991,16 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** 'dumb','thumb' **/ || next_char == 'M' ) ? 2 : 1; - meta_add_str(primary, "M"); - meta_add_str(secondary, "M"); + if (check(meta_add_str(primary, "M")) != 0) goto end_free; + if (check(meta_add_str(secondary, "M")) != 0) goto end_free; break; } case 'N': { current += (next_char == 'N') ? 2 : 1; - meta_add_str(primary, "N"); - meta_add_str(secondary, "N"); + if (check(meta_add_str(primary, "N")) != 0) goto end_free; + if (check(meta_add_str(secondary, "N")) != 0) goto end_free; break; } @@ -1037,24 +1008,24 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { if (next_char == 'H') { - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; current += 2; break; } /** Also account for "campbell", "raspberry" **/ current += (meta_is_str_at(original, (current + 1), "P", "B", "")) ? 2 : 1; - meta_add_str(primary, "P"); - meta_add_str(secondary, "P"); + if (check(meta_add_str(primary, "P")) != 0) goto end_free; + if (check(meta_add_str(secondary, "P")) != 0) goto end_free; break; } case 'Q': { current += (next_char == 'Q') ? 2 : 1; - meta_add_str(primary, "K"); - meta_add_str(secondary, "K"); + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; break; } @@ -1068,8 +1039,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && !meta_is_str_at(original, (current - 4), "ME", "MA", "") ); - meta_add_str(primary, (no_primary) ? "" : "R"); - meta_add_str(secondary, "R"); + if (check(meta_add_str(primary, (no_primary) ? "" : "R")) != 0) goto end_free; + if (check(meta_add_str(secondary, "R")) != 0) goto end_free; current += (next_char == 'R') ? 2 : 1; break; } @@ -1086,8 +1057,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Special case 'sugar-' **/ if (current == 0 && meta_is_str_at(original, current, "SUGAR", "")) { - meta_add_str(primary, "X"); - meta_add_str(secondary, "S"); + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; current += 1; break; } @@ -1096,8 +1067,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { const bool germanic = meta_is_str_at(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); const char* sound = (germanic) ? "S" : "X"; - meta_add_str(primary, sound); - meta_add_str(secondary, sound); + if (check(meta_add_str(primary, sound)) != 0) goto end_free; + if (check(meta_add_str(secondary, sound)) != 0) goto end_free; current += 2; break; } @@ -1105,8 +1076,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Italian & Armenian. **/ if (meta_is_str_at(original, current, "SIO", "SIA", "SIAN", "")) { - meta_add_str(primary, "S"); - meta_add_str(secondary, (is_slavo_germanic) ? "S" : "X"); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, (is_slavo_germanic) ? "S" : "X")) != 0) goto end_free; current += 3; break; } @@ -1115,15 +1086,15 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** also, -sz- in slavic language although in hungarian it is pronounced 's' **/ if (current == 0 && meta_is_str_at(original, (current + 1), "M", "N", "L", "W", "")) { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; current += 1; break; } if (meta_is_str_at(original, (current + 1), "Z", "")) { - meta_add_str(primary, "S"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; current += 2; break; } @@ -1138,8 +1109,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { /** 'schermerhorn', 'schenker' **/ const bool x_sound = meta_is_str_at(original, (current + 3), "ER", "EN", ""); - meta_add_str(primary, (x_sound) ? "X" : "SK"); - meta_add_str(secondary, "SK"); + if (check(meta_add_str(primary, (x_sound) ? "X" : "SK")) != 0) goto end_free; + if (check(meta_add_str(secondary, "SK")) != 0) goto end_free; current += 3; break; } @@ -1150,8 +1121,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod && !meta_is_vowel(original, 3) && meta_get_char_at(original, 3) != 'W' ); - meta_add_str(primary, "X"); - meta_add_str(secondary, (s_sound) ? "S" : "X"); + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, (s_sound) ? "S" : "X")) != 0) goto end_free; current += 3; break; } @@ -1159,16 +1130,16 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Default case. **/ const char* sound = (meta_is_str_at(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; - meta_add_str(primary, sound); - meta_add_str(secondary, sound); + if (check(meta_add_str(primary, sound)) != 0) goto end_free; + if (check(meta_add_str(secondary, sound)) != 0) goto end_free; current += 3; break; } /** French e.g. 'resnais', 'artois' **/ const bool no_primary = (current == last && meta_is_str_at(original, (current - 2), "AI", "OI", "")); - meta_add_str(primary, (no_primary) ? "" : "S"); - meta_add_str(secondary, "S"); + if (check(meta_add_str(primary, (no_primary) ? "" : "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; current += (meta_is_str_at(original, (current + 1), "S", "Z", "")) ? 2 : 1; break; } @@ -1177,8 +1148,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { if (meta_is_str_at(original, current, "TIA", "TCH", "TION", "")) { - meta_add_str(primary, "X"); - meta_add_str(secondary, "X"); + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; current += 3; break; } @@ -1186,28 +1157,27 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (meta_is_str_at(original, current, "TH", "TTH", "")) { /** Special case 'thomas', 'thames' or germanic. **/ - if ( + char* primary_char = ( meta_is_str_at(original, (current + 2), "OM", "AM", "") || meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") - ) - meta_add_str(primary, "T"); - else - meta_add_str(primary, "0"); /* Yes, zero. */ - meta_add_str(secondary, "T"); + ) ? "T" : "0"; /* Zero, not O. */ + + if (check(meta_add_str(primary, primary_char)) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; current += 2; break; } - meta_add_str(primary, "T"); - meta_add_str(secondary, "T"); + if (check(meta_add_str(primary, "T")) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; current += (meta_is_str_at(original, (current + 1), "T", "D", "")) ? 2 : 1; break; } case 'V': { - meta_add_str(primary, "F"); - meta_add_str(secondary, "F"); + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; current += (next_char == 'V') ? 2 : 1; break; } @@ -1217,8 +1187,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Can also be in middle of word. **/ if (meta_is_str_at(original, current, "WR", "")) { - meta_add_str(primary, "R"); - meta_add_str(secondary, "R"); + if (check(meta_add_str(primary, "R")) != 0) goto end_free; + if (check(meta_add_str(secondary, "R")) != 0) goto end_free; current += 2; break; } @@ -1227,8 +1197,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (current == 0 && (next_is_vowel || meta_is_str_at(original, current, "WH", ""))) { /** Wasserman should match Vasserman. **/ - meta_add_str(primary, "A"); - meta_add_str(secondary, (next_is_vowel) ? "F" : "A"); + if (check(meta_add_str(primary, "A")) != 0) goto end_free; + if (check(meta_add_str(secondary, (next_is_vowel) ? "F" : "A")) != 0) goto end_free; } /** Arnow should match Arnoff. **/ @@ -1237,8 +1207,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod || meta_is_str_at(original, 0, "SCH", "") ) { - meta_add_str(primary, ""); - meta_add_str(secondary, "F"); + if (check(meta_add_str(primary, "")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; current += 1; break; } @@ -1246,8 +1216,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Polish e.g. 'filipowicz' **/ if (meta_is_str_at(original, current, "WICZ", "WITZ", "")) { - meta_add_str(primary, "TS"); - meta_add_str(secondary, "FX"); + if (check(meta_add_str(primary, "TS")) != 0) goto end_free; + if (check(meta_add_str(secondary, "FX")) != 0) goto end_free; current += 4; break; } @@ -1269,8 +1239,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ); if (!silent) { - meta_add_str(primary, "KS"); - meta_add_str(secondary, "KS"); + if (check(meta_add_str(primary, "KS")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KS")) != 0) goto end_free; } current += (meta_is_str_at(original, (current + 1), "C", "X", "")) ? 2 : 1; @@ -1282,8 +1252,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Chinese pinyin e.g. 'zhao' **/ if (next_char == 'H') { - meta_add_str(primary, "J"); - meta_add_str(secondary, "J"); + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; current += 2; break; } @@ -1292,8 +1262,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod meta_is_str_at(original, (current + 1), "ZO", "ZI", "ZA", "") || (is_slavo_germanic && current > 0 && meta_get_char_at(original, (current - 1)) != 'T') ); - meta_add_str(primary, "S"); - meta_add_str(secondary, (has_t_sound) ? "TS" : "S"); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, (has_t_sound) ? "TS" : "S")) != 0) goto end_free; current += (next_char == 'Z') ? 2 : 1; break; } @@ -1303,277 +1273,18 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod } } + /** Write the output strings. **/ *primary_code = primary->str; *secondary_code = secondary->str; + primary->free_str_on_destroy = 0; + secondary->free_str_on_destroy = 0; + ret = 0; + end_free: + if (ret != 0) fprintf(stderr, "Error: meta_double_metaphone() failed (error code %d).\n", ret); meta_destroy_string(original); meta_destroy_string(primary); meta_destroy_string(secondary); - - return; - } - -#ifdef TESTING -/*** Built in test cases, written by Israel with inspiration from comments in - *** the above code, test cases written by Maurice Aubrey, and some words - *** suggested by AI. - *** - *** These tests have been integrated into the Centrallix testing environment, - *** where they can be run using `export TONLY=exp_fn_double_metaphone_00`, - *** followed by make test, in the Centrallix directory. - *** - *** The can also be run here by executing the following commands in the - *** centrallix/expression directory, which aditionally generates a coverage - *** report. These tests cover all parts of the double metaphone algorithm, - *** although some of the error cases in various helper functions (such as - *** meta_destroy_string(null)) are not covered by testing. - *** - *** Commands: - *** gcc exp_double_metaphone.c -o exp_double_metaphone.o -I .. -DTESTING -fprofile-arcs -ftest-coverage -O0 - *** ./exp_double_metaphone.o - *** gcov exp_double_metaphone.c - ***/ - -unsigned int num_tests_passed = 0u, num_tests_failed = 0u; - -void -test(const char* input, const char* expected_primary, const char* expected_secondary) - { - char* codes[2]; - - /** Run DoubleMetaphone() and extract results. **/ - char* actual_primary; - char* actual_secondary; - meta_double_metaphone( - input, - memset(&actual_primary, 0, sizeof(actual_primary)), - memset(&actual_secondary, 0, sizeof(actual_secondary)) - ); - - /** Test for correct value. **/ - if (!strcmp(expected_primary, actual_primary) && - !strcmp(expected_secondary, actual_secondary)) - num_tests_passed++; - else - { - printf( - "\nTEST FAILED: \"%s\"\n" - "Expected: %s %s\n" - "Actual: %s %s\n", - input, - expected_primary, expected_secondary, - actual_primary, actual_secondary - ); - num_tests_failed++; - } - - return; - } - -// Special thanks to the following websites for double checking the correct results: -// 1: https://words.github.io/double-metaphone -// 2: https://mainegenealogy.net/metaphone_converter.asp -// 3: https://en.toolpage.org/tool/metaphone -void -run_tests(void) - { - printf("\nRunning tests...\n"); - - /** Test that always fails. **/ - // test("This", "test", "fails."); - - /** Invalid string tests, by Israel. **/ - fprintf(stderr, "Expect two warnings between these two lines:\n"); - fprintf(stderr, "----------------\n"); - test(NULL, "", ""); - test("", "", ""); - fprintf(stderr, "----------------\n"); - - /** Basic tests, by Israel. **/ - test("Test", "TST", "TST"); - test("Basic", "PSK", "PSK"); - test("Centrallix", "SNTRLKS", "SNTRLKS"); - test("Lawrence", "LRNS", "LRNS"); - test("Philips", "FLPS", "FLPS"); - test("Acceptingness", "AKSPTNNS", "AKSPTNKNS"); - test("Supercalifragilisticexpialidocious", "SPRKLFRJLSTSKSPLTSS", "SPRKLFRKLSTSKSPLTXS"); - test("Suoicodilaipxecitsiligarfilacrepus", "SKTLPKSSTSLKRFLKRPS", "SKTLPKSSTSLKRFLKRPS"); - - /** Match tests, from code comments above. **/ - test("Smith", "SM0", "XMT"); - test("Schmidt", "XMT", "SMT"); - test("Snider", "SNTR", "XNTR"); - test("Schneider", "XNTR", "SNTR"); - test("Arnow", "ARN", "ARNF"); - test("Arnoff", "ARNF", "ARNF"); - - /** Example tests, from examples in code comments above. **/ - test("Accede", "AKST", "AKST"); - test("Accident", "AKSTNT", "AKSTNT"); - test("Actually", "AKTL", "AKTL"); - test("Arch", "ARX", "ARK"); - test("Artois", "ART", "ARTS"); - test("Bacchus", "PKS", "PKS"); - test("Bacci", "PX", "PX"); - test("Bajador", "PJTR", "PHTR"); - test("Bellocchio", "PLX", "PLX"); - test("Bertucci", "PRTX", "PRTX"); - test("Biaggi", "PJ", "PK"); - test("Bough", "P", "P"); - test("Breaux", "PR", "PR"); - test("Broughton", "PRTN", "PRTN"); - test("Cabrillo", "KPRL", "KPR"); - test("Caesar", "SSR", "SSR"); - test("Cagney", "KKN", "KKN"); - test("Campbell", "KMPL", "KMPL"); - test("Carlisle", "KRLL", "KRLL"); - test("Carlysle", "KRLL", "KRLL"); - test("Chemistry", "KMSTR", "KMSTR"); - test("Chianti", "KNT", "KNT"); - test("Chorus", "KRS", "KRS"); - test("Cough", "KF", "KF"); - test("Czerny", "SRN", "XRN"); - test("Dumb", "TM", "TM"); - test("Edgar", "ATKR", "ATKR"); - test("Edge", "AJ", "AJ"); - test("Filipowicz", "FLPTS", "FLPFX"); - test("Focaccia", "FKX", "FKX"); - test("Gallegos", "KLKS", "KKS"); - test("Germanic", "KRMNK", "JRMNK"); - test("Ghiradelli", "JRTL", "JRTL"); - test("Ghislane", "JLN", "JLN"); - test("Gospel", "KSPL", "KSPL"); - test("Gough", "KF", "KF"); - test("Greek", "KRK", "KRK"); - test("Hochmeier", "HKMR", "HKMR"); - test("Hugh", "H", "H"); - test("Island", "ALNT", "ALNT"); - test("Isle", "AL", "AL"); - test("Italian", "ATLN", "ATLN"); - test("Jankelowicz", "JNKLTS", "ANKLFX"); - test("Jose", "HS", "HS"); - test("Laugh", "LF", "LF"); - test("Mac Caffrey", "MKFR", "MKFR"); - test("Mac Gregor", "MKRKR", "MKRKR"); - test("Manager", "MNKR", "MNJR"); - test("McHugh", "MK", "MK"); - test("McLaughlin", "MKLFLN", "MKLFLN"); - test("Michael", "MKL", "MXL"); - test("Middle", "MTL", "MTL"); - test("Orchestra", "ARKSTR", "ARKSTR"); - test("Orchid", "ARKT", "ARKT"); - test("Pinyin", "PNN", "PNN"); - test("Raspberry", "RSPR", "RSPR"); - test("Resnais", "RSN", "RSNS"); - test("Rogier", "RJ", "RJR"); - test("Rough", "RF", "RF"); - test("Salvador", "SLFTR", "SLFTR"); - test("San jacinto", "SNHSNT", "SNHSNT"); - test("Schenker", "XNKR", "SKNKR"); - test("Schermerhorn", "XRMRRN", "SKRMRRN"); - test("Schlesinger", "XLSNKR", "SLSNJR"); - test("School", "SKL", "SKL"); - test("Schooner", "SKNR", "SKNR"); - test("Succeed", "SKST", "SKST"); - test("Sugar", "XKR", "SKR"); - test("Sugary", "XKR", "SKR"); - test("Tagliaro", "TKLR", "TLR"); - test("Thames", "TMS", "TMS"); - test("Thomas", "TMS", "TMS"); - test("Thumb", "0M", "TM"); - test("Tichner", "TXNR", "TKNR"); - test("Tough", "TF", "TF"); - test("Vghee", "FK", "FK"); - test("Wachtler", "AKTLR", "FKTLR"); - test("Wechsler", "AKSLR", "FKSLR"); - test("Word", "ART", "FRT"); - test("Xavier", "SF", "SFR"); - test("Yankelovich", "ANKLFX", "ANKLFK"); - test("Zhao", "J", "J"); - - /** Interesting Edge Case: "McClellan" **/ - /*** Note: Sources (1) and (3) both include a double K ("MKKLLN"), but the - *** original code on GitHub and mainegenealogy.net do not. I chose "MKLLN" - *** to be correct because I personally do not pronounce the second c. - ***/ - test("McClellan", "MKLLN", "MKLLN"); - - /** Maurice Aubrey's Tests. **/ - /** Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt **/ - test("maurice", "MRS", "MRS"); - test("aubrey", "APR", "APR"); - test("cambrillo", "KMPRL", "KMPR"); - test("heidi", "HT", "HT"); - test("katherine", "K0RN", "KTRN"); - test("catherine", "K0RN", "KTRN"); - test("richard", "RXRT", "RKRT"); - test("bob", "PP", "PP"); - test("eric", "ARK", "ARK"); - test("geoff", "JF", "KF"); - test("dave", "TF", "TF"); - test("ray", "R", "R"); - test("steven", "STFN", "STFN"); - test("bryce", "PRS", "PRS"); - test("randy", "RNT", "RNT"); - test("bryan", "PRN", "PRN"); - test("brian", "PRN", "PRN"); - test("otto", "AT", "AT"); - test("auto", "AT", "AT"); - /** GPT-5 Coverage Tests. **/ - /*** GPT-5 mini (Preview) running in GitHub Copilot suggested the words - *** after analizing a generated coverage report, and I (Israel) used - *** them to write the tests below. I kept the AI's reasoning for tests, - *** while removing tests that did not contribute any coverage, but after - *** a few reprompts, the AI started just giving words without reasoning. - *** I guess we were both getting pretty tired of writing tests. - ***/ - test("Abbott", "APT", "APT"); /* double-B ("BB") handling. */ - test("Back", "PK", "PK"); /* "CK"/"CG"/"CQ" branch. */ - test("Bacher", "PKR", "PKR"); /* matches "...BACHER" / ACH special-case. */ - test("Charles", "XRLS", "XRLS"); /* initial "CH" -> the branch that maps to "X"/"X" at start. */ - test("Ghana", "KN", "KN"); /* initial "GH" special-start handling. */ - test("Gnome", "NM", "NM"); /* "GN" sequence handling. */ - test("Raj", "RJ", "R"); /* J at end (exercise J-last behavior). */ - test("Quentin", "KNTN", "KNTN"); /* Q case (Q -> K mapping). */ - test("Who", "A", "A"); /* "WH" at start handling. */ - test("Shoemaker", "XMKR", "XMKR"); /* "SH" general mapping paths. */ - test("Sian", "SN", "XN"); /* "SIO"/"SIA"/"SIAN" branch. */ - test("Scold", "SKLT", "SKLT"); /* "SC" default / "SK" vs other SC subcases. */ - test("Station", "STXN", "STXN"); /* "TION" -> X mapping. */ - test("Match", "MX", "MX"); /* "TCH"/"TIA" -> X mapping. */ - test("Pizza", "PS", "PTS"); /* double-Z ("ZZ") handling. */ - test("Agnes", "AKNS", "ANS"); /* "GN" at index 1 (GN handling that yields KN / N). */ - test("Science", "SNS", "SNS"); /* "SC" followed by I (SC + I/E/Y branch). */ - test("Van Gogh", "FNKK", "FNKK"); - test("Josef", "JSF", "HSF"); - test("Object", "APJKT", "APJKT"); - test("Sholz", "SLS", "SLS"); - test("Scharf", "XRF", "XRF"); - test("Kasia", "KS", "KS"); - test("Van Geller", "FNKLR", "FNKLR"); - - const unsigned int total_tests = num_tests_passed + num_tests_failed; - printf("\nTests completed!\n"); - printf(" > Failed: %u\n", num_tests_failed); - printf(" > Skipped: %u\n", 0u); /* Implementation removed. */ - printf(" > Passed: %u/%u\n", num_tests_passed, total_tests); - - return; - } - -int main(void) - { - run_tests(); - - return 0; + return ret; } - -/** Prevent scope leak. **/ -#undef META_FREE -#undef META_MALLOC -#undef META_REALLOC -#undef SAFE_MALLOC -#undef SAFE_REALLOC - -#endif From 011d9525317c97a23bff532c68cd6dcb458e8a47 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 23 Mar 2026 17:15:47 -0600 Subject: [PATCH 102/361] Fix cluster driver not setting freed computed data to NULL. --- centrallix/osdrivers/objdrv_cluster.c | 49 +++++++++++++++++++-------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 6666d0594..340e45be3 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2434,9 +2434,21 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) ); /** Free computed data. **/ - if (source_data->Keys != NULL) nmSysFree(source_data->Keys); - if (source_data->Strings != NULL) nmSysFree(source_data->Strings); - if (source_data->Vectors != NULL) nmSysFree(source_data->Vectors); + if (source_data->Keys != NULL) + { + nmSysFree(source_data->Keys); + source_data->Keys = NULL; + } + if (source_data->Strings != NULL) + { + nmSysFree(source_data->Strings); + source_data->Strings = NULL; + } + if (source_data->Vectors != NULL) + { + nmSysFree(source_data->Vectors); + source_data->Vectors = NULL; + } } /** Clean up xarrays. **/ @@ -2446,7 +2458,6 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) { char* key = key_xarray.Items[i]; if (key != NULL) nmSysFree(key); - else break; } check(xaDeInit(&key_xarray)); /* Failure ignored. */ } @@ -2456,7 +2467,6 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) { char* str = data_xarray.Items[i]; if (str != NULL) nmSysFree(str); - else break; } check(xaDeInit(&data_xarray)); /* Failure ignored. */ } @@ -2466,7 +2476,6 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) { pVector vec = vector_xarray.Items[i]; if (vec != NULL) ca_free_vector(vec); - else break; } check(xaDeInit(&vector_xarray)); /* Failure ignored. */ } @@ -2661,7 +2670,11 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) return 0; err_free: - if (cluster_data->Sims != NULL) nmFree(cluster_data->Sims, sims_size); + if (cluster_data->Sims != NULL) + { + nmFree(cluster_data->Sims, sims_size); + cluster_data->Sims = NULL; + } if (cluster_data->Clusters != NULL) { @@ -2679,10 +2692,14 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Free the data for the cluster. **/ ASSERTMAGIC(cluster, MGK_CL_CLUSTER); - if (cluster->Indexes != NULL) nmSysFree(cluster->Indexes); - else break; + if (cluster->Indexes != NULL) + { + nmSysFree(cluster->Indexes); + cluster->Indexes = NULL; + } } nmFree(cluster_data->Clusters, clusters_size); + cluster_data->Clusters = NULL; } mssError(0, "Cluster", "ClusterData computation failed for \"%s\".", cluster_data->Name); @@ -2854,7 +2871,10 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Store pairs. **/ search_data->nPairs = pairs->nItems; if (pairs->nItems == 0) + { search_data->Pairs = check_ptr(nmSysMalloc(0)); + if (search_data->Pairs == NULL) goto err_free; + } else { search_data->Pairs = (pPair*)check_ptr(xaToArray(pairs)); @@ -2862,17 +2882,16 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) check(xaFree(pairs)); /* Failure ignored. */ pairs = NULL; } - if (search_data->Pairs == NULL) - { - mssError(1, "Cluster", "Failed to store pair after computing search data."); - goto err_free; - } /** Success. **/ return 0; err_free: - if (search_data->Pairs != NULL) nmSysFree(search_data->Pairs); + if (search_data->Pairs != NULL) + { + nmSysFree(search_data->Pairs); + search_data->Pairs = NULL; + } if (pairs != NULL) { for (unsigned int i = 0u; i < pairs->nItems; i++) From 8506a399439bf7501df27cb63801ec05b1d458c9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 23 Mar 2026 17:57:01 -0600 Subject: [PATCH 103/361] Clean up some typos and bugs. --- centrallix/expression/exp_functions.c | 3 ++- centrallix/utility/double_metaphone.c | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 96d0d452d..8d897dc6f 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4557,6 +4557,7 @@ exp_fn_metaphone(pExpression tree) { tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_STRING; + ret = 0; goto end_free; } const char* str = check_ptr(maybe_str->String); @@ -4570,7 +4571,7 @@ exp_fn_metaphone(pExpression tree) goto store_data; } - /** Compute DoubleMetaphone. **/ + /** Compute Double Metaphone. **/ ret = meta_double_metaphone(str, &primary, &secondary); if (ret != 0) { diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 71ea6c79f..e40be9fe6 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -322,7 +322,7 @@ meta_add_str(pMetaString s, const char* new_str) return 0; } - + /*** Computes double metaphone. *** *** Example Usage: @@ -431,7 +431,7 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod current += (next_char == 'B') ? 2 : 1; break; } - + case 'C': { /** Various germanic. **/ @@ -460,7 +460,7 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod current += 2; break; } - + /** Italian 'chianti' **/ if (meta_is_str_at(original, current, "CHIA", "")) { From f1e6bfe2d0516a1090d48cb02e06cec5694c6976 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 24 Mar 2026 08:06:07 -0600 Subject: [PATCH 104/361] Fix bug from Greptile. Fix ca_sparse_len() incrementing twice each iteration, leading to incorrect length results. --- centrallix-lib/src/clusters.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index e6f7c66a3..30640aa9a 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -316,7 +316,7 @@ ca_sparse_len(const pVector vector) *** traverse the array. ***/ - for (unsigned int dim = 0u; dim < CA_NUM_DIMS; i++) + for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) { /** Parse the vector token. **/ unsigned int dims_consumed = 0u, val = 0u; From e705a68fe12b43d4e16ce62a003f1a6f9bc34898 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 24 Mar 2026 09:55:54 -0600 Subject: [PATCH 105/361] Improve string buffer handling in exp_functions.c. --- centrallix/expression/exp_functions.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 8d897dc6f..04c87b3a8 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -487,7 +487,8 @@ exp_fn_i_free_result_string(pExpression tree) /** Free the string, if it exists. **/ if (tree->String != NULL) nmSysFree(tree->String); - /** No string is allocated anymore. */ + /** No string is allocated anymore. **/ + tree->String = tree->Types.StringBuf; tree->Alloc = 0; return; @@ -509,7 +510,7 @@ exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) exp_fn_i_free_result_string(tree); /** Decide how to allocate space. **/ - if (required_space <= 64) + if (required_space <= sizeof(tree->Types.StringBuf)) { /** We can use the preallocated buffer. **/ tree->String = tree->Types.StringBuf; @@ -518,7 +519,7 @@ exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) else { /** We need to allocate new memory. **/ - char* result = check_ptr(nmSysMalloc(required_space * sizeof(char*))); + char* result = check_ptr(nmSysMalloc(required_space * sizeof(char))); if (result == NULL) return -1; tree->String = result; tree->Alloc = 1; From 9a887870c66cde91a5ac33dd68d2d87a0038ba2e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 24 Mar 2026 13:05:36 -0600 Subject: [PATCH 106/361] Expand cluster driver test case. Add testing for attributes selected by *. Add some testing for the sliding window clustering algorithm. Add window_size cluster attribute. Clean up and update syntax. --- .../tests/test_objdrv_cluster_00.cluster | 1 + centrallix/osdrivers/objdrv_cluster.c | 3 +- centrallix/tests/test_objdrv_cluster_00.cmp | 39 ++++++++++++++++++- centrallix/tests/test_objdrv_cluster_00.to | 6 ++- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/centrallix-os/tests/test_objdrv_cluster_00.cluster b/centrallix-os/tests/test_objdrv_cluster_00.cluster index f3af089f3..496376982 100644 --- a/centrallix-os/tests/test_objdrv_cluster_00.cluster +++ b/centrallix-os/tests/test_objdrv_cluster_00.cluster @@ -20,6 +20,7 @@ test_objdrv_cluster_00 "system/cluster" num_clusters = runserver(:parameters:k); min_improvement = 0.0001; max_iterations = 16; + window_size = 3; seed = 1; } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 340e45be3..1cade6b62 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -215,6 +215,7 @@ char* CLUSTER_ATTRS[] = "num_clusters", "min_improvement", "max_iterations", + "window_size", "seed", "date_created", "date_computed", @@ -3913,7 +3914,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (strcmp(attr_name, "items") == 0) { /** Static variable to allow us to free the StringVecs from previous calls. **/ - static StringVec* vec = NULL; + static pStringVec vec = NULL; if (vec != NULL) { if (vec->Strings != NULL) nmSysFree(vec->Strings); diff --git a/centrallix/tests/test_objdrv_cluster_00.cmp b/centrallix/tests/test_objdrv_cluster_00.cmp index 877d020df..171c2a910 100644 --- a/centrallix/tests/test_objdrv_cluster_00.cmp +++ b/centrallix/tests/test_objdrv_cluster_00.cmp @@ -1,16 +1,33 @@ Attribute [algorithm]: string "k-means" Attribute [similarity_measure]: string "cosine" -Attribute [num_clusters]: integer 2 +Attribute [num_clusters]: integer 4 Attribute [min_improvement]: double 0.0001 Attribute [max_iterations]: integer 16 Attribute [seed]: integer 1 Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' Attribute [source]: string "cluster" -Attribute [threshold]: double 0.75 +Attribute [threshold]: double 0.9 Attribute [similarity_measure]: string "cosine" Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "johnny" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "MatTheW" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "john","johns" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "mathew","mathews","math" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" Attribute [k2_items]: stringve "john","johnny","johns" Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" @@ -73,6 +90,18 @@ Attribute [k4_sim]: double 0.757051 Attribute [k4_key1]: string "3: mathew" Attribute [k4_key2]: string "5: mathews" Attribute [k4_sim]: double 0.947714 +Attribute [slide_key1]: string "0: john" +Attribute [slide_key2]: string "2: johns" +Attribute [slide_sim]: double 0.757051 +Attribute [slide_key1]: string "3: mathew" +Attribute [slide_key2]: string "4: MatTheW" +Attribute [slide_sim]: double 0.922627 +Attribute [slide_key1]: string "3: mathew" +Attribute [slide_key2]: string "5: mathews" +Attribute [slide_sim]: double 0.947714 +Attribute [slide_key1]: string "4: MatTheW" +Attribute [slide_key2]: string "5: mathews" +Attribute [slide_sim]: double 0.874386 Attribute [k1_key1]: string "0: john" Attribute [k1_key2]: string "2: johns" Attribute [k1_sim]: double 0.8 @@ -97,6 +126,12 @@ Attribute [k4_sim]: double 0.8 Attribute [k4_key1]: string "3: mathew" Attribute [k4_key2]: string "5: mathews" Attribute [k4_sim]: double 0.857143 +Attribute [slide_key1]: string "0: john" +Attribute [slide_key2]: string "2: johns" +Attribute [slide_sim]: double 0.8 +Attribute [slide_key1]: string "3: mathew" +Attribute [slide_key2]: string "5: mathews" +Attribute [slide_sim]: double 0.857143 Attribute [items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' diff --git a/centrallix/tests/test_objdrv_cluster_00.to b/centrallix/tests/test_objdrv_cluster_00.to index 8c007e86b..71a3a5bb5 100644 --- a/centrallix/tests/test_objdrv_cluster_00.to +++ b/centrallix/tests/test_objdrv_cluster_00.to @@ -1,7 +1,9 @@ ##NAME Cluster Driver # Test parameters, initialization, and default attribute lists. -query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine ; +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.9&sim=cosine ; +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.9&sim=cosine/cluster ; +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.9&sim=cosine/search ; # Test cluster. query select k1_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; @@ -21,12 +23,14 @@ query select k1_key1 = :key1, k1_key2 = :key2, k1_sim = :sim from /tests/test_ob query select k2_key1 = :key1, k2_key2 = :key2, k2_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/search ; query select k3_key1 = :key1, k3_key2 = :key2, k3_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/search ; query select k4_key1 = :key1, k4_key2 = :key2, k4_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/search ; +query select slide_key1 = :key1, slide_key2 = :key2, slide_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=sliding-window&k=4&threshold=0.75&sim=cosine/search ; # Test searching with Levenshtein. query select k1_key1 = :key1, k1_key2 = :key2, k1_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=levenshtein/search ; query select k2_key1 = :key1, k2_key2 = :key2, k2_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=levenshtein/search ; query select k3_key1 = :key1, k3_key2 = :key2, k3_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=levenshtein/search ; query select k4_key1 = :key1, k4_key2 = :key2, k4_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=levenshtein/search ; +query select slide_key1 = :key1, slide_key2 = :key2, slide_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=sliding-window&k=4&threshold=0.75&sim=levenshtein/search ; # Test the default attribute list for clusters query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; From b369ee469a3a21c0b56483bd53d56ec422b9ce6c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Mar 2026 11:11:09 -0600 Subject: [PATCH 107/361] Add code to use expect.h features. Modify magic functions to print errors to stderr instead of stdout. Add explicit tree->Alloc = 0; to some exp_functions. Add error checking. Add consts. Re-add typecasts that I thought were optional. Improve comments. Clean up. --- centrallix-lib/include/magic.h | 21 +- centrallix-lib/include/util.h | 20 +- centrallix-lib/src/clusters.c | 7 +- centrallix-lib/src/util.c | 10 +- centrallix/expression/exp_functions.c | 106 +++---- centrallix/osdrivers/objdrv_cluster.c | 405 ++++++++++++++------------ centrallix/utility/double_metaphone.c | 33 ++- 7 files changed, 313 insertions(+), 289 deletions(-) diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index d6cb37ac8..92679e9c3 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -28,15 +28,16 @@ #ifdef DBMAGIC #include +#include "expect.h" #define ASSERTMAGIC(data, expect) \ ({ \ - pMagicHdr _data = (pMagicHdr)(data); \ - Magic_t _expect = (expect); \ - Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ - if (_data != NULL && _actual != _expect) \ + const pMagicHdr _data = (pMagicHdr)(data); \ + const Magic_t _expect = (expect); \ + const Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + if (UNLIKELY(_data != NULL && _actual != _expect)) \ { \ - printf( \ + fprintf(stderr, \ "%s:%d: Magic assertion failed, unexpected %u != %d for %8.8lX.\n", \ __FILE__, __LINE__, _actual, _expect, (long)_data \ ); \ @@ -47,12 +48,12 @@ #define ASSERTNOTMAGIC(data, expect) \ ({ \ - pMagicHdr _data = (pMagicHdr)(data); \ - Magic_t _expect = (expect); \ - Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ - if (_data != NULL && _actual == _expect) \ + const pMagicHdr _data = (pMagicHdr)(data); \ + const Magic_t _expect = (expect); \ + const Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + if (UNLIKELY(_data != NULL && _actual == _expect)) \ { \ - printf( \ + fprintf(stderr, \ "%s:%d: Magic assertion failed, unexpected %d.\n", \ __FILE__, __LINE__, _expect \ ); \ diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index 37f4601d7..c184caecf 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -57,6 +57,8 @@ extern "C" { #ifndef __cplusplus #include +#include "expect.h" + /*** @brief Returns the smaller of two values. *** *** @param a The first value. @@ -107,9 +109,8 @@ void print_err(int code, const char* function_name, const char* file_name, const #define check(result) \ ({ \ errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - const bool success = (_r == 0); \ - if (!success) print_err(_r, #result, __FILE__, __LINE__); \ + int _r = (result); \ + if (UNLIKELY(_r != 0)) print_err(_r, #result, __FILE__, __LINE__); \ _r; \ }) @@ -123,9 +124,8 @@ void print_err(int code, const char* function_name, const char* file_name, const #define check_neg(result) \ ({ \ errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - const bool success = (_r >= 0); \ - if (!success) print_err(_r, #result, __FILE__, __LINE__); \ + int _r = (result); \ + if (UNLIKELY(_r < 0)) print_err(_r, #result, __FILE__, __LINE__); \ _r; \ }) @@ -139,8 +139,8 @@ void print_err(int code, const char* function_name, const char* file_name, const #define check_double(result) \ ({ \ errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - if (isnan(_r)) print_err(0, #result, __FILE__, __LINE__); \ + double _r = (result); \ + if (UNLIKELY(isnan(_r))) print_err(0, #result, __FILE__, __LINE__); \ _r; \ }) @@ -154,8 +154,8 @@ void print_err(int code, const char* function_name, const char* file_name, const #define check_ptr(result) \ ({ \ errno = 0; /* Reset errno to prevent confusion. */ \ - __typeof__ (result) _r = (result); \ - if (_r == NULL) print_err(0, #result, __FILE__, __LINE__); \ + void* _r = (result); \ + if (UNLIKELY(_r == NULL)) print_err(0, #result, __FILE__, __LINE__); \ _r; \ }) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 30640aa9a..6e4ec1a3f 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -43,6 +43,7 @@ #include #include "clusters.h" +#include "expect.h" #include "newmalloc.h" #include "util.h" #include "xarray.h" @@ -155,14 +156,14 @@ ca_build_vector(const char* str) /** Allocate memory to store the characters. **/ unsigned int num_chars = 0u; chars = check_ptr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); - if (chars == NULL) goto err_free; + if (UNLIKELY(chars == NULL)) goto err_free; /** Store characters. **/ chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) { char maybe_char = *char_ptr; - if (maybe_char < 0) fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); + if (maybe_char < 0) fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); unsigned char c = (unsigned char)maybe_char; /** Ignore insignificant characters. **/ @@ -181,7 +182,7 @@ ca_build_vector(const char* str) /** Compute character pair hashes. **/ char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); - if (char_pairs == NULL) goto err_free; + if (UNLIKELY(char_pairs == NULL)) goto err_free; const unsigned int num_pairs = num_chars - 1u; for (unsigned int i = 0u; i < num_pairs; i++) { diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 9f1d590ab..988f000db 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -243,7 +243,7 @@ get_time(void) pTimer timer_init(pTimer timer) { - if (timer == NULL) return NULL; + if (UNLIKELY(timer == NULL)) return NULL; timer->start = NAN; timer->total = 0.0; @@ -268,7 +268,7 @@ timer_new(void) pTimer timer_start(pTimer timer) { - if (!timer) return timer; + if (UNLIKELY(timer == NULL)) return NULL; timer->start = get_time(); return timer; @@ -282,7 +282,7 @@ timer_start(pTimer timer) pTimer timer_stop(pTimer timer) { - if (!timer) return timer; + if (UNLIKELY(timer == NULL)) return NULL; timer->total += get_time() - timer->start; return timer; @@ -297,7 +297,9 @@ timer_stop(pTimer timer) double timer_get(pTimer timer) { - return (timer) ? timer->total : NAN; + if (UNLIKELY(timer == NULL)) return NAN; + + return timer->total; } /*** Reset a timer to its initial state so that it can be reused to time diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 04c87b3a8..16738b245 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -63,6 +63,7 @@ #include #include "cxlib/clusters.h" +#include "cxlib/expect.h" #include "cxlib/mtask.h" #include "cxlib/mtlexer.h" #include "cxlib/mtsession.h" @@ -131,7 +132,7 @@ static int exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) { /** The expectation struct cannot be NULL. **/ - if (arg_expect == NULL) + if (UNLIKELY(arg_expect == NULL)) { mssError(1, "EXP", "%s(...): Expectation struct cannot be NULL", @@ -142,7 +143,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e /** Extract values. **/ ASSERTMAGIC(arg, MGK_EXPRESSION); - int actual_datatype = arg->DataType; + const int actual_datatype = arg->DataType; /** Check for a provided NULL value. **/ if (arg->Flags & EXPR_F_NULL) @@ -379,7 +380,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) { if (arg_expects[i].Flags & EXP_ARG_OPTIONAL) opt_args++; - else if (opt_args > 0) + else if (UNLIKELY(opt_args > 0)) { /** Required argument follows optional argument (not allowed). **/ mssError(1, "EXP", "%s(?): Invalid Schema! Required argument #%u after optional argument.", tree->Name, i); @@ -394,7 +395,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) const int actual_args = tree->Children.nItems; if (opt_args == 0) { - if (actual_args != req_args) + if (UNLIKELY(actual_args != req_args)) { mssError(1, "EXP", "%s(?): Expects %u argument%s, got %d argument%s.", @@ -405,7 +406,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) } else { - if (actual_args < req_args || total_args < actual_args) + if (UNLIKELY(actual_args < req_args || total_args < actual_args)) { mssError(1, "EXP", "%s(?): Expects between %u and %u arguments, got %d argument%s.", @@ -418,7 +419,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) /** Verify arguments. **/ for (int i = 0; i < actual_args; i++) { - if (exp_fn_i_verify_arg(tree->Name, tree->Children.Items[i], &arg_expects[i]) != 0) + if (UNLIKELY(exp_fn_i_verify_arg(tree->Name, tree->Children.Items[i], &arg_expects[i]) != 0)) { mssError(0, "EXP", "%s(...): Error while reading arg #%d/%d.", @@ -447,7 +448,7 @@ exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) if (numeric_expr == NULL || numeric_expr->Flags & EXPR_F_NULL) return 1; /** Check for null destination. **/ - if (result_ptr == NULL) + if (UNLIKELY(result_ptr == NULL)) { mssError(1, "EXP", "Null location provided to store numeric result."); return -1; @@ -1579,10 +1580,10 @@ int exp_fn_lztrim(pExpression tree) { /** Expect one nullable string parameter. **/ - if (exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, - }, tree) != 0) + }, tree) != 0)) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; @@ -1590,7 +1591,7 @@ exp_fn_lztrim(pExpression tree) /** Extract the arg string. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); - if (maybe_str == NULL) return -1; + if (UNLIKELY(maybe_str == NULL)) return -1; if (maybe_str->Flags & EXPR_F_NULL) { /** Propagate null values. **/ @@ -1599,7 +1600,7 @@ exp_fn_lztrim(pExpression tree) return 0; } char* str = check_ptr(maybe_str->String); - if (str == NULL) return -1; + if (UNLIKELY(str == NULL)) return -1; /*** We don't need to allocate new memory or copy anything because we *** can simply point to the first character in the previous string @@ -1613,6 +1614,7 @@ exp_fn_lztrim(pExpression tree) exp_fn_i_free_result_string(tree); tree->DataType = DATA_T_STRING; tree->String = str; + tree->Alloc = 0; return 0; } @@ -1623,10 +1625,10 @@ int exp_fn_ltrim(pExpression tree) { /** Expect one nullable string parameter. **/ - if (exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, - }, tree) != 0) + }, tree) != 0)) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; @@ -1634,7 +1636,7 @@ exp_fn_ltrim(pExpression tree) /** Extract the arg string. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); - if (maybe_str == NULL) return -1; + if (UNLIKELY(maybe_str == NULL)) return -1; if (maybe_str->Flags & EXPR_F_NULL) { /** Propagate null values. **/ @@ -1643,7 +1645,7 @@ exp_fn_ltrim(pExpression tree) return 0; } char* str = check_ptr(maybe_str->String); - if (str == NULL) return -1; + if (UNLIKELY(str == NULL)) return -1; /*** We don't need to allocate new memory or copy anything because we *** can simply point to the first character in the previous string @@ -1658,6 +1660,7 @@ exp_fn_ltrim(pExpression tree) exp_fn_i_free_result_string(tree); tree->DataType = DATA_T_STRING; tree->String = str; + tree->Alloc = 0; return 0; } @@ -1668,10 +1671,10 @@ int exp_fn_rtrim(pExpression tree) { /** Expect one nullable string parameter. **/ - if (exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, - }, tree) != 0) + }, tree) != 0)) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; @@ -1679,7 +1682,7 @@ exp_fn_rtrim(pExpression tree) /** Extract the arg string. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); - if (maybe_str == NULL) return -1; + if (UNLIKELY(maybe_str == NULL)) return -1; if (maybe_str->Flags & EXPR_F_NULL) { /** Propagate null values. **/ @@ -1688,7 +1691,7 @@ exp_fn_rtrim(pExpression tree) return 0; } char* str = check_ptr(maybe_str->String); - if (str == NULL) return -1; + if (UNLIKELY(str == NULL)) return -1; /** Trim spaces from the end of the string. **/ /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ @@ -1696,14 +1699,14 @@ exp_fn_rtrim(pExpression tree) int n = len; while (n > 0 && str[n - 1] == ' ') n--; - /** Optimization for strings that are still the same. **/ + /** Shortcut for trimming nothing. **/ if (n == len) { tree->String = str; goto end; } - /** We need to copy to remove spaces. **/ + /** We need to copy to remove spaces (str is owned by a child expression). **/ if (check(exp_fn_i_alloc_result_string(tree, n + 1)) != 0) return -1; memcpy(tree->String, str, n); tree->String[n] = '\0'; @@ -1711,7 +1714,6 @@ exp_fn_rtrim(pExpression tree) end: /** Return the results in the tree. **/ tree->DataType = DATA_T_STRING; - tree->Alloc = 0; return 0; } @@ -1724,17 +1726,17 @@ exp_fn_trim(pExpression tree) /** Left trim the expression. **/ exp_fn_ltrim(tree); - /** Temporarily override the arg1 str pointer with the result from ltrim(). **/ - pExpression arg1 = tree->Children.Items[0]; - char* arg1_str = arg1->String; - arg1->String = tree->String; + /** Temporarily override the arg0 str pointer with the result from ltrim(). **/ + pExpression arg0 = tree->Children.Items[0]; + char* arg1_str = arg0->String; + arg0->String = tree->String; tree->Alloc = 0; /** Right trim the expression, which will use the overridden string above. **/ exp_fn_rtrim(tree); - /** Restore the arg1 tree. **/ - arg1->String = arg1_str; + /** Restore the arg0 tree. **/ + arg0->String = arg1_str; return 0; } @@ -4538,10 +4540,10 @@ exp_fn_metaphone(pExpression tree) bool free_strs = true; /** Verify function schema. **/ - if (exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, - }, tree) != 0) + }, tree) != 0)) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); goto end_free; @@ -4553,7 +4555,7 @@ exp_fn_metaphone(pExpression tree) /** Extract string param. **/ pExpression maybe_str = check_ptr(tree->Children.Items[0]); - if (maybe_str == NULL) goto end_free; + if (UNLIKELY(maybe_str == NULL)) goto end_free; if (maybe_str->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; @@ -4562,9 +4564,9 @@ exp_fn_metaphone(pExpression tree) goto end_free; } const char* str = check_ptr(maybe_str->String); - if (str == NULL) goto end_free; + if (UNLIKELY(str == NULL)) goto end_free; const size_t str_len = strlen(str); - if (str_len == 0u) + if (UNLIKELY(str_len == 0u)) { primary = ""; secondary = ""; @@ -4574,7 +4576,7 @@ exp_fn_metaphone(pExpression tree) /** Compute Double Metaphone. **/ ret = meta_double_metaphone(str, &primary, &secondary); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(1, "EXP", "Double metaphone computation failed (error code %d).", ret); goto end_free; @@ -4589,9 +4591,9 @@ exp_fn_metaphone(pExpression tree) ret = 0; end_free: - if (free_strs && primary != NULL) nmSysFree(primary); - if (free_strs && secondary != NULL) nmSysFree(secondary); - if (ret != 0) mssError(0, "EXP", "%s(): Failed to execute function.", tree->Name); + if (LIKELY(free_strs && primary != NULL)) nmSysFree(primary); + if (LIKELY(free_strs && secondary != NULL)) nmSysFree(secondary); + if (UNLIKELY(ret != 0)) mssError(0, "EXP", "%s(): Failed to execute function.", tree->Name); return ret; } @@ -4611,11 +4613,11 @@ static int exp_fn_compare(pExpression tree) { /** Verify function schema. **/ - if (exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, - }, tree) != 0) + }, tree) != 0)) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); goto err; @@ -4624,7 +4626,7 @@ exp_fn_compare(pExpression tree) /** Extract strings. **/ pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); - if (maybe_str1 == NULL || maybe_str2 == NULL) goto err; + if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) goto err; if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; @@ -4633,7 +4635,7 @@ exp_fn_compare(pExpression tree) } char* str1 = check_ptr(maybe_str1->String); char* str2 = check_ptr(maybe_str2->String); - if (str1 == NULL || str2 == NULL) goto err; + if (UNLIKELY(str1 == NULL || str2 == NULL)) goto err; /** Handle either cos_compare() or lev_compare(). **/ if (tree->Name[0] == 'c') @@ -4643,7 +4645,7 @@ exp_fn_compare(pExpression tree) /** Build vectors. **/ const pVector v1 = check_ptr(ca_build_vector(str1)); const pVector v2 = check_ptr(ca_build_vector(str2)); - if (v1 == NULL || v2 == NULL) + if (UNLIKELY(v1 == NULL || v2 == NULL)) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to build vectors.", @@ -4660,15 +4662,15 @@ exp_fn_compare(pExpression tree) } /** Clean up. **/ - if (v1 != NULL) ca_free_vector(v1); - if (v2 != NULL) ca_free_vector(v2); - if (ret == -1) goto err; + if (LIKELY(v1 != NULL)) ca_free_vector(v1); + if (LIKELY(v2 != NULL)) ca_free_vector(v2); + if (UNLIKELY(ret == -1)) goto err; else return 0; } else { /* lev_compare() */ const double lev_sim = check_double(ca_lev_compare(str1, str2)); - if (isnan(lev_sim)) + if (UNLIKELY(isnan(lev_sim))) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute Levenshtein edit distance.", @@ -4692,11 +4694,11 @@ int exp_fn_levenshtein(pExpression tree) { /** Verify function schema. **/ - if (exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, - }, tree) != 0) + }, tree) != 0)) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; @@ -4705,7 +4707,7 @@ exp_fn_levenshtein(pExpression tree) /** Extract strings. **/ pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); - if (maybe_str1 == NULL || maybe_str2 == NULL) return -1; + if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) return -1; if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; @@ -4714,12 +4716,12 @@ exp_fn_levenshtein(pExpression tree) } char* str1 = check_ptr(maybe_str1->String); char* str2 = check_ptr(maybe_str2->String); - if (str1 == NULL || str2 == NULL) return -1; + if (UNLIKELY(str1 == NULL || str2 == NULL)) return -1; /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ - int edit_dist = ca_edit_dist(str1, str2, 0lu, 0lu); - if (check_neg(edit_dist) < 0) + const int edit_dist = check_neg(ca_edit_dist(str1, str2, 0lu, 0lu)); + if (UNLIKELY(edit_dist < 0)) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); return -1; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 1cade6b62..c7ca8f41b 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -45,6 +45,7 @@ #include #include "cxlib/clusters.h" +#include "cxlib/expect.h" #include "cxlib/magic.h" #include "cxlib/mtsession.h" #include "cxlib/newmalloc.h" @@ -771,7 +772,7 @@ ci_ParseAttribute( /** Get attribute inf. **/ pStructInf attr_info = stLookup(inf, attr_name); - if (attr_info == NULL) + if (UNLIKELY(attr_info == NULL)) { if (required) mssError(1, "Cluster", "'%s' must be specified for clustering.", attr_name); return 1; @@ -779,8 +780,8 @@ ci_ParseAttribute( ASSERTMAGIC(attr_info, MGK_STRUCTINF); /** Allocate expression. **/ - pExpression exp = check_ptr(stGetExpression(attr_info, 0)); - if (exp == NULL) goto err; + pExpression exp = (pExpression)check_ptr(stGetExpression(attr_info, 0)); + if (UNLIKELY(exp == NULL)) goto err; /** Bind parameters. **/ /** TODO: Greg - What does this return? How do I know if it fails? **/ @@ -788,14 +789,14 @@ ci_ParseAttribute( /** Evaluate expression. **/ ret = expEvalTree(exp, param_list); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", "Expression evaluation failed (error code %d).", ret); goto err; } /** Check for data type mismatch. **/ - if (datatype != exp->DataType) + if (UNLIKELY(datatype != exp->DataType)) { mssError(1, "Cluster", "Expected ['%s' : %s], but got type %s.", @@ -806,7 +807,7 @@ ci_ParseAttribute( /** Get the data out of the expression. **/ ret = expExpressionToPod(exp, datatype, data); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(1, "Cluster", "Failed to get ['%s' : %s] using expression \"%s\" (error code %d).", @@ -844,7 +845,7 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) { /** Get the algorithm attribute. **/ char* algorithm; - if (ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0) + if (UNLIKELY(ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0)) { mssError(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); return ALGORITHM_NULL; @@ -892,15 +893,15 @@ ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) { /** Get the similarity_measure attribute. **/ char* measure; - if (ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0) + if (UNLIKELY(ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0)) { mssError(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); return SIMILARITY_NULL; } /** Parse known clustering algorithms. **/ - if (!strcasecmp(measure, "cosine")) return SIMILARITY_COSINE; - if (!strcasecmp(measure, "levenshtein")) return SIMILARITY_LEVENSHTEIN; + if (strcasecmp(measure, "cosine") == 0) return SIMILARITY_COSINE; + if (strcasecmp(measure, "levenshtein") == 0) return SIMILARITY_LEVENSHTEIN; /** Unknown similarity measure. **/ mssError(1, "Cluster", "Unknown \"similarity measure\": %s", measure); @@ -944,38 +945,38 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) ASSERTMAGIC(inf, MGK_STRUCTINF); /** Allocate SourceData. **/ - source_data = check_ptr(nmMalloc(sizeof(SourceData))); - if (source_data == NULL) goto err_free; + source_data = (pSourceData)check_ptr(nmMalloc(sizeof(SourceData))); + if (UNLIKELY(source_data == NULL)) goto err_free; memset(source_data, 0, sizeof(SourceData)); SETMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Initialize obvious values for SourceData. **/ - source_data->Name = check_ptr(nmSysStrdup(inf->Name)); - if (source_data->Name == NULL) goto err_free; + source_data->Name = (char*)check_ptr(nmSysStrdup(inf->Name)); + if (UNLIKELY(source_data->Name == NULL)) goto err_free; if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; /** Get source. **/ - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; - source_data->SourcePath = check_ptr(nmSysStrdup(buf)); - if (source_data->SourcePath == NULL) goto err_free; + if (UNLIKELY(ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + source_data->SourcePath = (char*)check_ptr(nmSysStrdup(buf)); + if (UNLIKELY(source_data->SourcePath == NULL)) goto err_free; /** Get the attribute name to use when querying keys from the source. **/ - if (ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; - source_data->KeyAttr = check_ptr(nmSysStrdup(buf)); - if (source_data->KeyAttr == NULL) goto err_free; + if (UNLIKELY(ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + source_data->KeyAttr = (char*)check_ptr(nmSysStrdup(buf)); + if (UNLIKELY(source_data->KeyAttr == NULL)) goto err_free; /** Get the attribute name to use for querying data from the source. **/ - if (ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0) goto err_free; - source_data->DataAttr = check_ptr(nmSysStrdup(buf)); - if (source_data->DataAttr == NULL) goto err_free; + if (UNLIKELY(ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + source_data->DataAttr = (char*)check_ptr(nmSysStrdup(buf)); + if (UNLIKELY(source_data->DataAttr == NULL)) goto err_free; /** Create cache entry key. **/ const size_t len = strlen(path) + strlen(source_data->SourcePath) + strlen(source_data->KeyAttr) + strlen(source_data->DataAttr) + 5lu; - source_data->CacheKey = check_ptr(nmSysMalloc(len * sizeof(char))); - if (source_data->CacheKey == NULL) goto err_free; + source_data->CacheKey = (char*)check_ptr(nmSysMalloc(len * sizeof(char))); + if (UNLIKELY(source_data->CacheKey == NULL)) goto err_free; snprintf(source_data->CacheKey, len, "%s?%s->%s:%s", path, source_data->SourcePath, source_data->KeyAttr, source_data->DataAttr @@ -1042,24 +1043,24 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source char* cache_key = NULL; /** Verify source_data value. **/ - if (source_data == NULL) goto err_free; + if (UNLIKELY(source_data == NULL)) goto err_free; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Allocate space for data struct. **/ cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); - if (cluster_data == NULL) goto err_free; + if (UNLIKELY(cluster_data == NULL)) goto err_free; memset(cluster_data, 0, sizeof(ClusterData)); SETMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); /** Basic fields. **/ cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); - if (cluster_data->Name == NULL) goto err_free; + if (UNLIKELY(cluster_data->Name == NULL)) goto err_free; cluster_data->SourceData = source_data; if (check(objCurrentDate(&cluster_data->DateCreated)) != 0) goto err_free; /** Get algorithm. **/ cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); - if (cluster_data->ClusterAlgorithm == ALGORITHM_NULL) goto err_free; + if (UNLIKELY(cluster_data->ClusterAlgorithm == ALGORITHM_NULL)) goto err_free; /** Handle no clustering case. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_NONE) @@ -1070,7 +1071,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get similarity_measure. **/ cluster_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); - if (cluster_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free; + if (UNLIKELY(cluster_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; /** Handle sliding window case. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) @@ -1095,7 +1096,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get num_clusters. **/ int num_clusters; - if (ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0) + if (UNLIKELY(ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0)) goto err_free; if (num_clusters < 2) { @@ -1108,7 +1109,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get min_improvement. **/ double improvement; result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); - if (result == -1) goto err_free; + if (UNLIKELY(result == -1)) goto err_free; else if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; else if (result == 0) { @@ -1124,7 +1125,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get max_iterations. **/ int max_iterations; result = ci_ParseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); - if (result == -1) goto err_free; + if (UNLIKELY(result == -1)) goto err_free; if (result == 0) { if (max_iterations < 1) @@ -1139,7 +1140,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get seed. **/ int seed; result = ci_ParseAttribute(inf, "seed", DATA_T_INTEGER, POD(&seed), param_list, false, true); - if (result == -1) goto err_free; + if (UNLIKELY(result == -1)) goto err_free; if (result == 0) { if (seed < 1) @@ -1156,7 +1157,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); - if (sub_inf == NULL) + if (UNLIKELY(sub_inf == NULL)) { mssError(1, "Cluster", "Failed to get %uth subinf.", i); goto err_free; @@ -1227,7 +1228,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source } cluster_data->nSubClusters = sub_clusters.nItems; cluster_data->SubClusters = (ClusterData**)check_ptr(xaToArray(&sub_clusters)); - if (cluster_data->SubClusters == NULL) goto err_free; + if (UNLIKELY(cluster_data->SubClusters == NULL)) goto err_free; check(xaDeInit(&sub_clusters)); /* Failure ignored. */ sub_clusters.nAlloc = 0; @@ -1239,7 +1240,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 8lu; cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (cache_key == NULL) goto err_free; + if (UNLIKELY(cache_key == NULL)) goto err_free; snprintf(cache_key, len, "%s/%s?%u", source_data->CacheKey, cluster_data->Name, @@ -1252,7 +1253,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 16lu; cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (cache_key == NULL) goto err_free; + if (UNLIKELY(cache_key == NULL)) goto err_free; snprintf(cache_key, len, "%s/%s?%u&%u&%u", source_data->CacheKey, cluster_data->Name, @@ -1267,7 +1268,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 32lu; cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (cache_key == NULL) goto err_free; + if (UNLIKELY(cache_key == NULL)) goto err_free; snprintf(cache_key, len, "%s/%s?%u&%u&%u&%g&%u", source_data->CacheKey, cluster_data->Name, @@ -1289,8 +1290,8 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source ASSERTMAGIC(cluster_maybe, MGK_CL_CLUSTER_DATA); /** Free the parsed cluster that we no longer need. */ - ci_FreeClusterData(cluster_data, false); - nmSysFree(cache_key); + if (LIKELY(cluster_data != NULL)) ci_FreeClusterData(cluster_data, false); + if (LIKELY(cache_key != NULL)) nmSysFree(cache_key); /** Return the cached cluster. **/ return cluster_maybe; @@ -1342,17 +1343,17 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Extract values. **/ pParamObjects param_list = check_ptr(node_data->ParamList); - if (param_list == NULL) goto err_free; + if (UNLIKELY(param_list == NULL)) goto err_free; /** Allocate space for search struct. **/ search_data = check_ptr(nmMalloc(sizeof(SearchData))); - if (search_data == NULL) goto err_free; + if (UNLIKELY(search_data == NULL)) goto err_free; memset(search_data, 0, sizeof(SearchData)); SETMAGIC(search_data, MGK_CL_SEARCH_DATA); /** Get basic information. **/ search_data->Name = check_ptr(nmSysStrdup(inf->Name)); - if (search_data->Name == NULL) goto err_free; + if (UNLIKELY(search_data->Name == NULL)) goto err_free; if (check(objCurrentDate(&search_data->DateCreated)) != 0) goto err_free; /** Search for the source cluster. **/ @@ -1373,7 +1374,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Did we find the requested source? **/ - if (search_data->SourceCluster == NULL) + if (UNLIKELY(search_data->SourceCluster == NULL)) { /** Print error. **/ mssError(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); @@ -1389,8 +1390,8 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Get threshold attribute. **/ - if (ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0) goto err_free; - if (search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold) + if (UNLIKELY(ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold)) { mssError(1, "Cluster", "Invalid value for [threshold : 0.0 < x < 1.0 | \"none\"]: %g", @@ -1401,20 +1402,20 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Get similarity measure. **/ search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); - if (search_data->SimilarityMeasure == SIMILARITY_NULL) goto err_free; + if (UNLIKELY(search_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; /** Check for additional data to warn the user about. **/ for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); - if (sub_inf == NULL) + if (UNLIKELY(sub_inf == NULL)) { mssError(1, "Cluster", "Failed to get %uth subinf.", i); goto err_free; } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = check_ptr(sub_inf->Name); - if (name == NULL) goto err_free; + if (UNLIKELY(name == NULL)) goto err_free; /** Handle various struct types. **/ const int struct_type = stStructType(sub_inf); @@ -1445,7 +1446,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) { /** The spec does not specify any valid sub-groups for searches. **/ char* group_type = check_ptr(sub_inf->UsrType); - if (group_type == NULL) goto err_free; + if (UNLIKELY(group_type == NULL)) goto err_free; fprintf(stderr, "Warning: Unknown group [\"%s\" : \"%s\"] in search \"%s\".\n", name, group_type, inf->Name @@ -1468,7 +1469,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) char* source_key = search_data->SourceCluster->CacheKey; const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; key = check_ptr(nmSysMalloc(len * sizeof(char))); - if (key == NULL) goto err_free; + if (UNLIKELY(key == NULL)) goto err_free; snprintf(key, len, "%s/%s?%g&%u", source_key, search_data->Name, @@ -1484,8 +1485,8 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) ASSERTMAGIC(search_maybe, MGK_CL_SEARCH_DATA); /** Free the parsed search that we no longer need. **/ - if (search_data != NULL) ci_FreeSearchData(search_data); - if (key != NULL) nmSysFree(key); + if (LIKELY(search_data != NULL)) ci_FreeSearchData(search_data); + if (LIKELY(key != NULL)) nmSysFree(key); /** Return the cached search. **/ return search_maybe; @@ -1536,20 +1537,20 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Get file path. **/ char* path = check_ptr(objFilePath(parent)); - if (path == NULL) goto err_free; + if (UNLIKELY(path == NULL)) goto err_free; /** Allocate node struct data. **/ node_data = check_ptr(nmMalloc(sizeof(NodeData))); - if (node_data == NULL) goto err_free; + if (UNLIKELY(node_data == NULL)) goto err_free; memset(node_data, 0, sizeof(NodeData)); SETMAGIC(node_data, MGK_CL_NODE_DATA); node_data->Parent = parent; /** Set up param list. **/ node_data->ParamList = check_ptr(expCreateParamList()); - if (node_data->ParamList == NULL) goto err_free; + if (UNLIKELY(node_data->ParamList == NULL)) goto err_free; node_data->ParamList->Session = check_ptr(parent->Session); - if (node_data->ParamList->Session == NULL) goto err_free; + if (UNLIKELY(node_data->ParamList->Session == NULL)) goto err_free; ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); if (ret != 0) { @@ -1565,7 +1566,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ci_GetParamValue, ci_SetParamValue ); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", "Failed to set param functions (error code %d).", ret); goto err_free; @@ -1578,7 +1579,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = check_ptr(inf->SubInf[i]); - if (sub_inf == NULL) + if (UNLIKELY(sub_inf == NULL)) { mssError(1, "Cluster", "Failed to get %uth subinf.", i); goto err_free; @@ -1614,7 +1615,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) case ST_T_SUBGROUP: { char* group_type = check_ptr(sub_inf->UsrType); - if (group_type == NULL) goto err_free; + if (UNLIKELY(group_type == NULL)) goto err_free; if (strcmp(group_type, "cluster/parameter") == 0) { if (check_neg(xaAddItem(¶m_infs, sub_inf) < 0)) @@ -1679,7 +1680,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) for (unsigned int i = 0u; i < node_data->nParams; i++) { pParam param = paramCreateFromInf(param_infs.Items[i]); - if (param == NULL) + if (UNLIKELY(param == NULL)) { mssError(0, "Cluster", "Failed to create param from inf for param #%u: %s", @@ -1693,14 +1694,14 @@ ci_ParseNodeData(pStructInf inf, pObject parent) for (unsigned int j = 0u; j < num_provided_params; j++) { pStruct provided_param = check_ptr(provided_params[j]); - if (provided_param == NULL) goto err_free; + if (UNLIKELY(provided_param == NULL)) goto err_free; /** If this provided param value isn't for the param, ignore it. **/ - if (strcmp(provided_param->Name, param->Name) != 0) continue; + if (UNLIKELY(strcmp(provided_param->Name, param->Name) != 0)) continue; /** Matched! The user is providing a value for this param. **/ ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, node_data->ParamList->Session); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", "Failed to set param value from struct info.\n" @@ -1720,7 +1721,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Invoke param hints parsing. **/ ret = paramEvalHints(param, node_data->ParamList, node_data->ParamList->Session); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", "Failed to evaluate parameter hints for parameter \"%s\" (error code %d).", @@ -1736,7 +1737,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) for (unsigned int i = 0u; i < num_provided_params; i++) { pStruct provided_param = check_ptr(provided_params[i]); - if (provided_param == NULL) goto err_free; + if (UNLIKELY(provided_param == NULL)) goto err_free; char* provided_name = provided_param->Name; /** Look to see if this provided param actually exists for this driver instance. **/ @@ -1747,25 +1748,26 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** This param doesn't exist, warn the user and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, objFileName(parent)); char** param_names = check_ptr(nmSysMalloc(node_data->nParams * sizeof(char*))); + if (UNLIKELY(param_names == NULL)) goto err_free; for (unsigned int j = 0u; j < node_data->nParams; j++) param_names[j] = node_data->Params[j]->Name; ci_TryHint(provided_name, param_names, node_data->nParams); - nmSysFree(param_names); + if (LIKELY(param_names != NULL)) nmSysFree(param_names); next_provided_param:; } /** Parse source data. **/ node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); - if (node_data->SourceData == NULL) goto err_free; + if (UNLIKELY(node_data->SourceData == NULL)) goto err_free; /** Parse each cluster. **/ node_data->nClusterDatas = cluster_infs.nItems; - if (node_data->nClusterDatas > 0) + if (LIKELY(node_data->nClusterDatas > 0)) { const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); node_data->ClusterDatas = check_ptr(nmSysMalloc(clusters_size)); - if (node_data->ClusterDatas == NULL) goto err_free; + if (UNLIKELY(node_data->ClusterDatas == NULL)) goto err_free; memset(node_data->ClusterDatas, 0, clusters_size); for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) { @@ -1779,11 +1781,11 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Parse each search. **/ node_data->nSearchDatas = search_infs.nItems; - if (node_data->nSearchDatas > 0) + if (LIKELY(node_data->nSearchDatas > 0)) { const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); node_data->SearchDatas = check_ptr(nmSysMalloc(searches_size)); - if (node_data->SearchDatas == NULL) goto err_free; + if (UNLIKELY(node_data->SearchDatas == NULL)) goto err_free; memset(node_data->SearchDatas, 0, searches_size); for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) { @@ -1818,7 +1820,7 @@ static void ci_FreeSourceData(pSourceData source_data) { /** Guard segfault. **/ - if (source_data == NULL) + if (UNLIKELY(source_data == NULL)) { fprintf(stderr, "Warning: Call to ci_FreeSourceData(NULL);\n"); return; @@ -1827,22 +1829,22 @@ ci_FreeSourceData(pSourceData source_data) /** Free top level attributes, if they exist. **/ /** Note: The key field is handled by the caching system, so we shouldn't free it here. **/ - if (source_data->Name != NULL) + if (LIKELY(source_data->Name != NULL)) { nmSysFree(source_data->Name); source_data->Name = NULL; } - if (source_data->SourcePath != NULL) + if (LIKELY(source_data->SourcePath != NULL)) { nmSysFree(source_data->SourcePath); source_data->SourcePath = NULL; } - if (source_data->KeyAttr != NULL) + if (LIKELY(source_data->KeyAttr != NULL)) { nmSysFree(source_data->KeyAttr); source_data->KeyAttr = NULL; } - if (source_data->DataAttr != NULL) + if (LIKELY(source_data->DataAttr != NULL)) { nmSysFree(source_data->DataAttr); source_data->DataAttr = NULL; @@ -1911,7 +1913,7 @@ static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) { /** Guard segfault. **/ - if (cluster_data == NULL) + if (UNLIKELY(cluster_data == NULL)) { fprintf(stderr, "Warning: Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); return; @@ -1919,7 +1921,7 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); /** Free attribute data. **/ - if (cluster_data->Name != NULL) + if (LIKELY(cluster_data->Name != NULL)) { nmSysFree(cluster_data->Name); cluster_data->Name = NULL; @@ -1978,7 +1980,7 @@ static void ci_FreeSearchData(pSearchData search_data) { /** Guard segfault. **/ - if (search_data == NULL) + if (UNLIKELY(search_data == NULL)) { fprintf(stderr, "Warning: Call to ci_FreeSearchData(NULL);\n"); return; @@ -1986,7 +1988,7 @@ ci_FreeSearchData(pSearchData search_data) ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); /** Free attribute data. **/ - if (search_data->Name != NULL) + if (LIKELY(search_data->Name != NULL)) { nmSysFree(search_data->Name); search_data->Name = NULL; @@ -2018,7 +2020,7 @@ static void ci_FreeNodeData(pNodeData node_data) { /** Guard segfault. **/ - if (node_data == NULL) + if (UNLIKELY(node_data == NULL)) { fprintf(stderr, "Warning: Call to ci_FreeNodeData(NULL);\n"); return; @@ -2026,7 +2028,7 @@ ci_FreeNodeData(pNodeData node_data) ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Free parsed params, if they exist. **/ - if (node_data->Params != NULL) + if (LIKELY(node_data->Params != NULL)) { for (unsigned int i = 0u; i < node_data->nParams; i++) { @@ -2037,7 +2039,7 @@ ci_FreeNodeData(pNodeData node_data) nmSysFree(node_data->Params); node_data->Params = NULL; } - if (node_data->ParamList != NULL) + if (LIKELY(node_data->ParamList != NULL)) { expFreeParamList(node_data->ParamList); node_data->ParamList = NULL; @@ -2119,7 +2121,7 @@ static size_t ci_SizeOfSourceData(pSourceData source_data) { /** Guard segfaults. **/ - if (source_data == NULL) + if (UNLIKELY(source_data == NULL)) { fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); return 0u; @@ -2170,7 +2172,7 @@ static size_t ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) { /** Guard segfaults. **/ - if (cluster_data == NULL) + if (UNLIKELY(cluster_data == NULL)) { fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); return 0u; @@ -2214,7 +2216,7 @@ static size_t ci_SizeOfSearchData(pSearchData search_data) { /** Guard segfaults. **/ - if (search_data == NULL) + if (UNLIKELY(search_data == NULL)) { fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); return 0u; @@ -2256,18 +2258,18 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) XArray vector_xarray = {0}; /** Guard segfaults. **/ - if (source_data == NULL) return -1; + if (UNLIKELY(check_ptr(source_data) == NULL)) return -1; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** If the vectors are already computed, we're done. **/ - if (source_data->Vectors != NULL) return 0; + if (LIKELY(source_data->Vectors != NULL)) return 0; /** Record the date and time. **/ if (check(objCurrentDate(&source_data->DateComputed)) != 0) goto end_free; /** Open the source path specified by the .cluster file. **/ obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); - if (obj == NULL) + if (UNLIKELY(obj == NULL)) { mssError(0, "Cluster", "Failed to open object driver."); goto end_free; @@ -2275,7 +2277,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Generate a "query" for retrieving data. **/ query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); - if (query == NULL) + if (UNLIKELY(query == NULL)) { mssError(0, "Cluster", "Failed to open query."); goto end_free; @@ -2294,7 +2296,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Data value: Type checking. **/ const int data_datatype = objGetAttrType(entry, source_data->DataAttr); - if (data_datatype == -1) + if (UNLIKELY(data_datatype == -1)) { mssError(0, "Cluster", "Failed to get type for data of entry #%u.", @@ -2302,7 +2304,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) ); goto end_free; } - if (data_datatype != DATA_T_STRING) + if (UNLIKELY(data_datatype != DATA_T_STRING)) { mssError(1, "Cluster", "Type for data of entry #%u was %s instead of String:\n", @@ -2314,7 +2316,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Data value: Get value from database. **/ char* data; ret = objGetAttrValue(entry, source_data->DataAttr, DATA_T_STRING, POD(&data)); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", "Failed to get attribute value for data entry #%u (error code: %d).", @@ -2328,12 +2330,12 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Convert the string to a vector. **/ pVector vector = ca_build_vector(data); - if (vector == NULL) + if (UNLIKELY(vector == NULL)) { mssError(1, "Cluster", "Failed to build vectors for string \"%s\".", data); goto end_free; } - if (ca_is_empty(vector)) + if (UNLIKELY(ca_is_empty(vector))) { mssError(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); goto end_free; @@ -2348,7 +2350,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Key value: Type checking. **/ const int key_datatype = objGetAttrType(entry, source_data->KeyAttr); - if (key_datatype == -1) + if (UNLIKELY(key_datatype == -1)) { mssError(0, "Cluster", "Failed to get type of key on entry #%u.", @@ -2356,7 +2358,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) ); goto end_free; } - if (key_datatype != DATA_T_STRING) + if (UNLIKELY(key_datatype != DATA_T_STRING)) { mssError(1, "Cluster", "Type of key on entry #%u was %s instead of String:", @@ -2368,7 +2370,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** key value: Get value from database. **/ char* key; ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", "Failed to value for key on entry #%u (error code: %d).", @@ -2391,7 +2393,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } source_data->nDatas = vector_xarray.nItems; - if (source_data->nDatas == 0) + if (UNLIKELY(source_data->nDatas == 0)) { mssError(0, "Cluster", "Data source path did not contain any valid data:\n"); goto end_free; @@ -2399,19 +2401,19 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) /** Trim and store keys. **/ source_data->Keys = (char**)check_ptr(xaToArray(&key_xarray)); - if (source_data->Keys == NULL) goto end_free; + if (UNLIKELY(source_data->Keys == NULL)) goto end_free; check(xaDeInit(&key_xarray)); /* Failure ignored. */ key_xarray.nAlloc = 0; /** Trim and store data strings. **/ source_data->Strings = (char**)check_ptr(xaToArray(&data_xarray)); - if (source_data->Strings == NULL) goto end_free; + if (UNLIKELY(source_data->Strings == NULL)) goto end_free; check(xaDeInit(&data_xarray)); /* Failure ignored. */ data_xarray.nAlloc = 0; /** Trim and store vectors. **/ source_data->Vectors = (int**)check_ptr(xaToArray(&vector_xarray)); - if (source_data->Vectors == NULL) goto end_free; + if (UNLIKELY(source_data->Vectors == NULL)) goto end_free; check(xaDeInit(&vector_xarray)); /* Failure ignored. */ vector_xarray.nAlloc = 0; @@ -2420,7 +2422,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) end_free: /** Print an error if the function failed. **/ - if (!successful) + if (UNLIKELY(!successful)) { mssError(0, "Cluster", "SourceData computation failed:\n" @@ -2453,7 +2455,8 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Clean up xarrays. **/ - if (key_xarray.nAlloc != 0) + /** Unlikely because (assuming no errors) these should already be freed. **/ + if (UNLIKELY(key_xarray.nAlloc != 0)) { for (unsigned int i = 0u; i < key_xarray.nItems; i++) { @@ -2462,7 +2465,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } check(xaDeInit(&key_xarray)); /* Failure ignored. */ } - if (data_xarray.nAlloc != 0) + if (UNLIKELY(data_xarray.nAlloc != 0)) { for (unsigned int i = 0u; i < data_xarray.nItems; i++) { @@ -2471,7 +2474,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } check(xaDeInit(&data_xarray)); /* Failure ignored. */ } - if (vector_xarray.nAlloc != 0) + if (UNLIKELY(vector_xarray.nAlloc != 0)) { for (unsigned int i = 0u; i < vector_xarray.nItems; i++) { @@ -2482,8 +2485,8 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Clean up query & object structs. **/ - if (query != NULL) check(objQueryClose(query)); /* Failure ignored. */ - if (obj != NULL) check(objClose(obj)); /* Failure ignored. */ + if (LIKELY(query != NULL)) check(objQueryClose(query)); /* Failure ignored. */ + if (LIKELY(obj != NULL)) check(objClose(obj)); /* Failure ignored. */ /** Return the function status code. **/ return (successful) ? 0 : -1; @@ -2508,16 +2511,16 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) size_t sims_size = -1; /** Guard segfaults. **/ - if (cluster_data == NULL || node_data == NULL) return -1; + if (UNLIKELY(check_ptr(cluster_data) == NULL || check_ptr(node_data) == NULL)) return -1; ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** If the clusters are already computed, we're done. **/ - if (cluster_data->Clusters != NULL) return 0; + if (LIKELY(cluster_data->Clusters != NULL)) return 0; /** Make source data available. **/ pSourceData source_data = check_ptr(node_data->SourceData); - if (source_data == NULL) + if (UNLIKELY(source_data == NULL)) { mssError(1, "Cluster", "Failed to get source data for cluster computation."); goto err_free; @@ -2526,9 +2529,9 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** We need the SourceData vectors to compute clusters. **/ pObjSession session = check_ptr(node_data->ParamList->Session); - if (session == NULL) goto err_free; + if (UNLIKELY(session == NULL)) goto err_free; ASSERTMAGIC(session, MGK_OBJSESSION); - if (ci_ComputeSourceData(source_data, session) != 0) + if (UNLIKELY(ci_ComputeSourceData(source_data, session) != 0)) { mssError(0, "Cluster", "ClusterData computation failed due to missing SourceData."); goto err_free; @@ -2543,8 +2546,8 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) sims_size = source_data->nDatas * sizeof(double); cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); - if (cluster_data->Clusters == NULL) goto err_free; - if (cluster_data->Sims == NULL) goto err_free; + if (UNLIKELY(cluster_data->Clusters == NULL)) goto err_free; + if (UNLIKELY(cluster_data->Sims == NULL)) goto err_free; memset(cluster_data->Clusters, 0, clusters_size); memset(cluster_data->Sims, 0, sims_size); @@ -2563,7 +2566,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Add all data points to that cluster. **/ only_cluster->Size = source_data->nDatas; only_cluster->Indexes = check_ptr(nmSysMalloc(only_cluster->Size * sizeof(int))); - if (only_cluster->Indexes == NULL) goto err_free; + if (UNLIKELY(only_cluster->Indexes == NULL)) goto err_free; for (unsigned int i = 0u; i < only_cluster->Size; i++) only_cluster->Indexes[i] = i; @@ -2578,7 +2581,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) case ALGORITHM_KMEANS: { /** Check for unimplemented similarity measures. **/ - if (cluster_data->SimilarityMeasure != SIMILARITY_COSINE) + if (UNLIKELY(cluster_data->SimilarityMeasure != SIMILARITY_COSINE)) { mssError(1, "Cluster", "The similarity measure \"%s\" is not implemented for 'k-means' clusters.", @@ -2590,7 +2593,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate labels. Note: ca_kmeans() initializes labels for us. **/ const size_t labels_size = source_data->nDatas * sizeof(unsigned int); unsigned int* labels = check_ptr(nmSysMalloc(labels_size)); - if (labels == NULL) goto err_free; + if (UNLIKELY(labels == NULL)) goto err_free; /** Handle seed for ca_kmeans(). **/ const bool auto_seed = (cluster_data->Seed == CI_NO_SEED); @@ -2607,7 +2610,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) cluster_data->Sims, auto_seed )) == 0); - if (!successful) goto err_free; + if (UNLIKELY(!successful)) goto err_free; /** Convert the labels into clusters. **/ @@ -2636,11 +2639,11 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) continue; } cluster->Indexes = check_ptr(nmSysMalloc(cluster->Size * sizeof(unsigned int*))); - if (cluster->Indexes == NULL) goto err_free; + if (UNLIKELY(cluster->Indexes == NULL)) goto err_free; for (unsigned int i = 0u; i < indexes_in_this_cluster->nItems; i++) { const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[i]; - if (index > __UINT32_MAX__) + if (UNLIKELY(index > __UINT32_MAX__)) { mssError(1, "Cluster", "How did you try to cluster more than %u data points and ci_ComputeSearchData() " @@ -2726,22 +2729,22 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) pXArray pairs = NULL; /** Guard segfaults. **/ - if (search_data == NULL || node_data == NULL) return -1; + if (UNLIKELY(check_ptr(search_data) == NULL || check_ptr(node_data) == NULL)) return -1; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** If the clusters are already computed, we're done. **/ - if (search_data->Pairs != NULL) return 0; + if (LIKELY(search_data->Pairs != NULL)) return 0; /** We need the cluster data to be computed before we search it. **/ pClusterData cluster_data = check_ptr(search_data->SourceCluster); - if (cluster_data == NULL) + if (UNLIKELY(cluster_data == NULL)) { mssError(1, "Cluster", "Failed to get cluster data for search computation."); goto err_free; } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); - if (ci_ComputeClusterData(cluster_data, node_data) != 0) + if (UNLIKELY(ci_ComputeClusterData(cluster_data, node_data) != 0)) { mssError(0, "Cluster", "SearchData computation failed due to missing clusters."); goto err_free; @@ -2755,7 +2758,8 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (check(objCurrentDate(&search_data->DateComputed)) != 0) goto err_free; /** Get the comparison function based on the similarity measure. **/ - const double (*similarity_function)(void *, void *) = ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure); + const double (*similarity_function)(void *, void *) = check_ptr(ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure)); + if (UNLIKELY(similarity_function == NULL)) goto err_free; /** Execute the search using the specified algorithm. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) @@ -2787,7 +2791,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) search_data->Threshold, NULL )); - if (pairs == NULL) + if (UNLIKELY(pairs == NULL)) { mssError(1, "Cluster", "Failed to compute sliding search with %s similarity measure.", @@ -2801,7 +2805,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) /** Initialize the pairs array with a size of double the amount of data. **/ const int guess_size = search_data->SourceCluster->SourceData->nDatas * 2; pairs = check_ptr(xaNew(guess_size)); - if (pairs == NULL) goto err_free; + if (UNLIKELY(pairs == NULL)) goto err_free; /** Iterate over each cluster. **/ for (unsigned int i = 0u; i < cluster_data->nClusters; i++) @@ -2848,7 +2852,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) NULL )); if (free_filtered_data) nmSysFree(filtered_data); - if (cluster_pairs == NULL) + if (UNLIKELY(cluster_pairs == NULL)) { mssError(1, "Cluster", "Failed to compute ca_complete_search() with %s similarity measure.", @@ -2924,13 +2928,15 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) static int ci_GetParamType(void* inf_v, const char* attr_name) { - pNodeData node_data = (pNodeData)inf_v; + pNodeData node_data = check_ptr(inf_v); + if (UNLIKELY(node_data == NULL)) return DATA_T_UNAVAILABLE; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Find the parameter. **/ for (unsigned int i = 0; i < node_data->nParams; i++) { - pParam param = node_data->Params[i]; + const pParam param = check_ptr(node_data->Params[i]); + if (UNLIKELY(param == NULL)) continue; if (strcmp(param->Name, attr_name) != 0) continue; /** Parameter found. **/ @@ -2968,19 +2974,21 @@ ci_GetParamType(void* inf_v, const char* attr_name) static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - pNodeData node_data = (pNodeData)inf_v; + pNodeData node_data = check_ptr(inf_v); + if (UNLIKELY(node_data == NULL)) goto err; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Find the parameter. **/ for (unsigned int i = 0; i < node_data->nParams; i++) { pParam param = (pParam)node_data->Params[i]; + if (UNLIKELY(param == NULL)) continue; if (strcmp(param->Name, attr_name) != 0) continue; /** Parameter found. **/ if (param->Value == NULL) return 1; if (param->Value->Flags & DATA_TF_NULL) return 1; - if (param->Value->DataType != datatype) + if (UNLIKELY(param->Value->DataType != datatype)) { mssError(1, "Cluster", "Type mismatch accessing parameter '%s'.", param->Name); return -1; @@ -3040,9 +3048,9 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb ClusterStatistics.OpenCalls++; /** Guard segfaults. **/ - if (parent == NULL) + if (UNLIKELY(parent == NULL)) { - fprintf(stderr, "Warning: Call to clusterOpen(NULL, ...);\n"); + mssError(0, "Cluster", "Warning: Call to clusterOpen(NULL, ...);\n"); return NULL; } ASSERTMAGIC(parent, MGK_OBJECT); @@ -3053,7 +3061,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb if (can_create && (parent->Mode & O_EXCL)) { node_struct = snNewNode(parent->Prev, usr_type); - if (node_struct == NULL) + if (UNLIKELY(node_struct == NULL)) { mssError(0, "Cluster", "Failed to exclusively create new node struct."); goto err_free; @@ -3069,7 +3077,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb node_struct = snNewNode(parent->Prev, usr_type); /** If there still isn't a node, fail early. **/ - if (node_struct == NULL) + if (UNLIKELY(node_struct == NULL)) { mssError(0, "Cluster", "Failed to create node struct from provided cluster file."); goto err_free; @@ -3081,7 +3089,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Parse node data from the node_struct. **/ node_data = ci_ParseNodeData(node_struct->Data, parent); - if (node_data == NULL) + if (UNLIKELY(node_data == NULL)) { mssError(0, "Cluster", "Failed to parse structure file \"%s\".", objFileName(parent)); goto err_free; @@ -3090,12 +3098,12 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Allocate driver instance data. **/ driver_data = check_ptr(nmMalloc(sizeof(DriverData))); - if (driver_data == NULL) goto err_free; + if (UNLIKELY(driver_data == NULL)) goto err_free; memset(driver_data, 0, sizeof(DriverData)); SETMAGIC(driver_data, MGK_CL_DRIVER_DATA); driver_data->NodeData = node_data; driver_data->NodeData->OpenCount++; - + /** Detect target from path. **/ char* target_name = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); if (target_name == NULL) @@ -3160,7 +3168,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) { pSearchData search_data = node_data->SearchDatas[i]; - // ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); /** Skip clusters with the wrong name. **/ if (strcmp(search_data->Name, target_name) != 0) continue; @@ -3171,7 +3179,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Check for extra, invalid path parts. **/ char* extra_data = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); - if (extra_data != NULL) + if (UNLIKELY(extra_data != NULL)) { mssError(1, "Cluster", "Unknown path part %s.", extra_data); goto err_free; @@ -3222,19 +3230,17 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb int clusterClose(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return 0; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); /** Update statistics. **/ ClusterStatistics.CloseCalls++; - /** No work needed. **/ - if (driver_data == NULL) return 0; - /** Unlink the driver's node data. **/ - pNodeData node_data = check_ptr(driver_data->NodeData); /** Failure ignored. **/ + pNodeData node_data = check_ptr(driver_data->NodeData); /* Failure ignored. */ ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); - if (node_data != NULL && --node_data->OpenCount == 0) + if (UNLIKELY(node_data != NULL && --node_data->OpenCount == 0)) ci_FreeNodeData(driver_data->NodeData); /** Free driver data. **/ @@ -3279,7 +3285,7 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) /** Allocate memory for the query. **/ query_data = check_ptr(nmMalloc(sizeof(ClusterQuery))); - if (query_data == NULL) goto err_free; + if (UNLIKELY(query_data == NULL)) goto err_free; /** Initialize the query. **/ SETMAGIC(query_data, MGK_CL_QUERY_DATA); @@ -3316,14 +3322,14 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) pDriverData result_data = NULL; /** Unpack data into local variables. **/ - pQueryData query_data = check_ptr((pQueryData)qy_v); - if (query_data == NULL) goto err_free; + pQueryData query_data = check_ptr(qy_v); + if (UNLIKELY(query_data == NULL)) goto err_free; ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); pDriverData driver_data = check_ptr(query_data->DriverData); - if (driver_data == NULL) goto err_free; + if (UNLIKELY(driver_data == NULL)) goto err_free; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); pNodeData node_data = check_ptr(driver_data->NodeData); - if (node_data == NULL) goto err_free; + if (UNLIKELY(node_data == NULL)) goto err_free; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Update statistics. **/ @@ -3331,7 +3337,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) /** Allocate result struct. **/ result_data = check_ptr(nmMalloc(sizeof(DriverData))); - if (result_data == NULL) goto err_free; + if (UNLIKELY(result_data == NULL)) goto err_free; /** Default initialization. **/ SETMAGIC(result_data, MGK_CL_DRIVER_DATA); @@ -3379,16 +3385,17 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_CLUSTER: { /** Ensure the required data is computed. **/ - pClusterData target = (pClusterData)driver_data->TargetData; + pClusterData target = (pClusterData)check_ptr(driver_data->TargetData); + if (UNLIKELY(target == NULL)) goto err_free; ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); - if (ci_ComputeClusterData(target, node_data) != 0) + if (UNLIKELY(ci_ComputeClusterData(target, node_data) != 0)) { mssError(0, "Cluster", "Failed to compute ClusterData for query."); goto err_free; } /** Stop iteration if the requested data does not exist. **/ - if (query_data->RowIndex >= target->nClusters) goto done_free; + if (UNLIKELY(query_data->RowIndex >= target->nClusters)) goto done_free; /** Set the data being fetched. **/ result_data->TargetType = TARGET_CLUSTER_ENTRY; @@ -3400,16 +3407,17 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_SEARCH: { /** Ensure the required data is computed. **/ - pSearchData target = (pSearchData)driver_data->TargetData; + pSearchData target = (pSearchData)check_ptr(driver_data->TargetData); + if (UNLIKELY(target == NULL)) goto err_free; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - if (ci_ComputeSearchData(target, node_data) != 0) + if (UNLIKELY(ci_ComputeSearchData(target, node_data) != 0)) { mssError(0, "Cluster", "Failed to compute SearchData for query."); goto err_free; } /** Stop iteration if the requested data does not exist. **/ - if (query_data->RowIndex >= target->nPairs) goto done_free; + if (UNLIKELY(query_data->RowIndex >= target->nPairs)) goto done_free; /** Set the data being fetched. **/ result_data->TargetType = TARGET_SEARCH_ENTRY; @@ -3438,7 +3446,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) mssError(0, "Cluster", "Failed to fetch query result."); done_free: - if (result_data != NULL) nmFree(result_data, sizeof(DriverData)); + if (LIKELY(result_data != NULL)) nmFree(result_data, sizeof(DriverData)); return NULL; } @@ -3455,11 +3463,9 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) { - /** No work needed to free NULL. **/ - if (qy_v == NULL) return 0; - /** Cast the query data. **/ pQueryData query_data = qy_v; + if (UNLIKELY(check_ptr(qy_v) == NULL)) return -1; ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); /** Free the query data. **/ @@ -3485,7 +3491,7 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { /** Extract target type from driver data. **/ pDriverData driver_data = check_ptr(inf_v); - if (driver_data == NULL) goto err; + if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const TargetType target_type = driver_data->TargetType; @@ -3493,7 +3499,7 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) ClusterStatistics.GetTypeCalls++; /** Guard possible segfault. **/ - if (attr_name == NULL) + if (UNLIKELY(attr_name == NULL)) { fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); return DATA_T_UNAVAILABLE; @@ -3503,7 +3509,7 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (attr_name[0] == 'k' || attr_name[0] == 's') goto handle_targets; /** Types for general attributes. **/ - if (strcmp(attr_name, "name") == 0 + if (LIKELY(strcmp(attr_name, "name") == 0) || strcmp(attr_name, "annotation") == 0 || strcmp(attr_name, "content_type") == 0 || strcmp(attr_name, "inner_type") == 0 @@ -3605,7 +3611,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Extract target type from driver data. **/ pDriverData driver_data = check_ptr(inf_v); - if (driver_data == NULL) goto err; + if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); target_type = driver_data->TargetType; @@ -3613,7 +3619,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO ClusterStatistics.GetValCalls++; /** Guard possible segfault. **/ - if (attr_name == NULL) + if (UNLIKELY(attr_name == NULL)) { fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); goto err; @@ -3626,8 +3632,8 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Type check. **/ const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); - if (expected_datatype == DATA_T_UNAVAILABLE) goto unknown_attribute; - if (datatype != expected_datatype) + if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE)) goto unknown_attribute; + if (UNLIKELY(datatype != expected_datatype)) { mssError(1, "Cluster", "Type mismatch: Accessing attribute ['%s' : %s] as type %s.", @@ -3637,7 +3643,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } /** Handle name. **/ - if (strcmp(attr_name, "name") == 0) + if (LIKELY(strcmp(attr_name, "name") == 0)) { ClusterStatistics.GetValCalls_name++; switch (target_type) @@ -4009,13 +4015,13 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Extract target type from driver data. **/ pDriverData driver_data = check_ptr(inf_v); - if (driver_data == NULL) goto err_free; + if (UNLIKELY(driver_data == NULL)) goto err_free; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const TargetType target_type = driver_data->TargetType; /** Malloc presentation hints struct. **/ hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); - if (hints == NULL) goto err_free; + if (UNLIKELY(hints == NULL)) goto err_free; memset(hints, 0, sizeof(ObjPresentationHints)); /** Hints that are the same for all attributes. **/ @@ -4026,7 +4032,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Temporary param list for compiling expressions. **/ tmp_list = check_ptr(expCreateParamList()); - if (hints == NULL) goto err_free; + if (UNLIKELY(hints == NULL)) goto err_free; /** Search for the requested attribute through attributes common to all instances. **/ if (strcmp(attr_name, "name") == 0) @@ -4227,7 +4233,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { /** Unused. **/ // pClusterData target = check_ptr(driver_data->TargetData); - // if (target == NULL) goto err_free; + // if (UNLIKELY(target == NULL)) goto err_free; // ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (strcmp(attr_name, "items") == 0) @@ -4257,7 +4263,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { /** Unused. **/ // pSearchData target = check_ptr(driver_data->TargetData); - // if (target == NULL) goto err_free; + // if (UNLIKELY(target == NULL)) goto err_free; // ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (strcmp(attr_name, "key1") == 0) @@ -4332,7 +4338,8 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); driver_data->TargetAttrIndex = 0u; @@ -4354,7 +4361,8 @@ clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const unsigned int i = driver_data->TargetAttrIndex++; @@ -4385,9 +4393,11 @@ clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) int clusterInfo(void* inf_v, pObjectInfo info) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); - pNodeData node_data = (pNodeData)driver_data->NodeData; + pNodeData node_data = check_ptr(driver_data->NodeData); + if (UNLIKELY(node_data == NULL)) goto err; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Reset flags buffer. **/ @@ -4425,7 +4435,9 @@ clusterInfo(void* inf_v, pObjectInfo info) case TARGET_SEARCH: { - pSearchData search_data = (pSearchData)driver_data->TargetData; + pSearchData search_data = check_ptr(driver_data->TargetData); + if (UNLIKELY(search_data == NULL)) goto err; + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; if (search_data->Pairs != NULL) { @@ -4474,7 +4486,8 @@ clusterInfo(void* inf_v, pObjectInfo info) char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); driver_data->TargetMethodIndex = 0u; @@ -4496,7 +4509,8 @@ clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); return METHOD_NAMES[driver_data->TargetMethodIndex++]; @@ -4665,7 +4679,8 @@ ci_CacheFreeSearch(pXHashEntry entry, void* path) int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) { - pDriverData driver_data = (pDriverData)inf_v; + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); /** Cache management method. **/ @@ -4674,7 +4689,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree char* path = NULL; /** Second parameter is required. **/ - if (param->String == NULL) + if (UNLIKELY(param->String == NULL)) { mssError(1, "Cluster", "[param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] is required for the cache method." @@ -4686,8 +4701,10 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree bool show = false; unsigned long long skip_uncomputed = 0llu; if (strcmp(param->String, "show_less") == 0) + { /** Specify show_less to skip uncomputed caches. **/ skip_uncomputed = 1ull; + } if (skip_uncomputed == 1ull || strcmp(param->String, "show") == 0) { show = true; @@ -4794,7 +4811,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree /** Attempt to give hint. **/ unsigned int n_methods = 0; while (METHOD_NAMES[n_methods] != NULL) n_methods++; - if (ci_TryHint(method_name, METHOD_NAMES, n_methods)); + ci_TryHint(method_name, METHOD_NAMES, n_methods); err: mssError(0, "Cluster", "Failed execute command."); @@ -4903,8 +4920,8 @@ int clusterInitialize(void) { /** Allocate the driver. **/ - pObjDriver drv = (pObjDriver)check_ptr(nmMalloc(sizeof(ObjDriver))); - if (drv == NULL) goto err_free; + pObjDriver drv = check_ptr(nmMalloc(sizeof(ObjDriver))); + if (UNLIKELY(drv == NULL)) goto err_free; memset(drv, 0, sizeof(ObjDriver)); /** Initialize caches. **/ @@ -4967,9 +4984,9 @@ clusterInitialize(void) err_free: /** Error cleanup. **/ - if (ClusterDriverCaches.SourceDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SourceDataCache)); /* Failure ignored. **/ - if (ClusterDriverCaches.ClusterDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.ClusterDataCache)); /* Failure ignored. **/ - if (ClusterDriverCaches.SearchDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SearchDataCache)); /* Failure ignored. **/ + if (ClusterDriverCaches.SourceDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SourceDataCache)); /* Failure ignored. */ + if (ClusterDriverCaches.ClusterDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.ClusterDataCache)); /* Failure ignored. */ + if (ClusterDriverCaches.SearchDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SearchDataCache)); /* Failure ignored. */ if (drv != NULL) { if (drv->RootContentTypes.nAlloc != 0) check(xaDeInit(&drv->RootContentTypes)); /* Failure ignored. */ diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index e40be9fe6..b0ed45cbe 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -107,6 +107,7 @@ #include "cxlib/newmalloc.h" #include "cxlib/strtcpy.h" #include "cxlib/util.h" +#include "cxlib/expect.h" typedef struct { @@ -129,7 +130,7 @@ meta_new_string(const char* init_str) char empty_string[] = ""; s = (pMetaString)check_ptr(nmSysMalloc(sizeof(MetaString))); - if (s == NULL) goto err_free; + if (UNLIKELY(s == NULL)) goto err_free; if (init_str == NULL) init_str = empty_string; @@ -139,7 +140,7 @@ meta_new_string(const char* init_str) s->bufsize = s->length + 7u; s->str = (char*)check_ptr(nmSysMalloc(s->bufsize * sizeof(char))); - if (s->str == NULL) goto err_free; + if (UNLIKELY(s->str == NULL)) goto err_free; strtcpy(s->str, init_str, s->bufsize); s->free_str_on_destroy = 1; @@ -163,7 +164,7 @@ meta_new_string(const char* init_str) void meta_destroy_string(pMetaString s) { - if (s == NULL) + if (UNLIKELY(s == NULL)) return; if (s->free_str_on_destroy && s->str != NULL) @@ -185,7 +186,7 @@ meta_increase_buffer(pMetaString s, const size_t chars_needed) { s->bufsize += chars_needed + 8u; s->str = check_ptr(nmSysRealloc(s->str, s->bufsize * sizeof(char))); - if (s->str == NULL) return -1; + if (UNLIKELY(s->str == NULL)) return -1; return 0; } @@ -222,7 +223,7 @@ meta_is_out_of_bounds(pMetaString s, unsigned int pos) bool meta_is_vowel(pMetaString s, unsigned int pos) { - if (meta_is_out_of_bounds(s, pos)) return 0; + if (UNLIKELY(meta_is_out_of_bounds(s, pos))) return 0; const char c = *(s->str + pos); @@ -253,7 +254,7 @@ meta_is_slavo_germanic(pMetaString s) char meta_get_char_at(pMetaString s, unsigned int pos) { - return (meta_is_out_of_bounds(s, pos)) ? '\0' : ((char) *(s->str + pos)); + return (UNLIKELY(meta_is_out_of_bounds(s, pos))) ? '\0' : ((char) *(s->str + pos)); } /*** Checks for to see if any of a list of strings appear in a the given @@ -275,7 +276,7 @@ meta_is_str_at(pMetaString s, unsigned int start, ...) bool found = false; /** Should never happen. **/ - if (meta_is_out_of_bounds(s, start)) + if (UNLIKELY(meta_is_out_of_bounds(s, start))) return false; const char* pos = (s->str + start); @@ -307,13 +308,13 @@ meta_is_str_at(pMetaString s, unsigned int start, ...) int meta_add_str(pMetaString s, const char* new_str) { - if (new_str == NULL) + if (UNLIKELY(new_str == NULL)) return -1; /** Increase the buffer to the required size. **/ const size_t add_length = strlen(new_str); const size_t new_length = s->length + add_length + 1; - if (new_length > s->bufsize && check(meta_increase_buffer(s, add_length)) != 0) + if (UNLIKELY(new_length > s->bufsize) && check(meta_increase_buffer(s, add_length)) != 0) return -1; /** Write the data to the buffer. **/ @@ -345,23 +346,23 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod int ret = -1; /** Edge cases. **/ - if (str == NULL) + if (UNLIKELY(str == NULL)) { fprintf(stderr, "Error: Missing input string.\n"); goto end_free; } const size_t length = strlen(str); - if (length == 0lu) + if (UNLIKELY(length == 0lu)) { fprintf(stderr, "Error: Empty input string.\n"); goto end_free; } - if (primary_code == NULL) + if (UNLIKELY(primary_code == NULL)) { fprintf(stderr, "Error: Missing a pointer to store primary code.\n"); goto end_free; } - if (secondary_code == NULL) + if (UNLIKELY(secondary_code == NULL)) { fprintf(stderr, "Error: Missing a pointer to store secondary code.\n"); goto end_free; @@ -373,14 +374,14 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Pad original so we can index beyond end. **/ pMetaString original = check_ptr(meta_new_string(str)); - if (original == NULL) goto end_free; + if (UNLIKELY(original == NULL)) goto end_free; meta_make_upper(original); if (check(meta_add_str(original, " ")) != 0) goto end_free; /** Allocate the primary and secondary output strings. **/ pMetaString primary = check_ptr(meta_new_string("")); pMetaString secondary = check_ptr(meta_new_string("")); - if (primary == NULL || secondary == NULL) goto end_free; + if (UNLIKELY(primary == NULL || secondary == NULL)) goto end_free; /** Skip these if they are at start of a word. **/ if (meta_is_str_at(original, 0, "GN", "KN", "PN", "WR", "PS", "")) @@ -1281,7 +1282,7 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ret = 0; end_free: - if (ret != 0) fprintf(stderr, "Error: meta_double_metaphone() failed (error code %d).\n", ret); + if (UNLIKELY(ret != 0)) fprintf(stderr, "Error: meta_double_metaphone() failed (error code %d).\n", ret); meta_destroy_string(original); meta_destroy_string(primary); meta_destroy_string(secondary); From 5ea6848c69c75f1540234f19095e16d631e94fcc Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Mar 2026 11:29:36 -0600 Subject: [PATCH 108/361] Fix Greptile comments. --- centrallix/expression/exp_functions.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 16738b245..abebf91c7 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -298,11 +298,12 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->WholePart > 0) { mssError(1, "EXP", - "%s(...): Expects negative money value but got $%d.%d.", + "%s(...): Expects negative money value but got $%d.%g.", fn_name, value->WholePart, (double)value->FractionPart / 100.0 ); return -1; } + break; } case DATA_T_STRINGVEC: @@ -4585,7 +4586,7 @@ exp_fn_metaphone(pExpression tree) /** Store the results. **/ store_data:; const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; - if (check(exp_fn_i_alloc_result_string(tree, length)) != 0) return -1; + if (check(exp_fn_i_alloc_result_string(tree, length)) != 0) goto end_free; sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); tree->DataType = DATA_T_STRING; ret = 0; From 0a96f955ee1d07bcf6b651b14a74a5edfc546c47 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Mar 2026 12:28:00 -0600 Subject: [PATCH 109/361] Fix Greptile comments. Add some error checking. Add expect.h to mtask.c. Add recursion checking. --- centrallix-lib/src/mtask.c | 5 +++-- centrallix/expression/exp_functions.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 14 ++++++++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/src/mtask.c b/centrallix-lib/src/mtask.c index 8af29c3c8..829a12c03 100644 --- a/centrallix-lib/src/mtask.c +++ b/centrallix-lib/src/mtask.c @@ -61,6 +61,7 @@ /************************************************************************/ +#include "expect.h" #include "newmalloc.h" #include "mtask.h" #include "xstring.h" @@ -2208,8 +2209,8 @@ thClearFlags(pThread thr, int flags) int thExcessiveRecursion() { - unsigned char buf[1]; - return (MTASK.CurrentThread->Stack - buf > MT_STACK_HIGHWATER); + unsigned char stack_ptr[1]; + return UNLIKELY(MTASK.CurrentThread->Stack - stack_ptr > MT_STACK_HIGHWATER); } diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index abebf91c7..be01c7cb1 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -3801,7 +3801,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) } /** Get the numbers for the args. **/ - double n[4]; + double n[4] = {0.0, 0.0, 0.0, 0.0}; for (int i = 0; i < arg_num; i++) { if (check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i]))) != 0) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index c7ca8f41b..9e127d54a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1042,6 +1042,12 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source XArray sub_clusters = {0}; char* cache_key = NULL; + if (thExcessiveRecursion()) + { + mssError(1, "Cluster", "Resource exhaustion occurred."); + goto err_free; + } + /** Verify source_data value. **/ if (UNLIKELY(source_data == NULL)) goto err_free; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); @@ -1143,7 +1149,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source if (UNLIKELY(result == -1)) goto err_free; if (result == 0) { - if (seed < 1) + if (UNLIKELY(seed < 1)) { mssError(1, "Cluster", "Invalid value for [seed : uint > 0]: %d", seed); goto err_free; @@ -1166,7 +1172,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source char* name = sub_inf->Name; /** Handle various struct types. **/ - const int struct_type = stStructType(sub_inf); + const int struct_type = check_neg(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1418,7 +1424,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) if (UNLIKELY(name == NULL)) goto err_free; /** Handle various struct types. **/ - const int struct_type = stStructType(sub_inf); + const int struct_type = check_neg(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1588,7 +1594,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) char* name = sub_inf->Name; /** Handle various struct types. **/ - const int struct_type = stStructType(sub_inf); + const int struct_type = check_neg(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: From ef1107b9f88ba5e366c997ac18ca5a98e3837895 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Mar 2026 17:46:24 -0600 Subject: [PATCH 110/361] Update and reduce returns in the cluster driver. --- centrallix/osdrivers/objdrv_cluster.c | 42 +++++++++++++++------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 9e127d54a..d8b8d3baf 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -742,6 +742,8 @@ ci_UnknownAttribute(char* attr_name, const int target_type) /** Attempt to give hints. **/ if (ci_TryHint(attr_name, my_attrs, n_my_attrs)); else if (ci_TryHint(attr_name, DRIVER_ATTRIBUTE_NAMES, N_DRIVER_ATTRIBUTE_NAMES)); + + return; } @@ -1364,7 +1366,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Search for the source cluster. **/ char* source_cluster_name; - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), param_list, true, true) != 0) return NULL; + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), param_list, true, true) != 0) goto err_free; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { pClusterData cluster_data = node_data->ClusterDatas[i]; @@ -2264,11 +2266,15 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) XArray vector_xarray = {0}; /** Guard segfaults. **/ - if (UNLIKELY(check_ptr(source_data) == NULL)) return -1; + if (UNLIKELY(check_ptr(source_data) == NULL)) goto end_free; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** If the vectors are already computed, we're done. **/ - if (LIKELY(source_data->Vectors != NULL)) return 0; + if (LIKELY(source_data->Vectors != NULL)) + { + successful = true; + goto end_free; + } /** Record the date and time. **/ if (check(objCurrentDate(&source_data->DateComputed)) != 0) goto end_free; @@ -2517,7 +2523,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) size_t sims_size = -1; /** Guard segfaults. **/ - if (UNLIKELY(check_ptr(cluster_data) == NULL || check_ptr(node_data) == NULL)) return -1; + if (UNLIKELY(check_ptr(cluster_data) == NULL || check_ptr(node_data) == NULL)) goto err_free; ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -2735,7 +2741,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) pXArray pairs = NULL; /** Guard segfaults. **/ - if (UNLIKELY(check_ptr(search_data) == NULL || check_ptr(node_data) == NULL)) return -1; + if (UNLIKELY(check_ptr(search_data) == NULL || check_ptr(node_data) == NULL)) goto err_free; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -3057,7 +3063,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb if (UNLIKELY(parent == NULL)) { mssError(0, "Cluster", "Warning: Call to clusterOpen(NULL, ...);\n"); - return NULL; + goto err_free; } ASSERTMAGIC(parent, MGK_OBJECT); @@ -3231,13 +3237,13 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb *** *** @param inf_v The affected driver instance. *** @param oxt The transaction tree (for the incomplete transaction system). - *** @returns 0, success. + *** @returns 0 for success, or -1 if an error occurs. ***/ int clusterClose(void* inf_v, pObjTrxTree* oxt) { pDriverData driver_data = check_ptr(inf_v); - if (UNLIKELY(driver_data == NULL)) return 0; + if (UNLIKELY(driver_data == NULL)) return -1; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); /** Update statistics. **/ @@ -3464,7 +3470,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) *** *** @param qy_v The affected query instance. *** @param oxt The transaction tree (for the incomplete transaction system). - *** @returns 0, success. + *** @returns 0 for success, or -1 if an error occurs. ***/ int clusterQueryClose(void* qy_v, pObjTrxTree* oxt) @@ -3645,7 +3651,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO "Type mismatch: Accessing attribute ['%s' : %s] as type %s.", attr_name, objTypeToStr(expected_datatype), objTypeToStr(datatype) ); - return -1; + goto err; } /** Handle name. **/ @@ -3685,7 +3691,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO default: mssError(1, "Cluster", "Unknown target type %u.", target_type); - return -1; + goto err; } return 0; @@ -3704,7 +3710,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO default: mssError(1, "Cluster", "Unknown target type %u.", target_type); - return -1; + goto err; } return 0; } @@ -3732,7 +3738,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH_ENTRY: val->String = "search/entry"; break; default: mssError(1, "Cluster", "Unknown target type %u.", target_type); - return -1; + goto err; } return 0; @@ -3756,7 +3762,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { case TARGET_NODE: /** Attribute is not defined for this target type. **/ - return -1; + goto err; case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: @@ -3780,7 +3786,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO return 0; } } - return -1; + goto err; } /** Handle date_computed. **/ @@ -3791,7 +3797,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { case TARGET_NODE: /** Attribute is not defined for this target type. **/ - return -1; + goto err; case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: @@ -3818,7 +3824,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Default: Unknown type. **/ mssError(1, "Cluster", "Unknown target type %u.", target_type); - return -1; + goto err; } /** Handle attributes for specific data targets. **/ @@ -3980,7 +3986,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO default: mssError(1, "Cluster", "Unknown target type %u.", target_type); - return -1; + goto err; } unknown_attribute: From de74a99611a650c3ab5631c4d351fd56d3e3fddd Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Mar 2026 18:24:16 -0600 Subject: [PATCH 111/361] Add recursion checks and update recursion error messages. --- centrallix/osdrivers/objdrv_cluster.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index d8b8d3baf..350afd473 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1046,7 +1046,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source if (thExcessiveRecursion()) { - mssError(1, "Cluster", "Resource exhaustion occurred."); + mssError(1, "Cluster", "Resource exhaustion occurred while parsing cluster data."); goto err_free; } @@ -1920,6 +1920,12 @@ ci_FreeSourceData(pSourceData source_data) static void ci_FreeClusterData(pClusterData cluster_data, bool recursive) { + if (thExcessiveRecursion()) + { + mssError(1, "Cluster", "Resource exhaustion occurred while freeing cluster datas."); + return; + } + /** Guard segfault. **/ if (UNLIKELY(cluster_data == NULL)) { @@ -2179,6 +2185,12 @@ ci_SizeOfSourceData(pSourceData source_data) static size_t ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) { + if (thExcessiveRecursion()) + { + mssError(1, "Cluster", "Resource exhaustion occurred while counting size of cluster data structs."); + return 0u; + } + /** Guard segfaults. **/ if (UNLIKELY(cluster_data == NULL)) { From c5b0607866bb8d9725927635568f682a0b320f97 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 14 May 2026 13:54:47 -0600 Subject: [PATCH 112/361] Add line numbers to mssError(). Rename mssError() to mssError_internal() which takes a file name and line number. Add mssError() macro that collects and passes the file and line number to mssError_internal(). Remove newline when the error stack is cleared because libraries might have already printed relevant errors. --- centrallix-lib/include/mtsession.h | 4 +++- centrallix-lib/src/mtsession.c | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/include/mtsession.h b/centrallix-lib/include/mtsession.h index 7a65fe1b0..cff7101d5 100644 --- a/centrallix-lib/include/mtsession.h +++ b/centrallix-lib/include/mtsession.h @@ -81,7 +81,9 @@ void* mssGetParam(char* paramname); /** Error handling functions **/ int mssLog(int level, char* msg); -void mssError(int clr, char* module, char* message, ...); +void mssError_internal(int clr, char* module, char* file, int line, char* message, ...); +#define mssError(clear, module, message, ...) \ + mssError_internal(clear, module, __FILE__, __LINE__, message, ##__VA_ARGS__) int mssErrorErrno(int clr, char* module, char* message, ...); int mssClearError(); int mssPrintError(pFile fd); diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index 3e35dea16..dc516e9f2 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -471,7 +471,7 @@ mssEndSession(pMtSession s) *** @param ... Variables matching format specifiers in the format. ***/ void -mssError(int clr, char* module, char* message, ...) +mssError_internal(int clr, char* module, char* file, int line, char* message, ...) { char err_msg[BUFSIZ]; unsigned int i = 0u; @@ -479,6 +479,9 @@ mssError(int clr, char* module, char* message, ...) /** Prevent issues from interlacing this function with prints to stdout. **/ check(fflush(stdout)); /* Failure ignored. */ + /** Add line number to error message. **/ + i += snprintf(err_msg + i, sizeof(err_msg) - i, "%s:%d: ", file, line); + /** Write the module to the start of the error message. */ i += snprintf(err_msg + i, sizeof(err_msg) - i, "%s: ", module); @@ -495,9 +498,6 @@ mssError(int clr, char* module, char* message, ...) /** Use standard logging without a session context, if needed. **/ if (log_error) { - /** Start new error stacks with a newline to make them distinct. **/ - if (clr && MSS.LogMethod[0] != '\0') fprintf(stderr, "\n"); - /** Use the requested logging method. **/ if (strcmp(MSS.LogMethod, "syslog") == 0) { From 57e691494d1b163259eddc32b6248409d6cd2e69 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 2 Sep 2026 16:42:44 -0600 Subject: [PATCH 113/361] Fix typos found by reviews. --- centrallix-sysdoc/OSDriver_Authoring.md | 8 ++++---- centrallix/osdrivers/objdrv_cluster.c | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index ce117966b..6ad17ce07 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -455,7 +455,7 @@ The `Create()` function is used to create a new object, and uses the same parame ### Function: Delete() ```c -int clusterDelete(pObject obj, pObjTrxTree* oxt); +int xxxDelete(pObject obj, pObjTrxTree* oxt); ``` The `Delete()` function is used to delete an object, which often means removing a file from the file system. The Delete routine is passed the following parameters: @@ -717,14 +717,14 @@ These functions should only return the names of significant values, so `name`, ` ### Function: AddAttr() ```c -int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt); +int xxxAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt); ``` The `AddAttr()` function adds a new attribute to an existing object. Not all objects support this, and many will refuse the operation. The parameters are the same as those of `GetAttrValue()` and `SetAttrValue()`, documented in detail above. ### Function: OpenAttr() ```c -void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt); +void* xxxOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt); ``` The `OpenAttr()` function is used to open an attribute for `objRead()`/`objWrite()` as if it were an object with content. Not all object drivers will support this, and many will refuse the operation. @@ -733,7 +733,7 @@ This function takes 4 parameters. `inf_v`, `attr_name`, and `oxt` are the same a ### Function: ExecuteMethod() ```c -int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); +int xxxExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); ``` The `ExecuteMethod()` function is used to execute a method on an object. This feature is rarely used, but some drivers have created methods for actions like dropping their cache or printing debug information. Each method has a unique name within that object, and can take a single string parameter. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 350afd473..489fadacc 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4189,7 +4189,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); char buf[8]; - snprintf(buf, sizeof(buf), "%d", N_CLUSTERING_ALGORITHMS); + snprintf(buf, sizeof(buf), "%u", N_CLUSTERING_ALGORITHMS - 1u); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); /** Display flags. **/ @@ -4219,7 +4219,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); char buf[8]; - snprintf(buf, sizeof(buf), "%d", N_SIMILARITY_MEASURES); + snprintf(buf, sizeof(buf), "%u", N_SIMILARITY_MEASURES - 1u); hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); /** Other hints. **/ @@ -4537,6 +4537,8 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + if (driver_data->TargetMethodIndex >= METHOD_NAMES_COUNT) return NULL; + return METHOD_NAMES[driver_data->TargetMethodIndex++]; } @@ -4558,7 +4560,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) char* path = (char*)args[3]; /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return 0; + if (path != NULL && strncmp(key, path, strlen(path)) != 0) return 0; /** Handle type. **/ char* type; @@ -4833,9 +4835,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree mssError(1, "Cluster", "Unknown command: \"%s\"", method_name); /** Attempt to give hint. **/ - unsigned int n_methods = 0; - while (METHOD_NAMES[n_methods] != NULL) n_methods++; - ci_TryHint(method_name, METHOD_NAMES, n_methods); + ci_TryHint(method_name, METHOD_NAMES, METHOD_NAMES_COUNT); err: mssError(0, "Cluster", "Failed execute command."); From bc8627b7a6264ef53400c0ec72e71fd20b7dcb95 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 2 Sep 2026 16:49:12 -0600 Subject: [PATCH 114/361] Fix macros. --- centrallix/expression/exp_functions.c | 18 ++++++++-------- centrallix/osdrivers/objdrv_cluster.c | 30 +++++++++++++-------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index be01c7cb1..b201915cb 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -108,15 +108,15 @@ typedef struct } ArgExpect, *pArgExpect; -#define EXP_ARG_END (ArgExpect){NULL, -1} -#define EXP_ARG_NO_FLAGS (0) -#define EXP_ARG_OPTIONAL (1 << 0) -#define EXP_ARG_NOT_NULL (1 << 1) -#define EXP_ARG_FORCE_TYPE (1 << 2) -#define EXP_ARG_NON_EMPTY (1 << 3) -#define EXP_ARG_NEGATIVE (1 << 4) -#define EXP_ARG_POSITIVE (1 << 5) -#define EXP_ARG_NON_NAN (1 << 6) +#define EXP_ARG_END ((ArgExpect){NULL, -1}) +#define EXP_ARG_NO_FLAGS (0) +#define EXP_ARG_OPTIONAL (1 << 0) +#define EXP_ARG_NOT_NULL (1 << 1) +#define EXP_ARG_FORCE_TYPE (1 << 2) +#define EXP_ARG_NON_EMPTY (1 << 3) +#define EXP_ARG_NEGATIVE (1 << 4) +#define EXP_ARG_POSITIVE (1 << 5) +#define EXP_ARG_NON_NAN (1 << 6) /*** An internal function used by the schema verifier (below) to verify each *** argument of the provided schema. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 489fadacc..e3015ddac 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -73,13 +73,13 @@ /** Enum type representing a clustering algorithm. **/ typedef unsigned char ClusterAlgorithm; -#define ALGORITHM_NULL (ClusterAlgorithm)0u -#define ALGORITHM_NONE (ClusterAlgorithm)1u -#define ALGORITHM_SLIDING_WINDOW (ClusterAlgorithm)2u -#define ALGORITHM_KMEANS (ClusterAlgorithm)3u -#define ALGORITHM_KMEANS_PLUS_PLUS (ClusterAlgorithm)4u -#define ALGORITHM_KMEDOIDS (ClusterAlgorithm)5u -#define ALGORITHM_DB_SCAN (ClusterAlgorithm)6u +#define ALGORITHM_NULL ((ClusterAlgorithm)0u) +#define ALGORITHM_NONE ((ClusterAlgorithm)1u) +#define ALGORITHM_SLIDING_WINDOW ((ClusterAlgorithm)2u) +#define ALGORITHM_KMEANS ((ClusterAlgorithm)3u) +#define ALGORITHM_KMEANS_PLUS_PLUS ((ClusterAlgorithm)4u) +#define ALGORITHM_KMEDOIDS ((ClusterAlgorithm)5u) +#define ALGORITHM_DB_SCAN ((ClusterAlgorithm)6u) ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[] = { @@ -114,9 +114,9 @@ ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) /** Enum type representing a similarity measurement algorithm. **/ typedef unsigned char SimilarityMeasure; -#define SIMILARITY_NULL (SimilarityMeasure)0u -#define SIMILARITY_COSINE (SimilarityMeasure)1u -#define SIMILARITY_LEVENSHTEIN (SimilarityMeasure)2u +#define SIMILARITY_NULL ((SimilarityMeasure)0u) +#define SIMILARITY_COSINE ((SimilarityMeasure)1u) +#define SIMILARITY_LEVENSHTEIN ((SimilarityMeasure)2u) SimilarityMeasure ALL_SIMILARITY_MEASURES[] = { @@ -179,11 +179,11 @@ double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(v *** values for TargetType are not currently allowed. ***/ typedef unsigned char TargetType; -#define TARGET_NODE (TargetType)1u -#define TARGET_CLUSTER (TargetType)2u -#define TARGET_SEARCH (TargetType)3u -#define TARGET_CLUSTER_ENTRY (TargetType)4u -#define TARGET_SEARCH_ENTRY (TargetType)5u +#define TARGET_NODE ((TargetType)1u) +#define TARGET_CLUSTER ((TargetType)2u) +#define TARGET_SEARCH ((TargetType)3u) +#define TARGET_CLUSTER_ENTRY ((TargetType)4u) +#define TARGET_SEARCH_ENTRY ((TargetType)5u) TargetType ALL_TARGET_TYPES[] = { From b4c99fd8848696db3604755767a31e59cc895daf Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 2 Sep 2026 16:56:09 -0600 Subject: [PATCH 115/361] Fix typos found by Claude. --- centrallix-lib/include/clusters.h | 6 +++--- centrallix-lib/src/clusters.c | 6 +++--- centrallix-os/datasets/README.md | 2 +- centrallix-sysdoc/ClusterDriverRequirements-old.md | 12 ++++++------ centrallix-sysdoc/CommentAnchorsExtension.md | 2 +- centrallix-sysdoc/GCC_Dependencies.md | 4 ++-- centrallix-sysdoc/Libraries/xarray.md | 2 +- centrallix-sysdoc/Libraries/xstring.md | 8 ++++---- centrallix-sysdoc/OSDriver_Authoring.md | 8 ++++---- centrallix-sysdoc/string_similarity.md | 6 +++--- centrallix/osdrivers/objdrv_cluster.c | 6 +++--- 11 files changed, 31 insertions(+), 31 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index c813e2169..403fcc227 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -139,19 +139,19 @@ void* ca_most_similar( void* target, void** data, const unsigned int num_data, - const double (*similarity)(void*, void*), + double (*similarity)(void*, void*), const double threshold); pXArray ca_sliding_search( void** data, const unsigned int num_data, const unsigned int window_size, - const double (*similarity)(void*, void*), + double (*similarity)(void*, void*), const double threshold, pXArray maybe_pairs); pXArray ca_complete_search( void** data, const unsigned int num_data, - const double (*similarity)(void*, void*), + double (*similarity)(void*, void*), const double threshold, pXArray maybe_pairs); diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 6e4ec1a3f..c4bcc3db9 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -984,7 +984,7 @@ ca_most_similar( void* target, void** data, const unsigned int num_data, - const double (*similarity)(void*, void*), + double (*similarity)(void*, void*), const double threshold) { void* most_similar = NULL; @@ -1026,7 +1026,7 @@ ca_sliding_search( void** data, const unsigned int num_data, const unsigned int window_size, - const double (*similarity)(void*, void*), + double (*similarity)(void*, void*), const double threshold, pXArray maybe_pairs) { @@ -1098,7 +1098,7 @@ pXArray ca_complete_search( void** data, const unsigned int num_data, - const double (*similarity)(void*, void*), + double (*similarity)(void*, void*), const double threshold, pXArray maybe_pairs) { diff --git a/centrallix-os/datasets/README.md b/centrallix-os/datasets/README.md index 7633d84a4..055770a55 100644 --- a/centrallix-os/datasets/README.md +++ b/centrallix-os/datasets/README.md @@ -1,6 +1,6 @@ # Datasets Folder -The datasets folder is intended for storing datasets used in testing that must not be committed to GitHub. Datasets in this folder may contain sensitive data, including production data or patterns from production data. This data _must_ be treated with care, even if it is obfuscated. Thus, this directory should be "git-ignored". If you ever see files in this directory listed as changes when you run `git status` or use similar commands or tools, imedately remove these changes and investigate. Data in this directory must _never_ be committed or pushed! +The datasets folder is intended for storing datasets used in testing that must not be committed to GitHub. Datasets in this folder may contain sensitive data, including production data or patterns from production data. This data _must_ be treated with care, even if it is obfuscated. Thus, this directory should be "git-ignored". If you ever see files in this directory listed as changes when you run `git status` or use similar commands or tools, immediately remove these changes and investigate. Data in this directory must _never_ be committed or pushed! ## Modifying this README.md If you need to modify this readme file, you may need to pass `-f` when adding it because the directory is part of the `.gitignore` (e.g. `git add -f README.md`). Obviously, be careful _not to `git add` any other files_ in this directory. diff --git a/centrallix-sysdoc/ClusterDriverRequirements-old.md b/centrallix-sysdoc/ClusterDriverRequirements-old.md index 601f41703..d5891304d 100644 --- a/centrallix-sysdoc/ClusterDriverRequirements-old.md +++ b/centrallix-sysdoc/ClusterDriverRequirements-old.md @@ -64,7 +64,7 @@ void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) `clusterQueryFetch()` shall... - If the driver struct targets the root node, this function shall produce an error. - If the driver struct targets an entry, this function shall produce a different error. -- If the driver targets a cluster or search, this function shall return a driver struct targetting the cluster or search *entry* (respectively) indicated by the query struct's row pointer, and increment the pointer. +- If the driver targets a cluster or search, this function shall return a driver struct targeting the cluster or search *entry* (respectively) indicated by the query struct's row pointer, and increment the pointer. - Exception: If no data remains, this function shall return `NULL` instead. - This request shall cause clustering / searching to execute, if it has not executed already. @@ -153,11 +153,11 @@ char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt); - Return `NULL` if the end of the list has been reached. - Increase the attribute index on the driver struct by 1. -- The attribute name list for a targetting root shall include "source" and "attr_name". -- The attribute name list for a targetting a cluster shall include "algorithm", "similarity_measure", "num_clusters", "improvement_threshold", and "max_iterations". -- The attribute name list for a targetting a search shall include "source", "threshold", and "similarity_measure". -- The attribute name list for a targetting a cluster entry shall include "val" and "sim". -- The attribute name list for a targetting a search entry shall include "val1", "val2", and "sim". +- The attribute name list for a targeting root shall include "source" and "attr_name". +- The attribute name list for a targeting cluster shall include "algorithm", "similarity_measure", "num_clusters", "improvement_threshold", and "max_iterations". +- The attribute name list for a targeting search shall include "source", "threshold", and "similarity_measure". +- The attribute name list for a targeting cluster entry shall include "val" and "sim". +- The attribute name list for a targeting search entry shall include "val1", "val2", and "sim". ### Cluster Get Next Attribute ```c diff --git a/centrallix-sysdoc/CommentAnchorsExtension.md b/centrallix-sysdoc/CommentAnchorsExtension.md index 756eb463a..9178ff6e1 100644 --- a/centrallix-sysdoc/CommentAnchorsExtension.md +++ b/centrallix-sysdoc/CommentAnchorsExtension.md @@ -79,7 +79,7 @@ The comment anchors extension is a little less robust than I would like. Sometim ## Informing other developers -When using the comment anchors extension, include something closely resembling the following snippet at the top of the file, just below the copywrite notice and include statements: +When using the comment anchors extension, include something closely resembling the following snippet at the top of the file, just below the copyright notice and include statements: **.c or .h file** ```c diff --git a/centrallix-sysdoc/GCC_Dependencies.md b/centrallix-sysdoc/GCC_Dependencies.md index 5467ee13b..778a6fb68 100644 --- a/centrallix-sysdoc/GCC_Dependencies.md +++ b/centrallix-sysdoc/GCC_Dependencies.md @@ -7,10 +7,10 @@ Date: Descember 4, 2025 ## Table of Contents - [GCC Dependencies](#gcc-dependencies) - [Table of Contents](#table-of-contents) - - [Introduction](#intoduction) + - [Introduction](#introduction) - [List of Dependencies](#list-of-dependencies) -## Intoduction +## Introduction This document tracks dependencies on the GCC toolchain in the centrallix codebase. As code is added which relies on GCC specific behavior, such additions should be noted here to make possible use of a different toolchain (e.g. LLVM) in the future less painful. ## List of Dependencies diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 4e67be437..c94f7db04 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -120,7 +120,7 @@ arr.nAlloc = 0; ```c int xaAddItem(pXArray this, void* item); ``` -This function adds an item to the end of the xarray. The item is assumed to be a `void*`, but this function will _not_ follow pointeres stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). +This function adds an item to the end of the xarray. The item is assumed to be a `void*`, but this function will _not_ follow pointers stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). This function returns 0 on success, or -1 if an error occurs. diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 4924a0803..bc5902eba 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -82,7 +82,7 @@ The xstring (xs) module is used for managing growable strings. It is based on a ```c pXString xsNew() ``` -This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](#nmmalloc) because the XString struct is always a consistant size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. +This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](#nmmalloc) because the XString struct is always a consistent size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. ## xsFree() @@ -117,7 +117,7 @@ This function will optionally allocate more memory, if needed, given the current ```c int xsConcatenate(pXString this, char* text, int len); ``` -This function concatenates the `text` string onto the end of the XString's value. If `len` is set, that number of characters are copied, including possible null characters (`'\0'`). If `len` is -1, all data up to the null-terminater is copied. This function returns 0 if successful, or -1 if an error occurs. +This function concatenates the `text` string onto the end of the XString's value. If `len` is set, that number of characters are copied, including possible null characters (`'\0'`). If `len` is -1, all data up to the null-terminator is copied. This function returns 0 if successful, or -1 if an error occurs. - ⚠️ **Warning**: Do not store pointers to values within the string while adding text to the end of the string. The string may be reallocated to increase space, causing such pointers to break. Instead, use offset indexes into the string and calculate pointers on demand with `xs->String + offset`. @@ -226,7 +226,7 @@ int xsFind(pXString this, char* find, int findlen, int offset) This function searches for a specific string (`find`) in the xstring, starting at the provided `offset`. `findlen` is the length of the provided string, allowing it to include null characters (pass -1 to have the length calculated using `strlen(find)`). This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). -## xsFind() +## xsFindRev() ```c int xsFindRev(pXString this, char* find, int findlen, int offset) ``` @@ -237,7 +237,7 @@ This function works the same as [`xsFind()`](#xsfind) except that it searches fr ```c int xsSubst(pXString this, int offset, int len, char* rep, int replen) ``` -This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsrepalce), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified handle null characters. Both `len` and `replen` can be left blank to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. +This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsreplace), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified handle null characters. Both `len` and `replen` can be left blank to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. ## xsReplace() diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 6ad17ce07..5cef12146 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -674,7 +674,7 @@ The value pointer points to a union struct which can hold one of several types o | `IntVec` | `DATA_T_INTVEC` | Store a `pIntVec`* in the `val->IntVec` field. | `StringVec` | `DATA_T_STRINGVEC` | Store a `pStringVec`* in the `val->StringVec` field. | `Money` | `DATA_T_MONEY` | Store a `pMoneyType`* in the `val->Money` field. -| `Generic` | ? | Store a `void*` in the `val->Generic` field (target data is usually implementation dependant). +| `Generic` | ? | Store a `void*` in the `val->Generic` field (target data is usually implementation dependent). \*_See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more info about this datatype._ @@ -1126,7 +1126,7 @@ The `cmpflags` parameter is a bitmask that provides flags which will be passed t ```c pExpression expCompileExpressionFromLxs(pLxSession s, pParamObjects objlist, int cmpflags); ``` -This function is similar to [`expCompileExpression()`](#expcompileexpression), excpet that it compiles from a provided lexer session instead of from a string. +This function is similar to [`expCompileExpression()`](#expcompileexpression), except that it compiles from a provided lexer session instead of from a string. ### expPodToExpression() ```c @@ -1307,7 +1307,7 @@ The mtlexer (MLX) module is a lexical analyzer library provided by Centrallix fo ```c pLxSession mlxOpenSession(pFile fd, int flags); ``` -This function opens a lexer session, using a file descripter as its source. Some of the more useful values for `flags` include: +This function opens a lexer session, using a file descriptor as its source. Some of the more useful values for `flags` include: | Value | Description | ----------------- | ------------ @@ -1325,7 +1325,7 @@ This function opens a lexer session, using a file descripter as its source. Som | `MLX_F_IFSONLY` | Only return string values separated by tabs, spaces, newlines, and carriage returns. For example, normally the brace in `"this{brace"` is a token and that string will result in three tokens, but in `IFSONLY` mode it is just one token. | `MLX_F_DASHKW` | Keywords can include the dash (`-`). Otherwise, the keyword is treated as two keywords with a minus sign between them. | `MLX_F_FILENAMES` | Treat a non-quoted string beginning with a slash (`/`) or dot-slash (`./`) as a filename, and allow slashes and dots in the string without requiring quotes. -| `MLX_F_NODISCARD` | Attempt to unread unused buffered data rather than discarding it, allowing the calling function to continue reading with `fdRead()` or another lexer session after the last token is read and the session is closed. The lexer `fdRead()`s in 2k or so chunks for performance, and normally discards this data when done, causing future file decriptors to start at an undefined file location. +| `MLX_F_NODISCARD` | Attempt to unread unused buffered data rather than discarding it, allowing the calling function to continue reading with `fdRead()` or another lexer session after the last token is read and the session is closed. The lexer `fdRead()`s in 2k or so chunks for performance, and normally discards this data when done, causing future file descriptors to start at an undefined file location. | `MLX_F_DBLBRACE` | Treat `{{` and `}}` as double brace tokens, not two single brace tokens. | `MLX_F_NOUNESC` | Do not remove escapes in strings. | `MLX_F_SSTRING` | Differentiate between strings values using `""` and `''`. diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 2685edf98..805b281e5 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -37,10 +37,10 @@ # String Similarity -The following sections discuss the approaches to calculating similarity between two strings using the `clusters.c` library. This library can be included using `#include "cxlib/clusters.h"` in the centrallix codebase (use `#include "clusters.h"` in other libaries in centrallix-lib). +The following sections discuss the approaches to calculating similarity between two strings using the `clusters.c` library. This library can be included using `#include "cxlib/clusters.h"` in the centrallix codebase (use `#include "clusters.h"` in other libraries in centrallix-lib). ## Table of Contents -- [String Comparison](#string-comparison) +- [String Similarity](#string-similarity) - [Table of Contents](#table-of-contents) - [Cosine Similarity](#cosine-similarity) - [Character Sets](#character-sets) @@ -99,7 +99,7 @@ As noted above, the vast majority of elements in a vector generated by a typical **Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. ### Computing Similarity -Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. +Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their corresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. ## Levenshtein Similarity diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index e3015ddac..b16bbdbf6 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2782,7 +2782,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (check(objCurrentDate(&search_data->DateComputed)) != 0) goto err_free; /** Get the comparison function based on the similarity measure. **/ - const double (*similarity_function)(void *, void *) = check_ptr(ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure)); + double (*similarity_function)(void *, void *) = check_ptr(ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure)); if (UNLIKELY(similarity_function == NULL)) goto err_free; /** Execute the search using the specified algorithm. **/ @@ -4184,7 +4184,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) - check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -4210,7 +4210,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) - check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; From a8541dfdc5b69e7f4ec86501ba6e0f9108709bb1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 2 Sep 2026 16:59:00 -0600 Subject: [PATCH 116/361] Fix doc links. --- centrallix-sysdoc/Libraries/mtask.md | 2 +- centrallix-sysdoc/Libraries/newmalloc.md | 2 +- centrallix-sysdoc/Libraries/xarray.md | 2 +- centrallix-sysdoc/Libraries/xstring.md | 2 +- centrallix-sysdoc/OSDriver_Authoring.md | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/centrallix-sysdoc/Libraries/mtask.md b/centrallix-sysdoc/Libraries/mtask.md index 26982ff34..0cbadf1cd 100644 --- a/centrallix-sysdoc/Libraries/mtask.md +++ b/centrallix-sysdoc/Libraries/mtask.md @@ -43,7 +43,7 @@ ## Table of Contents -- [Handling Network Connection](#the-mtsession-library) +- [Handling Network Connection](#handling-network-connection) - [Introduction](#introduction) - [netConnectTCP()](#netconnecttcp) - [netCloseTCP()](#netclosetcp) diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index cba631154..9183ca02a 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -44,7 +44,7 @@ ## Table of Contents -- [Memory Management in Centrallix](#objectsystem-driver-interface) +- [Memory Management in Centrallix](#memory-management-in-centrallix) - [Introduction](#introduction) - [nmMalloc()](#nmmalloc) - [nmFree()](#nmfree) diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index c94f7db04..7275cee2f 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -77,7 +77,7 @@ The xarray (xa) module is intended to manage sized growable arrays, similar to a ```c pXArray xaNew(int init_size); ``` -Allocates a new `XArray` struct on the heap (using [`nmMalloc()`](#nmmalloc) for caching) and returns a pointer to it, or returns `NULL` if an error occurs. +Allocates a new `XArray` struct on the heap (using [`nmMalloc()`](newmalloc.md#nmmalloc) for caching) and returns a pointer to it, or returns `NULL` if an error occurs. ## xaFree() diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index bc5902eba..d6e0d0b90 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -82,7 +82,7 @@ The xstring (xs) module is used for managing growable strings. It is based on a ```c pXString xsNew() ``` -This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](#nmmalloc) because the XString struct is always a consistent size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. +This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](newmalloc.md#nmmalloc) because the XString struct is always a consistent size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. ## xsFree() diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 5cef12146..8ee3a6693 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -690,7 +690,7 @@ This function should return 0 on success, 1 if the value is `NULL` or undefined printf("Object name: \"%s\"\n", name); ``` -- 📖 **Note**: In legacy code, a type cast `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix/include/datatypes.h) for more information). +- 📖 **Note**: In legacy code, a type cast `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more information). ### Function: SetAttrValue() @@ -799,7 +799,7 @@ The return value, `hints : ObjPresentationHints`, contains the following useful - `hints->GroupID : int`: Used to assign attributes to groups. Use -1 if the attribute is not in a group. - `hints->GroupName : char*`: The name of the group to which this attribute belongs, or NULL if it is ungrouped or if the group is named elsewhere. - `hints->OrderID : int`: Used to specify an attribute order. -- `hints->FriendlyName : char*`: Used to specify a "display name" for an attribute (e.g. `n_rows` might have a friendly name of `"Number of Rows"`). Should be [`nmSysMalloc()`](#nmsysmalloc)ed, often using [`nmSysStrdup()`](#nmsysstrdup). +- `hints->FriendlyName : char*`: Used to specify a "display name" for an attribute (e.g. `n_rows` might have a friendly name of `"Number of Rows"`). Should be [`nmSysMalloc()`](Libraries/newmalloc.md#nmsysmalloc)ed, often using [`nmSysStrdup()`](Libraries/newmalloc.md#nmsysstrdup). - ⚠️ **Warning**: Behavior is undefined if: - The data is longer than length. From cbd6b3b98643437b19d06838becd02ba5b3dc9c4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 2 Sep 2026 17:06:23 -0600 Subject: [PATCH 117/361] Fix doc links. --- centrallix-lib/include/util.h | 145 +---------------- centrallix-lib/src/util.c | 291 ++-------------------------------- 2 files changed, 16 insertions(+), 420 deletions(-) diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index c184caecf..8e7978e03 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -2,163 +2,32 @@ #define UTILITY_H /************************************************************************/ -/* Centrallix Application Server System */ +/* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: util.c, util.h */ -/* Author: Micah Shennum and Israel Fuller */ -/* Date: May 26, 2011 */ -/* Description: Collection of utilities including: */ -/* - Utilities for parsing numbers. */ -/* - The timer utility for benchmarking code. */ -/* - snprint_bytes() for formatting a byte count. */ -/* - snprint_commas_llu() for formatting large numbers. */ -/* - fprint_mem() for printing memory stats. */ -/* - min() and max() for handling numbers. */ -/* - The check functions for reliably printing debug data. */ +/* Module: (util.c,.h) */ +/* Author: Micah Shennum */ +/* Date: May 26, 2011 */ +/* Description: Collection of utilities */ /************************************************************************/ #ifdef __cplusplus extern "C" { #endif + int strtoi(const char *nptr, char **endptr, int base); unsigned int strtoui(const char *nptr, char **endptr, int base); - char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes); - char* snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value); - void fprint_mem(FILE* out); - - typedef struct - { - double start, total; - } - Timer, *pTimer; - - pTimer timer_init(pTimer timer); - pTimer timer_new(void); - pTimer timer_start(pTimer timer); - pTimer timer_stop(pTimer timer); - double timer_get(pTimer timer); - pTimer timer_reset(pTimer timer); - void timer_de_init(pTimer timer); - void timer_free(pTimer timer); #ifdef __cplusplus } #endif -#ifndef __cplusplus -#include - -#include "expect.h" - -/*** @brief Returns the smaller of two values. - *** - *** @param a The first value. - *** @param b The second value. - *** @return The smaller of the two values. - *** - *** @note This macro uses GCC extensions to ensure type safety. - ***/ -#define min(a, b) \ - ({ \ - __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - (_a < _b) ? _a : _b; \ - }) - -/*** @brief Returns the larger of two values. - *** - *** @param a The first value. - *** @param b The second value. - *** @return The larger of the two values. - *** - *** @note This macro uses GCC extensions to ensure type safety. - ***/ -#define max(a, b) \ - ({ \ - __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - (_a > _b) ? _a : _b; \ - }) - -/** File name macro, expanding functionality like __FILE__ and __LINE__. **/ -#define __FILENAME__ \ - ({ \ - const char* last_directory = strrchr(__FILE__, '/'); \ - ((last_directory != NULL) ? last_directory + 1 : __FILE__); \ - }) - -/** Error Handling. **/ -void print_err(int code, const char* function_name, const char* file_name, const int line_number); - -/*** Ensures that developer diagnostics are printed if the result of the - *** passed function call is not zero. Not intended for user errors. - *** - *** @param result The expression to check. The text of this expression is - *** included in the error message if an error occurs. - *** @returns The result of the checked expression. - ***/ -#define check(result) \ - ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - int _r = (result); \ - if (UNLIKELY(_r != 0)) print_err(_r, #result, __FILE__, __LINE__); \ - _r; \ - }) - -/*** Ensures that developer diagnostics are printed if the result of the - *** passed function call is negative. Not intended for user errors. - *** - *** @param result The expression to check. The text of this expression is - *** included in the error message if an error occurs. - *** @returns The result of the checked expression. - ***/ -#define check_neg(result) \ - ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - int _r = (result); \ - if (UNLIKELY(_r < 0)) print_err(_r, #result, __FILE__, __LINE__); \ - _r; \ - }) - -/*** Ensures that developer diagnostics are printed if the result of the - *** passed function call is a NAN double. Not intended for user errors. - *** - *** @param result The expression to check. The text of this expression is - *** included in the error message if an error occurs. - *** @returns The result of the checked expression. - ***/ -#define check_double(result) \ - ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - double _r = (result); \ - if (UNLIKELY(isnan(_r))) print_err(0, #result, __FILE__, __LINE__); \ - _r; \ - }) - -/*** Ensures that developer diagnostics are printed if the result of the - *** passed function call is a NULL pointer. Not intended for user errors. - *** - *** @param result The expression to check. The text of this expression is - *** included in the error message if an error occurs. - *** @returns The result of the checked expression. - ***/ -#define check_ptr(result) \ - ({ \ - errno = 0; /* Reset errno to prevent confusion. */ \ - void* _r = (result); \ - if (UNLIKELY(_r == NULL)) print_err(0, #result, __FILE__, __LINE__); \ - _r; \ - }) - -#endif /* __cplusplus */ - #endif /* UTILITY_H */ diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 988f000db..629b59c79 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -1,36 +1,24 @@ /************************************************************************/ -/* Centrallix Application Server System */ +/* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: util.c, util.h */ -/* Author: Micah Shennum and Israel Fuller */ -/* Date: May 26, 2011 and October 13, 2025 (respectively) */ -/* Description: Collection of utilities including: */ -/* - Utilities for parsing numbers. */ -/* - The timer utility for benchmarking code. */ -/* - snprint_bytes() for formatting a byte count. */ -/* - snprint_commas_llu() for formatting large numbers. */ -/* - fprint_mem() for printing memory stats. */ -/* - min() and max() for handling numbers. */ -/* - The check functions for reliably printing debug data. */ +/* Module: (util.c,.h) */ +/* Author: Micah Shennum */ +/* Date: May 26, 2011 */ +/* Description: Collection of utilities */ /************************************************************************/ -#include -#include -#include -#include #include -#include -#include -#include -#include "newmalloc.h" +#include +#include +#include #include "util.h" /** @@ -89,264 +77,3 @@ unsigned int strtoui(const char *nptr, char **endptr, int base){ //return as tmp; return (unsigned int)tmp; } - -/*** snprint_bytes() allows one to pick between CS units, where the kibibyte - *** (KiB) is 1024 bytes, and metric units where the kilobyte (KB) is 1000 bytes. - *** Fun Fact: Windows uses kibibytes, but displays them as KB. - ***/ -#define USE_METRIC false -static char* units_cs[] = {"bytes", "KiB", "MiB", "GiB"}; -static char* units_metric[] = {"bytes", "KB", "MB", "GB"}; -#define N_UNITS ((unsigned int)(sizeof(units_cs) / sizeof(units_cs[0]))) - -/*** Displays a size in bytes using the largest unit where the result would be - *** at least 1.0. Note that units larger than GB and GiB are not supported - *** because the largest possible unsigned int is 4,294,967,295, which is - *** exactly 4 GiB (or approximately 4.29 GB). - *** - *** @param buf The buffer to which new text will be written, using snprintf(). - *** @param buf_size The amount of space in the buffer, passed to snprintf(). - *** It is recommended to have at least 12 characters available. - *** @param bytes The number of bytes, which will be formatted and written - *** to the buffer.. - *** @returns buf, for chaining. - ***/ -char* -snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) - { - char** units = (USE_METRIC) ? units_metric : units_cs; - const double unit_size = (USE_METRIC) ? 1000.0 : 1024.0; - - /** Search for the largest unit where the value would be at least 1. **/ - const double size = (double)bytes; - for (unsigned char i = N_UNITS - 1; i >= 1u; i--) - { - const double denominator = pow(unit_size, i); - if (size >= denominator) - { - const double converted_size = size / denominator; - if (converted_size >= 100.0) - snprintf(buf, buf_size, "%.5g %s", converted_size, units[i]); - else if (converted_size >= 10.0) - snprintf(buf, buf_size, "%.4g %s", converted_size, units[i]); - else /* if (converted_size >= 1.0) - Always true. */ - snprintf(buf, buf_size, "%.3g %s", converted_size, units[i]); - return buf; - } - } - - /** None of the larger units work, so we just use bytes. **/ - snprintf(buf, buf_size, "%u %s", bytes, units[0]); - - return buf; - } -#undef N_UNITS - -/*** Print a large number formatted with comas to a buffer. - *** - *** @param buf The buffer to print the number into. - *** @param buf_size The maximum number of characters to add to the buffer. - *** @param value The value to write into the buffer. - *** @returns `buf`, or NULL if `buf_size` is 0. - */ -char* -snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) - { - if (buf_size == 0) return NULL; - if (value == 0) - { - if (buf_size > 1) { buf[0] = '0'; buf[1] = '\0'; } - else buf[0] = '\0'; - return buf; - } - - /*** Write the number to the string in reverse order, adding commas as - *** they are needed. - ***/ - char tmp[32]; - unsigned int ti = 0; - while (value > 0 && ti < sizeof(tmp) - 1) - { - if (ti % 4 == 3) tmp[ti++] = ','; - tmp[ti++] = '0' + (value % 10); - value /= 10; - } - tmp[ti] = '\0'; - - unsigned int outlen = min(ti, buf_size - 1u); - for (unsigned int i = 0u; i < outlen; i++) buf[i] = tmp[ti - i - 1]; - buf[outlen] = '\0'; - - return buf; - } - -/** Print summary the current memory in use to the file pointer. **/ -void -fprint_mem(FILE* out) - { - FILE* fp = fopen("/proc/self/statm", "r"); - if (fp == NULL) { perror("fopen()"); return; } - - /** Get page counts. **/ - long size, resident, share, text, lib, data, dt; - if (fscanf(fp, "%ld %ld %ld %ld %ld %ld %ld", - &size, &resident, &share, &text, &lib, &data, &dt) != 7) - { - fprintf(stderr, "Failed to read memory info\n"); - check(fclose(fp)); /* Failure ignored. */ - return; - } - check(fclose(fp)); /* Failure ignored. */ - - /** Get page size. **/ - const long page_size = sysconf(_SC_PAGESIZE); /* in bytes */ - if (page_size == -1) - { - fprintf(stderr, "Failed to get page size.\n"); - return; - } - - /** Get the number of resident bytes used. **/ - const long resident_bytes = resident * page_size; - char buf[16]; - snprint_bytes(buf, sizeof(buf), (unsigned int)(resident_bytes)); - - /** fprintf() out data. **/ - fprintf(out, "Memory used: %ld bytes (%s)\n", resident_bytes, buf); - fprintf(out, - "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", - share * page_size, text * page_size, lib * page_size, data * page_size - ); - - return; - } - -/*** Get the current monotonic time in seconds. - *** - *** @returns The current monotonic time as a fractional number of seconds. - ***/ -static double -get_time(void) - { - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - - return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9f; - } - -/*** Initialize a timer struct. - *** - *** @param timer The timer to initialize. - *** @returns `timer`, for chaining. - ***/ -pTimer -timer_init(pTimer timer) - { - if (UNLIKELY(timer == NULL)) return NULL; - timer->start = NAN; - timer->total = 0.0; - - return timer; - } - -/*** Allocate and initialize a new timer. - *** - *** @returns A newly allocated timer, or NULL if allocation fails. - ***/ -pTimer -timer_new(void) - { - return timer_init(check_ptr(nmMalloc(sizeof(Timer)))); - } - -/*** Start timing. - *** - *** @param timer The timer to start. - *** @returns `timer`, for chaining. - ***/ -pTimer -timer_start(pTimer timer) - { - if (UNLIKELY(timer == NULL)) return NULL; - timer->start = get_time(); - - return timer; - } - -/*** Stop timing and add the elapsed time to the timer total. - *** - *** @param timer The timer to stop. - *** @returns `timer`, for chaining. - ***/ -pTimer -timer_stop(pTimer timer) - { - if (UNLIKELY(timer == NULL)) return NULL; - timer->total += get_time() - timer->start; - - return timer; - } - -/*** Get the total accumulated time for a timer. - *** - *** @param timer The timer to read. - *** @returns The total accumulated time in seconds, or NAN if `timer` - *** is NULL. - ***/ -double -timer_get(pTimer timer) - { - if (UNLIKELY(timer == NULL)) return NAN; - - return timer->total; - } - -/*** Reset a timer to its initial state so that it can be reused to time - *** something else. - *** - *** @param timer The timer to reset. - *** @returns `timer`, for chaining. - ***/ -pTimer -timer_reset(pTimer timer) - { - return timer_init(timer); - } - -/*** De-initialize a timer before it is freed. - *** - *** @param timer The timer to de-initialize. - ***/ -void -timer_de_init(pTimer timer) {} - -/*** De-initialize and free a timer allocated by timer_new(). - *** - *** @param timer The timer to free. - ***/ -void -timer_free(pTimer timer) - { - timer_de_init(timer); - nmFree(timer, sizeof(Timer)); - - return; - } - -/*** Function for failing on error, assuming the error came from a library or - *** system function call, so that the error buffer is set to a valid value. - ***/ -void print_err(int code, const char* function_name, const char* file_name, const int line_number) - { - /** Create a descriptive error message. **/ - char error_buf[BUFSIZ]; - snprintf(error_buf, sizeof(error_buf), "%s:%d: %s failed", file_name, line_number, function_name); - - /** Print it with as much info as we can reasonably find. **/ - if (errno != 0) perror(error_buf); - else if (code != 0) fprintf(stderr, "%s (error code %d).\n", error_buf, code); - else fprintf(stderr, "%s.\n", error_buf); - - return; - } From 3cbcdc1ecf98c6c6fe526bdc2aa85a36bd7635e8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 2 Sep 2026 17:11:55 -0600 Subject: [PATCH 118/361] Update code to handle differences on add-utils. Rename #includes to new file names. Rename functions to the new names. --- centrallix-lib/src/clusters.c | 41 ++-- centrallix-lib/src/mtsession.c | 6 +- centrallix-lib/src/xarray.c | 6 +- centrallix/expression/exp_functions.c | 49 ++-- centrallix/osdrivers/objdrv_cluster.c | 313 +++++++++++++------------- centrallix/utility/double_metaphone.c | 14 +- 6 files changed, 216 insertions(+), 213 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index c4bcc3db9..f4e90cdc8 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -42,10 +42,11 @@ #include #include +#include "check.h" #include "clusters.h" #include "expect.h" #include "newmalloc.h" -#include "util.h" +#include "range.h" #include "xarray.h" /** This file has additional documentation in string_similarity.md. **/ @@ -155,7 +156,7 @@ ca_build_vector(const char* str) /** Allocate memory to store the characters. **/ unsigned int num_chars = 0u; - chars = check_ptr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); + chars = checkPtr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); if (UNLIKELY(chars == NULL)) goto err_free; /** Store characters. **/ @@ -181,7 +182,7 @@ ca_build_vector(const char* str) /** Compute character pair hashes. **/ - char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); + char_pairs = checkPtr(nmSysMalloc(num_chars * sizeof(CharPair))); if (UNLIKELY(char_pairs == NULL)) goto err_free; const unsigned int num_pairs = num_chars - 1u; for (unsigned int i = 0u; i < num_pairs; i++) @@ -205,7 +206,7 @@ ca_build_vector(const char* str) /** Allocate space for the sparse vector. **/ - sparse_vector = check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); + sparse_vector = checkPtr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); if (sparse_vector == NULL) goto err_free; /** Build the sparse vector from the character pairs. **/ @@ -243,7 +244,7 @@ ca_build_vector(const char* str) /** Trim extra space wasted by identical hashes. **/ - trimmed_sparse_vector = check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); + trimmed_sparse_vector = checkPtr(nmSysRealloc(sparse_vector, cur * sizeof(int))); if (trimmed_sparse_vector == NULL) goto err_free; sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ @@ -515,11 +516,11 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const ***/ const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; - lev_matrix = check_ptr(nmSysMalloc((str1_len + 1) * sizeof(unsigned int*))); + lev_matrix = checkPtr(nmSysMalloc((str1_len + 1) * sizeof(unsigned int*))); if (lev_matrix == NULL) goto end; for (unsigned int i = 0u; i < str1_len + 1u; i++) { - lev_matrix[i] = check_ptr(nmSysMalloc((str2_len + 1) * sizeof(unsigned int))); + lev_matrix[i] = checkPtr(nmSysMalloc((str2_len + 1) * sizeof(unsigned int))); if (lev_matrix[i] == NULL) goto end; } @@ -671,7 +672,7 @@ ca_lev_compare(void* str1, void* str2) if (len1 == 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ - const int edit_dist = check_neg(ca_edit_dist((const char*)str1, (const char*)str2, len1, len2)); + const int edit_dist = checkNeg(ca_edit_dist((const char*)str1, (const char*)str2, len1, len2)); if (edit_dist < 0) return NAN; /** Normalize edit distance into a similarity measure. **/ @@ -732,8 +733,8 @@ get_cluster_size( *** loop in ca_kmeans(). Also, ca_kmeans() may be called multiple *** times with the same k value, increasing this benefit. ***/ - cluster_sums = check_ptr(nmMalloc(num_clusters * sizeof(double))); - cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); + cluster_sums = checkPtr(nmMalloc(num_clusters * sizeof(double))); + cluster_counts = checkPtr(nmMalloc(num_clusters * sizeof(unsigned int))); if (cluster_sums == NULL || cluster_counts == NULL) goto end; for (unsigned int i = 0u; i < num_clusters; i++) { @@ -826,15 +827,15 @@ ca_kmeans( /** Allocate space to store centroids and new_centroids. **/ /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ const size_t centroids_size = num_clusters * sizeof(pCentroid); - centroids = check_ptr(nmMalloc(centroids_size)); - new_centroids = check_ptr(nmMalloc(centroids_size)); + centroids = checkPtr(nmMalloc(centroids_size)); + new_centroids = checkPtr(nmMalloc(centroids_size)); if (centroids == NULL || new_centroids == NULL) goto end; memset(centroids, 0, centroids_size); memset(new_centroids, 0, centroids_size); for (unsigned int i = 0u; i < num_clusters; i++) { - centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); - new_centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); + centroids[i] = checkPtr(nmMalloc(CENTROID_SIZE)); + new_centroids[i] = checkPtr(nmMalloc(CENTROID_SIZE)); if (centroids[i] == NULL || new_centroids[i] == NULL) goto end; memset(centroids[i], 0, CENTROID_SIZE); memset(new_centroids[i], 0, CENTROID_SIZE); @@ -923,7 +924,7 @@ ca_kmeans( /** Is there enough improvement? **/ if (min_improvement <= -1.0) continue; /** Skip check if it will never end the loop. **/ - const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); + const double average_cluster_size = checkDouble(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); if (isnan(average_cluster_size)) goto end; const double improvement = old_average_cluster_size - average_cluster_size; if (improvement < min_improvement) break; @@ -993,7 +994,7 @@ ca_most_similar( /** Iterate over all data options to find the one with the highest similarity. **/ for (unsigned int i = 0u; (num_data == 0u && data[i] != NULL) || (i < num_data); i++) { - const double sim = check_double(similarity(target, data[i])); + const double sim = checkDouble(similarity(target, data[i])); if (isnan(sim)) continue; /* Skip failed comparison. */ if (sim > best_sim && sim > threshold) { @@ -1037,7 +1038,7 @@ ca_sliding_search( { /** Guess that we will need space for two pairs per data point. **/ const int guess_size = num_data * 2; - pairs = check_ptr(xaNew(guess_size)); + pairs = checkPtr(xaNew(guess_size)); if (pairs == NULL) goto err; } const int num_starting_pairs = pairs->nItems; @@ -1049,7 +1050,7 @@ ca_sliding_search( const unsigned int window_end = min(i + window_size, num_data); for (unsigned int j = window_start; j < window_end; j++) { - const double sim = check_double(similarity(data[i], data[j])); + const double sim = checkDouble(similarity(data[i], data[j])); if (isnan(sim) || sim < 0.0 || 1.0 < sim) { fprintf(stderr, "Invalid similarity %g %lf.\n", sim, sim); @@ -1057,12 +1058,12 @@ ca_sliding_search( } if (sim > threshold) /* Pair found! */ { - pPair pair = (pPair)check_ptr(nmMalloc(sizeof(Pair))); + pPair pair = (pPair)checkPtr(nmMalloc(sizeof(Pair))); if (pair == NULL) goto err_free; pair->i = i; pair->j = j; pair->similarity = sim; - if (check_neg(xaAddItem(pairs, (void*)pair)) < 0) goto err_free; + if (checkNeg(xaAddItem(pairs, (void*)pair)) < 0) goto err_free; } } } diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index d23a987b6..14709f5c0 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -24,7 +24,7 @@ #include "xstring.h" #include "xhash.h" #include "strtcpy.h" -#include "util.h" +#include "check.h" #include "cxsec.h" /************************************************************************/ @@ -520,7 +520,7 @@ mssError_internal(int clr, char* module, char* file, int line, char* message, .. if (clr) check(mssClearError()); /* Failure ignored. */ /** Allocate space and construct the error text. **/ - const char* allocated_err_msg = check_ptr(nmSysStrdup(err_msg)); + const char* allocated_err_msg = checkPtr(nmSysStrdup(err_msg)); if (allocated_err_msg == NULL) { fprintf(stderr, "Failed to store error message: %s\n", err_msg); @@ -528,7 +528,7 @@ mssError_internal(int clr, char* module, char* file, int line, char* message, .. } /** Store the error. **/ - if (check_neg(xaAddItem(&(s->ErrList), (void*)allocated_err_msg)) < 0) + if (checkNeg(xaAddItem(&(s->ErrList), (void*)allocated_err_msg)) < 0) { fprintf(stderr, "Failed to add error message to session error list: %s\n", err_msg); return; /* Give up. */ diff --git a/centrallix-lib/src/xarray.c b/centrallix-lib/src/xarray.c index 3a7fa3429..e91d8a69b 100644 --- a/centrallix-lib/src/xarray.c +++ b/centrallix-lib/src/xarray.c @@ -6,7 +6,7 @@ #include #include #include "xarray.h" -#include "util.h" +#include "check.h" #include "newmalloc.h" /************************************************************************/ @@ -420,7 +420,7 @@ xaTrim(pXArray this) { /** Allocate the new internal items array. **/ const size_t new_size = this->nItems * sizeof(void*); - void* new_items = check_ptr(nmSysRealloc(this->Items, new_size)); + void* new_items = checkPtr(nmSysRealloc(this->Items, new_size)); if (new_items == NULL) return -1; /** Update the struct. **/ @@ -441,7 +441,7 @@ void** xaToArray(pXArray this) { const size_t size = this->nItems * sizeof(void*); - void** result = check_ptr(nmSysMalloc(size)); + void** result = checkPtr(nmSysMalloc(size)); if (result == NULL) return NULL; memcpy(result, this->Items, size); diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index e0478e395..1896103d0 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -62,13 +62,14 @@ #include #include +#include "cxlib/check.h" #include "cxlib/clusters.h" #include "cxlib/expect.h" #include "cxlib/mtask.h" #include "cxlib/mtlexer.h" #include "cxlib/mtsession.h" #include "cxlib/newmalloc.h" -#include "cxlib/util.h" +#include "cxlib/range.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "cxss/cxss.h" @@ -521,7 +522,7 @@ exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) else { /** We need to allocate new memory. **/ - char* result = check_ptr(nmSysMalloc(required_space * sizeof(char))); + char* result = checkPtr(nmSysMalloc(required_space * sizeof(char))); if (result == NULL) return -1; tree->String = result; tree->Alloc = 1; @@ -1596,7 +1597,7 @@ exp_fn_lztrim(pExpression tree) } /** Extract the arg string. **/ - pExpression maybe_str = check_ptr(tree->Children.Items[0]); + pExpression maybe_str = checkPtr(tree->Children.Items[0]); if (UNLIKELY(maybe_str == NULL)) return -1; if (maybe_str->Flags & EXPR_F_NULL) { @@ -1605,7 +1606,7 @@ exp_fn_lztrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = check_ptr(maybe_str->String); + char* str = checkPtr(maybe_str->String); if (UNLIKELY(str == NULL)) return -1; /*** We don't need to allocate new memory or copy anything because we @@ -1641,7 +1642,7 @@ exp_fn_ltrim(pExpression tree) } /** Extract the arg string. **/ - pExpression maybe_str = check_ptr(tree->Children.Items[0]); + pExpression maybe_str = checkPtr(tree->Children.Items[0]); if (UNLIKELY(maybe_str == NULL)) return -1; if (maybe_str->Flags & EXPR_F_NULL) { @@ -1650,7 +1651,7 @@ exp_fn_ltrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = check_ptr(maybe_str->String); + char* str = checkPtr(maybe_str->String); if (UNLIKELY(str == NULL)) return -1; /*** We don't need to allocate new memory or copy anything because we @@ -1687,7 +1688,7 @@ exp_fn_rtrim(pExpression tree) } /** Extract the arg string. **/ - pExpression maybe_str = check_ptr(tree->Children.Items[0]); + pExpression maybe_str = checkPtr(tree->Children.Items[0]); if (UNLIKELY(maybe_str == NULL)) return -1; if (maybe_str->Flags & EXPR_F_NULL) { @@ -1696,7 +1697,7 @@ exp_fn_rtrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = check_ptr(maybe_str->String); + char* str = checkPtr(maybe_str->String); if (UNLIKELY(str == NULL)) return -1; /** Trim spaces from the end of the string. **/ @@ -3857,14 +3858,14 @@ exp_fn_log(pExpression tree) /** Extract args. **/ double number, base; - if (check(exp_fn_i_get_number(check_ptr(tree->Children.Items[0]), &number)) != 0) + if (check(exp_fn_i_get_number(checkPtr(tree->Children.Items[0]), &number)) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); return -1; } if (tree->Children.nItems > 1) { - if (check(exp_fn_i_get_number(check_ptr(tree->Children.Items[1]), &base)) != 0) + if (check(exp_fn_i_get_number(checkPtr(tree->Children.Items[1]), &base)) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); return -1; @@ -4560,7 +4561,7 @@ exp_fn_metaphone(pExpression tree) char* secondary = NULL; /** Extract string param. **/ - pExpression maybe_str = check_ptr(tree->Children.Items[0]); + pExpression maybe_str = checkPtr(tree->Children.Items[0]); if (UNLIKELY(maybe_str == NULL)) goto end_free; if (maybe_str->Flags & EXPR_F_NULL) { @@ -4569,7 +4570,7 @@ exp_fn_metaphone(pExpression tree) ret = 0; goto end_free; } - const char* str = check_ptr(maybe_str->String); + const char* str = checkPtr(maybe_str->String); if (UNLIKELY(str == NULL)) goto end_free; const size_t str_len = strlen(str); if (UNLIKELY(str_len == 0u)) @@ -4630,8 +4631,8 @@ exp_fn_compare(pExpression tree) } /** Extract strings. **/ - pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); - pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + pExpression maybe_str1 = checkPtr(tree->Children.Items[0]); + pExpression maybe_str2 = checkPtr(tree->Children.Items[1]); if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) goto err; if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { @@ -4639,8 +4640,8 @@ exp_fn_compare(pExpression tree) tree->DataType = DATA_T_DOUBLE; return 0; } - char* str1 = check_ptr(maybe_str1->String); - char* str2 = check_ptr(maybe_str2->String); + char* str1 = checkPtr(maybe_str1->String); + char* str2 = checkPtr(maybe_str2->String); if (UNLIKELY(str1 == NULL || str2 == NULL)) goto err; /** Handle either cos_compare() or lev_compare(). **/ @@ -4649,8 +4650,8 @@ exp_fn_compare(pExpression tree) int ret; /** Build vectors. **/ - const pVector v1 = check_ptr(ca_build_vector(str1)); - const pVector v2 = check_ptr(ca_build_vector(str2)); + const pVector v1 = checkPtr(ca_build_vector(str1)); + const pVector v2 = checkPtr(ca_build_vector(str2)); if (UNLIKELY(v1 == NULL || v2 == NULL)) { mssError(1, "EXP", @@ -4675,7 +4676,7 @@ exp_fn_compare(pExpression tree) } else { /* lev_compare() */ - const double lev_sim = check_double(ca_lev_compare(str1, str2)); + const double lev_sim = checkDouble(ca_lev_compare(str1, str2)); if (UNLIKELY(isnan(lev_sim))) { mssError(1, "EXP", @@ -4711,8 +4712,8 @@ exp_fn_levenshtein(pExpression tree) } /** Extract strings. **/ - pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); - pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + pExpression maybe_str1 = checkPtr(tree->Children.Items[0]); + pExpression maybe_str2 = checkPtr(tree->Children.Items[1]); if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) return -1; if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { @@ -4720,13 +4721,13 @@ exp_fn_levenshtein(pExpression tree) tree->DataType = DATA_T_INTEGER; return 0; } - char* str1 = check_ptr(maybe_str1->String); - char* str2 = check_ptr(maybe_str2->String); + char* str1 = checkPtr(maybe_str1->String); + char* str2 = checkPtr(maybe_str2->String); if (UNLIKELY(str1 == NULL || str2 == NULL)) return -1; /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ - const int edit_dist = check_neg(ca_edit_dist(str1, str2, 0lu, 0lu)); + const int edit_dist = checkNeg(ca_edit_dist(str1, str2, 0lu, 0lu)); if (UNLIKELY(edit_dist < 0)) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index b16bbdbf6..1ac216bd4 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -44,6 +44,7 @@ #include #include +#include "cxlib/check.h" #include "cxlib/clusters.h" #include "cxlib/expect.h" #include "cxlib/magic.h" @@ -782,7 +783,7 @@ ci_ParseAttribute( ASSERTMAGIC(attr_info, MGK_STRUCTINF); /** Allocate expression. **/ - pExpression exp = (pExpression)check_ptr(stGetExpression(attr_info, 0)); + pExpression exp = (pExpression)checkPtr(stGetExpression(attr_info, 0)); if (UNLIKELY(exp == NULL)) goto err; /** Bind parameters. **/ @@ -947,29 +948,29 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) ASSERTMAGIC(inf, MGK_STRUCTINF); /** Allocate SourceData. **/ - source_data = (pSourceData)check_ptr(nmMalloc(sizeof(SourceData))); + source_data = (pSourceData)checkPtr(nmMalloc(sizeof(SourceData))); if (UNLIKELY(source_data == NULL)) goto err_free; memset(source_data, 0, sizeof(SourceData)); SETMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Initialize obvious values for SourceData. **/ - source_data->Name = (char*)check_ptr(nmSysStrdup(inf->Name)); + source_data->Name = (char*)checkPtr(nmSysStrdup(inf->Name)); if (UNLIKELY(source_data->Name == NULL)) goto err_free; if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; /** Get source. **/ if (UNLIKELY(ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; - source_data->SourcePath = (char*)check_ptr(nmSysStrdup(buf)); + source_data->SourcePath = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->SourcePath == NULL)) goto err_free; /** Get the attribute name to use when querying keys from the source. **/ if (UNLIKELY(ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; - source_data->KeyAttr = (char*)check_ptr(nmSysStrdup(buf)); + source_data->KeyAttr = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->KeyAttr == NULL)) goto err_free; /** Get the attribute name to use for querying data from the source. **/ if (UNLIKELY(ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; - source_data->DataAttr = (char*)check_ptr(nmSysStrdup(buf)); + source_data->DataAttr = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->DataAttr == NULL)) goto err_free; /** Create cache entry key. **/ @@ -977,7 +978,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) + strlen(source_data->SourcePath) + strlen(source_data->KeyAttr) + strlen(source_data->DataAttr) + 5lu; - source_data->CacheKey = (char*)check_ptr(nmSysMalloc(len * sizeof(char))); + source_data->CacheKey = (char*)checkPtr(nmSysMalloc(len * sizeof(char))); if (UNLIKELY(source_data->CacheKey == NULL)) goto err_free; snprintf(source_data->CacheKey, len, "%s?%s->%s:%s", @@ -1055,13 +1056,13 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Allocate space for data struct. **/ - cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); + cluster_data = checkPtr(nmMalloc(sizeof(ClusterData))); if (UNLIKELY(cluster_data == NULL)) goto err_free; memset(cluster_data, 0, sizeof(ClusterData)); SETMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); /** Basic fields. **/ - cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); + cluster_data->Name = checkPtr(nmSysStrdup(inf->Name)); if (UNLIKELY(cluster_data->Name == NULL)) goto err_free; cluster_data->SourceData = source_data; if (check(objCurrentDate(&cluster_data->DateCreated)) != 0) goto err_free; @@ -1164,7 +1165,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source if (check(xaInit(&sub_clusters, 4u)) != 0) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) { - pStructInf sub_inf = check_ptr(inf->SubInf[i]); + pStructInf sub_inf = checkPtr(inf->SubInf[i]); if (UNLIKELY(sub_inf == NULL)) { mssError(1, "Cluster", "Failed to get %uth subinf.", i); @@ -1174,7 +1175,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source char* name = sub_inf->Name; /** Handle various struct types. **/ - const int struct_type = check_neg(stStructType(sub_inf)); + const int struct_type = checkNeg(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1203,7 +1204,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source case ST_T_SUBGROUP: { /** Select array by group type. **/ - char* group_type = check_ptr(sub_inf->UsrType); + char* group_type = checkPtr(sub_inf->UsrType); if (group_type == NULL) goto err_free; if (strcmp(group_type, "cluster/cluster") != 0) { @@ -1219,7 +1220,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source pClusterData sub_cluster = ci_ParseClusterData(sub_inf, param_list, source_data); if (sub_cluster == NULL) goto err_free; sub_cluster->Parent = cluster_data; - if (check_neg(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; + if (checkNeg(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; break; } @@ -1235,7 +1236,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source } } cluster_data->nSubClusters = sub_clusters.nItems; - cluster_data->SubClusters = (ClusterData**)check_ptr(xaToArray(&sub_clusters)); + cluster_data->SubClusters = (ClusterData**)checkPtr(xaToArray(&sub_clusters)); if (UNLIKELY(cluster_data->SubClusters == NULL)) goto err_free; check(xaDeInit(&sub_clusters)); /* Failure ignored. */ sub_clusters.nAlloc = 0; @@ -1247,7 +1248,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source case ALGORITHM_NONE: { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 8lu; - cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + cache_key = checkPtr(nmSysMalloc(len * sizeof(char))); if (UNLIKELY(cache_key == NULL)) goto err_free; snprintf(cache_key, len, "%s/%s?%u", source_data->CacheKey, @@ -1260,7 +1261,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source case ALGORITHM_SLIDING_WINDOW: { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 16lu; - cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + cache_key = checkPtr(nmSysMalloc(len * sizeof(char))); if (UNLIKELY(cache_key == NULL)) goto err_free; snprintf(cache_key, len, "%s/%s?%u&%u&%u", source_data->CacheKey, @@ -1275,7 +1276,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source default: { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 32lu; - cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + cache_key = checkPtr(nmSysMalloc(len * sizeof(char))); if (UNLIKELY(cache_key == NULL)) goto err_free; snprintf(cache_key, len, "%s/%s?%u&%u&%u&%g&%u", source_data->CacheKey, @@ -1350,17 +1351,17 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) char* key = NULL; /** Extract values. **/ - pParamObjects param_list = check_ptr(node_data->ParamList); + pParamObjects param_list = checkPtr(node_data->ParamList); if (UNLIKELY(param_list == NULL)) goto err_free; /** Allocate space for search struct. **/ - search_data = check_ptr(nmMalloc(sizeof(SearchData))); + search_data = checkPtr(nmMalloc(sizeof(SearchData))); if (UNLIKELY(search_data == NULL)) goto err_free; memset(search_data, 0, sizeof(SearchData)); SETMAGIC(search_data, MGK_CL_SEARCH_DATA); /** Get basic information. **/ - search_data->Name = check_ptr(nmSysStrdup(inf->Name)); + search_data->Name = checkPtr(nmSysStrdup(inf->Name)); if (UNLIKELY(search_data->Name == NULL)) goto err_free; if (check(objCurrentDate(&search_data->DateCreated)) != 0) goto err_free; @@ -1415,18 +1416,18 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Check for additional data to warn the user about. **/ for (unsigned int i = 0u; i < inf->nSubInf; i++) { - pStructInf sub_inf = check_ptr(inf->SubInf[i]); + pStructInf sub_inf = checkPtr(inf->SubInf[i]); if (UNLIKELY(sub_inf == NULL)) { mssError(1, "Cluster", "Failed to get %uth subinf.", i); goto err_free; } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); - char* name = check_ptr(sub_inf->Name); + char* name = checkPtr(sub_inf->Name); if (UNLIKELY(name == NULL)) goto err_free; /** Handle various struct types. **/ - const int struct_type = check_neg(stStructType(sub_inf)); + const int struct_type = checkNeg(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1453,7 +1454,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) case ST_T_SUBGROUP: { /** The spec does not specify any valid sub-groups for searches. **/ - char* group_type = check_ptr(sub_inf->UsrType); + char* group_type = checkPtr(sub_inf->UsrType); if (UNLIKELY(group_type == NULL)) goto err_free; fprintf(stderr, "Warning: Unknown group [\"%s\" : \"%s\"] in search \"%s\".\n", @@ -1476,7 +1477,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Create cache entry key. **/ char* source_key = search_data->SourceCluster->CacheKey; const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; - key = check_ptr(nmSysMalloc(len * sizeof(char))); + key = checkPtr(nmSysMalloc(len * sizeof(char))); if (UNLIKELY(key == NULL)) goto err_free; snprintf(key, len, "%s/%s?%g&%u", source_key, @@ -1544,20 +1545,20 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ASSERTMAGIC(parent, MGK_OBJECT); /** Get file path. **/ - char* path = check_ptr(objFilePath(parent)); + char* path = checkPtr(objFilePath(parent)); if (UNLIKELY(path == NULL)) goto err_free; /** Allocate node struct data. **/ - node_data = check_ptr(nmMalloc(sizeof(NodeData))); + node_data = checkPtr(nmMalloc(sizeof(NodeData))); if (UNLIKELY(node_data == NULL)) goto err_free; memset(node_data, 0, sizeof(NodeData)); SETMAGIC(node_data, MGK_CL_NODE_DATA); node_data->Parent = parent; /** Set up param list. **/ - node_data->ParamList = check_ptr(expCreateParamList()); + node_data->ParamList = checkPtr(expCreateParamList()); if (UNLIKELY(node_data->ParamList == NULL)) goto err_free; - node_data->ParamList->Session = check_ptr(parent->Session); + node_data->ParamList->Session = checkPtr(parent->Session); if (UNLIKELY(node_data->ParamList->Session == NULL)) goto err_free; ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); if (ret != 0) @@ -1586,7 +1587,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (check(xaInit(&search_infs, 8)) != 0) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) { - pStructInf sub_inf = check_ptr(inf->SubInf[i]); + pStructInf sub_inf = checkPtr(inf->SubInf[i]); if (UNLIKELY(sub_inf == NULL)) { mssError(1, "Cluster", "Failed to get %uth subinf.", i); @@ -1596,7 +1597,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) char* name = sub_inf->Name; /** Handle various struct types. **/ - const int struct_type = check_neg(stStructType(sub_inf)); + const int struct_type = checkNeg(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1622,21 +1623,21 @@ ci_ParseNodeData(pStructInf inf, pObject parent) case ST_T_SUBGROUP: { - char* group_type = check_ptr(sub_inf->UsrType); + char* group_type = checkPtr(sub_inf->UsrType); if (UNLIKELY(group_type == NULL)) goto err_free; if (strcmp(group_type, "cluster/parameter") == 0) { - if (check_neg(xaAddItem(¶m_infs, sub_inf) < 0)) + if (checkNeg(xaAddItem(¶m_infs, sub_inf) < 0)) goto err_free; } else if (strcmp(group_type, "cluster/cluster") == 0) { - if (check_neg(xaAddItem(&cluster_infs, sub_inf) < 0)) + if (checkNeg(xaAddItem(&cluster_infs, sub_inf) < 0)) goto err_free; } else if (strcmp(group_type, "cluster/search") == 0) { - if (check_neg(xaAddItem(&search_infs, sub_inf) < 0)) + if (checkNeg(xaAddItem(&search_infs, sub_inf) < 0)) goto err_free; } else @@ -1680,7 +1681,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Allocate space to store params. **/ node_data->nParams = param_infs.nItems; const size_t params_size = node_data->nParams * sizeof(pParam); - node_data->Params = check_ptr(nmSysMalloc(params_size)); + node_data->Params = checkPtr(nmSysMalloc(params_size)); if (node_data->Params == NULL) goto err_free; memset(node_data->Params, 0, params_size); @@ -1701,7 +1702,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Check each provided param to see if the user provided value. **/ for (unsigned int j = 0u; j < num_provided_params; j++) { - pStruct provided_param = check_ptr(provided_params[j]); + pStruct provided_param = checkPtr(provided_params[j]); if (UNLIKELY(provided_param == NULL)) goto err_free; /** If this provided param value isn't for the param, ignore it. **/ @@ -1744,7 +1745,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Iterate over provided parameters and warn the user if they specified a parameter that does not exist. **/ for (unsigned int i = 0u; i < num_provided_params; i++) { - pStruct provided_param = check_ptr(provided_params[i]); + pStruct provided_param = checkPtr(provided_params[i]); if (UNLIKELY(provided_param == NULL)) goto err_free; char* provided_name = provided_param->Name; @@ -1755,7 +1756,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** This param doesn't exist, warn the user and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, objFileName(parent)); - char** param_names = check_ptr(nmSysMalloc(node_data->nParams * sizeof(char*))); + char** param_names = checkPtr(nmSysMalloc(node_data->nParams * sizeof(char*))); if (UNLIKELY(param_names == NULL)) goto err_free; for (unsigned int j = 0u; j < node_data->nParams; j++) param_names[j] = node_data->Params[j]->Name; @@ -1774,7 +1775,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (LIKELY(node_data->nClusterDatas > 0)) { const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); - node_data->ClusterDatas = check_ptr(nmSysMalloc(clusters_size)); + node_data->ClusterDatas = checkPtr(nmSysMalloc(clusters_size)); if (UNLIKELY(node_data->ClusterDatas == NULL)) goto err_free; memset(node_data->ClusterDatas, 0, clusters_size); for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) @@ -1792,7 +1793,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (LIKELY(node_data->nSearchDatas > 0)) { const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); - node_data->SearchDatas = check_ptr(nmSysMalloc(searches_size)); + node_data->SearchDatas = checkPtr(nmSysMalloc(searches_size)); if (UNLIKELY(node_data->SearchDatas == NULL)) goto err_free; memset(node_data->SearchDatas, 0, searches_size); for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) @@ -2278,7 +2279,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) XArray vector_xarray = {0}; /** Guard segfaults. **/ - if (UNLIKELY(check_ptr(source_data) == NULL)) goto end_free; + if (UNLIKELY(checkPtr(source_data) == NULL)) goto end_free; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** If the vectors are already computed, we're done. **/ @@ -2404,13 +2405,13 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Store values. **/ - char* key_dup = check_ptr(nmSysStrdup(key)); + char* key_dup = checkPtr(nmSysStrdup(key)); if (key_dup == NULL) goto end_free; - char* data_dup = check_ptr(nmSysStrdup(data)); + char* data_dup = checkPtr(nmSysStrdup(data)); if (data_dup == NULL) goto end_free; - if (check_neg(xaAddItem(&key_xarray, (void*)key_dup) < 0)) goto end_free; - if (check_neg(xaAddItem(&data_xarray, (void*)data_dup) < 0)) goto end_free; - if (check_neg(xaAddItem(&vector_xarray, (void*)vector) < 0)) goto end_free; + if (checkNeg(xaAddItem(&key_xarray, (void*)key_dup) < 0)) goto end_free; + if (checkNeg(xaAddItem(&data_xarray, (void*)data_dup) < 0)) goto end_free; + if (checkNeg(xaAddItem(&vector_xarray, (void*)vector) < 0)) goto end_free; /** Clean up. **/ check(objClose(entry)); /* Failure ignored. */ @@ -2424,19 +2425,19 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) } /** Trim and store keys. **/ - source_data->Keys = (char**)check_ptr(xaToArray(&key_xarray)); + source_data->Keys = (char**)checkPtr(xaToArray(&key_xarray)); if (UNLIKELY(source_data->Keys == NULL)) goto end_free; check(xaDeInit(&key_xarray)); /* Failure ignored. */ key_xarray.nAlloc = 0; /** Trim and store data strings. **/ - source_data->Strings = (char**)check_ptr(xaToArray(&data_xarray)); + source_data->Strings = (char**)checkPtr(xaToArray(&data_xarray)); if (UNLIKELY(source_data->Strings == NULL)) goto end_free; check(xaDeInit(&data_xarray)); /* Failure ignored. */ data_xarray.nAlloc = 0; /** Trim and store vectors. **/ - source_data->Vectors = (int**)check_ptr(xaToArray(&vector_xarray)); + source_data->Vectors = (int**)checkPtr(xaToArray(&vector_xarray)); if (UNLIKELY(source_data->Vectors == NULL)) goto end_free; check(xaDeInit(&vector_xarray)); /* Failure ignored. */ vector_xarray.nAlloc = 0; @@ -2535,7 +2536,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) size_t sims_size = -1; /** Guard segfaults. **/ - if (UNLIKELY(check_ptr(cluster_data) == NULL || check_ptr(node_data) == NULL)) goto err_free; + if (UNLIKELY(checkPtr(cluster_data) == NULL || checkPtr(node_data) == NULL)) goto err_free; ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -2543,7 +2544,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (LIKELY(cluster_data->Clusters != NULL)) return 0; /** Make source data available. **/ - pSourceData source_data = check_ptr(node_data->SourceData); + pSourceData source_data = checkPtr(node_data->SourceData); if (UNLIKELY(source_data == NULL)) { mssError(1, "Cluster", "Failed to get source data for cluster computation."); @@ -2552,7 +2553,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** We need the SourceData vectors to compute clusters. **/ - pObjSession session = check_ptr(node_data->ParamList->Session); + pObjSession session = checkPtr(node_data->ParamList->Session); if (UNLIKELY(session == NULL)) goto err_free; ASSERTMAGIC(session, MGK_OBJSESSION); if (UNLIKELY(ci_ComputeSourceData(source_data, session) != 0)) @@ -2568,8 +2569,8 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate static memory for finding clusters. **/ clusters_size = cluster_data->nClusters * sizeof(Cluster); sims_size = source_data->nDatas * sizeof(double); - cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); - cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); + cluster_data->Clusters = checkPtr(nmSysMalloc(clusters_size)); + cluster_data->Sims = checkPtr(nmSysMalloc(sims_size)); if (UNLIKELY(cluster_data->Clusters == NULL)) goto err_free; if (UNLIKELY(cluster_data->Sims == NULL)) goto err_free; memset(cluster_data->Clusters, 0, clusters_size); @@ -2589,7 +2590,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Add all data points to that cluster. **/ only_cluster->Size = source_data->nDatas; - only_cluster->Indexes = check_ptr(nmSysMalloc(only_cluster->Size * sizeof(int))); + only_cluster->Indexes = checkPtr(nmSysMalloc(only_cluster->Size * sizeof(int))); if (UNLIKELY(only_cluster->Indexes == NULL)) goto err_free; for (unsigned int i = 0u; i < only_cluster->Size; i++) only_cluster->Indexes[i] = i; @@ -2616,7 +2617,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate labels. Note: ca_kmeans() initializes labels for us. **/ const size_t labels_size = source_data->nDatas * sizeof(unsigned int); - unsigned int* labels = check_ptr(nmSysMalloc(labels_size)); + unsigned int* labels = checkPtr(nmSysMalloc(labels_size)); if (UNLIKELY(labels == NULL)) goto err_free; /** Handle seed for ca_kmeans(). **/ @@ -2645,7 +2646,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Iterate through each label and add the index of the specified cluster to the xArray. **/ for (unsigned long long i = 0llu; i < source_data->nDatas; i++) - if (check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; + if (checkNeg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; nmSysFree(labels); /* Free unused data. */ /** Store the indices for each cluster and free the temporary xArray. **/ @@ -2662,7 +2663,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) cluster->Indexes = NULL; continue; } - cluster->Indexes = check_ptr(nmSysMalloc(cluster->Size * sizeof(unsigned int*))); + cluster->Indexes = checkPtr(nmSysMalloc(cluster->Size * sizeof(unsigned int*))); if (UNLIKELY(cluster->Indexes == NULL)) goto err_free; for (unsigned int i = 0u; i < indexes_in_this_cluster->nItems; i++) { @@ -2753,7 +2754,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) pXArray pairs = NULL; /** Guard segfaults. **/ - if (UNLIKELY(check_ptr(search_data) == NULL || check_ptr(node_data) == NULL)) goto err_free; + if (UNLIKELY(checkPtr(search_data) == NULL || checkPtr(node_data) == NULL)) goto err_free; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -2761,7 +2762,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (LIKELY(search_data->Pairs != NULL)) return 0; /** We need the cluster data to be computed before we search it. **/ - pClusterData cluster_data = check_ptr(search_data->SourceCluster); + pClusterData cluster_data = checkPtr(search_data->SourceCluster); if (UNLIKELY(cluster_data == NULL)) { mssError(1, "Cluster", "Failed to get cluster data for search computation."); @@ -2782,7 +2783,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (check(objCurrentDate(&search_data->DateComputed)) != 0) goto err_free; /** Get the comparison function based on the similarity measure. **/ - double (*similarity_function)(void *, void *) = check_ptr(ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure)); + double (*similarity_function)(void *, void *) = checkPtr(ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure)); if (UNLIKELY(similarity_function == NULL)) goto err_free; /** Execute the search using the specified algorithm. **/ @@ -2807,7 +2808,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } /** Execute sliding search. **/ - pairs = check_ptr(ca_sliding_search( + pairs = checkPtr(ca_sliding_search( data, source_data->nDatas, cluster_data->WindowSize, @@ -2828,7 +2829,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { /** Initialize the pairs array with a size of double the amount of data. **/ const int guess_size = search_data->SourceCluster->SourceData->nDatas * 2; - pairs = check_ptr(xaNew(guess_size)); + pairs = checkPtr(xaNew(guess_size)); if (UNLIKELY(pairs == NULL)) goto err_free; /** Iterate over each cluster. **/ @@ -2858,7 +2859,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (cluster_data->nClusters > 1) { /** Allocate space. **/ - filtered_data = check_ptr(nmSysMalloc(cluster->Size * sizeof(void*))); + filtered_data = checkPtr(nmSysMalloc(cluster->Size * sizeof(void*))); if (filtered_data == NULL) goto err_free; free_filtered_data = true; @@ -2868,7 +2869,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) } /** Execute complete search. **/ - const pXArray cluster_pairs = check_ptr(ca_complete_search( + const pXArray cluster_pairs = checkPtr(ca_complete_search( filtered_data, cluster->Size, similarity_function, @@ -2891,7 +2892,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) const pPair pair = (pPair)cluster_pairs->Items[i]; pair->i = cluster->Indexes[pair->i]; pair->j = cluster->Indexes[pair->j]; - if (check_neg(xaAddItem(pairs, pair)) < 0) goto err_free; + if (checkNeg(xaAddItem(pairs, pair)) < 0) goto err_free; } check(xaFree(cluster_pairs)); /* Failure ignored. */ } @@ -2901,12 +2902,12 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) search_data->nPairs = pairs->nItems; if (pairs->nItems == 0) { - search_data->Pairs = check_ptr(nmSysMalloc(0)); + search_data->Pairs = checkPtr(nmSysMalloc(0)); if (search_data->Pairs == NULL) goto err_free; } else { - search_data->Pairs = (pPair*)check_ptr(xaToArray(pairs)); + search_data->Pairs = (pPair*)checkPtr(xaToArray(pairs)); if (search_data->Pairs == NULL) goto err_free; check(xaFree(pairs)); /* Failure ignored. */ pairs = NULL; @@ -2952,14 +2953,14 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) static int ci_GetParamType(void* inf_v, const char* attr_name) { - pNodeData node_data = check_ptr(inf_v); + pNodeData node_data = checkPtr(inf_v); if (UNLIKELY(node_data == NULL)) return DATA_T_UNAVAILABLE; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Find the parameter. **/ for (unsigned int i = 0; i < node_data->nParams; i++) { - const pParam param = check_ptr(node_data->Params[i]); + const pParam param = checkPtr(node_data->Params[i]); if (UNLIKELY(param == NULL)) continue; if (strcmp(param->Name, attr_name) != 0) continue; @@ -2998,7 +2999,7 @@ ci_GetParamType(void* inf_v, const char* attr_name) static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - pNodeData node_data = check_ptr(inf_v); + pNodeData node_data = checkPtr(inf_v); if (UNLIKELY(node_data == NULL)) goto err; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -3121,7 +3122,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Allocate driver instance data. **/ - driver_data = check_ptr(nmMalloc(sizeof(DriverData))); + driver_data = checkPtr(nmMalloc(sizeof(DriverData))); if (UNLIKELY(driver_data == NULL)) goto err_free; memset(driver_data, 0, sizeof(DriverData)); SETMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -3254,7 +3255,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb int clusterClose(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) return -1; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -3262,7 +3263,7 @@ clusterClose(void* inf_v, pObjTrxTree* oxt) ClusterStatistics.CloseCalls++; /** Unlink the driver's node data. **/ - pNodeData node_data = check_ptr(driver_data->NodeData); /* Failure ignored. */ + pNodeData node_data = checkPtr(driver_data->NodeData); /* Failure ignored. */ ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); if (UNLIKELY(node_data != NULL && --node_data->OpenCount == 0)) ci_FreeNodeData(driver_data->NodeData); @@ -3292,7 +3293,7 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) pQueryData query_data = NULL; /** Get driver data. **/ - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (driver_data == NULL) goto err_free; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -3308,7 +3309,7 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) ClusterStatistics.OpenQueryCalls++; /** Allocate memory for the query. **/ - query_data = check_ptr(nmMalloc(sizeof(ClusterQuery))); + query_data = checkPtr(nmMalloc(sizeof(ClusterQuery))); if (UNLIKELY(query_data == NULL)) goto err_free; /** Initialize the query. **/ @@ -3346,13 +3347,13 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) pDriverData result_data = NULL; /** Unpack data into local variables. **/ - pQueryData query_data = check_ptr(qy_v); + pQueryData query_data = checkPtr(qy_v); if (UNLIKELY(query_data == NULL)) goto err_free; ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); - pDriverData driver_data = check_ptr(query_data->DriverData); + pDriverData driver_data = checkPtr(query_data->DriverData); if (UNLIKELY(driver_data == NULL)) goto err_free; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); - pNodeData node_data = check_ptr(driver_data->NodeData); + pNodeData node_data = checkPtr(driver_data->NodeData); if (UNLIKELY(node_data == NULL)) goto err_free; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -3360,7 +3361,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) ClusterStatistics.FetchCalls++; /** Allocate result struct. **/ - result_data = check_ptr(nmMalloc(sizeof(DriverData))); + result_data = checkPtr(nmMalloc(sizeof(DriverData))); if (UNLIKELY(result_data == NULL)) goto err_free; /** Default initialization. **/ @@ -3409,7 +3410,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_CLUSTER: { /** Ensure the required data is computed. **/ - pClusterData target = (pClusterData)check_ptr(driver_data->TargetData); + pClusterData target = (pClusterData)checkPtr(driver_data->TargetData); if (UNLIKELY(target == NULL)) goto err_free; ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (UNLIKELY(ci_ComputeClusterData(target, node_data) != 0)) @@ -3431,7 +3432,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_SEARCH: { /** Ensure the required data is computed. **/ - pSearchData target = (pSearchData)check_ptr(driver_data->TargetData); + pSearchData target = (pSearchData)checkPtr(driver_data->TargetData); if (UNLIKELY(target == NULL)) goto err_free; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (UNLIKELY(ci_ComputeSearchData(target, node_data) != 0)) @@ -3489,7 +3490,7 @@ clusterQueryClose(void* qy_v, pObjTrxTree* oxt) { /** Cast the query data. **/ pQueryData query_data = qy_v; - if (UNLIKELY(check_ptr(qy_v) == NULL)) return -1; + if (UNLIKELY(checkPtr(qy_v) == NULL)) return -1; ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); /** Free the query data. **/ @@ -3514,7 +3515,7 @@ int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { /** Extract target type from driver data. **/ - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const TargetType target_type = driver_data->TargetType; @@ -3634,7 +3635,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO TargetType target_type = -1; /** Extract target type from driver data. **/ - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); target_type = driver_data->TargetType; @@ -3674,7 +3675,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { case TARGET_NODE: { - pSourceData source_data = check_ptr(driver_data->TargetData); + pSourceData source_data = checkPtr(driver_data->TargetData); if (source_data == NULL) goto err; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); val->String = source_data->Name; @@ -3684,7 +3685,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: { - pClusterData cluster_data = check_ptr(driver_data->TargetData); + pClusterData cluster_data = checkPtr(driver_data->TargetData); if (cluster_data == NULL) goto err; ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); val->String = cluster_data->Name; @@ -3694,7 +3695,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: { - pSearchData search_data = check_ptr(driver_data->TargetData); + pSearchData search_data = checkPtr(driver_data->TargetData); if (search_data == NULL) goto err; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); val->String = search_data->Name; @@ -3779,7 +3780,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: { - pClusterData cluster_data = check_ptr(driver_data->TargetData); + pClusterData cluster_data = checkPtr(driver_data->TargetData); if (cluster_data == NULL) goto err; ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); if (cluster_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ @@ -3790,7 +3791,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: { - pSearchData search_data = check_ptr(driver_data->TargetData); + pSearchData search_data = checkPtr(driver_data->TargetData); if (search_data == NULL) goto err; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); if (search_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ @@ -3814,7 +3815,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: { - pClusterData target = check_ptr((pClusterData)driver_data->TargetData); + pClusterData target = checkPtr((pClusterData)driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ @@ -3825,7 +3826,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: { - pSearchData target = check_ptr((pSearchData)driver_data->TargetData); + pSearchData target = checkPtr((pSearchData)driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ @@ -3845,7 +3846,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { case TARGET_NODE: { - pSourceData source_data = check_ptr(driver_data->TargetData); + pSourceData source_data = checkPtr(driver_data->TargetData); if (source_data == NULL) goto err; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); @@ -3869,7 +3870,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: { - pClusterData target = check_ptr(driver_data->TargetData); + pClusterData target = checkPtr(driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); @@ -3912,7 +3913,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: { - pSearchData target = check_ptr(driver_data->TargetData); + pSearchData target = checkPtr(driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); @@ -3935,7 +3936,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER_ENTRY: { - pClusterData target = check_ptr(driver_data->TargetData); + pClusterData target = checkPtr(driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; @@ -3952,11 +3953,11 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } /** Allocate and initialize the requested data. **/ - vec = val->StringVec = check_ptr(nmMalloc(sizeof(StringVec))); + vec = val->StringVec = checkPtr(nmMalloc(sizeof(StringVec))); if (vec == NULL) goto err; memset(vec, 0, sizeof(StringVec)); vec->nStrings = target_cluster->Size; - vec->Strings = check_ptr(nmSysMalloc(target_cluster->Size * sizeof(char*))); + vec->Strings = checkPtr(nmSysMalloc(target_cluster->Size * sizeof(char*))); if (vec->Strings == NULL) goto err; for (unsigned int i = 0u; i < target_cluster->Size; i++) vec->Strings[i] = target->SourceData->Strings[target_cluster->Indexes[i]]; @@ -3969,10 +3970,10 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH_ENTRY: { - pSearchData target = check_ptr(driver_data->TargetData); + pSearchData target = checkPtr(driver_data->TargetData); if (target == NULL) goto err; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - pPair target_dup = check_ptr(target->Pairs[driver_data->TargetIndex]); + pPair target_dup = checkPtr(target->Pairs[driver_data->TargetIndex]); if (target_dup == NULL) goto err; if (strcmp(attr_name, "sim") == 0) @@ -4038,13 +4039,13 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) pParamObjects tmp_list = NULL; /** Extract target type from driver data. **/ - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) goto err_free; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const TargetType target_type = driver_data->TargetType; /** Malloc presentation hints struct. **/ - hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); + hints = checkPtr(nmMalloc(sizeof(ObjPresentationHints))); if (UNLIKELY(hints == NULL)) goto err_free; memset(hints, 0, sizeof(ObjPresentationHints)); @@ -4055,7 +4056,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) hints->StyleMask |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; /** Temporary param list for compiling expressions. **/ - tmp_list = check_ptr(expCreateParamList()); + tmp_list = checkPtr(expCreateParamList()); if (UNLIKELY(hints == NULL)) goto err_free; /** Search for the requested attribute through attributes common to all instances. **/ @@ -4083,14 +4084,14 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "internal_type") == 0) { check(xaInit(&(hints->EnumList), 5)); /* Failure ignored. */ - check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ - check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ - check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ - check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ - check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("search/entry")))); /* Failure ignored. */ + checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ + checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ + checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ + checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ + checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("search/entry")))); /* Failure ignored. */ hints->Length = 16; hints->VisualLength = 16; - hints->FriendlyName = check_ptr(nmSysStrdup("Internal Type")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Internal Type")); /* Failure ignored. */ hints->Style |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; hints->StyleMask |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; goto end; @@ -4107,7 +4108,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = 24; hints->VisualLength = 20; - hints->Format = check_ptr(nmSysStrdup("datetime")); /* Failure ignored. */ + hints->Format = checkPtr(nmSysStrdup("datetime")); /* Failure ignored. */ goto end; } else goto unknown_attribute; @@ -4121,21 +4122,21 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = _PC_PATH_MAX; hints->VisualLength = 64; - hints->FriendlyName = check_ptr(nmSysStrdup("Source Path")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Source Path")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "key_attr") == 0) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Key Attribute Name")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Key Attribute Name")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "data_attr") == 0) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Data Attribute Name")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Data Attribute Name")); /* Failure ignored. */ goto end; } break; @@ -4150,7 +4151,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - hints->FriendlyName = check_ptr(nmSysStrdup("Number of Clusters")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Number of Clusters")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "min_improvement") == 0) @@ -4163,7 +4164,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Minimum Improvement Threshold")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Minimum Improvement Threshold")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "max_iterations") == 0) @@ -4176,7 +4177,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - hints->FriendlyName = check_ptr(nmSysStrdup("Maximum Iterations")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Maximum Iterations")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "algorithm") == 0) @@ -4184,7 +4185,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) - check_neg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ + checkNeg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -4199,7 +4200,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 24; hints->VisualLength = 20; - hints->FriendlyName = check_ptr(nmSysStrdup("Clustering Algorithm")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Clustering Algorithm")); /* Failure ignored. */ goto end; } /** Fall-through: Start of overlapping region. **/ @@ -4210,7 +4211,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) - check_neg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ + checkNeg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; @@ -4225,7 +4226,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 32; hints->VisualLength = 20; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Measure")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Similarity Measure")); /* Failure ignored. */ goto end; } @@ -4236,7 +4237,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = 64; hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Source Cluster Name")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Source Cluster Name")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "threshold") == 0) @@ -4248,7 +4249,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Threshold")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Similarity Threshold")); /* Failure ignored. */ goto end; } break; @@ -4256,7 +4257,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) case TARGET_CLUSTER_ENTRY: { /** Unused. **/ - // pClusterData target = check_ptr(driver_data->TargetData); + // pClusterData target = checkPtr(driver_data->TargetData); // if (UNLIKELY(target == NULL)) goto err_free; // ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); @@ -4265,7 +4266,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 65536; hints->VisualLength = 256; - hints->FriendlyName = check_ptr(nmSysStrdup("Cluster Data")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Cluster Data")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "sim") == 0) @@ -4277,7 +4278,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Similarity")); /* Failure ignored. */ goto end; } break; @@ -4286,7 +4287,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) case TARGET_SEARCH_ENTRY: { /** Unused. **/ - // pSearchData target = check_ptr(driver_data->TargetData); + // pSearchData target = checkPtr(driver_data->TargetData); // if (UNLIKELY(target == NULL)) goto err_free; // ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); @@ -4294,14 +4295,14 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Key 1")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Key 1")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "key2") == 0) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = check_ptr(nmSysStrdup("Key 2")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Key 2")); /* Failure ignored. */ goto end; } if (strcmp(attr_name, "sim") == 0) @@ -4313,7 +4314,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ + hints->FriendlyName = checkPtr(nmSysStrdup("Similarity")); /* Failure ignored. */ goto end; } break; @@ -4362,7 +4363,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -4385,7 +4386,7 @@ clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -4417,10 +4418,10 @@ clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) int clusterInfo(void* inf_v, pObjectInfo info) { - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); - pNodeData node_data = check_ptr(driver_data->NodeData); + pNodeData node_data = checkPtr(driver_data->NodeData); if (UNLIKELY(node_data == NULL)) goto err; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -4459,7 +4460,7 @@ clusterInfo(void* inf_v, pObjectInfo info) case TARGET_SEARCH: { - pSearchData search_data = check_ptr(driver_data->TargetData); + pSearchData search_data = checkPtr(driver_data->TargetData); if (UNLIKELY(search_data == NULL)) goto err; info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; @@ -4510,7 +4511,7 @@ clusterInfo(void* inf_v, pObjectInfo info) char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -4533,7 +4534,7 @@ clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) return NULL; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -4620,7 +4621,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) /** Print the cache entry data. **/ char buf[12]; - snprint_bytes(buf, sizeof(buf), bytes); + snprintBytes(buf, sizeof(buf), bytes); printf("%-8s %-16s %-12s \"%s\"\n", type, name, buf, key); goto increment_total; @@ -4705,7 +4706,7 @@ ci_CacheFreeSearch(pXHashEntry entry, void* path) int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) { - pDriverData driver_data = check_ptr(inf_v); + pDriverData driver_data = checkPtr(inf_v); if (UNLIKELY(driver_data == NULL)) goto err; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); @@ -4782,10 +4783,10 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree char buf[16]; printf("\nCache Stats:\n"); printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); - printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprint_bytes(buf, sizeof(buf), source_bytes)); - printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprint_bytes(buf, sizeof(buf), cluster_bytes)); - printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprint_bytes(buf, sizeof(buf), search_bytes)); - printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); + printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprintBytes(buf, sizeof(buf), source_bytes)); + printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprintBytes(buf, sizeof(buf), cluster_bytes)); + printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprintBytes(buf, sizeof(buf), search_bytes)); + printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprintBytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); /** Print skip stats (if anything was skipped.) **/ if (skip_uncomputed > 0llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); @@ -4814,16 +4815,16 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree char buf[12]; printf("Cluster Driver Statistics:\n"); printf(" Stat Name %12s\n", "Value"); - printf(" OpenCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); - printf(" OpenQueryCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); - printf(" FetchCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); - printf(" CloseCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); - printf(" GetTypeCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); - printf(" GetValCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); - printf(" GetValCalls_name %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); - printf(" GetValCalls_key1 %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); - printf(" GetValCalls_key2 %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); - printf(" GetValCalls_sim %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); + printf(" OpenCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); + printf(" OpenQueryCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); + printf(" FetchCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); + printf(" CloseCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); + printf(" GetTypeCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); + printf(" GetValCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); + printf(" GetValCalls_name %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); + printf(" GetValCalls_key1 %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); + printf(" GetValCalls_key2 %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); + printf(" GetValCalls_sim %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); printf("\n"); nmStats(); @@ -4944,7 +4945,7 @@ int clusterInitialize(void) { /** Allocate the driver. **/ - pObjDriver drv = check_ptr(nmMalloc(sizeof(ObjDriver))); + pObjDriver drv = checkPtr(nmMalloc(sizeof(ObjDriver))); if (UNLIKELY(drv == NULL)) goto err_free; memset(drv, 0, sizeof(ObjDriver)); @@ -4955,9 +4956,9 @@ clusterInitialize(void) if (check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0)) != 0) goto err_free; /** Setup the structure. **/ - if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; + if (checkPtr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; if (check(xaInit(&drv->RootContentTypes, 1)) != 0) goto err_free; - if (check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster")) < 0) goto err_free; + if (checkNeg(xaAddItem(&drv->RootContentTypes, "system/cluster")) < 0) goto err_free; drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index b0ed45cbe..b3bfce4ab 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -104,9 +104,9 @@ #include #include +#include "cxlib/check.h" #include "cxlib/newmalloc.h" #include "cxlib/strtcpy.h" -#include "cxlib/util.h" #include "cxlib/expect.h" typedef struct @@ -129,7 +129,7 @@ meta_new_string(const char* init_str) pMetaString s; char empty_string[] = ""; - s = (pMetaString)check_ptr(nmSysMalloc(sizeof(MetaString))); + s = (pMetaString)checkPtr(nmSysMalloc(sizeof(MetaString))); if (UNLIKELY(s == NULL)) goto err_free; if (init_str == NULL) @@ -139,7 +139,7 @@ meta_new_string(const char* init_str) /** Preallocate a bit more for potential growth. **/ s->bufsize = s->length + 7u; - s->str = (char*)check_ptr(nmSysMalloc(s->bufsize * sizeof(char))); + s->str = (char*)checkPtr(nmSysMalloc(s->bufsize * sizeof(char))); if (UNLIKELY(s->str == NULL)) goto err_free; strtcpy(s->str, init_str, s->bufsize); @@ -185,7 +185,7 @@ int meta_increase_buffer(pMetaString s, const size_t chars_needed) { s->bufsize += chars_needed + 8u; - s->str = check_ptr(nmSysRealloc(s->str, s->bufsize * sizeof(char))); + s->str = checkPtr(nmSysRealloc(s->str, s->bufsize * sizeof(char))); if (UNLIKELY(s->str == NULL)) return -1; return 0; @@ -373,14 +373,14 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod const unsigned int last = (unsigned int)(length - 1); /** Pad original so we can index beyond end. **/ - pMetaString original = check_ptr(meta_new_string(str)); + pMetaString original = checkPtr(meta_new_string(str)); if (UNLIKELY(original == NULL)) goto end_free; meta_make_upper(original); if (check(meta_add_str(original, " ")) != 0) goto end_free; /** Allocate the primary and secondary output strings. **/ - pMetaString primary = check_ptr(meta_new_string("")); - pMetaString secondary = check_ptr(meta_new_string("")); + pMetaString primary = checkPtr(meta_new_string("")); + pMetaString secondary = checkPtr(meta_new_string("")); if (UNLIKELY(primary == NULL || secondary == NULL)) goto end_free; /** Skip these if they are at start of a word. **/ From e4fc6262ef011a7eeab91b37c2048ac960bfaafb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 3 Sep 2026 16:50:57 -0600 Subject: [PATCH 119/361] Add calls to strtcatf() for safer printing. --- centrallix-lib/src/mtsession.c | 9 +++++---- centrallix/expression/exp_functions.c | 8 +++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index 14709f5c0..b300598f9 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -475,21 +475,22 @@ void mssError_internal(int clr, char* module, char* file, int line, char* message, ...) { char err_msg[BUFSIZ]; - unsigned int i = 0u; + size_t i = 0; /** Prevent issues from interlacing this function with prints to stdout. **/ check(fflush(stdout)); /* Failure ignored. */ /** Add line number to error message. **/ - i += snprintf(err_msg + i, sizeof(err_msg) - i, "%s:%d: ", file, line); + err_msg[0] = '\0'; + strtcatf(err_msg, sizeof(err_msg), &i, "%s:%d: ", file, line); /** Write the module to the start of the error message. */ - i += snprintf(err_msg + i, sizeof(err_msg) - i, "%s: ", module); + strtcatf(err_msg, sizeof(err_msg), &i, "%s: ", module); /** Process the message format with all the same rules as printf(). **/ va_list args; va_start(args, message); - i += vsnprintf(err_msg + i, sizeof(err_msg) - i, message, args); + strtcatf_va(err_msg, sizeof(err_msg), &i, message, args); va_end(args); /** Get current session **/ diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 1896103d0..72822efe2 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -70,6 +70,7 @@ #include "cxlib/mtsession.h" #include "cxlib/newmalloc.h" #include "cxlib/range.h" +#include "cxlib/strtcpy.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "cxss/cxss.h" @@ -190,14 +191,15 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e { /** Accumulate additional valid types. **/ char buf[256] = {'\0'}; - int cur = 0, j = 1; + size_t cur = 0; + int j = 1; while (true) { int datatype = arg_expect->Datatypes[j++]; if (datatype == -1) break; - cur += snprintf( - buf + cur, 256 - cur, + strtcatf( + buf, sizeof(buf), &cur, " or %s (%d)", objTypeToStr(datatype), datatype ); From b5bbcfa7171acfbcb0db84c63c5f1cfb85898359 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:04:15 -0600 Subject: [PATCH 120/361] Rename a parameter. --- centrallix-lib/src/clusters.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index f4e90cdc8..bd9284e4a 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -451,13 +451,13 @@ ca_sparse_similarity(const pVector v1, const pVector v2) *** allocated centroid by taking their dot product. *** *** @param v1 Sparse vector #1. - *** @param c1 Dense centroid #2. + *** @param c1 Dense centroid #1. *** @returns Similarity between 0 and 1 where *** 1 indicates identical and *** 0 indicates completely different. ***/ static double -ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) +ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c1) { double dot_product = 0.0; @@ -468,14 +468,14 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) ca_parse_vector_token(v1[i++], &dims_consumed, &val); /** Increase dot product (skipped for zero-values). **/ - if (val > 0u) dot_product += (double)val * c2[dim]; + if (val > 0u) dot_product += (double)val * c1[dim]; /** Move ahead the requested number of dimensions. **/ dim += dims_consumed; } /** Return the difference score. **/ - return dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_dense(c2)); + return dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_dense(c1)); } /*** Calculate the difference between a sparsely allocated vector and a densely @@ -487,7 +487,7 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) *** 1 indicates completely different and *** 0 indicates identical. ***/ -#define ca_sparse_dif_to_centroid(v1, c2) (1.0 - ca_sparse_similarity_to_centroid(v1, c2)) +#define ca_sparse_dif_to_centroid(v1, c1) (1.0 - ca_sparse_similarity_to_centroid(v1, c1)) /*** Computes Levenshtein distance between two strings. *** From de84bb9432ba054d321ed4fe28cfb39df485709d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:06:12 -0600 Subject: [PATCH 121/361] Add an optimization for the edit distance between identical string pointers. --- centrallix-lib/src/clusters.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index bd9284e4a..24f60a671 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -506,6 +506,9 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const { int result = -1; unsigned int** lev_matrix = NULL; + + /** Optimization: Identical string pointers. **/ + if (str1 == str2) return 0; /*** lev_matrix: *** For all i and j, d[i][j] will hold the Levenshtein distance between From 12b0f2165593d0c08094cd589577bc88c47413be Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:08:54 -0600 Subject: [PATCH 122/361] Handle error cases. --- centrallix-lib/src/clusters.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 24f60a671..8cd6748a8 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -994,6 +994,15 @@ ca_most_similar( void* most_similar = NULL; double best_sim = -INFINITY; + /** Error cases. **/ + if (target == NULL + || data == NULL + || similarity == NULL + || num_data == 0u + || (threshold < 0.0 || 1.0 < threshold) + || isnan(threshold) + ) return NULL; + /** Iterate over all data options to find the one with the highest similarity. **/ for (unsigned int i = 0u; (num_data == 0u && data[i] != NULL) || (i < num_data); i++) { @@ -1036,6 +1045,15 @@ ca_sliding_search( { pXArray pairs = maybe_pairs; + /** Error cases. **/ + if (data == NULL + || num_data == 0 + || window_size == 0 + || similarity == NULL + || (threshold < 0.0 || 1.0 < threshold) + || isnan(threshold) + ) goto err; + /** Allocate space for pairs (if it is not provided). **/ if (pairs == NULL) { From 89e15f3a37a7eee0a4dc8bcce8098b6d68b0251d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:14:58 -0600 Subject: [PATCH 123/361] Update and fix test driver copyright statement. --- centrallix-lib/tests/t_driver.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 20c0091c3..9b686e5a6 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -13,16 +13,16 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* Copyright (C) 2005-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: test_00baseline.c */ +/* Module: test_00baseline.c */ /* Author: Greg Beeley (GRB) */ -/* Creation: March 11th, 2005 */ -/* Description: Test suite entry to generate a baseline comparison value*/ +/* Creation: March 11th, 2005 */ +/* Description: Test suite driver for centrallix-lib tests. */ /************************************************************************/ From 391781ba5471f8806fb380e1ee5c2623baad6f13 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:15:07 -0600 Subject: [PATCH 124/361] Move test driver includes. --- centrallix-lib/tests/t_driver.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 9b686e5a6..75e885577 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -1,14 +1,3 @@ -#include "cxlibconfig-internal.h" -#include "mtask.h" -#include -#include -#include -#include -#include -#include -#include -#include - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Base Library */ @@ -25,6 +14,18 @@ /* Description: Test suite driver for centrallix-lib tests. */ /************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cxlibconfig-internal.h" +#include "mtask.h" + long long test(char**); From f1dff7da5aff3f2bd167597f68ea1a2a2acedb08 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:15:41 -0600 Subject: [PATCH 125/361] Remove unused includes in the test driver. --- centrallix-lib/tests/t_driver.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 75e885577..62a7f3b97 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -14,12 +14,9 @@ /* Description: Test suite driver for centrallix-lib tests. */ /************************************************************************/ -#include #include -#include #include #include -#include #include #include From f4292f54f0f8463b77998d97f56b594b1674f6b0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:17:32 -0600 Subject: [PATCH 126/361] Adjust lockup timer to be shorter normally but much longer for Valgrind. --- centrallix-lib/tests/t_driver.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 62a7f3b97..583ffb912 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -57,7 +57,17 @@ start(void* v) signal(SIGSEGV, segv_handler); signal(SIGABRT, abort_handler); signal(SIGALRM, alarm_handler); - alarm(10); + + /*** Set a timer before Lockup is triggered, using a significantly + *** larger value if Valgrind appears to be enabled. + ***/ + #ifdef USING_VALGRIND + alarm(90); /* Valgrind detected. */ + #else + alarm(5); /* Normal timeout. */ + #endif + + times(&t); start = t.tms_utime + t.tms_stime + t.tms_cutime + t.tms_cstime; rval = test(&tname); From 6d483d4dc8359285c83eeee751ccf84cd0b819e1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:19:33 -0600 Subject: [PATCH 127/361] Improve test speed handling. Add a clearer error when tests run too fast. Handle slow tests with less than one operation per second. --- centrallix-lib/tests/t_driver.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 583ffb912..7cc0389b5 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -76,7 +76,18 @@ start(void* v) if (rval < 0) printf("%-62.62s FAIL\n", tname); else - printf("%-62.62s PASS %lld\n", tname, rval*100/(long long)(end - start)); + { + long long duration = end - start; + if (duration == 0) + { + printf("%-62.62s PASS ???\n", tname); + printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loop_tests() from test_utils.h.\n"); + return; + } + long long ops_per_second = rval * (100 / duration); + if (ops_per_second > 0) printf("%-62.62s PASS %lld\n", tname, ops_per_second); + else printf("%-62.62s PASS %.4lf\n", tname, rval * (100.0 / duration)); + } return; } From af16a4b746e354ceca545da3f27f8805c9f389a5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:21:19 -0600 Subject: [PATCH 128/361] Add spacing and comments. --- centrallix-lib/tests/t_driver.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 7cc0389b5..2233b76d9 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -34,12 +34,14 @@ segv_handler(int v) printf("%-62.62s CRASH\n", tname); exit(0); } + void abort_handler(int v) { printf("%-62.62s ABORT\n", tname); exit(0); } + void alarm_handler(int v) { @@ -54,10 +56,11 @@ start(void* v) clock_t start,end; long long rval; + /** Register handlers for signals that may occur during a test. **/ signal(SIGSEGV, segv_handler); signal(SIGABRT, abort_handler); signal(SIGALRM, alarm_handler); - + /*** Set a timer before Lockup is triggered, using a significantly *** larger value if Valgrind appears to be enabled. ***/ @@ -66,13 +69,15 @@ start(void* v) #else alarm(5); /* Normal timeout. */ #endif - - + + /** Run the test while tracking CPU time. **/ times(&t); start = t.tms_utime + t.tms_stime + t.tms_cutime + t.tms_cstime; rval = test(&tname); times(&t); end = t.tms_utime + t.tms_stime + t.tms_cutime + t.tms_cstime; + + /** Print test results. **/ if (rval < 0) printf("%-62.62s FAIL\n", tname); else @@ -98,4 +103,3 @@ main(int argc, char* argv[]) mtInitialize(0, start); return 0; } - From e5e402bef0d300340ebea3361c21a173e4978cf7 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 16:28:07 -0600 Subject: [PATCH 129/361] Add iterations to the memstr-00 test to allow it to be measured. --- centrallix-lib/tests/test_memstr_00.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_memstr_00.c b/centrallix-lib/tests/test_memstr_00.c index 38d13f938..13022b935 100755 --- a/centrallix-lib/tests/test_memstr_00.c +++ b/centrallix-lib/tests/test_memstr_00.c @@ -16,7 +16,7 @@ test(char** tname) char * ptr; *tname = "memstr-00 correct null ptr"; - iter = 4000; + iter = 64000; for(i=0;i Date: Fri, 4 Sep 2026 17:05:27 -0600 Subject: [PATCH 130/361] Add test_utils.h. --- centrallix-lib/tests/test_utils.h | 150 ++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 centrallix-lib/tests/test_utils.h diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h new file mode 100644 index 000000000..358452bd8 --- /dev/null +++ b/centrallix-lib/tests/test_utils.h @@ -0,0 +1,150 @@ +#ifndef TEST_UTILITY_H +#define TEST_UTILITY_H + +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_utils.h */ +/* Author: Israel Fuller */ +/* Creation: November 24th, 2025 */ +/* Description: Useful utils to improve the developer experience when */ +/* testing centrallix-lib. */ +/************************************************************************/ + +#include +#include + +#include "timer.h" + +/*** The minimum number of seconds that a test can take. Tests that take less + *** time than this might not use enough cpu cycles, confusing the test suite + *** performance tracking. + ***/ +#define MIN_TEST_SECONDS 0.02 + +/*** Expect two values to be equal. + *** + *** @param v1 The first value. + *** @param v2 The second value. + *** @param sp The specifier to print the values if there is an error. This + *** MUST be a string known to the compiler at compile time, such as a + *** sting literal or a macro that expands to one. + *** @returns true if successful, false otherwise. + ***/ +#define EXPECT_EQL(v1, v2, sp) \ + ({ \ + __typeof__ (v1) _v1 = (v1); \ + __typeof__ (v2) _v2 = (v2); \ + int success = (_v1 == _v2); \ + if (!success) fprintf(stderr, \ + " > Expected %s ("sp") to equal %s ("sp") at %s:%d\n", \ + #v1, _v1, #v2, _v2, __FILE__, __LINE__ \ + ); \ + success; \ + }) + +/*** Expect two strings to be equal. + *** + *** @param str1 The first string. + *** @param str2 The second string. + *** @returns true if successful, false otherwise. + ***/ +#define EXPECT_STR_EQL(str1, str2) \ + ({ \ + char* _str1 = (str1); \ + char* _str2 = (str2); \ + int success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ + if (!success) fprintf(stderr, \ + " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", \ + #str1, _str1, #str2, _str2, __FILE__, __LINE__ \ + ); \ + success; \ + }) + +/*** Expect two cosine vectors from `cluster.c` to be equal. + *** + *** @param v1 The first vector. + *** @param v2 The second vector. + *** @returns true if successful, false otherwise. + ***/ +#define EXPECT_VEC_EQL(v1, v2) \ + ({ \ + pVector _v1 = (v1); \ + pVector _v2 = (v2); \ + int success = ca_eql(_v1, _v2); \ + if (!success) \ + { \ + printf(" > Expected %s (1.) to equal %s (2.) at %s:%d, but got:\n", #v1, #v2, __FILE__, __LINE__); \ + printf(" > 1. (%p) ", _v1); ca_print_vector(_v1); printf("\n"); \ + printf(" > 2. (%p) ", _v2); ca_print_vector(_v2); printf("\n"); \ + fflush(stdout); \ + } \ + success; \ + }) + +/*** Expect a value to fall within a range. + *** + *** @param v The value. + *** @param min_v The minimum acceptable value. + *** @param max_v The maximum acceptable value. + *** @param sp The specifier to print the values if there is an error. This + *** MUST be a string known to the compiler at compile time, such as a + *** sting literal or a macro that expands to one. + *** @returns true if successful, false otherwise. + ***/ +#define EXPECT_RANGE(v, min_v, max_v, sp) \ + ({ \ + __typeof__ (v) _v = (v); \ + __typeof__ (min_v) _min = (min_v); \ + __typeof__ (max_v) _max = (max_v); \ + int success = (_min <= _v && _v <= _max); \ + if (!success) fprintf(stderr, \ + " > Expected %s ("sp") to be in the range %s ("sp") - %s ("sp") at %s:%d\n", \ + #v, _v, #min_v, _min, #max_v, _max, __FILE__, __LINE__ \ + ); \ + success; \ + }) + +/*** Syntactic sugar to expect a pointer to be non null in a clearer, more + *** concise way. + *** + *** @param ptr The pointer. + *** @returns true if successful, false otherwise. + ***/ +#define EXPECT_NOT_NULL(ptr) \ + ({ \ + __typeof__ (ptr) _ptr = (ptr); \ + int success = (_ptr != NULL); \ + if (!success) fprintf(stderr, \ + " > Expected %s (%p) to be non null at %s:%d\n", \ + #ptr, _ptr, __FILE__, __LINE__ \ + ); \ + success; \ + }) + +/** Repeat the test as many times as possible within a set time window. **/ +#define loop_tests(test_fn) \ + ({ \ + long long result = 0ll; \ + Timer iter_timer_buf, *iter_timer = timerStart(timerInit(&iter_timer_buf)); \ + while (timerGet(iter_timer) < MIN_TEST_SECONDS) \ + { \ + result++; \ + if (!test_fn()) \ + { \ + result = -1; \ + break; \ + } \ + } \ + timerDeInit(iter_timer); \ + result; \ + }) + +#endif From 0091e72ef59c592256fc8c7c52e3de2f3220b6dc Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:05:35 -0600 Subject: [PATCH 131/361] Add tests for newmalloc. --- centrallix-lib/tests/test_newmalloc_00.c | 190 +++++++++++++++++++++++ centrallix-lib/tests/test_newmalloc_01.c | 161 +++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 centrallix-lib/tests/test_newmalloc_00.c create mode 100644 centrallix-lib/tests/test_newmalloc_01.c diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c new file mode 100644 index 000000000..b2f91ba6f --- /dev/null +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -0,0 +1,190 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_newmalloc_00.c */ +/* Author: Israel Fuller */ +/* Creation: November 25th, 2025 */ +/* Description: Test the nmSysMalloc(), nmSysFree(), nmSysRealloc(), */ +/* and nmSysStrDup functions from the NewMalloc library. */ +/************************************************************************/ + +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" +#include "range.h" + +/** Tested module. **/ +#include "newmalloc.h" + +static unsigned int seed_counter = 0; +static char* err_buf; +static unsigned int err_buf_i; +static unsigned int err_buf_size; + +static int mock_error_fn(char* error_msg) + { + const size_t len = strlen(error_msg) + 1lu; + + /** Ensure enough space to store the error. **/ + while (len > err_buf_size - err_buf_i) + { + err_buf_size *= 2; + err_buf = checkPtr(realloc(err_buf, err_buf_size)); + } + + err_buf_i += snprintf( + err_buf + err_buf_i, + err_buf_size - err_buf_i, + "> %s\n", error_msg + ); + + return 0; + } + +/** Initialize memory of a given size with random data. **/ +static void* random_init(void* ptr, size_t size) + { + if (ptr == NULL) return NULL; + unsigned char* p = (unsigned char*)ptr; + for (size_t i = 0; i < size; i++) { + p[i] = (unsigned char)(rand() % 256); + } + return ptr; + } + +static bool do_tests(void) + { + bool success = true; + + /** Set a consistent, distinct seed for each test iteration. **/ + srand(seed_counter++); + + /** Initialize the mock error function. **/ + err_buf = checkPtr(malloc(err_buf_size = 256)); + err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); + nmSetErrFunction(mock_error_fn); + + /** Baseline: Should leak. **/ + success &= EXPECT_NOT_NULL(nmSysMalloc(42)); + + /** Basic string data. **/ + char* str1; + success &= EXPECT_NOT_NULL(str1 = nmSysMalloc(16)); + snprintf(str1, 16, "ThisIsSomeData!"); + char* str2; + success &= EXPECT_NOT_NULL(str2 = nmSysMalloc(32)); + snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** 128 MB random data, varying sizes. **/ + #define TEST_LIMIT 16384 + void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + for (size_t i = 1lu; i < TEST_LIMIT; i++) + { + success &= EXPECT_NOT_NULL(test[i] = nmSysMalloc(i)); + data[i] = random_init(checkPtr(malloc(i)), i); + memcpy(test[i], data[i], i); /* Write test data into test memory. */ + } + for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) + success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); + + /** Basic string data is unharmed. **/ + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** Reallocate all variably sized memory to a different size. **/ + for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) + success &= EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i)); + for (size_t i = 1lu; i < TEST_LIMIT; i++) + success &= EXPECT_EQL(memcmp(data[i], test[i], min(i, TEST_LIMIT - i)), 0, "%d"); + + /** Basic string data is unharmed. **/ + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** Testing strdup. **/ + char* str_dup1; + char* str_dup2; + success &= EXPECT_NOT_NULL(str_dup1 = nmSysStrdup(str1)); + success &= EXPECT_NOT_NULL(str_dup2 = nmSysStrdup(str2)); + success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str_dup2, "ThisDataIsDifferentStringData.\n"); + str_dup1[12] = '\0'; + str_dup2[2] = 'a'; + str_dup2[3] = 't'; + success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeDa"); + success &= EXPECT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); + + /** Basic string data is unharmed. **/ + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** Free random data, varying sizes. **/ + for (size_t i = 1lu; i < TEST_LIMIT; i++) + { + free(data[i]); + nmSysFree(test[i]); + } + + /** Basic string data is unharmed. **/ + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** Free data. **/ + nmSysFree(str1); + nmSysFree(str2); + + /** Dup string data is unharmed. **/ + success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeDa"); + success &= EXPECT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); + + /** Large singular allocation. **/ + #define _256MB 256000000lu + void* large_buf; + success &= EXPECT_NOT_NULL(large_buf = nmSysMalloc(_256MB)); + for (size_t i = _256MB - 1lu; i > 0lu; i--) + *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); + *(unsigned char*)large_buf = 0u; + for (size_t i = 0lu; i < _256MB; i++) + success &= EXPECT_EQL(*((unsigned char*)large_buf + i), (unsigned char)(i % 255lu), "%d"); + + /** Dup string data is unharmed. **/ + success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeDa"); + success &= EXPECT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); + + /** Free dups. **/ + nmSysFree(str_dup1); + nmSysFree(str_dup2); + + /** Free large allocation. **/ + nmSysFree(large_buf); + + /** Expect no captured errors. **/ + success &= EXPECT_STR_EQL(err_buf, ""); + + return success; + } + +long long test(char** tname) + { + *tname = "newmalloc-00 nmSysMalloc(), nmSysFree(), nmSysRealloc(), & nmSysStrdup()"; + return loop_tests(do_tests); + } + +/** Scope cleanup. **/ +#undef TEST_LIMIT +#undef _256MB diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c new file mode 100644 index 000000000..0980ac781 --- /dev/null +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -0,0 +1,161 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_newmalloc_01.c */ +/* Author: Israel Fuller */ +/* Creation: December 15th, 2025 */ +/* Description: Test the nmMalloc(), nmFree(), and nmClear() functions */ +/* from the NewMalloc library. */ +/************************************************************************/ + +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "newmalloc.h" + +static unsigned int seed_counter = 0; +static char* err_buf; +static unsigned int err_buf_i; +static unsigned int err_buf_size; + +static int mock_error_fn(char* error_msg) + { + const size_t len = strlen(error_msg) + 1lu; + + /** Ensure enough space to store the error. **/ + while (len > err_buf_size - err_buf_i) + { + err_buf_size *= 2; + err_buf = checkPtr(realloc(err_buf, err_buf_size)); + } + + err_buf_i += snprintf( + err_buf + err_buf_i, + err_buf_size - err_buf_i, + "> %s\n", error_msg + ); + + return 0; + } + +/** Initialize memory of a given size with random data. **/ +static void* random_init(void* ptr, size_t size) + { + if (ptr == NULL) return NULL; + unsigned char* p = (unsigned char*)ptr; + for (size_t i = 0; i < size; i++) { + p[i] = (unsigned char)(rand() % 256); + } + return ptr; + } + +static bool do_tests(void) + { + bool success = true; + + /** Set a consistent, distinct seed for each test iteration. **/ + srand(seed_counter++); + + /** Initialize the mock error function. **/ + err_buf = checkPtr(malloc(err_buf_size = 256)); + err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); + nmSetErrFunction(mock_error_fn); + + /** Baseline: Should leak. **/ + success &= EXPECT_NOT_NULL(nmMalloc(42)); + + /** Basic string data. **/ + char* str1; + success &= EXPECT_NOT_NULL(str1 = nmMalloc(16)); + snprintf(str1, 16, "ThisIsSomeData!"); + char* str2; + success &= EXPECT_NOT_NULL(str2 = nmMalloc(32)); + snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** 128 MB random data, varying sizes. **/ + #define TEST_LIMIT 16384 + void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + for (size_t i = 1lu; i < TEST_LIMIT; i++) + { + success &= EXPECT_NOT_NULL(test[i] = nmMalloc(i)); + data[i] = random_init(checkPtr(malloc(i)), i); + memcpy(test[i], data[i], i); + } + for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) + success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); + + /** Basic string data is unharmed. **/ + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** Large singular allocation. **/ + #define _256MB 256000000lu + void* large_buf; + success &= EXPECT_NOT_NULL(large_buf = nmMalloc(_256MB)); + for (size_t i = _256MB - 1lu; i > 0lu; i--) + *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); + *(unsigned char*)large_buf = 0u; + for (size_t i = 0lu; i < _256MB; i++) + success &= EXPECT_EQL(*((unsigned char*)large_buf + i), (unsigned char)(i % 255lu), "%d"); + + /** Dup string data is unharmed. **/ + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** Free random data, varying sizes. **/ + for (size_t i = 1lu; i < TEST_LIMIT; i++) + { + free(data[i]); + nmFree(test[i], i); + } + + /** Basic string data is unharmed. **/ + success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); + success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + + /** Free data. **/ + nmFree(str1, 16); + nmFree(str2, 32); + + /** Free large allocation. **/ + nmFree(large_buf, _256MB); + + /** Clear cache. **/ + nmClear(); + + /** Debug info. **/ + printf("\n"); + nmStats(); + + /** Expect no captured errors. **/ + success &= EXPECT_STR_EQL(err_buf, ""); + + return success; + } + +long long test(char** tname) + { + *tname = "newmalloc-01 nmMalloc(), nmFree(), & nmClear()"; + return loop_tests(do_tests); + } + +/** Scope cleanup. **/ +#undef TEST_LIMIT +#undef _256MB From c1ee096dbb2b72a2cac3fa07fcaf227f75ec0134 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:06:31 -0600 Subject: [PATCH 132/361] Improve tests for nmStats(). --- centrallix-lib/tests/test_newmalloc_01.c | 25 +++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 0980ac781..ee6ad6e94 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -19,6 +19,7 @@ #include #include #include +#include /** Test dependencies. **/ #include "test_utils.h" @@ -140,9 +141,27 @@ static bool do_tests(void) /** Clear cache. **/ nmClear(); - /** Debug info. **/ - printf("\n"); - nmStats(); + /*** Debug info, captured to verify that nmStats() prints something. + *** nmStats() prints via the library's own stdout, so capturing it + *** requires us to redirect that file descriptor into a pipe that + *** we flush into a buffer. This deadlocks if stats prints over + *** 64kb of data and fills the pipe, but that shouldn't happen. + ***/ + char stats_buf[2048]; + int stats_pipe[2]; + success &= EXPECT_EQL(pipe(stats_pipe), 0, "%d"); + fflush(stdout); + int saved_stdout = dup(STDOUT_FILENO); + dup2(stats_pipe[1], STDOUT_FILENO); + close(stats_pipe[1]); + nmStats(); /** Run target code. **/ + fflush(stdout); + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + ssize_t stats_len = read(stats_pipe[0], stats_buf, sizeof(stats_buf) - 1lu); + close(stats_pipe[0]); + stats_buf[(stats_len > 0) ? stats_len : 0] = '\0'; + success &= EXPECT_RANGE(strlen(stats_buf), (size_t)32, sizeof(stats_buf) - 1lu, "%zu"); /** Expect no captured errors. **/ success &= EXPECT_STR_EQL(err_buf, ""); From 4e5022b4621dd7659cbddbdf1891bc562e38b5cc Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:24:33 -0600 Subject: [PATCH 133/361] Add tests for xarrays. --- centrallix-lib/tests/test_xarray_00.c | 160 ++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 centrallix-lib/tests/test_xarray_00.c diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c new file mode 100644 index 000000000..82cb998d3 --- /dev/null +++ b/centrallix-lib/tests/test_xarray_00.c @@ -0,0 +1,160 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_xarray_00.c */ +/* Author: Israel Fuller */ +/* Creation: November 25th, 2025 */ +/* Description: Test all the functions in the xarray library, except */ +/* xaInit() and xaDeInit() because testing xaNew() and */ +/* xaFree() should cover them, and xaAddItemSorted() and */ +/* xaAddItemSortedInt32(). */ +/************************************************************************/ + +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "newmalloc.h" +#include "check.h" + +/** Tested module. **/ +#include "xarray.h" + +/** Helper used by xaClear/xaClearR to free allocated items and count them. **/ +static int test_free(void* p, void* arg) + { + if (p == NULL) return 0; + + nmFree(p, sizeof(int)); + if (arg != NULL) (*(unsigned int*)arg)++; + + return 0; + } + +static unsigned int seed_counter = 0; + +static bool do_tests(void) + { + bool success = true; + + /** Create random values using a consistent, distinct seed. **/ + srand(seed_counter++); + const int value1 = rand(); + const int value2 = rand(); + const int value3 = rand(); + const int value4 = rand(); + const int value5 = rand(); + + /** Get a random value that is not one of the previous values. **/ + int possible_value_not; + do possible_value_not = rand(); + while (possible_value_not == value1 + || possible_value_not == value2 + || possible_value_not == value3 + || possible_value_not == value4 + || possible_value_not == value5 + ); + const int value_not = possible_value_not; + + /** Create a new xarray. **/ + pXArray xa = checkPtr(xaNew(2)); + success &= EXPECT_EQL(xa != NULL, true, "%d"); + if (xa == NULL) return false; + + /** A new xarray should have 0 items. **/ + success &= EXPECT_EQL(xaCount(xa), 0, "%d"); + + /** Test adding an item. **/ + int* v1 = checkPtr(nmMalloc(sizeof(int))); + if (v1 == NULL) return false; + *v1 = value1; + success &= EXPECT_EQL(xaAddItem(xa, v1), 0, "%d"); + success &= EXPECT_EQL(xaCount(xa), 1, "%d"); + success &= EXPECT_EQL(*(int*)xaGetItem(xa, 0), value1, "%d"); + + /** Test adding another item. **/ + int* v2 = checkPtr(nmMalloc(sizeof(int))); + if (v2 == NULL) return false; + *v2 = value2; + success &= EXPECT_EQL(xaAddItem(xa, v2), 1, "%d"); + success &= EXPECT_EQL(xaCount(xa), 2, "%d"); + success &= EXPECT_EQL(*(int*)xaGetItem(xa, 1), value2, "%d"); + + /** Test finding items. **/ + success &= EXPECT_EQL(xaFindItem(xa, v2), 1, "%d"); + success &= EXPECT_EQL(xaFindItem(xa, v1), 0, "%d"); + success &= EXPECT_EQL(xaFindItemR(xa, v2), 1, "%d"); + success &= EXPECT_EQL(xaFindItemR(xa, v1), 0, "%d"); + + /** Test finding items that don't exist. **/ + int* v_not = checkPtr(nmMalloc(sizeof(int))); + if (v_not == NULL) return false; + *v_not = value_not; + success &= EXPECT_EQL(xaFindItem(xa, NULL), -1, "%d"); + success &= EXPECT_EQL(xaFindItemR(xa, NULL), -1, "%d"); + success &= EXPECT_EQL(xaFindItem(xa, &v_not), -1, "%d"); + success &= EXPECT_EQL(xaFindItemR(xa, &v_not), -1, "%d"); + nmFree(v_not, sizeof(int)); v_not = NULL; + + /** Insert before index 1. **/ + int* v3 = checkPtr(nmMalloc(sizeof(int))); + if (v3 == NULL) return false; + *v3 = value3; + success &= EXPECT_EQL(xaInsertBefore(xa, 1, v3), 1, "%d"); + success &= EXPECT_EQL(*(int*)xaGetItem(xa, 1), value3, "%d"); + success &= EXPECT_EQL(xaCount(xa), 3, "%d"); + + /** Insert after index 2. **/ + int* v4 = checkPtr(nmMalloc(sizeof(int))); + if (v4 == NULL) return false; + *v4 = value4; + success &= EXPECT_EQL(xaInsertAfter(xa, 2, v4), 3, "%d"); + success &= EXPECT_EQL(*(int*)xaGetItem(xa, 3), value4, "%d"); + success &= EXPECT_EQL(xaCount(xa), 4, "%d"); + + /** xaSetItem() beyond current end should create NULL gaps. **/ + int* vset = checkPtr(nmMalloc(sizeof(int))); + if (vset == NULL) return false; + *vset = value5; + success &= EXPECT_EQL(xaSetItem(xa, 5, vset), 5, "%d"); + success &= EXPECT_EQL(xaCount(xa), 6, "%d"); + success &= EXPECT_EQL(*(int*)xaGetItem(xa, 5), value5, "%d"); + success &= EXPECT_EQL(xaGetItem(xa, 4), NULL, "%p"); /* Check null gap. */ + + /** Remove an item and ensure that it is gone. **/ + success &= EXPECT_EQL(xaRemoveItem(xa, 2), 0, "%d"); /* Remove original index 2. */ + success &= EXPECT_EQL(xaCount(xa), 5, "%d"); + + /** Count non-NULL items prior to clearing. **/ + int count_before = xaCount(xa); + int nonnull = 0; + for (unsigned int i = 0; i < count_before; i++) + if (xaGetItem(xa, i) != NULL) nonnull++; + + /** Clear and verify free was called for each non-NULL item. **/ + unsigned int freed_count = 0; + success &= EXPECT_EQL(xaClear(xa, test_free, &freed_count), 0, "%d"); + success &= EXPECT_EQL(freed_count, nonnull, "%d"); + success &= EXPECT_EQL(xaCount(xa), 0, "%d"); + + /** Clean up. **/ + success &= EXPECT_EQL(xaFree(xa), 0, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "xarray-00 Full Test"; + return loop_tests(do_tests); + } From d88fd95d0409e25304ac6c2d4b4d24b9e315b5a4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:32:55 -0600 Subject: [PATCH 134/361] Fix typo in test_util_01.c. --- centrallix-lib/tests/test_util_01.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_util_01.c b/centrallix-lib/tests/test_util_01.c index a4c2efaef..5eafd5276 100644 --- a/centrallix-lib/tests/test_util_01.c +++ b/centrallix-lib/tests/test_util_01.c @@ -8,7 +8,7 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: test_util_00.c */ +/* Module: test_util_01.c */ /* Author: Micah Shennum */ /* Creation: May 26th, 2011 */ /* Description: Test strtoi */ From 29627c404b05d9c080515b8e3495e888008dadf9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:37:13 -0600 Subject: [PATCH 135/361] Add tests for the range module. --- centrallix-lib/tests/test_range_00.c | 79 ++++++++++++++++++++++++++++ centrallix-lib/tests/test_range_01.c | 72 +++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 centrallix-lib/tests/test_range_00.c create mode 100644 centrallix-lib/tests/test_range_01.c diff --git a/centrallix-lib/tests/test_range_00.c b/centrallix-lib/tests/test_range_00.c new file mode 100644 index 000000000..a60a39fe3 --- /dev/null +++ b/centrallix-lib/tests/test_range_00.c @@ -0,0 +1,79 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_range_00.c */ +/* Author: Israel Fuller */ +/* Creation: November 24th, 2025 */ +/* Description: Test the range.h min() and max() functions. */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "range.h" + +static bool do_tests(void) + { + bool success = true; + + /** Min with doubles. **/ + success &= EXPECT_EQL(min(0.0, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(min(1.0, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(min(-1.0, 0.0), -1.0, "%g"); + success &= EXPECT_EQL(min(-1.1, 0.1), -1.1, "%g"); + success &= EXPECT_EQL(min(0.0001, 0.00011), 0.0001, "%g"); + success &= EXPECT_EQL(min(DBL_MAX, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(min(DBL_MIN, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(min(-DBL_MIN, 0.0), -DBL_MIN, "%g"); + success &= EXPECT_EQL(min(-DBL_MAX, 0.0), -DBL_MAX, "%g"); + success &= EXPECT_EQL(min(pow(10, DBL_DIG), pow(10, FLT_DIG)), pow(10, FLT_DIG), "%g"); + + /** Max with doubles. **/ + success &= EXPECT_EQL(max(0.0, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(max(1.0, 0.0), 1.0, "%g"); + success &= EXPECT_EQL(max(-1.0, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(max(-1.1, 0.1), 0.1, "%g"); + success &= EXPECT_EQL(max(0.0001, 0.00011), 0.00011, "%g"); + success &= EXPECT_EQL(max(DBL_MAX, 0.0), DBL_MAX, "%g"); + success &= EXPECT_EQL(max(DBL_MIN, 0.0), DBL_MIN, "%g"); + success &= EXPECT_EQL(max(-DBL_MIN, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(max(-DBL_MAX, 0.0), 0.0, "%g"); + success &= EXPECT_EQL(max(pow(10, DBL_DIG), pow(10, FLT_DIG)), pow(10, DBL_DIG), "%g"); + + /** Min with ints. **/ + success &= EXPECT_EQL(min(0, 0), 0, "%d"); + success &= EXPECT_EQL(min(1, 0), 0, "%d"); + success &= EXPECT_EQL(min(-1, 0), -1, "%d"); + success &= EXPECT_EQL(min(INT_MAX, 0), 0, "%d"); + success &= EXPECT_EQL(min(INT_MIN, 0), INT_MIN, "%d"); + + /** Max with ints. **/ + success &= EXPECT_EQL(max(0, 0), 0, "%d"); + success &= EXPECT_EQL(max(1, 0), 1, "%d"); + success &= EXPECT_EQL(max(-1, 0), 0, "%d"); + success &= EXPECT_EQL(max(INT_MAX, 0), INT_MAX, "%d"); + success &= EXPECT_EQL(max(INT_MIN, 0), 0, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "range-00 min() & max()"; + return loop_tests(do_tests); + } diff --git a/centrallix-lib/tests/test_range_01.c b/centrallix-lib/tests/test_range_01.c new file mode 100644 index 000000000..57569bdae --- /dev/null +++ b/centrallix-lib/tests/test_range_01.c @@ -0,0 +1,72 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_range_01.c */ +/* Author: Israel Fuller */ +/* Creation: November 24th, 2025 */ +/* Description: Test the range.h roundTo() function. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "range.h" + +static bool do_tests(void) + { + bool success = true; + + /** Rounding to 0 decimals does not change whole numbers. **/ + success &= EXPECT_EQL(roundTo(0.0, 0), 0.0, "%g"); + success &= EXPECT_EQL(roundTo(123, 0), 123.0, "%g"); + success &= EXPECT_EQL(roundTo(-123, 0), -123.0, "%g"); + + /** Rounding to 0 decimals rounds decimals. **/ + success &= EXPECT_EQL(roundTo(1.23, 0), 1.0, "%g"); + success &= EXPECT_EQL(roundTo(24.43, 0), 24.0, "%g"); + success &= EXPECT_EQL(roundTo(1234567890.499, 0), 1234567890.0, "%g"); + success &= EXPECT_EQL(roundTo(-1.82, 0), -2.0, "%g"); + success &= EXPECT_EQL(roundTo(-987654321.499, 0), -987654321.0, "%g"); + + /** Test rounding to various numbers of decimals. **/ + success &= EXPECT_EQL(roundTo(1.23, 1), 1.2, "%g"); + success &= EXPECT_EQL(roundTo(1.23, 2), 1.23, "%g"); + success &= EXPECT_EQL(roundTo(1.23, 3), 1.23, "%g"); + success &= EXPECT_EQL(roundTo(1.23, 8), 1.23, "%g"); + success &= EXPECT_EQL(roundTo(5824113.8, 8), 5824113.8, "%g"); + + /** Test rounding to negative numbers of decimals. **/ + success &= EXPECT_EQL(roundTo(1.23, -1), 0.0, "%g"); + success &= EXPECT_EQL(roundTo(123.4, -1), 120.0, "%g"); + success &= EXPECT_EQL(roundTo(586241.7, -4), 590000.0, "%g"); + + /** Rounding infinity. */ + success &= EXPECT_EQL(roundTo( INFINITY, 0), INFINITY, "%g"); + success &= EXPECT_EQL(roundTo(-INFINITY, 0), -INFINITY, "%g"); + success &= EXPECT_EQL(roundTo( INFINITY, 16), INFINITY, "%g"); + success &= EXPECT_EQL(roundTo(-INFINITY, 16), -INFINITY, "%g"); + success &= EXPECT_EQL(roundTo( INFINITY, -16), INFINITY, "%g"); + success &= EXPECT_EQL(roundTo(-INFINITY, -16), -INFINITY, "%g"); + + return success; + } + +long long test(char** tname) + { + *tname = "range-01 roundTo()"; + return loop_tests(do_tests); + } From f9f7566a7d04c5732ebf180f4228a1a0bfdbef20 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:40:16 -0600 Subject: [PATCH 136/361] Add tests for the timer module. --- centrallix-lib/tests/test_timer_00.c | 106 +++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 centrallix-lib/tests/test_timer_00.c diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c new file mode 100644 index 000000000..fd07b3ab7 --- /dev/null +++ b/centrallix-lib/tests/test_timer_00.c @@ -0,0 +1,106 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_timer_00.c */ +/* Author: Israel Fuller */ +/* Creation: November 24th, 2025 */ +/* Description: Test the util.h timer1 functionality. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "range.h" + +/** Tested module. **/ +#include "timer.h" + +/** A function for wasting cpu cycles. **/ +static bool doNothing(void) + { + return true; + } + +long long test(char** tname) + { + *tname = "timer-00 Timer"; + + /** Allocate a stack and a heap timer. **/ + Timer t; + Timer *timer1 = timerInit(&t); + pTimer timer2 = timerNew(); + + /** 0.1 second wait. **/ + timerStart(timer1); + timerStart(timer2); + usleep(99900); /* 0.0999 seconds (leave room for overhead). */ + double t1_inter = roundTo(timerGet(timer1), 3); + double t2_inter = roundTo(timerGet(timer2), 3); + usleep(99900); /* 0.0999 seconds (leave room for overhead). */ + timerStop(timer1); + timerStop(timer2); + + /** Extract values with rounding to give margin for error. **/ + double t1_val = roundTo(timerGet(timer1), 3); + double t2_val = roundTo(timerGet(timer2), 3); + + /** Check for incorrect values. **/ + if (!EXPECT_EQL(t1_inter, 0.1, "%g")) goto fail; + if (!EXPECT_EQL(t2_inter, 0.1, "%g")) goto fail; + if (!EXPECT_EQL(t1_inter, t2_inter, "%g")) goto fail; + if (!EXPECT_EQL(t1_val, 0.2, "%g")) goto fail; + if (!EXPECT_EQL(t2_val, 0.2, "%g")) goto fail; + if (!EXPECT_EQL(t1_val, t2_val, "%g")) goto fail; + + /** Test that timer can resume properly. **/ + timerStart(timer1); + timerStart(timer2); + usleep(99900); /* 0.0999 seconds (leave room for overhead). */ + double t1_inter2 = roundTo(timerGet(timer1), 3); + double t2_inter2 = roundTo(timerGet(timer2), 3); + usleep(99900); /* 0.0999 seconds (leave room for overhead). */ + timerStop(timer1); + timerStop(timer2); + + /** Extract values with rounding to give margin for error. **/ + double t1_val2 = roundTo(timerGet(timer1), 3); + double t2_val2 = roundTo(timerGet(timer2), 3); + + /** Check for incorrect values. **/ + if (!EXPECT_EQL(t1_inter2, 0.3, "%g")) goto fail; + if (!EXPECT_EQL(t2_inter2, 0.3, "%g")) goto fail; + if (!EXPECT_EQL(t1_inter2, t2_inter2, "%g")) goto fail; + if (!EXPECT_EQL(t1_val2, 0.4, "%g")) goto fail; + if (!EXPECT_EQL(t2_val2, 0.4, "%g")) goto fail; + if (!EXPECT_EQL(t1_val2, t2_val2, "%g")) goto fail; + + /** Clean up. **/ + timerDeInit(timer1); + timerFree(timer2); + + /*** This test takes a lot of real time (calling usleep()) without + *** using very many CPU cycles. This means we need to waste some + *** CPU cycles so that the test runner doesn't crash because the + *** CPU clock time was too low. + ***/ + long long i = loop_tests(doNothing); + + /** Return success. **/ + return i; + + /** Return failure. **/ + fail: + return -1; + } From 63dd165dbbd9456c7d7f01730a05e30e0e6dc866 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:45:22 -0600 Subject: [PATCH 137/361] Add tests for snprint_commas_llu() and the timer module. --- centrallix-lib/tests/test_util_02.c | 125 ++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 centrallix-lib/tests/test_util_02.c diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c new file mode 100644 index 000000000..d4b24b4ef --- /dev/null +++ b/centrallix-lib/tests/test_util_02.c @@ -0,0 +1,125 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_util_02.c */ +/* Author: Israel Fuller */ +/* Creation: November 24th, 2025 */ +/* Description: Test the util.h printing functionality. */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "util.h" + +/** Assumes success is in scope. **/ +#define TEST_SNPRINT_COMMAS_LLU(buf, buf_size, value, expect) \ + EXPECT_STR_EQL(snprintCommasLlu(buf, buf_size, value), expect) && \ + EXPECT_EQL(snprintCommasLlu(buf, buf_size, value), &buf[0], "%p") + +#define TEST_SNPRINT_BYTES(buf, buf_size, value, expect) \ + EXPECT_STR_EQL(snprintBytes(buf, buf_size, value), expect) && \ + EXPECT_EQL(snprintBytes(buf, buf_size, value), &buf[0], "%p") + +static bool do_tests(void) + { + bool success = true; + + /** Detect if metric or CS units are intended. **/ + bool cs = true; + #ifdef UTIL_USE_METRIC + if (UTIL_USE_METRIC) cs = false; + #endif + + /** Allocate space for the string buffer. **/ + char buf[32]; + const size_t buf_size = sizeof(buf) / sizeof(char); + memset(buf, UINT_MAX, sizeof(buf)); /* Use unexpected data to catch uninitialized reads. */ + + /** Test snprintBytes(). **/ + success &= TEST_SNPRINT_BYTES(buf, buf_size, 0, "0 bytes"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, 10, "10 bytes"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, 100, "100 bytes"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 1) - pow(1000, 0), (cs) ? "999 bytes" : "999 bytes"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 1), (cs) ? "1000 bytes" : "1 KB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 2) - pow(1000, 1), (cs) ? "975.59 KiB" : "999 KB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 2), (cs) ? "976.56 KiB" : "1 MB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 3) - pow(1000, 2), (cs) ? "952.72 MiB" : "999 MB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 3), (cs) ? "953.67 MiB" : "1 GB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1024, 1) - pow(1024, 0), (cs) ? "1023 bytes" : "1.02 KB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1024, 2) - pow(1024, 1), (cs) ? "1023 KiB" : "1.05 MB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1024, 2), (cs) ? "1 MiB" : "1.05 MB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1024, 3) - pow(1024, 2), (cs) ? "1023 MiB" : "1.07 GB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1024, 3), (cs) ? "1 GiB" : "1.07 GB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, INT_MAX, (cs) ? "2 GiB" : "2.15 GB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, UINT_MAX, (cs) ? "4 GiB" : "4.29 GB"); + + /** Test snprintCommasLlu(). Note: 10^16 would fail due to the double precision limit. **/ + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, 0, "0"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 1) - 1, "9"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 1), "10"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 2) - 1, "99"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 2), "100"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 3) - 1, "999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 3), "1,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 4) - 1, "9,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 4), "10,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 5) - 1, "99,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 5), "100,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 6) - 1, "999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 6), "1,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 7) - 1, "9,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 7), "10,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 8) - 1, "99,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 8), "100,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 9) - 1, "999,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 9), "1,000,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 10) - 1, "9,999,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 10), "10,000,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 11) - 1, "99,999,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 11), "100,000,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 12) - 1, "999,999,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 12), "1,000,000,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 13) - 1, "9,999,999,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 13), "10,000,000,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 14) - 1, "99,999,999,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 14), "100,000,000,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 15) - 1, "999,999,999,999,999"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 15), "1,000,000,000,000,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(1024, 1) - 1, "1,023"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(1024, 1), "1,024"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(1024, 2) - 1, "1,048,575"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(1024, 2), "1,048,576"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(1024, 3) - 1, "1,073,741,823"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(1024, 3), "1,073,741,824"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, SHRT_MAX, "32,767"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, USHRT_MAX, "65,535"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, INT_MAX, "2,147,483,647"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, UINT_MAX, "4,294,967,295"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, LLONG_MAX, "9,223,372,036,854,775,807"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, ULLONG_MAX, "18,446,744,073,709,551,615"); + + return success; + } + +long long test(char** tname) + { + *tname = "util-02 Printing"; + return loop_tests(do_tests); + } From 1bea68e2c74041b8d84fb150907a7283a98a74cb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:47:52 -0600 Subject: [PATCH 138/361] Add a copyright notice to test_memstr_00.c. --- centrallix-lib/tests/test_memstr_00.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/centrallix-lib/tests/test_memstr_00.c b/centrallix-lib/tests/test_memstr_00.c index 13022b935..b7268358f 100755 --- a/centrallix-lib/tests/test_memstr_00.c +++ b/centrallix-lib/tests/test_memstr_00.c @@ -1,3 +1,19 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2014-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_memstr_00.c */ +/* Author: Brady Steed */ +/* Creation: July 10, 2014 */ +/* Description: Test the memstr function. */ +/************************************************************************/ + #include #include #include From d807a7fc43068f02946a802efe2e2ae28b1a6b5e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:50:18 -0600 Subject: [PATCH 139/361] Update copyright notices that got missed in passed commits. --- centrallix-lib/tests/test_newmalloc_00.c | 2 +- centrallix-lib/tests/test_newmalloc_01.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index b2f91ba6f..73552d117 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index ee6ad6e94..97c99d852 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ From 8ac03d9a6fe14485eb1d6634d3b8e9d9f0e50fad Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 4 Sep 2026 17:50:32 -0600 Subject: [PATCH 140/361] Fix the test_util_01.c copyright notice. --- centrallix-lib/tests/test_util_01.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/tests/test_util_01.c b/centrallix-lib/tests/test_util_01.c index 5eafd5276..ed6ef3442 100644 --- a/centrallix-lib/tests/test_util_01.c +++ b/centrallix-lib/tests/test_util_01.c @@ -2,16 +2,16 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* Copyright (C) 2011-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: test_util_01.c */ -/* Author: Micah Shennum */ -/* Creation: May 26th, 2011 */ -/* Description: Test strtoi */ +/* Module: test_util_01.c */ +/* Author: Micah Shennum */ +/* Creation: May 26th, 2011 */ +/* Description: Test the strtoi function. */ /************************************************************************/ #include From 2ba0c9dbd3fda3f5bc0570abd761264eb2dde3e9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 14:24:12 -0600 Subject: [PATCH 141/361] Fix flakiness in the timer test if it lags. --- centrallix-lib/tests/test_timer_00.c | 68 +++++++++++++++++----------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c index fd07b3ab7..f0e4d7c34 100644 --- a/centrallix-lib/tests/test_timer_00.c +++ b/centrallix-lib/tests/test_timer_00.c @@ -22,11 +22,27 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "range.h" /** Tested module. **/ #include "timer.h" +/** The length of one wait, in microseconds and in seconds. **/ +#define SLEEP_USEC 100000 +#define SLEEP_SEC (SLEEP_USEC / 1000000.0) + +/** Scheduler and Valgrind overhead allowed per wait, in seconds. **/ +#define SLACK_SEC 0.1 + +/*** usleep() guarantees a minimum delay, not an exact one, so elapsed time is + *** checked against a range rather than a single value. After n waits the + *** timer must read at least the time requested, plus at most n waits' slack. + ***/ +#define ELAPSED_MIN(n) ((n) * SLEEP_SEC) +#define ELAPSED_MAX(n) ((n) * (SLEEP_SEC + SLACK_SEC)) + +/** The most the two timers may disagree, having been started microseconds apart. **/ +#define SKEW 0.005 + /** A function for wasting cpu cycles. **/ static bool doNothing(void) { @@ -45,46 +61,44 @@ long long test(char** tname) /** 0.1 second wait. **/ timerStart(timer1); timerStart(timer2); - usleep(99900); /* 0.0999 seconds (leave room for overhead). */ - double t1_inter = roundTo(timerGet(timer1), 3); - double t2_inter = roundTo(timerGet(timer2), 3); - usleep(99900); /* 0.0999 seconds (leave room for overhead). */ + usleep(SLEEP_USEC); + double t1_inter = timerGet(timer1); + double t2_inter = timerGet(timer2); + usleep(SLEEP_USEC); timerStop(timer1); timerStop(timer2); - /** Extract values with rounding to give margin for error. **/ - double t1_val = roundTo(timerGet(timer1), 3); - double t2_val = roundTo(timerGet(timer2), 3); + double t1_val = timerGet(timer1); + double t2_val = timerGet(timer2); /** Check for incorrect values. **/ - if (!EXPECT_EQL(t1_inter, 0.1, "%g")) goto fail; - if (!EXPECT_EQL(t2_inter, 0.1, "%g")) goto fail; - if (!EXPECT_EQL(t1_inter, t2_inter, "%g")) goto fail; - if (!EXPECT_EQL(t1_val, 0.2, "%g")) goto fail; - if (!EXPECT_EQL(t2_val, 0.2, "%g")) goto fail; - if (!EXPECT_EQL(t1_val, t2_val, "%g")) goto fail; + if (!EXPECT_RANGE(t1_inter, ELAPSED_MIN(1), ELAPSED_MAX(1), "%g")) goto fail; + if (!EXPECT_RANGE(t2_inter, ELAPSED_MIN(1), ELAPSED_MAX(1), "%g")) goto fail; + if (!EXPECT_RANGE(fabs(t1_inter - t2_inter), 0.0, SKEW, "%g")) goto fail; + if (!EXPECT_RANGE(t1_val, ELAPSED_MIN(2), ELAPSED_MAX(2), "%g")) goto fail; + if (!EXPECT_RANGE(t2_val, ELAPSED_MIN(2), ELAPSED_MAX(2), "%g")) goto fail; + if (!EXPECT_RANGE(fabs(t1_val - t2_val), 0.0, SKEW, "%g")) goto fail; /** Test that timer can resume properly. **/ timerStart(timer1); timerStart(timer2); - usleep(99900); /* 0.0999 seconds (leave room for overhead). */ - double t1_inter2 = roundTo(timerGet(timer1), 3); - double t2_inter2 = roundTo(timerGet(timer2), 3); - usleep(99900); /* 0.0999 seconds (leave room for overhead). */ + usleep(SLEEP_USEC); + double t1_inter2 = timerGet(timer1); + double t2_inter2 = timerGet(timer2); + usleep(SLEEP_USEC); timerStop(timer1); timerStop(timer2); - /** Extract values with rounding to give margin for error. **/ - double t1_val2 = roundTo(timerGet(timer1), 3); - double t2_val2 = roundTo(timerGet(timer2), 3); + double t1_val2 = timerGet(timer1); + double t2_val2 = timerGet(timer2); /** Check for incorrect values. **/ - if (!EXPECT_EQL(t1_inter2, 0.3, "%g")) goto fail; - if (!EXPECT_EQL(t2_inter2, 0.3, "%g")) goto fail; - if (!EXPECT_EQL(t1_inter2, t2_inter2, "%g")) goto fail; - if (!EXPECT_EQL(t1_val2, 0.4, "%g")) goto fail; - if (!EXPECT_EQL(t2_val2, 0.4, "%g")) goto fail; - if (!EXPECT_EQL(t1_val2, t2_val2, "%g")) goto fail; + if (!EXPECT_RANGE(t1_inter2, ELAPSED_MIN(3), ELAPSED_MAX(3), "%g")) goto fail; + if (!EXPECT_RANGE(t2_inter2, ELAPSED_MIN(3), ELAPSED_MAX(3), "%g")) goto fail; + if (!EXPECT_RANGE(fabs(t1_inter2 - t2_inter2), 0.0, SKEW, "%g")) goto fail; + if (!EXPECT_RANGE(t1_val2, ELAPSED_MIN(4), ELAPSED_MAX(4), "%g")) goto fail; + if (!EXPECT_RANGE(t2_val2, ELAPSED_MIN(4), ELAPSED_MAX(4), "%g")) goto fail; + if (!EXPECT_RANGE(fabs(t1_val2 - t2_val2), 0.0, SKEW, "%g")) goto fail; /** Clean up. **/ timerDeInit(timer1); From 25a7c86d07d21cf7a61997152446bf8a54f38626 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 14:33:31 -0600 Subject: [PATCH 142/361] Improve test operations per second printing. --- centrallix-lib/tests/t_driver.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 2233b76d9..39d87c64a 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -22,6 +22,7 @@ #include "cxlibconfig-internal.h" #include "mtask.h" +#include "util.h" long long test(char**); @@ -89,9 +90,35 @@ start(void* v) printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loop_tests() from test_utils.h.\n"); return; } - long long ops_per_second = rval * (100 / duration); - if (ops_per_second > 0) printf("%-62.62s PASS %lld\n", tname, ops_per_second); - else printf("%-62.62s PASS %.4lf\n", tname, rval * (100.0 / duration)); + double ops_per_second = rval * (100.0 / duration); + + /** Round to four significant figures. **/ + int precision = 3; + unsigned long long factor = 1; + double scaled = ops_per_second; + while (scaled >= 10.0) + { + scaled /= 10.0; + if (precision > 0) + precision--; + else + factor *= 10; + } + while (scaled > 0.0 && scaled < 1.0) + { + scaled *= 10.0; + precision++; + } + + /** Print whole numbers with commas. **/ + if (precision == 0) + { + char buf[32]; + unsigned long long rounded = (unsigned long long)(ops_per_second / factor + 0.5) * factor; + printf("%-62.62s PASS %s\n", tname, snprintCommasLlu(buf, sizeof(buf), rounded)); + } + else + printf("%-62.62s PASS %.*f\n", tname, precision, ops_per_second); } return; From d6913468c5c838462406072ada844799cefb0f18 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 15:03:04 -0600 Subject: [PATCH 143/361] Fix make valtest. --- centrallix-lib/Makefile.in | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index b8b857c9c..6252b5101 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -6,7 +6,7 @@ ## This library is made available under the GNU Lesser General Public ## License, version 2.1. ## -## Copyright (C) 1998-2001 LightSys Technology Services, Inc. +## Copyright (C) 1998-2026 LightSys Technology Services, Inc. ## @SET_MAKE@ @@ -76,6 +76,13 @@ INCLUDEFILES:=$(patsubst include/cxlibconfig-all.h,,$(INCLUDEFILES)) TESTFILES:=$(wildcard tests/test_$(TONLY)*.c) TESTPROGS:=$(patsubst %.c,%.bin,$(TESTFILES)) +## Valgrind setup for "make valtest". Override VALGRINDFLAGS to change the +## checks performed, and set VALGRINDLOG to a directory to get one valgrind +## log per test instead of the errors being printed with the test results. +VALGRIND?=valgrind +VALGRINDFLAGS?=-q +VALGRINDLOG?= + .SUFFIXES: .c .o .lo .PHONY: all @@ -175,9 +182,20 @@ valtest: $(TESTPROGS) @printf "Running Regression Tests Under Valgrind...\n" @printf "%-62.62s %s\n" "Test Name" "Stat Ops/sec" @printf "%-62.62s %s\n" "----------------------------------------------------------------" "--------------" + @if [ "$(VALGRINDLOG)" != "" ]; then mkdir -p "$(VALGRINDLOG)"; fi @for PROG in $$( ls $(TESTPROGS) ); do \ if [ "$$TONLY" = "" -o "$${PROG#tests/test_$$TONLY}" != "$$PROG" ]; then \ - valgrind -q $$PROG; \ + NAME=$${PROG##*/}; \ + NAME=$${NAME%.bin}; \ + if [ "$(VALGRINDLOG)" = "" ]; then \ + $(VALGRIND) $(VALGRINDFLAGS) $$PROG; \ + else \ + LOG="$(VALGRINDLOG)/$$NAME.log"; \ + $(VALGRIND) $(VALGRINDFLAGS) --error-exitcode=125 --log-file="$$LOG" $$PROG; \ + if [ "$$?" = "125" ]; then \ + printf " valgrind: errors reported -- %s\n" "$$LOG"; \ + fi; \ + fi; \ fi \ done From ff2c036b29678dd40abb072f8d15c8a70e53a4cf Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 15:29:31 -0600 Subject: [PATCH 144/361] Fix newmalloc test memory leaks. --- centrallix-lib/tests/test_newmalloc_00.c | 5 +++++ centrallix-lib/tests/test_newmalloc_01.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 73552d117..25b40abe0 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -139,6 +139,8 @@ static bool do_tests(void) free(data[i]); nmSysFree(test[i]); } + free(data); + free(test); /** Basic string data is unharmed. **/ success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); @@ -176,6 +178,9 @@ static bool do_tests(void) /** Expect no captured errors. **/ success &= EXPECT_STR_EQL(err_buf, ""); + /** Clean up. **/ + free(err_buf); + return success; } diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 97c99d852..128fbe63b 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -126,6 +126,8 @@ static bool do_tests(void) free(data[i]); nmFree(test[i], i); } + free(data); + free(test); /** Basic string data is unharmed. **/ success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); @@ -166,6 +168,9 @@ static bool do_tests(void) /** Expect no captured errors. **/ success &= EXPECT_STR_EQL(err_buf, ""); + /** Clean up. **/ + free(err_buf); + return success; } From 9115b168f6a4301c93b2b65691040c145eaef607 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 15:37:46 -0600 Subject: [PATCH 145/361] Scale down newmalloc tests when running under Valgrind so they don't take several minutes each. --- centrallix-lib/tests/test_newmalloc_00.c | 58 ++++++++++++++++-------- centrallix-lib/tests/test_newmalloc_01.c | 52 ++++++++++++++------- 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 25b40abe0..75089ec46 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -28,6 +28,20 @@ /** Tested module. **/ #include "newmalloc.h" +/*** Valgrind instruments every memory access, so the bulk data sizes are + *** divided by this factor when running under it, keeping the test inside + *** the driver's lockup timeout. + ***/ +#ifdef USING_VALGRIND +#include "valgrind/valgrind.h" +#define BULK_DIVISOR (RUNNING_ON_VALGRIND ? 16lu : 1lu) +#else +#define BULK_DIVISOR 1lu +#endif + +#define TEST_LIMIT (16384lu / BULK_DIVISOR) +#define LARGE_BUF_SIZE (256000000lu / BULK_DIVISOR) + static unsigned int seed_counter = 0; static char* err_buf; static unsigned int err_buf_i; @@ -68,6 +82,12 @@ static bool do_tests(void) { bool success = true; + /*** Evaluate the bulk data sizes once, since the Valgrind check behind + *** them is a client request rather than a plain constant. + ***/ + const size_t test_limit = TEST_LIMIT; + const size_t large_buf_size = LARGE_BUF_SIZE; + /** Set a consistent, distinct seed for each test iteration. **/ srand(seed_counter++); @@ -76,9 +96,6 @@ static bool do_tests(void) err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mock_error_fn); - /** Baseline: Should leak. **/ - success &= EXPECT_NOT_NULL(nmSysMalloc(42)); - /** Basic string data. **/ char* str1; success &= EXPECT_NOT_NULL(str1 = nmSysMalloc(16)); @@ -89,17 +106,16 @@ static bool do_tests(void) success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); - /** 128 MB random data, varying sizes. **/ - #define TEST_LIMIT 16384 - void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); - void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); - for (size_t i = 1lu; i < TEST_LIMIT; i++) + /** Random data, varying sizes. **/ + void** data = checkPtr(malloc(test_limit * sizeof(void*))); + void** test = checkPtr(malloc(test_limit * sizeof(void*))); + for (size_t i = 1lu; i < test_limit; i++) { success &= EXPECT_NOT_NULL(test[i] = nmSysMalloc(i)); data[i] = random_init(checkPtr(malloc(i)), i); memcpy(test[i], data[i], i); /* Write test data into test memory. */ } - for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) + for (size_t i = test_limit - 1lu; i > 0lu; i--) success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); /** Basic string data is unharmed. **/ @@ -107,10 +123,10 @@ static bool do_tests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Reallocate all variably sized memory to a different size. **/ - for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) - success &= EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i)); - for (size_t i = 1lu; i < TEST_LIMIT; i++) - success &= EXPECT_EQL(memcmp(data[i], test[i], min(i, TEST_LIMIT - i)), 0, "%d"); + for (size_t i = test_limit - 1lu; i > 0lu; i--) + success &= EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], test_limit - i)); + for (size_t i = 1lu; i < test_limit; i++) + success &= EXPECT_EQL(memcmp(data[i], test[i], min(i, test_limit - i)), 0, "%d"); /** Basic string data is unharmed. **/ success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); @@ -134,7 +150,7 @@ static bool do_tests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ - for (size_t i = 1lu; i < TEST_LIMIT; i++) + for (size_t i = 1lu; i < test_limit; i++) { free(data[i]); nmSysFree(test[i]); @@ -155,14 +171,15 @@ static bool do_tests(void) success &= EXPECT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); /** Large singular allocation. **/ - #define _256MB 256000000lu void* large_buf; - success &= EXPECT_NOT_NULL(large_buf = nmSysMalloc(_256MB)); - for (size_t i = _256MB - 1lu; i > 0lu; i--) + success &= EXPECT_NOT_NULL(large_buf = nmSysMalloc(large_buf_size)); + for (size_t i = large_buf_size - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; - for (size_t i = 0lu; i < _256MB; i++) - success &= EXPECT_EQL(*((unsigned char*)large_buf + i), (unsigned char)(i % 255lu), "%d"); + size_t mismatches = 0lu; + for (size_t i = 0lu; i < large_buf_size; i++) + if (*((unsigned char*)large_buf + i) != (unsigned char)(i % 255lu)) mismatches++; + success &= EXPECT_EQL(mismatches, 0lu, "%zu"); /** Dup string data is unharmed. **/ success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeDa"); @@ -191,5 +208,6 @@ long long test(char** tname) } /** Scope cleanup. **/ +#undef BULK_DIVISOR #undef TEST_LIMIT -#undef _256MB +#undef LARGE_BUF_SIZE diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 128fbe63b..b3e51b84a 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -28,6 +28,20 @@ /** Tested module. **/ #include "newmalloc.h" +/*** Valgrind instruments every memory access, so the bulk data sizes are + *** divided by this factor when running under it, keeping the test inside + *** the driver's lockup timeout. + ***/ +#ifdef USING_VALGRIND +#include "valgrind/valgrind.h" +#define BULK_DIVISOR (RUNNING_ON_VALGRIND ? 16lu : 1lu) +#else +#define BULK_DIVISOR 1lu +#endif + +#define TEST_LIMIT (16384lu / BULK_DIVISOR) +#define LARGE_BUF_SIZE (256000000lu / BULK_DIVISOR) + static unsigned int seed_counter = 0; static char* err_buf; static unsigned int err_buf_i; @@ -68,6 +82,12 @@ static bool do_tests(void) { bool success = true; + /*** Evaluate the bulk data sizes once, since the Valgrind check behind + *** them is a client request rather than a plain constant. + ***/ + const size_t test_limit = TEST_LIMIT; + const size_t large_buf_size = LARGE_BUF_SIZE; + /** Set a consistent, distinct seed for each test iteration. **/ srand(seed_counter++); @@ -76,9 +96,6 @@ static bool do_tests(void) err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mock_error_fn); - /** Baseline: Should leak. **/ - success &= EXPECT_NOT_NULL(nmMalloc(42)); - /** Basic string data. **/ char* str1; success &= EXPECT_NOT_NULL(str1 = nmMalloc(16)); @@ -89,17 +106,16 @@ static bool do_tests(void) success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); - /** 128 MB random data, varying sizes. **/ - #define TEST_LIMIT 16384 - void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); - void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); - for (size_t i = 1lu; i < TEST_LIMIT; i++) + /** Random data, varying sizes. **/ + void** data = checkPtr(malloc(test_limit * sizeof(void*))); + void** test = checkPtr(malloc(test_limit * sizeof(void*))); + for (size_t i = 1lu; i < test_limit; i++) { success &= EXPECT_NOT_NULL(test[i] = nmMalloc(i)); data[i] = random_init(checkPtr(malloc(i)), i); memcpy(test[i], data[i], i); } - for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) + for (size_t i = test_limit - 1lu; i > 0lu; i--) success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); /** Basic string data is unharmed. **/ @@ -107,21 +123,22 @@ static bool do_tests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Large singular allocation. **/ - #define _256MB 256000000lu void* large_buf; - success &= EXPECT_NOT_NULL(large_buf = nmMalloc(_256MB)); - for (size_t i = _256MB - 1lu; i > 0lu; i--) + success &= EXPECT_NOT_NULL(large_buf = nmMalloc(large_buf_size)); + for (size_t i = large_buf_size - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; - for (size_t i = 0lu; i < _256MB; i++) - success &= EXPECT_EQL(*((unsigned char*)large_buf + i), (unsigned char)(i % 255lu), "%d"); + size_t mismatches = 0lu; + for (size_t i = 0lu; i < large_buf_size; i++) + if (*((unsigned char*)large_buf + i) != (unsigned char)(i % 255lu)) mismatches++; + success &= EXPECT_EQL(mismatches, 0lu, "%zu"); /** Dup string data is unharmed. **/ success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ - for (size_t i = 1lu; i < TEST_LIMIT; i++) + for (size_t i = 1lu; i < test_limit; i++) { free(data[i]); nmFree(test[i], i); @@ -138,7 +155,7 @@ static bool do_tests(void) nmFree(str2, 32); /** Free large allocation. **/ - nmFree(large_buf, _256MB); + nmFree(large_buf, large_buf_size); /** Clear cache. **/ nmClear(); @@ -181,5 +198,6 @@ long long test(char** tname) } /** Scope cleanup. **/ +#undef BULK_DIVISOR #undef TEST_LIMIT -#undef _256MB +#undef LARGE_BUF_SIZE From 2a0a390f86e597e45b17c4dfe6c76b7104a1c308 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 16:11:22 -0600 Subject: [PATCH 146/361] Fix memory leak. --- centrallix-lib/tests/test_xarray_00.c | 1 + 1 file changed, 1 insertion(+) diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 82cb998d3..e7e2c2c11 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -134,6 +134,7 @@ static bool do_tests(void) /** Remove an item and ensure that it is gone. **/ success &= EXPECT_EQL(xaRemoveItem(xa, 2), 0, "%d"); /* Remove original index 2. */ success &= EXPECT_EQL(xaCount(xa), 5, "%d"); + nmFree(v2, sizeof(int)); v2 = NULL; /* v2 is removed so it isn't freed with xaClear(). */ /** Count non-NULL items prior to clearing. **/ int count_before = xaCount(xa); From dc9a98192b4372afda11962772dc20a6926f0836 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 16:13:50 -0600 Subject: [PATCH 147/361] Rename test_utils.h functions that did not follow naming conventions. --- centrallix-lib/tests/t_driver.c | 2 +- centrallix-lib/tests/test_newmalloc_00.c | 4 ++-- centrallix-lib/tests/test_newmalloc_01.c | 4 ++-- centrallix-lib/tests/test_range_00.c | 4 ++-- centrallix-lib/tests/test_range_01.c | 4 ++-- centrallix-lib/tests/test_timer_00.c | 2 +- centrallix-lib/tests/test_util_02.c | 4 ++-- centrallix-lib/tests/test_utils.h | 2 +- centrallix-lib/tests/test_xarray_00.c | 4 ++-- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 39d87c64a..42a5d7604 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -87,7 +87,7 @@ start(void* v) if (duration == 0) { printf("%-62.62s PASS ???\n", tname); - printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loop_tests() from test_utils.h.\n"); + printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loopTests() from test_utils.h.\n"); return; } double ops_per_second = rval * (100.0 / duration); diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 75089ec46..83ebc564d 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -78,7 +78,7 @@ static void* random_init(void* ptr, size_t size) return ptr; } -static bool do_tests(void) +static bool doTests(void) { bool success = true; @@ -204,7 +204,7 @@ static bool do_tests(void) long long test(char** tname) { *tname = "newmalloc-00 nmSysMalloc(), nmSysFree(), nmSysRealloc(), & nmSysStrdup()"; - return loop_tests(do_tests); + return loopTests(doTests); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index b3e51b84a..b09deb624 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -78,7 +78,7 @@ static void* random_init(void* ptr, size_t size) return ptr; } -static bool do_tests(void) +static bool doTests(void) { bool success = true; @@ -194,7 +194,7 @@ static bool do_tests(void) long long test(char** tname) { *tname = "newmalloc-01 nmMalloc(), nmFree(), & nmClear()"; - return loop_tests(do_tests); + return loopTests(doTests); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_range_00.c b/centrallix-lib/tests/test_range_00.c index a60a39fe3..9cc767033 100644 --- a/centrallix-lib/tests/test_range_00.c +++ b/centrallix-lib/tests/test_range_00.c @@ -27,7 +27,7 @@ /** Tested module. **/ #include "range.h" -static bool do_tests(void) +static bool doTests(void) { bool success = true; @@ -75,5 +75,5 @@ static bool do_tests(void) long long test(char** tname) { *tname = "range-00 min() & max()"; - return loop_tests(do_tests); + return loopTests(doTests); } diff --git a/centrallix-lib/tests/test_range_01.c b/centrallix-lib/tests/test_range_01.c index 57569bdae..0b199da9e 100644 --- a/centrallix-lib/tests/test_range_01.c +++ b/centrallix-lib/tests/test_range_01.c @@ -26,7 +26,7 @@ /** Tested module. **/ #include "range.h" -static bool do_tests(void) +static bool doTests(void) { bool success = true; @@ -68,5 +68,5 @@ static bool do_tests(void) long long test(char** tname) { *tname = "range-01 roundTo()"; - return loop_tests(do_tests); + return loopTests(doTests); } diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c index f0e4d7c34..532c7eddb 100644 --- a/centrallix-lib/tests/test_timer_00.c +++ b/centrallix-lib/tests/test_timer_00.c @@ -109,7 +109,7 @@ long long test(char** tname) *** CPU cycles so that the test runner doesn't crash because the *** CPU clock time was too low. ***/ - long long i = loop_tests(doNothing); + long long i = loopTests(doNothing); /** Return success. **/ return i; diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index d4b24b4ef..0ea32f516 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -36,7 +36,7 @@ EXPECT_STR_EQL(snprintBytes(buf, buf_size, value), expect) && \ EXPECT_EQL(snprintBytes(buf, buf_size, value), &buf[0], "%p") -static bool do_tests(void) +static bool doTests(void) { bool success = true; @@ -121,5 +121,5 @@ static bool do_tests(void) long long test(char** tname) { *tname = "util-02 Printing"; - return loop_tests(do_tests); + return loopTests(doTests); } diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 358452bd8..76a9bb9c8 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -130,7 +130,7 @@ }) /** Repeat the test as many times as possible within a set time window. **/ -#define loop_tests(test_fn) \ +#define loopTests(test_fn) \ ({ \ long long result = 0ll; \ Timer iter_timer_buf, *iter_timer = timerStart(timerInit(&iter_timer_buf)); \ diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index e7e2c2c11..3ed7944d1 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -43,7 +43,7 @@ static int test_free(void* p, void* arg) static unsigned int seed_counter = 0; -static bool do_tests(void) +static bool doTests(void) { bool success = true; @@ -157,5 +157,5 @@ static bool do_tests(void) long long test(char** tname) { *tname = "xarray-00 Full Test"; - return loop_tests(do_tests); + return loopTests(doTests); } From 70889756e1bcf97759a2ba47c318cfa2efad452c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 16:50:24 -0600 Subject: [PATCH 148/361] Migrate tests to use loopTests(), fixing 20-25m valgrind runs. Reduce test timeout in valgrind to 10 seconds. --- centrallix-lib/tests/t_driver.c | 2 +- centrallix-lib/tests/test_00baseline.c | 26 ++-- centrallix-lib/tests/test_memstr_00.c | 58 ++++----- centrallix-lib/tests/test_mtlexer_00.c | 52 ++++---- centrallix-lib/tests/test_mtlexer_01.c | 56 +++++---- centrallix-lib/tests/test_mtlexer_02.c | 57 ++++----- centrallix-lib/tests/test_mtlexer_03.c | 124 ++++++++++--------- centrallix-lib/tests/test_mtlexer_04.c | 35 +++--- centrallix-lib/tests/test_mtlexer_05.c | 57 ++++----- centrallix-lib/tests/test_mtlexer_06.c | 57 ++++----- centrallix-lib/tests/test_mtlexer_07.c | 87 ++++++++------ centrallix-lib/tests/test_mtlexer_08.c | 55 +++++---- centrallix-lib/tests/test_mtlexer_09.c | 134 +++++++++++++-------- centrallix-lib/tests/test_mtlexer_10.c | 67 ++++++----- centrallix-lib/tests/test_mtlexer_11.c | 61 +++++----- centrallix-lib/tests/test_mtlexer_12.c | 84 +++++++------ centrallix-lib/tests/test_mtlexer_13.c | 89 ++++++++------ centrallix-lib/tests/test_mtlexer_14.c | 84 +++++++------ centrallix-lib/tests/test_mtlexer_15.c | 61 +++++----- centrallix-lib/tests/test_mtlexer_16.c | 61 +++++----- centrallix-lib/tests/test_mtlexer_17.c | 57 ++++----- centrallix-lib/tests/test_mtlexer_18.c | 57 ++++----- centrallix-lib/tests/test_mtlexer_19.c | 73 ++++++------ centrallix-lib/tests/test_mtlexer_20.c | 64 +++++----- centrallix-lib/tests/test_mtlexer_21.c | 52 ++++---- centrallix-lib/tests/test_qprintf_00.c | 68 +++++------ centrallix-lib/tests/test_qprintf_01.c | 68 +++++------ centrallix-lib/tests/test_qprintf_02.c | 68 +++++------ centrallix-lib/tests/test_qprintf_03.c | 68 +++++------ centrallix-lib/tests/test_qprintf_04.c | 72 +++++------ centrallix-lib/tests/test_qprintf_05.c | 74 ++++++------ centrallix-lib/tests/test_qprintf_06.c | 76 ++++++------ centrallix-lib/tests/test_qprintf_07.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_08.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_09.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_10.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_11.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_12.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_13.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_14.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_15.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_16.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_17.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_18.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_19.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_20.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_21.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_22.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_23.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_24.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_25.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_26.c | 65 +++++----- centrallix-lib/tests/test_qprintf_27.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_28.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_29.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_30.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_31.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_32.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_33.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_34.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_35.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_36.c | 71 +++++------ centrallix-lib/tests/test_qprintf_37.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_38.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_39.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_40.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_41.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_42.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_43.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_44.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_45.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_46.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_47.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_48.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_49.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_50.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_51.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_52.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_53.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_54.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_55.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_56.c | 83 ++++++------- centrallix-lib/tests/test_qprintf_57.c | 75 ++++++------ centrallix-lib/tests/test_qprintf_58.c | 67 ++++++----- centrallix-lib/tests/test_qprintf_59.c | 83 ++++++------- centrallix-lib/tests/test_qprintf_60.c | 77 ++++++------ centrallix-lib/tests/test_qprintf_61.c | 83 ++++++------- centrallix-lib/tests/test_qprintf_62.c | 77 ++++++------ centrallix-lib/tests/test_qprintf_63.c | 63 +++++----- centrallix-lib/tests/test_qprintf_64.c | 61 +++++----- centrallix-lib/tests/test_qprintf_65.c | 63 +++++----- centrallix-lib/tests/test_qprintf_66.c | 88 +++++++------- centrallix-lib/tests/test_qprintf_67.c | 87 +++++++------- centrallix-lib/tests/test_qprintf_68.c | 63 +++++----- centrallix-lib/tests/test_smmalloc_01.c | 30 ++--- centrallix-lib/tests/test_smmalloc_02.c | 48 ++++---- centrallix-lib/tests/test_smmalloc_03.c | 67 +++++++---- centrallix-lib/tests/test_smmalloc_04.c | 63 ++++++---- centrallix-lib/tests/test_smmalloc_05.c | 80 +++++++------ centrallix-lib/tests/test_smmalloc_06.c | 78 +++++++----- centrallix-lib/tests/test_smmalloc_07.c | 109 +++++++++-------- centrallix-lib/tests/test_smmalloc_08.c | 93 ++++++++------- centrallix-lib/tests/test_strtcpy_00.c | 68 +++++------ centrallix-lib/tests/test_strtcpy_01.c | 76 ++++++------ centrallix-lib/tests/test_strtcpy_02.c | 68 +++++------ centrallix-lib/tests/test_strtcpy_03.c | 71 +++++------ centrallix-lib/tests/test_strtcpy_04.c | 71 +++++------ centrallix-lib/tests/test_strtcpy_05.c | 71 +++++------ centrallix-lib/tests/test_strtcpy_06.c | 77 ++++++------ centrallix-lib/tests/test_strtcpy_07.c | 81 +++++++------ centrallix-lib/tests/test_strtcpy_08.c | 92 ++++++++------- centrallix-lib/tests/test_strtcpy_09.c | 75 ++++++------ centrallix-lib/tests/test_strtcpy_10.c | 75 ++++++------ centrallix-lib/tests/test_strtcpy_11.c | 98 +++++++-------- centrallix-lib/tests/test_strtcpy_12.c | 151 +++++++++++++----------- centrallix-lib/tests/test_strtcpy_13.c | 145 ++++++++++++----------- centrallix-lib/tests/test_util_00.c | 71 +++++++---- centrallix-lib/tests/test_util_01.c | 73 ++++++++---- 118 files changed, 4450 insertions(+), 3904 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 42a5d7604..ad8f41dd5 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -66,7 +66,7 @@ start(void* v) *** larger value if Valgrind appears to be enabled. ***/ #ifdef USING_VALGRIND - alarm(90); /* Valgrind detected. */ + alarm(10); /* Valgrind detected. */ #else alarm(5); /* Normal timeout. */ #endif diff --git a/centrallix-lib/tests/test_00baseline.c b/centrallix-lib/tests/test_00baseline.c index b2012c6b1..3373c2050 100644 --- a/centrallix-lib/tests/test_00baseline.c +++ b/centrallix-lib/tests/test_00baseline.c @@ -3,17 +3,29 @@ #include #include #include +#include +#include "test_utils.h" -long long -test(char** tname) +/*** Number of no-op operations performed per call to doTests(). This is large + *** enough that the loop, rather than loopTests()'s own timer check, dominates + *** the measurement. + ***/ +#define OPS_PER_PASS (1000*1000) + +static bool +doTests(void) { int i; - int iter; int array[2] = {0}; - *tname = "BASELINE - should pass"; - iter = 1000*1000*300; - for(i=0;i #include "memstr.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; char buffer1[40]; char buffer2[20]; char * ptr; - *tname = "memstr-00 correct null ptr"; - iter = 64000; - for(i=0;i #include #include +#include #include "mtsession.h" #include "mtlexer.h" #include +#include "test_utils.h" -long long -test(char** tname) - { - int i; - int iter; - int flags; - pLxSession lxs; - int t; - int j; - char str[65536] = ""; - int n_flagtype = 4; - int n_tok = 3; - int flagtype[4] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; - int toktype[4][3] = { {MLX_TOK_EOF, MLX_TOK_ERROR, MLX_TOK_ERROR}, +#define N_FLAGTYPE 4 +#define N_TOK 3 + +static char str[65536] = ""; +static int flagtype[N_FLAGTYPE] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; +static int toktype[N_FLAGTYPE][N_TOK] = { + {MLX_TOK_EOF, MLX_TOK_ERROR, MLX_TOK_ERROR}, {MLX_TOK_EOL, MLX_TOK_EOF, MLX_TOK_ERROR}, {MLX_TOK_EOL, MLX_TOK_ERROR, MLX_TOK_ERROR}, {MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR} }; - *tname = "mtlexer-00 empty string and eol/eof/error test"; - - mssInitialize("system", "", "", 0, "test"); +static bool +doTests(void) + { + int f; + int j; + int t; + pLxSession lxs; - iter = 400000; - for(i=0;i #include #include +#include #include "mtsession.h" #include "mtlexer.h" #include +#include "test_utils.h" -long long -test(char** tname) - { - int i; - int iter; - int flags; - pLxSession lxs; - int t; - char* strval; - int j; - char str[65536] = "'hello world'"; - int n_flagtype = 4; - int n_tok = 4; - int flagtype[4] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; - int toktype[4][4] = { {MLX_TOK_STRING, MLX_TOK_EOF, MLX_TOK_ERROR, MLX_TOK_ERROR}, +#define N_FLAGTYPE 4 +#define N_TOK 4 + +static char str[65536] = "'hello world'"; +static int flagtype[N_FLAGTYPE] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; +static int toktype[N_FLAGTYPE][N_TOK] = { + {MLX_TOK_STRING, MLX_TOK_EOF, MLX_TOK_ERROR, MLX_TOK_ERROR}, {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF, MLX_TOK_ERROR}, {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_ERROR, MLX_TOK_ERROR}, {MLX_TOK_STRING, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR} }; - char* tokstr[4] = { "hello world", NULL, NULL, NULL }; - - *tname = "mtlexer-01 string token and eol/eof/error test"; +static char* tokstr[N_TOK] = { "hello world", NULL, NULL, NULL }; - mssInitialize("system", "", "", 0, "test"); +static bool +doTests(void) + { + int f; + int j; + int t; + char* strval; + pLxSession lxs; - iter = 400000; - for(i=0;i #include #include +#include #include "mtsession.h" #include "mtlexer.h" #include +#include "test_utils.h" -long long -test(char** tname) - { - int i; - int iter; - int flags; - pLxSession lxs; - int t; - char* strval; - int j; - int strcnt; - char str[65536] = "'string one' 'string two'\n'string three' 'string four'\r\n'string five'"; - int n_flagtype = 4; - int n_tok = 9; - int flagtype[4] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; - int toktype[4][9] = { +#define N_FLAGTYPE 4 +#define N_TOK 9 + +static char str[65536] = "'string one' 'string two'\n'string three' 'string four'\r\n'string five'"; +static int flagtype[N_FLAGTYPE] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; +static int toktype[N_FLAGTYPE][N_TOK] = { {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOF, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR}, {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }, {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_ERROR }, {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR}, }; - char* tokstr[6] = { "string one", "string two", "string three", "string four", "string five", NULL }; - - *tname = "mtlexer-02 three lines of strings and eol/eof/error test"; +static char* tokstr[6] = { "string one", "string two", "string three", "string four", "string five", NULL }; - mssInitialize("system", "", "", 0, "test"); +static bool +doTests(void) + { + int f; + int j; + int t; + int strcnt; + char* strval; + pLxSession lxs; - iter = 200000; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Longest line the sweep builds. **/ +#define MAX_LEN 6000 + +#define N_TOK 7 + +static char str[65536] = ""; +static int toktype[9] = {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }; +static char* tokstr[6]; + +/*** Line length under test, advanced by one on each pass and wrapped once the + *** longest case has been reached. A native run walks the whole sweep many + *** times over; a slower run, such as one under Valgrind, covers a prefix of + *** it rather than taking proportionally longer. + ***/ +static int sweep = 0; + +static bool +doTests(void) { int i; - int iter; - pLxSession lxs; - int t; - char* strval; - int alloc; int j; + int t; int strcnt; - char str[65536] = ""; - int n_tok = 7; - int toktype[9] = {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }; - char* tokstr[6]; - - *tname = "mtlexer-03 BID#156 - line length based failure"; + int alloc; + char* strval; + pLxSession lxs; - mssInitialize("system", "", "", 0, "test"); + if (sweep >= MAX_LEN) sweep = 0; + i = sweep++; - iter = 6000; + /** Both the input and the expected first token grow one 'a' per pass. **/ + strcpy(str+i, "a\r\nnextline\r\nthirdline"); + tokstr[0][i] = 'a'; + tokstr[0][i+1] = '\0'; - memset(str, 'a', iter+1); - tokstr[0] = malloc(iter+2); - memset(tokstr[0], 'a', iter+1); - tokstr[1] = "nextline"; - tokstr[2] = "thirdline"; - /*tokstr[0] = malloc(2046); - memset(tokstr[0], 'a', 2045); - tokstr[0][2045] = '\0'; - tokstr[1] = malloc(2045); - memset(tokstr[1], 'b', 2044); - tokstr[1][2044] = '\0'; - tokstr[2] = malloc(2047); - memset(tokstr[2], 'c', 2046); - tokstr[2][2046] = '\0'; - sprintf(str, "%s\r\n%s\r\n%s\r\n", tokstr[0], tokstr[1], tokstr[2]);*/ - - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +static char str[65536] = ""; + +static bool +doTests(void) { - int i; - int iter; pLxSession lxs; - char str[65536] = ""; - - *tname = "mtlexer-04 open/close session"; - - mssInitialize("system", "", "", 0, "test"); - iter = 700000; + lxs = mlxStringSession(str, MLX_F_EOL | MLX_F_EOF | MLX_F_IFSONLY); + assert(lxs != NULL); + mlxCloseSession(lxs); - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Integers in the data file, and so the ops performed by one pass. **/ +#define N_INTS 60000 + +static bool +doTests(void) { - int i; int j; int t; int n; - int iter; pLxSession lxs; pFile fd; - *tname = "mtlexer-05 integer data, one per line with eol/eof"; - - mssInitialize("system", "", "", 0, "test"); - - iter = 10; - - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Integers in the data file, and so the ops performed by one pass. **/ +#define N_INTS 60000 + +static bool +doTests(void) { - int i; int j; int t; int n; - int iter; pLxSession lxs; pFile fd; - *tname = "mtlexer-06 integer data, all on one line with eol/eof"; - - mssInitialize("system", "", "", 0, "test"); - - iter = 10; - - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +static char* reswds[] = { "reserved", NULL }; + +/** Token count and repeat count read from the data file by the last pass. **/ +static int n_tokens = 0; +static int n_iter = 0; + +static bool +doTests(void) { - int i; int j; int k; int t; - int iter; int flags; pLxSession lxs; pFile fd; int tokens[256]; - int n_tokens; - int n_iter; - char* reswds[] = { "reserved", NULL }; + + fd = fdOpen("tests/test_mtlexer_07.txt", O_RDONLY, 0600); + assert(fd != NULL); + flags = MLX_F_EOL | MLX_F_FILENAMES | MLX_F_DBLBRACE | MLX_F_SSTRING; + lxs = mlxOpenSession(fd, flags); + assert(lxs != NULL); + mlxSetReservedWords(lxs, reswds); + t = mlxNextToken(lxs); + assert(t == MLX_TOK_INTEGER); + n_iter = mlxIntVal(lxs); + assert(mlxNextToken(lxs) == MLX_TOK_EOL); + n_tokens = 0; + while((t = mlxNextToken(lxs)) == MLX_TOK_INTEGER) + { + assert(n_tokens < 256); + tokens[n_tokens++] = mlxIntVal(lxs); + assert(tokens[n_tokens-1] > 0 && tokens[n_tokens-1] <= MLX_TOK_MAX); + } + assert(t == MLX_TOK_EOL); + for(j=0;j 0 && tokens[n_tokens-1] <= MLX_TOK_MAX); - } - assert(t == MLX_TOK_EOL); - for(j=0;j 0) rval *= (long long)n_tokens * n_iter; - return iter * n_tokens * n_iter; + return rval; } diff --git a/centrallix-lib/tests/test_mtlexer_08.c b/centrallix-lib/tests/test_mtlexer_08.c index 315912840..680c35947 100644 --- a/centrallix-lib/tests/test_mtlexer_08.c +++ b/centrallix-lib/tests/test_mtlexer_08.c @@ -7,42 +7,41 @@ #include "mtsession.h" #include "mtlexer.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static char* teststr = "'string' 'test string' \"string\" \"test string\" 'string\\'s' \"\\\"string\\\"\" 'string\\\\string' 'string\"string' \"string'string\""; +static char* strs[] = {"string", "test string", "string", "test string", "string's", "\"string\"", "string\\string", "string\"string", "string'string", NULL}; + +static bool +doTests(void) { - int i; int cnt; int t; - char* teststr = "'string' 'test string' \"string\" \"test string\" 'string\\'s' \"\\\"string\\\"\" 'string\\\\string' 'string\"string' \"string'string\""; - char* strs[] = {"string", "test string", "string", "test string", "string's", "\"string\"", "string\\string", "string\"string", "string'string", NULL}; - char *str; - int iter; + char* str; pLxSession lxs; - *tname = "mtlexer-08 string quoting"; - - mssInitialize("system", "", "", 0, "test"); - - iter = 150000; - - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Longest string the sweep builds. **/ +#define MAX_LEN 6000 + +#define N_TOK 3 + +static char str[65536] = ""; +static int toktype[N_TOK] = {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }; +static char* tokstr[6]; + +/*** Line length under test, advanced by one on each pass and wrapped once the + *** longest case has been reached. A native run walks the whole sweep many + *** times over; a slower run, such as one under Valgrind, covers a prefix of + *** it rather than taking proportionally longer. + ***/ +static int sweep = 0; + +static bool +doTests(void) { int i; - int iter; - pLxSession lxs; - int t; - char* strval; - int alloc; int j; + int t; int strcnt; - char str[65536] = ""; - int n_tok = 3; - int toktype[3] = {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }; - char* tokstr[6]; - - *tname = "mtlexer-09 strings spanning multiple lines"; - - mssInitialize("system", "", "", 0, "test"); + int alloc; + char* strval; + pLxSession lxs; - iter = 6000; + if (sweep >= MAX_LEN-20) sweep = 0; + i = sweep++; - memset(str, 'a', iter+3); - tokstr[0] = malloc(iter+2); - memset(tokstr[0], 'a', iter+1); - str[0] = '"'; - str[iter+2] = '"'; + /** Put the line breaks in place for this pass. **/ + str[i+1] = '\r'; + str[i+2] = '\n'; + tokstr[0][i] = '\r'; + tokstr[0][i+1] = '\n'; + str[i+20] = '\n'; + tokstr[0][i+19] = '\n'; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) - { - int i,j; - char* teststr = "0 -1 1 -255 -256 255 256 -127 -128 127 128 32767 32768 -32767 -32768 65535 65536 -65535 -65536 16777216 -16777216 2147483647 -2147483648\n" +static char* teststr = "0 -1 1 -255 -256 255 256 -127 -128 127 128 32767 32768 -32767 -32768 65535 65536 -65535 -65536 16777216 -16777216 2147483647 -2147483648\n" ".0 .1 .10 .01 0.0 0.1 0.10 0.01 1.0 1.1 1.10 1.01 10.0 10.1 10.10 10.01 -.0 -.1 -.10 -.01 -0.0 -0.1 -0.10 -0.01 -1.0 -1.1 -1.10 -1.01 -10.0 -10.1 -10.10 -10.01"; - int integers[] = {0, -1, 1, -255, -256, 255, 256, -127, -128, 127, 128, 32767, 32768, -32767, -32768, 65535, 65536, -65535, -65536, 16777216, -16777216, 2147483647, -2147483647-1 }; - double doubles[] = {.0, .1, .10, .01, 0.0, 0.1, 0.10, 0.01, 1.0, 1.1, 1.10, 1.01, 10.0, 10.1, 10.10, 10.01, -.0, -.1, -.10, -.01, -0.0, -0.1, -0.10, -0.01, -1.0, -1.1, -1.10, -1.01, -10.0, -10.1, -10.10, -10.01}; +static int integers[] = {0, -1, 1, -255, -256, 255, 256, -127, -128, 127, 128, 32767, 32768, -32767, -32768, 65535, 65536, -65535, -65536, 16777216, -16777216, 2147483647, -2147483647-1 }; +static double doubles[] = {.0, .1, .10, .01, 0.0, 0.1, 0.10, 0.01, 1.0, 1.1, 1.10, 1.01, 10.0, 10.1, 10.10, 10.01, -.0, -.1, -.10, -.01, -0.0, -0.1, -0.10, -0.01, -1.0, -1.1, -1.10, -1.01, -10.0, -10.1, -10.10, -10.01}; + +#define N_INTEGERS (sizeof(integers)/sizeof(integers[0])) +#define N_DOUBLES (sizeof(doubles)/sizeof(doubles[0])) + +static bool +doTests(void) + { + unsigned int j; int n; double d; - int iter; pLxSession lxs; - *tname = "mtlexer-10 integer and double parsing"; - - mssInitialize("system", "", "", 0, "test"); - - iter = 15000; - - for(i=0;i #include #include +#include #include "mtsession.h" #include "mtlexer.h" #include +#include "test_utils.h" -long long -test(char** tname) - { - int i; - int iter; - int flags; - pLxSession lxs; - int t; - char* strval; - int j; - int strcnt; - char str[65536] = "select Select SELECT insert Insert INSERT what What WHAT"; - char* reswds[] = {"select", "insert", NULL}; - int n_flagtype = 4; - int n_tok = 9; - int flagtype[4] = {MLX_F_ICASE, MLX_F_ICASER, MLX_F_ICASEK, 0}; - int toktype[4][9] = { +#define N_FLAGTYPE 4 +#define N_TOK 9 + +static char str[65536] = "select Select SELECT insert Insert INSERT what What WHAT"; +static char* reswds[] = {"select", "insert", NULL}; +static int flagtype[N_FLAGTYPE] = {MLX_F_ICASE, MLX_F_ICASER, MLX_F_ICASEK, 0}; +static int toktype[N_FLAGTYPE][N_TOK] = { {MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD}, {MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_RESERVEDWD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD}, {MLX_TOK_RESERVEDWD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_RESERVEDWD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD}, {MLX_TOK_RESERVEDWD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_RESERVEDWD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD, MLX_TOK_KEYWORD}, }; - char* tokstr[4][9] ={ +static char* tokstr[N_FLAGTYPE][N_TOK] = { {"select","select","select", "insert","insert","insert", "what","what","what"}, {"select","select","select", "insert","insert","insert", "what","What","WHAT"}, {"select","select","select", "insert","insert","insert", "what","what","what"}, {"select","Select","SELECT", "insert","Insert","INSERT", "what","What","WHAT"}, }; - *tname = "mtlexer-11 case (in)sensitive keywords and reserved words"; - - mssInitialize("system", "", "", 0, "test"); +static bool +doTests(void) + { + int f; + int j; + int t; + int strcnt; + char* strval; + pLxSession lxs; - iter = 200000; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Longest keyword the sweep builds. **/ +#define MAX_LEN 20000 + +static char str[65536] = ""; + +/*** Length under test, advanced by one on each pass and wrapped once the + *** longest case has been reached. A native run walks the whole sweep many + *** times over; a slower run, such as one under Valgrind, covers a prefix of + *** it rather than taking proportionally longer. + ***/ +static int sweep = 0; + +static bool +doTests(void) { int i; - int iter; pLxSession lxs; char* strval; int alloc; - char str[65536] = ""; - - *tname = "mtlexer-12 normal/oversized keywords"; - mssInitialize("system", "", "", 0, "test"); + if (sweep >= MAX_LEN) sweep = 0; + i = sweep++; - iter = 20000; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Longest integer the sweep builds. **/ +#define MAX_LEN 20000 + +static char str[65536] = ""; + +/*** Length under test, advanced by one on each pass and wrapped once the + *** longest case has been reached. A native run walks the whole sweep many + *** times over; a slower run, such as one under Valgrind, covers a prefix of + *** it rather than taking proportionally longer. + ***/ +static int sweep = 0; + +/** The repunit str currently holds, grown alongside the sweep. **/ +static int iv = 0; + +static bool +doTests(void) { int i; - int iv; - int iter; - pLxSession lxs; int n; - char str[65536] = ""; - - *tname = "mtlexer-13 normal/oversized integers"; + pLxSession lxs; - mssInitialize("system", "", "", 0, "test"); + if (sweep >= MAX_LEN) + { + sweep = 0; + iv = 0; + } + i = sweep++; - iter = 20000; - iv=0; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Longest number the sweep builds. **/ +#define MAX_LEN 20000 + +static char str[65536] = ""; + +/*** Length under test, advanced by one on each pass and wrapped once the + *** longest case has been reached. A native run walks the whole sweep many + *** times over; a slower run, such as one under Valgrind, covers a prefix of + *** it rather than taking proportionally longer. + ***/ +static int sweep = 0; + +static bool +doTests(void) { int i; - int iter; - pLxSession lxs; double d; - char str[65536] = ""; - - *tname = "mtlexer-14 normal/oversized double floating point"; + pLxSession lxs; - mssInitialize("system", "", "", 0, "test"); + if (sweep >= MAX_LEN) sweep = 0; + i = sweep++; - iter = 20000; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Integers in the data file, and so the ops performed by one pass. **/ +#define N_INTS 12 + +static int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; + +#define N_FLAGTYPES ((int)(sizeof(flagtypes)/sizeof(flagtypes[0]))) + +static bool +doTests(void) { int i; int j; int t; int n; - int iter; - int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; - int n_flagtypes = 5; int flags; pLxSession lxs; pFile fd; - *tname = "mtlexer-15 comments // # ; -- /**/ long and short"; - - mssInitialize("system", "", "", 0, "test"); - - iter = 6000; - flags = 0; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +/** Integers in the data file, and so the ops performed by one pass. **/ +#define N_INTS 12 + +static int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; + +#define N_FLAGTYPES ((int)(sizeof(flagtypes)/sizeof(flagtypes[0]))) + +static bool +doTests(void) { int i; int j; int t; int n; - int iter; - int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; - int n_flagtypes = 5; int flags; pLxSession lxs; pFile fd; - *tname = "mtlexer-16 comments // # ; -- /**/ short only"; - - mssInitialize("system", "", "none", 0, "test"); - - iter = 6000; - flags = 0; - for(i=0;i #include #include +#include #include "mtsession.h" #include "mtlexer.h" #include +#include "test_utils.h" -long long -test(char** tname) - { - int i; - int iter; - int flags; - pLxSession lxs; - int t; - char* strval; - int j; - int strcnt; - char str[65536] = "\n\n'string one' 'string two'\n'string three' 'string four'\r\n'string five'\r\n\r\nString Six"; - int n_flagtype = 4; - int n_tok = 15; - int flagtype[4] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; - int toktype[4][15] = { +#define N_FLAGTYPE 4 +#define N_TOK 15 + +static char str[65536] = "\n\n'string one' 'string two'\n'string three' 'string four'\r\n'string five'\r\n\r\nString Six"; +static int flagtype[N_FLAGTYPE] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; +static int toktype[N_FLAGTYPE][N_TOK] = { {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOF, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR}, {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }, {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_ERROR }, {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR}, }; - char* tokstr[8] = { "\n", "\n", "'string one' 'string two'\n", "'string three' 'string four'\r\n", "'string five'\r\n", "\r\n", "String Six", NULL }; - - *tname = "mtlexer-17 LINEONLY mode test"; +static char* tokstr[8] = { "\n", "\n", "'string one' 'string two'\n", "'string three' 'string four'\r\n", "'string five'\r\n", "\r\n", "String Six", NULL }; - mssInitialize("system", "", "", 0, "test"); +static bool +doTests(void) + { + int f; + int j; + int t; + int strcnt; + char* strval; + pLxSession lxs; - iter = 100000; - for(i=0;i #include #include +#include #include "mtsession.h" #include "mtlexer.h" #include +#include "test_utils.h" -long long -test(char** tname) - { - int i; - int iter; - int flags; - pLxSession lxs; - int t; - char* strval; - int j; - int strcnt; - char str[65536] = "\n\n'string one' 'string two'\n'string three' 'string four'\r\n'string five'\r\n\r\nString Six\n"; - int n_flagtype = 4; - int n_tok = 15; - int flagtype[4] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; - int toktype[4][15] = { +#define N_FLAGTYPE 4 +#define N_TOK 15 + +static char str[65536] = "\n\n'string one' 'string two'\n'string three' 'string four'\r\n'string five'\r\n\r\nString Six\n"; +static int flagtype[N_FLAGTYPE] = {MLX_F_EOF, MLX_F_EOF | MLX_F_EOL, MLX_F_EOL, 0}; +static int toktype[N_FLAGTYPE][N_TOK] = { {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOF, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR}, {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }, {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_ERROR }, {MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR}, }; - char* tokstr[8] = { "\n", "\n", "'string one' 'string two'\n", "'string three' 'string four'\r\n", "'string five'\r\n", "\r\n", "String Six\n", NULL }; - - *tname = "mtlexer-18 LINEONLY mode test - file ends in newline"; +static char* tokstr[8] = { "\n", "\n", "'string one' 'string two'\n", "'string three' 'string four'\r\n", "'string five'\r\n", "\r\n", "String Six\n", NULL }; - mssInitialize("system", "", "", 0, "test"); +static bool +doTests(void) + { + int f; + int j; + int t; + int strcnt; + char* strval; + pLxSession lxs; - iter = 100000; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +#define N_TOK 16 + +static char str[65536] = "Header: 'Val1 Val2 Val3'\r\nHeader-2: Val1 'Val2 Val3'\r\nHeader-3: 'Val1 Val2' Val3\r\n"; +static int toktype[N_TOK] = {MLX_TOK_KEYWORD, MLX_TOK_COLON, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_KEYWORD, MLX_TOK_COLON, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_KEYWORD, MLX_TOK_COLON, MLX_TOK_STRING, MLX_TOK_KEYWORD, MLX_TOK_EOL, MLX_TOK_EOF}; +static char* tokstr[10] = { "Header", "'Val1", "Val2", "Val3'", "Header-2", " Val1 'Val2 Val3'\r\n", "Header-3", "Val1 Val2", "Val3", NULL }; +static int setflags[N_TOK] = {0, 0, MLX_F_IFSONLY, 0, 0, 0, 0, 0, MLX_F_LINEONLY, 0, 0, 0, 0, 0, 0, 0 }; +static int unsetflags[N_TOK] = {0, 0, 0, 0, 0, MLX_F_IFSONLY, 0, 0, 0, MLX_F_LINEONLY, 0, 0, 0, 0, 0, 0 }; + +static bool +doTests(void) { - int i; - int iter; - pLxSession lxs; - int t; - char* strval; int j; + int t; int strcnt; - char str[65536] = "Header: 'Val1 Val2 Val3'\r\nHeader-2: Val1 'Val2 Val3'\r\nHeader-3: 'Val1 Val2' Val3\r\n"; - int n_tok = 16; - int toktype[16] = {MLX_TOK_KEYWORD, MLX_TOK_COLON, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_KEYWORD, MLX_TOK_COLON, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_KEYWORD, MLX_TOK_COLON, MLX_TOK_STRING, MLX_TOK_KEYWORD, MLX_TOK_EOL, MLX_TOK_EOF}; - char* tokstr[10] = { "Header", "'Val1", "Val2", "Val3'", "Header-2", " Val1 'Val2 Val3'\r\n", "Header-3", "Val1 Val2", "Val3", NULL }; - int setflags[16] = {0, 0, MLX_F_IFSONLY, 0, 0, 0, 0, 0, MLX_F_LINEONLY, 0, 0, 0, 0, 0, 0, 0 }; - int unsetflags[16] = {0, 0, 0, 0, 0, MLX_F_IFSONLY, 0, 0, 0, MLX_F_LINEONLY, 0, 0, 0, 0, 0, 0 }; - - *tname = "mtlexer-19 enabling/disabling LINEONLY/IFSONLY during parsing"; - - mssInitialize("system", "", "", 0, "test"); + char* strval; + pLxSession lxs; - iter = 100000; - for(i=0;i +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; int j; int t; int n; - int iter; pLxSession lxs; pFile fd; char buf[256]; - *tname = "mtlexer-20 NODISCARD flag test"; - - mssInitialize("system", "", "", 0, "test"); - - iter = 60000; - - for(i=0;i= 0); - buf[n] = '\0'; - assert(strcmp(buf, "This is some text.\n") == 0); - fdClose(fd, 0); } + t = mlxNextToken(lxs); + assert(t == MLX_TOK_EOL); + mlxCloseSession(lxs); + n = fdRead(fd, buf, sizeof(buf) - 1, 0, 0); + assert(n >= 0); + buf[n] = '\0'; + assert(strcmp(buf, "This is some text.\n") == 0); + fdClose(fd, 0); - return iter; + return true; } +long long +test(char** tname) + { + *tname = "mtlexer-20 NODISCARD flag test"; + mssInitialize("system", "", "", 0, "test"); + return loopTests(doTests); + } diff --git a/centrallix-lib/tests/test_mtlexer_21.c b/centrallix-lib/tests/test_mtlexer_21.c index 59e4281fa..a8dc93fc4 100644 --- a/centrallix-lib/tests/test_mtlexer_21.c +++ b/centrallix-lib/tests/test_mtlexer_21.c @@ -7,46 +7,44 @@ #include "mtsession.h" #include "mtlexer.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; int j; int t; int n; - int iter; pLxSession lxs; pFile fd; char buf[256]; - *tname = "mtlexer-21 NODISCARD test (LINEONLY + ~EOL)"; - - mssInitialize("system", "", "", 0, "test"); - - iter = 60000; - - for(i=0;i= 0); - buf[n] = '\0'; - assert(strcmp(buf, "This is some text.\n") == 0); - fdClose(fd, 0); } + t = mlxNextToken(lxs); + assert(t == MLX_TOK_STRING); + mlxCloseSession(lxs); + n = fdRead(fd, buf, sizeof(buf) - 1, 0, 0); + assert(n >= 0); + buf[n] = '\0'; + assert(strcmp(buf, "This is some text.\n") == 0); + fdClose(fd, 0); - return iter; + return true; } +long long +test(char** tname) + { + *tname = "mtlexer-21 NODISCARD test (LINEONLY + ~EOL)"; + mssInitialize("system", "", "", 0, "test"); + return loopTests(doTests); + } diff --git a/centrallix-lib/tests/test_qprintf_00.c b/centrallix-lib/tests/test_qprintf_00.c index f8ad85dbc..841f40f1e 100644 --- a/centrallix-lib/tests/test_qprintf_00.c +++ b/centrallix-lib/tests/test_qprintf_00.c @@ -5,42 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "qprintf-00 constant string using snprintf()"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "qprintf-01 constant string using qpfPrintf()"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "qprintf-02 constant string with truncation, qpfPrintf()"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "qprintf-03 constant string, 1char overflow, using qpfPrintf()"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "qprintf-04 empty string using qpfPrintf()"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "qprintf-05 constant string into 0-sized buf using qpfPrintf()"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "qprintf-06 constant string into 1-sized buf using qpfPrintf()"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-07 %STR insertion in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-08 %STR insertion at end without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-09 %STR insertion at beginning without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-10 compare with snprintf(), %s inserted"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-11 %STR insertion in middle with overflow after STR"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-12 %STR insertion in middle with overflow in STR"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-13 %STR insertion in middle with overflow before STR"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-14 %INT insertion in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-15 %POS insertion in middle without overflow"; - iter = 200000; - for(i=0;i #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-16 %POS insertion without overflow, negative"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-17 %NSTR insertion in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-18 %NSTR insertion in middle with insert overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-19 %*STR insertion in middle with insert overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-20 %CHR insertion in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-21 %DBL insertion in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-22 %STR&NLEN in middle with insert overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-23 %STR&NLEN in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-24 %STR&*LEN in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-25 %STR&SYM in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-26 %STR&SYM in middle with illegal symbol"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-27 %STR&ESCQ in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-28 %STR&ESCQ in middle with overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-29 %STR&ESCQ in middle with overflow of insert"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-30 %STR&ESCQ in middle with 2-overflow of insert"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-31 %STR&ESCQ&NLEN in middle less than LEN"; - iter = 100000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-32 %STR&ESCQ&NLEN in middle eq to LEN"; - iter = 100000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-33 %STR&ESCQ&NLEN in middle 1 greater than LEN"; - iter = 100000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-34 %STR&ESCQ&NLEN in middle 2 greater than LEN"; - iter = 100000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-35 %STR&HTE in middle, no overflows"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); - assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); - assert(rval == 34); - assert(buf[43] == '\n'); - assert(buf[42] == '\0'); - assert(buf[41] == 0xff); - assert(buf[40] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[43] = '\n'; + buf[42] = '\0'; + buf[41] = 0xff; + buf[40] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); + assert(rval == 34); + assert(buf[43] == '\n'); + assert(buf[42] == '\0'); + assert(buf[41] == 0xff); + assert(buf[40] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-35 %STR&HTE in middle, no overflows"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_36.c b/centrallix-lib/tests/test_qprintf_36.c index cd41edc45..c48a4cf8b 100644 --- a/centrallix-lib/tests/test_qprintf_36.c +++ b/centrallix-lib/tests/test_qprintf_36.c @@ -5,43 +5,46 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-36 %STR&HTE in middle, overflow 1 char"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); - qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); - assert(!strcmp((char*)buf+4, "The HTML: '<b c="w"")); - assert(rval == 38); - assert(buf[39] == '\n'); - assert(buf[38] == '\0'); - assert(buf[37] == 0xff); - assert(buf[36] == '\0'); - assert(buf[35] != '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[39] = '\n'; + buf[38] = '\0'; + buf[37] = 0xff; + buf[36] = '\0'; + buf[35] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + assert(!strcmp((char*)buf+4, "The HTML: '<b c="w"")); + assert(rval == 38); + assert(buf[39] == '\n'); + assert(buf[38] == '\0'); + assert(buf[37] == 0xff); + assert(buf[36] == '\0'); + assert(buf[35] != '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-36 %STR&HTE in middle, overflow 1 char"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_37.c b/centrallix-lib/tests/test_qprintf_37.c index 8dad336b2..550dcb0ec 100644 --- a/centrallix-lib/tests/test_qprintf_37.c +++ b/centrallix-lib/tests/test_qprintf_37.c @@ -5,41 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-37 %STR&HTE&NLEN in middle, len 1 greater"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); - assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); - assert(rval == 34); - assert(buf[43] == '\n'); - assert(buf[42] == '\0'); - assert(buf[41] == 0xff); - assert(buf[40] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[43] = '\n'; + buf[42] = '\0'; + buf[41] = 0xff; + buf[40] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); + assert(rval == 34); + assert(buf[43] == '\n'); + assert(buf[42] == '\0'); + assert(buf[41] == 0xff); + assert(buf[40] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-37 %STR&HTE&NLEN in middle, len 1 greater"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_38.c b/centrallix-lib/tests/test_qprintf_38.c index 98815af42..55c0b6c0f 100644 --- a/centrallix-lib/tests/test_qprintf_38.c +++ b/centrallix-lib/tests/test_qprintf_38.c @@ -5,41 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-38 %STR&HTE&NLEN in middle, len equal"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); - assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); - assert(rval == 34); - assert(buf[43] == '\n'); - assert(buf[42] == '\0'); - assert(buf[41] == 0xff); - assert(buf[40] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[43] = '\n'; + buf[42] = '\0'; + buf[41] = 0xff; + buf[40] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); + assert(rval == 34); + assert(buf[43] == '\n'); + assert(buf[42] == '\0'); + assert(buf[41] == 0xff); + assert(buf[40] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-38 %STR&HTE&NLEN in middle, len equal"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_39.c b/centrallix-lib/tests/test_qprintf_39.c index 426b1f7af..479abbeed 100644 --- a/centrallix-lib/tests/test_qprintf_39.c +++ b/centrallix-lib/tests/test_qprintf_39.c @@ -5,41 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-39 %STR&HTE&NLEN in middle, len 1 less"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); - assert(!strcmp((char*)buf+4, "HTML: '<b c="w"'.")); - assert(rval == 30); - assert(buf[43] == '\n'); - assert(buf[42] == '\0'); - assert(buf[41] == 0xff); - assert(buf[40] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[43] = '\n'; + buf[42] = '\0'; + buf[41] = 0xff; + buf[40] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w"'.")); + assert(rval == 30); + assert(buf[43] == '\n'); + assert(buf[42] == '\0'); + assert(buf[41] == 0xff); + assert(buf[40] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-39 %STR&HTE&NLEN in middle, len 1 less"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_40.c b/centrallix-lib/tests/test_qprintf_40.c index bbc67eab7..334fa4edf 100644 --- a/centrallix-lib/tests/test_qprintf_40.c +++ b/centrallix-lib/tests/test_qprintf_40.c @@ -5,41 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-40 %STR&HTE&NLEN in middle, len 2 less"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); - qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); - assert(!strcmp((char*)buf+4, "HTML: '<b c="w"'.")); - assert(rval == 30); - assert(buf[43] == '\n'); - assert(buf[42] == '\0'); - assert(buf[41] == 0xff); - assert(buf[40] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[43] = '\n'; + buf[42] = '\0'; + buf[41] = 0xff; + buf[40] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w"'.")); + assert(rval == 30); + assert(buf[43] == '\n'); + assert(buf[42] == '\0'); + assert(buf[41] == 0xff); + assert(buf[40] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-40 %STR&HTE&NLEN in middle, len 2 less"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_41.c b/centrallix-lib/tests/test_qprintf_41.c index 4c127739d..aabad7ac1 100644 --- a/centrallix-lib/tests/test_qprintf_41.c +++ b/centrallix-lib/tests/test_qprintf_41.c @@ -5,41 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-41 %STR&HEX in middle, no overflows"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); - qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); - assert(!strcmp((char*)buf+4, "Encode: '3c6220633d2277223e'.")); - assert(rval == 29); - assert(buf[36] == '\n'); - assert(buf[35] == '\0'); - assert(buf[34] == 0xff); - assert(buf[33] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[36] = '\n'; + buf[35] = '\0'; + buf[34] = 0xff; + buf[33] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + assert(!strcmp((char*)buf+4, "Encode: '3c6220633d2277223e'.")); + assert(rval == 29); + assert(buf[36] == '\n'); + assert(buf[35] == '\0'); + assert(buf[34] == 0xff); + assert(buf[33] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-41 %STR&HEX in middle, no overflows"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_42.c b/centrallix-lib/tests/test_qprintf_42.c index eb8646f68..cc2abe93d 100644 --- a/centrallix-lib/tests/test_qprintf_42.c +++ b/centrallix-lib/tests/test_qprintf_42.c @@ -5,45 +5,48 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-42 %STR&HEX at end, overflow(1) in insert"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); - qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); - assert(!strcmp((char*)buf+4, "Encode: 3c6220633d227722")); - assert(rval == 26); - assert(buf[32] == '\n'); - assert(buf[31] == '\0'); - assert(buf[30] == 0xff); - assert(buf[29] == '\0'); - assert(buf[28] == '\0'); - assert(buf[27] != '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[32] = '\n'; + buf[31] = '\0'; + buf[30] = 0xff; + buf[29] = '\0'; + buf[28] = '\0'; + buf[27] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + assert(!strcmp((char*)buf+4, "Encode: 3c6220633d227722")); + assert(rval == 26); + assert(buf[32] == '\n'); + assert(buf[31] == '\0'); + assert(buf[30] == 0xff); + assert(buf[29] == '\0'); + assert(buf[28] == '\0'); + assert(buf[27] != '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-42 %STR&HEX at end, overflow(1) in insert"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_43.c b/centrallix-lib/tests/test_qprintf_43.c index a7a2f597a..f52d1efdf 100644 --- a/centrallix-lib/tests/test_qprintf_43.c +++ b/centrallix-lib/tests/test_qprintf_43.c @@ -5,45 +5,48 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-43 %STR&HEX at end, overflow(2) in insert"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); - qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); - assert(!strcmp((char*)buf+4, "Encode: 3c6220633d227722")); - assert(rval == 26); - assert(buf[32] == '\n'); - assert(buf[31] == '\0'); - assert(buf[30] == 0xff); - assert(buf[29] == '\0'); - assert(buf[28] == '\0'); - assert(buf[27] != '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[32] = '\n'; + buf[31] = '\0'; + buf[30] = 0xff; + buf[29] = '\0'; + buf[28] = '\0'; + buf[27] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + assert(!strcmp((char*)buf+4, "Encode: 3c6220633d227722")); + assert(rval == 26); + assert(buf[32] == '\n'); + assert(buf[31] == '\0'); + assert(buf[30] == 0xff); + assert(buf[29] == '\0'); + assert(buf[28] == '\0'); + assert(buf[27] != '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-43 %STR&HEX at end, overflow(2) in insert"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_44.c b/centrallix-lib/tests/test_qprintf_44.c index 4288ae108..5e17a3bd8 100644 --- a/centrallix-lib/tests/test_qprintf_44.c +++ b/centrallix-lib/tests/test_qprintf_44.c @@ -5,45 +5,48 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-44 %STR&HEX in middle, overflow(1) post-insert"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); - qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); - assert(!strcmp((char*)buf+4, "Enc: 3c6220633d2277223e..")); - assert(rval == 26); - assert(buf[32] == '\n'); - assert(buf[31] == '\0'); - assert(buf[30] == 0xff); - assert(buf[29] == '\0'); - assert(buf[28] != '\0'); - assert(buf[27] != '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[32] = '\n'; + buf[31] = '\0'; + buf[30] = 0xff; + buf[29] = '\0'; + buf[28] = '\0'; + buf[27] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d2277223e..")); + assert(rval == 26); + assert(buf[32] == '\n'); + assert(buf[31] == '\0'); + assert(buf[30] == 0xff); + assert(buf[29] == '\0'); + assert(buf[28] != '\0'); + assert(buf[27] != '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-44 %STR&HEX in middle, overflow(1) post-insert"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_45.c b/centrallix-lib/tests/test_qprintf_45.c index 8f53f8c32..38bde9b67 100644 --- a/centrallix-lib/tests/test_qprintf_45.c +++ b/centrallix-lib/tests/test_qprintf_45.c @@ -5,45 +5,48 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-45 %STR&HEX&NLEN in middle, no overflow"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); - qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); - assert(!strcmp((char*)buf+4, "Enc: 3c6220633d2277223e...")); - assert(rval == 26); - assert(buf[33] == '\n'); - assert(buf[32] == '\0'); - assert(buf[31] == 0xff); - assert(buf[30] == '\0'); - assert(buf[29] != '\0'); - assert(buf[28] != '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[33] = '\n'; + buf[32] = '\0'; + buf[31] = 0xff; + buf[30] = '\0'; + buf[29] = '\0'; + buf[28] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d2277223e...")); + assert(rval == 26); + assert(buf[33] == '\n'); + assert(buf[32] == '\0'); + assert(buf[31] == 0xff); + assert(buf[30] == '\0'); + assert(buf[29] != '\0'); + assert(buf[28] != '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-45 %STR&HEX&NLEN in middle, no overflow"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_46.c b/centrallix-lib/tests/test_qprintf_46.c index eade4eb5f..86c7d7e18 100644 --- a/centrallix-lib/tests/test_qprintf_46.c +++ b/centrallix-lib/tests/test_qprintf_46.c @@ -5,45 +5,48 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-46 %STR&HEX&NLEN in middle, insert overflow(1)"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); - qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); - assert(!strcmp((char*)buf+4, "Enc: 3c6220633d227722...")); - assert(rval == 24); - assert(buf[31] == '\n'); - assert(buf[30] == '\0'); - assert(buf[29] == 0xff); - assert(buf[28] == '\0'); - assert(buf[27] != '\0'); - assert(buf[26] != '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[31] = '\n'; + buf[30] = '\0'; + buf[29] = 0xff; + buf[28] = '\0'; + buf[27] = '\0'; + buf[26] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d227722...")); + assert(rval == 24); + assert(buf[31] == '\n'); + assert(buf[30] == '\0'); + assert(buf[29] == 0xff); + assert(buf[28] == '\0'); + assert(buf[27] != '\0'); + assert(buf[26] != '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-46 %STR&HEX&NLEN in middle, insert overflow(1)"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_47.c b/centrallix-lib/tests/test_qprintf_47.c index 714aac51b..e73e64298 100644 --- a/centrallix-lib/tests/test_qprintf_47.c +++ b/centrallix-lib/tests/test_qprintf_47.c @@ -5,45 +5,48 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-47 %STR&HEX&NLEN in middle, insert overflow(2)"; - iter = 100000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); - qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); - assert(!strcmp((char*)buf+4, "Enc: 3c6220633d227722...")); - assert(rval == 24); - assert(buf[31] == '\n'); - assert(buf[30] == '\0'); - assert(buf[29] == 0xff); - assert(buf[28] == '\0'); - assert(buf[27] != '\0'); - assert(buf[26] != '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[31] = '\n'; + buf[30] = '\0'; + buf[29] = 0xff; + buf[28] = '\0'; + buf[27] = '\0'; + buf[26] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d227722...")); + assert(rval == 24); + assert(buf[31] == '\n'); + assert(buf[30] == '\0'); + assert(buf[29] == 0xff); + assert(buf[28] == '\0'); + assert(buf[27] != '\0'); + assert(buf[26] != '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-47 %STR&HEX&NLEN in middle, insert overflow(2)"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_48.c b/centrallix-lib/tests/test_qprintf_48.c index ba83728d5..4227fb201 100644 --- a/centrallix-lib/tests/test_qprintf_48.c +++ b/centrallix-lib/tests/test_qprintf_48.c @@ -5,41 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-48 %STR" in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-49 %STR&DQUOT in middle without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-50 %STR" at end without overflow"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-51 %STR" at end with overflow(1)"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-52 %STR" at end with overflow(2)"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-53 Bugtest:   following %STR&HTE"; - iter = 200000; - for(i=0;i"); - qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); - qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); - rval = qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); - assert(!strcmp((char*)buf+4, "Here is the str: <tag> ")); - assert(rval == 34); - assert(buf[41] == '\n'); - assert(buf[40] == '\0'); - assert(buf[39] == 0xff); - assert(buf[38] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); - } + buf[41] = '\n'; + buf[40] = '\0'; + buf[39] = 0xff; + buf[38] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + assert(!strcmp((char*)buf+4, "Here is the str: <tag> ")); + assert(rval == 34); + assert(buf[41] == '\n'); + assert(buf[40] == '\0'); + assert(buf[39] == 0xff); + assert(buf[38] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; + } - return iter*4; +long long +test(char** tname) + { + *tname = "qprintf-53 Bugtest:   following %STR&HTE"; + return loopTests(doTests) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_54.c b/centrallix-lib/tests/test_qprintf_54.c index 1b9ebd1f1..125aead74 100644 --- a/centrallix-lib/tests/test_qprintf_54.c +++ b/centrallix-lib/tests/test_qprintf_54.c @@ -5,41 +5,44 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-54 Bugtest: %[ %] with static data"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-55 %STR&FILE valid filename"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-56 %STR&FILE various invalid filenames"; - iter = 100000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-57 %nSTR&FILE tests with fixed length insert"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-58 %STR&PATH valid pathname"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-59 %STR&PATH various invalid pathnames"; - iter = 100000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-60 %nSTR&PATH fixed-length insert tests"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-61 %STR&PATH&nLEN length-limited insert tests"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-62 %STR&DB64 integrity test"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-63 %STR&DB64 overflow test"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-64 %STR&B64 integrity test"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-65 %STR&B64 overflow test"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) -{ - int i, rval; - int iter; +static bool +doTests(void) + { + int rval; unsigned char buf[39]; - *tname = "qprintf-66 %LL insertion in middle without overflow"; - iter = 200000; - for(i=0;i INT_MAX - long long testNum = 2200000000ll; - - qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); - qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); - qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); - rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); - - assert(!strcmp((char*)buf+4, "Here is the ll: 2200000000...")); - //For the long long case, rval will be set to the return value of snprintf, i.e. length of string - assert(rval == 29); - assert(buf[34] == 0xff); - assert(buf[35] == '\n'); - assert(buf[36] == '\0'); - assert(buf[37] == 0xff); - assert(buf[38] == '\0'); - assert(buf[3] == '\n'); - assert(buf[2] == '\0'); - assert(buf[1] == 0xff); - assert(buf[0] == '\0'); + buf[34] = 0xff; + buf[35] = '\n'; + buf[36] = '\0'; + buf[37] = 0xff; + buf[38] = '\0'; + buf[3] = '\n'; + buf[2] = '\0'; + buf[1] = 0xff; + buf[0] = '\0'; + + /** Test value > INT_MAX. **/ + long long testNum = 2200000000ll; + + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); + + assert(!strcmp((char*)buf+4, "Here is the ll: 2200000000...")); + + /** For the long long case, rval is snprintf()'s return value, i.e. + ** the length of the string. + **/ + assert(rval == 29); + assert(buf[34] == 0xff); + assert(buf[35] == '\n'); + assert(buf[36] == '\0'); + assert(buf[37] == 0xff); + assert(buf[38] == '\0'); + assert(buf[3] == '\n'); + assert(buf[2] == '\0'); + assert(buf[1] == 0xff); + assert(buf[0] == '\0'); + + return true; } - return iter*4; -} +long long +test(char** tname) + { + *tname = "qprintf-66 %LL insertion in middle without overflow"; + return loopTests(doTests) * 4; + } diff --git a/centrallix-lib/tests/test_qprintf_67.c b/centrallix-lib/tests/test_qprintf_67.c index 38fd50235..a9b8a334f 100644 --- a/centrallix-lib/tests/test_qprintf_67.c +++ b/centrallix-lib/tests/test_qprintf_67.c @@ -5,51 +5,54 @@ #include #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-67 %STR&DHEX integrity test"; - iter = 200000; - for(i=0;i #include "qprintf.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i, rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "qprintf-68 %STR&DHEX overflow test"; - iter = 200000; - for(i=0;i #include #include "smmalloc.h" +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; pSmRegion r; - int iter; - smInitialize(); + r = smCreate(1024*1024); + if (!r) return false; + smDestroy(r); - *tname = "smmalloc-01 create/destroy region"; - iter = 30000; - for(i=0;i #include #include "smmalloc.h" +#include +#include "test_utils.h" + +/** Region shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; + +static bool +doTests(void) + { + void* ptr; + + ptr = smMalloc(region, 1024); + if (!ptr) return false; + smFree(ptr); + + return true; + } long long test(char** tname) { - int i; - pSmRegion r; - void* ptr; - int iter; + long long rval; + + *tname = "smmalloc-02 malloc/free 1024 bytes"; smInitialize(); + region = smCreate(1024*1024); + if (!region) return -1; - *tname = "smmalloc-02 malloc/free 1024 bytes"; - iter = 100000; - r = smCreate(1024*1024); - for(i=0;i #include "smmalloc.h" #include "smmalloc_private.h" +#include +#include "test_utils.h" -long long -test(char** tname) +/** Region shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; + +/** Blocks allocated by the most recent pass, used for the op count. **/ +static int blocks = 0; + +static bool +doTests(void) { - int i; - pSmRegion r; - int iter; int j,k; void* alloc[1024]; int min_blocks; - smInitialize(); - - *tname = "smmalloc-03 malloc/free 1MB, free order = LIFO, size=1K"; - iter = 300; /** Each allocation consumes a block header too, so how many 1K blocks ** fit in a 1MB region depends on the header size, not just on the ** region size. Leave slack for the region's own overhead. **/ min_blocks = ((1024*1024 - sizeof(SmRegion)) / (1024 + sizeof(SmBlock))) * 9 / 10; - r = smCreate(1024*1024); - for(i=0;i 0) { - j=0; - while((alloc[j] = smMalloc(r,1024)) != NULL && j < 1023) j++; - if (j < min_blocks) - { - smDestroy(r); - return -1; - } - k = j; - while(k > 0) - { - k--; - smFree(alloc[k]); - } + k--; + smFree(alloc[k]); } - smDestroy(r); - return iter*j; + return true; + } + +long long +test(char** tname) + { + long long rval; + + *tname = "smmalloc-03 malloc/free 1MB, free order = LIFO, size=1K"; + + smInitialize(); + region = smCreate(1024*1024); + if (!region) return -1; + + rval = loopTests(doTests); + + if (rval > 0) rval *= blocks; + + smDestroy(region); + region = NULL; + + return rval; } diff --git a/centrallix-lib/tests/test_smmalloc_04.c b/centrallix-lib/tests/test_smmalloc_04.c index 8947d7e85..b75993f29 100644 --- a/centrallix-lib/tests/test_smmalloc_04.c +++ b/centrallix-lib/tests/test_smmalloc_04.c @@ -5,43 +5,58 @@ #include #include "smmalloc.h" #include "smmalloc_private.h" +#include +#include "test_utils.h" -long long -test(char** tname) +/** Region shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; + +/** Blocks allocated by the most recent pass, used for the op count. **/ +static int blocks = 0; + +static bool +doTests(void) { - int i; - pSmRegion r; - int iter; int j,k; void* alloc[1024]; int min_blocks; - smInitialize(); - - *tname = "smmalloc-04 malloc/free 1MB, free order = FIFO, size=1K"; - iter = 200; /** Each allocation consumes a block header too, so how many 1K blocks ** fit in a 1MB region depends on the header size, not just on the ** region size. Leave slack for the region's own overhead. **/ min_blocks = ((1024*1024 - sizeof(SmRegion)) / (1024 + sizeof(SmBlock))) * 9 / 10; - r = smCreate(1024*1024); - for(i=0;i 0) rval *= blocks; + + smDestroy(region); + region = NULL; + + return rval; } diff --git a/centrallix-lib/tests/test_smmalloc_05.c b/centrallix-lib/tests/test_smmalloc_05.c index cf064bd75..1690a0478 100644 --- a/centrallix-lib/tests/test_smmalloc_05.c +++ b/centrallix-lib/tests/test_smmalloc_05.c @@ -6,53 +6,67 @@ #include #include "smmalloc.h" #include "smmalloc_private.h" +#include +#include "test_utils.h" -long long -test(char** tname) +/** Region shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; + +/** Blocks allocated by the most recent pass, used for the op count. **/ +static int blocks = 0; + +static bool +doTests(void) { - int i; - pSmRegion r; - int iter; int j,k,l; void* alloc[1024]; int min_blocks; - smInitialize(); - - *tname = "smmalloc-05 malloc/free 1MB, free order = random, size=1K"; - srand(time(NULL)); - iter = 300; /** Each allocation consumes a block header too, so how many 1K blocks ** fit in a 1MB region depends on the header size, not just on the ** region size. Leave slack for the region's own overhead. **/ min_blocks = ((1024*1024 - sizeof(SmRegion)) / (1024 + sizeof(SmBlock))) * 9 / 10; - r = smCreate(1024*1024); - for(i=0;i 0) rval *= blocks; + + smDestroy(region); + region = NULL; + + return rval; + } diff --git a/centrallix-lib/tests/test_smmalloc_06.c b/centrallix-lib/tests/test_smmalloc_06.c index f50915298..8afe32394 100644 --- a/centrallix-lib/tests/test_smmalloc_06.c +++ b/centrallix-lib/tests/test_smmalloc_06.c @@ -5,46 +5,60 @@ #include #include #include "smmalloc.h" +#include +#include "test_utils.h" -long long -test(char** tname) +/** Region shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; + +/** Blocks allocated by the most recent pass, used for the op count. **/ +static int blocks = 0; + +static bool +doTests(void) { - int i; - pSmRegion r; - int iter; int j,k,l; void* alloc[1024]; - smInitialize(); + j=0; + while((alloc[j] = smMalloc(region,1 + rand()%8192)) != NULL && j < 1023) j++; + if (j < 120) return false; + blocks = j; - *tname = "smmalloc-06 malloc/free 1MB, free order=random, size=[1-8192]"; - srand(time(NULL)); - iter = 2000; - r = smCreate(1024*1024); - for(i=0;i 0) rval *= blocks; + + smDestroy(region); + region = NULL; + + return rval; + } diff --git a/centrallix-lib/tests/test_smmalloc_07.c b/centrallix-lib/tests/test_smmalloc_07.c index 1fa5cb48b..8e82e265b 100644 --- a/centrallix-lib/tests/test_smmalloc_07.c +++ b/centrallix-lib/tests/test_smmalloc_07.c @@ -5,64 +5,79 @@ #include #include #include "smmalloc.h" +#include +#include "test_utils.h" -long long -test(char** tname) +/** Region shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; + +/** References freed by the most recent pass, used for the op count. **/ +static int refs = 0; + +static bool +doTests(void) { - int i; - pSmRegion r; - int iter; int j,k,l,t; void* alloc[1024]; int cnt[1024]; - smInitialize(); + /** allocate **/ + j=0; + while((alloc[j] = smMalloc(region,1 + rand()%8192)) != NULL && j < 1023) + { + cnt[j++] = 1; + } + if (j < 120) return false; - *tname = "smmalloc-07 reference counting (randomized free order)"; - srand(time(NULL)); - iter = 300; - r = smCreate(1024*1024); - for(i=0;i 0) rval *= refs; + + smDestroy(region); + region = NULL; + + return rval; + } diff --git a/centrallix-lib/tests/test_smmalloc_08.c b/centrallix-lib/tests/test_smmalloc_08.c index 63f4a8a85..0c0a8f22a 100644 --- a/centrallix-lib/tests/test_smmalloc_08.c +++ b/centrallix-lib/tests/test_smmalloc_08.c @@ -5,57 +5,68 @@ #include #include #include "smmalloc.h" +#include +#include "test_utils.h" -long long -test(char** tname) +/** Region shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; + +static bool +doTests(void) { - int i; - pSmRegion r; - int iter; int j,l,s; void* alloc[128]; int size[128]; int tsize; - smInitialize(); + tsize = 0; - *tname = "smmalloc-08 block realloc to 512K of 1M - 128 1K blocks"; - srand(time(NULL)); - iter = 300; - r = smCreate(1024*1024); - for(i=0;i=0;j--) - { - smFree(alloc[j]); - } + alloc[j] = smMalloc(region, 1024); + size[j] = 1024; + tsize += size[j]; } - smDestroy(r); - return iter*128; + /** realloc **/ + while(tsize < 512*1024) /* fill up half */ + { + l = rand()%j; + s = size[l] + rand()%2048; + alloc[l] = smRealloc(alloc[l], s); + if (!alloc[l]) return false; + tsize += (s - size[l]); + size[l] = s; + } + + /** free **/ + for(j=127;j>=0;j--) + { + smFree(alloc[j]); + } + + return true; } +long long +test(char** tname) + { + long long rval; + + *tname = "smmalloc-08 block realloc to 512K of 1M - 128 1K blocks"; + + smInitialize(); + srand(time(NULL)); + region = smCreate(1024*1024); + if (!region) return -1; + + rval = loopTests(doTests); + + if (rval > 0) rval *= 128; + + smDestroy(region); + region = NULL; + + return rval; + } diff --git a/centrallix-lib/tests/test_strtcpy_00.c b/centrallix-lib/tests/test_strtcpy_00.c index 090c57d94..c6e7ef4d8 100644 --- a/centrallix-lib/tests/test_strtcpy_00.c +++ b/centrallix-lib/tests/test_strtcpy_00.c @@ -5,42 +5,44 @@ #include #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "strtcpy-00 compare to strcpy()"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "strtcpy-01 compare to memccpy() and arr[n-1] = '\\0'"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i; - int iter; unsigned char buf[44]; - *tname = "strtcpy-02 compare to snprintf()"; - iter = 300000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "strtcpy-03 strtcpy() full-size non-overflow"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "strtcpy-04 strtcpy() overflow by 1 char"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "strtcpy-05 strtcpy() overflow / truncation"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "strtcpy-06 strtcpy() copy to 0-length buffer"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "strtcpy-07 strtcpy() copy to 1 char buffer"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +/*** Main reason to have both a 1 char and 2 char test is that the compiler's + *** optimization seems to partly unroll the loop into a copy-first-char and + *** then copy-subsequent-chars sections (since 1st char requires no index + *** offsets). + ***/ +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - /** - ** Main reason to have both a 1 char and 2 char test is that - ** the compiler's optimization seems to partly unroll the - ** loop into a copy-first-char and then copy-subsequent-chars - ** sections (since 1st char requires no index offsets) - **/ - *tname = "strtcpy-08 strtcpy() copy to 2 char buffer"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "strtcpy-09 strtcpy() use less than entire buffer"; - iter = 800000; - for(i=0;i #include "strtcpy.h" #include +#include +#include "test_utils.h" -long long -test(char** tname) +static bool +doTests(void) { - int i,rval; - int iter; + int rval; unsigned char buf[44]; - *tname = "strtcpy-10 strtcpy() copy empty string into nonempty buffer"; - iter = 800000; - for(i=0;i +#include #include #include #include +#include "test_utils.h" + #include "strtcpy.h" /** Working area is raw[GUARD] through raw[GUARD+AREA-1], guarded on both sides. **/ @@ -62,61 +65,62 @@ static Case cases[] = { "", "ab", 2, -2, "a", 1 }, }; -long long -test(char** tname) +/** Number of cases run per call to doTests(). **/ +#define NCASES ((int)(sizeof(cases) / sizeof(Case))) + +/*** This test verifies strtcatf() over a table of buffer sizes and append + *** lengths, checking the four things the function promises: the strtcat() + *** return convention (bytes appended including the null terminator, negated + *** when truncated, zero when nothing fits), that *pos is left on the + *** terminating null, that dst is always null-terminated, and that no byte + *** outside the caller's declared DstLen is ever touched. + ***/ +static bool +doTests(void) { - int i, c, rval; - int iter; - int ncases = sizeof(cases) / sizeof(Case); + int c, rval; unsigned char raw[RAW]; unsigned char snapshot[RAW]; char* dst = (char*)raw + GUARD; size_t pos; size_t n; - /*** This test verifies strtcatf() over a table of buffer sizes and - *** append lengths, checking the four things the function promises: - *** the strtcat() return convention (bytes appended including the - *** null terminator, negated when truncated, zero when nothing fits), - *** that *pos is left on the terminating null, that dst is always - *** null-terminated, and that no byte outside the caller's declared - *** DstLen is ever touched. - ***/ - - *tname = "strtcpy-11 strtcatf() return values and buffer bounds"; - iter = 40000; - for(i=0;i +#include #include #include #include #include +#include "test_utils.h" + #include "strtcpy.h" /** Working area is raw[GUARD] through raw[GUARD+AREA-1], guarded on both sides. **/ @@ -103,13 +106,23 @@ wrapper(char* dst, size_t dstlen, size_t* pos, const char* fmt, ...) return rval; } -long long -test(char** tname) +/** Number of chains run per call to doTests(). **/ +#define NCHAINS ((int)(sizeof(chains) / sizeof(Chain))) + +/*** This test verifies strtcatf_va() reached through a caller's own varargs + *** wrapper, which is the reason the _va entry point exists. Chains of + *** appends run through both entry points, and each append is checked against + *** its own expected return value and position as well as against the other + *** entry point, so that a fault common to both cannot hide. Chain lengths + *** run from one append to eight, and DstLen varies per chain, so that a + *** buffer can fill up early in a chain, exactly at its end, or not at all. + *** The chains also cover resuming onto text already in dst, formats + *** contributing no text, and empty appends at the front and back of a chain. + ***/ +static bool +doTests(void) { - int i, c, a; - int iter; - int nchains = sizeof(chains) / sizeof(Chain); - int nops; + int c, a; size_t prefixlen; unsigned char raw_direct[RAW]; unsigned char raw_va[RAW]; @@ -119,74 +132,70 @@ test(char** tname) int rval_direct, rval_va; size_t n; - /*** This test verifies strtcatf_va() reached through a caller's own - *** varargs wrapper, which is the reason the _va entry point exists. - *** Chains of appends run through both entry points, and each append - *** is checked against its own expected return value and position as - *** well as against the other entry point, so that a fault common to - *** both cannot hide. Chain lengths run from one append to eight, and - *** DstLen varies per chain, so that a buffer can fill up early in a - *** chain, exactly at its end, or not at all. The chains also cover - *** resuming onto text already in dst, formats contributing no text, - *** and empty appends at the front and back of a chain. - ***/ - - *tname = "strtcpy-12 strtcatf_va() parity and chained appends"; - iter = 40000; - nops = 0; - for(i=0;i +#include #include #include #include #include #include +#include "test_utils.h" + #include "strtcpy.h" /** Working area is raw[GUARD] through raw[GUARD+AREA-1], guarded on both sides. **/ @@ -51,88 +54,92 @@ static size_t bad_positions[] = (size_t)-1, }; -long long -test(char** tname) +/** Sizes of the case tables run per call to doTests(). **/ +#define NFMTS ((int)(sizeof(failing_fmts) / sizeof(failing_fmts[0]))) +#define NPFX ((int)(sizeof(prefixes) / sizeof(const char*))) +#define NBAD ((int)(sizeof(bad_positions) / sizeof(size_t))) + +/** Set by test() once the platform's vsnprintf() has been probed. **/ +static bool can_fail = false; + +/*** This test verifies that strtcatf() refuses bad input safely, covering the + *** two paths ordinary appends never reach. When a conversion fails, the + *** append must be abandoned and the string already in dst left intact, even + *** though vsnprintf() may have written part of its output first. A *pos at + *** or past the end of dst, including one large enough to overflow the guard's + *** own arithmetic, must append nothing. Both return 0, leave *pos alone, and + *** write nothing outside the caller's dstlen. + ***/ +static bool +doTests(void) { - int i, c, f, rval; - int iter; - int nfmts = sizeof(failing_fmts) / sizeof(failing_fmts[0]); - int npfx = sizeof(prefixes) / sizeof(const char*); - int nbad = sizeof(bad_positions) / sizeof(size_t); - int ncases; - int can_fail; + int c, f, rval; unsigned char raw[RAW]; char* dst = (char*)raw + GUARD; - char probe[32]; size_t pos; size_t n; - /*** This test verifies that strtcatf() refuses bad input safely, - *** covering the two paths ordinary appends never reach. When a - *** conversion fails, the append must be abandoned and the string - *** already in dst left intact, even though vsnprintf() may have - *** written part of its output first. A *pos at or past the end of - *** dst, including one large enough to overflow the guard's own - *** arithmetic, must append nothing. Both return 0, leave *pos - *** alone, and write nothing outside the caller's dstlen. - ***/ - - *tname = "strtcpy-13 strtcatf() failed conversions and bad positions"; - - /** Only run the conversion cases where the platform really fails. **/ - setlocale(LC_ALL, "C"); - can_fail = (snprintf(probe, sizeof(probe), failing_fmts[0], unconvertible) < 0); - if (!can_fail) - printf("(vsnprintf() converts %%ls here, skipping those cases) "); - ncases = nbad + (can_fail ? nfmts * npfx : 0); - - iter = 40000; - for(i=0;i GUARD + 3 ? 0xAA : "abc"[n-GUARD])); + assert(pos == strlen(prefixes[f])); + assert(!strcmp(dst, prefixes[f])); + + /** Partial output may remain, but never outside dstlen. **/ + for(n=0;n GUARD + 3 ? 0xAA : "abc"[n-GUARD])); + } + + return true; + } + +long long +test(char** tname) + { + char probe[32]; + int ncases; + + *tname = "strtcpy-13 strtcatf() failed conversions and bad positions"; + + /** Only run the conversion cases where the platform really fails. **/ + setlocale(LC_ALL, "C"); + can_fail = (snprintf(probe, sizeof(probe), failing_fmts[0], unconvertible) < 0); + if (!can_fail) + printf("(vsnprintf() converts %%ls here, skipping those cases) "); + ncases = NBAD + (can_fail ? NFMTS * NPFX : 0); + + return loopTests(doTests) * ncases; } diff --git a/centrallix-lib/tests/test_util_00.c b/centrallix-lib/tests/test_util_00.c index 2ee0a4726..9cea94b4e 100644 --- a/centrallix-lib/tests/test_util_00.c +++ b/centrallix-lib/tests/test_util_00.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* Copyright (C) 2005-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -14,37 +14,64 @@ /* Description: Test strtoi */ /************************************************************************/ +#include #include #include #include #include #include +#include "test_utils.h" + #include "util.h" #define TXT_SIZE 1024 #define RANGE 500000 -long long -test(char** tname){ + +/** Values from the sweep converted per call to doTests(). **/ +#define CHUNK 10000 + +/*** Next value in the sweep, advanced by CHUNK on each pass and wrapped once + *** the whole range has been covered. A native run walks the range many times + *** over; a slower run, such as one under Valgrind, covers a part of it rather + *** than taking proportionally longer. + ***/ +static int sweep = -RANGE; + +static bool +doTests(void) + { int i; + int end; char text[TXT_SIZE]=""; + + if (sweep >= RANGE) sweep = -RANGE; + end = sweep + CHUNK; + if (end > RANGE) end = RANGE; + + for(i=sweep; i #include #include #include #include #include +#include "test_utils.h" + #include "util.h" #define TXT_SIZE 1024 #define RANGE 500000 -long long -test(char** tname){ + +/** Values from the sweep converted per call to doTests(). **/ +#define CHUNK 10000 + +/*** Next value in the sweep, advanced by CHUNK on each pass and wrapped once + *** the whole range has been covered. A native run walks the range many times + *** over; a slower run, such as one under Valgrind, covers a part of it rather + *** than taking proportionally longer. + ***/ +static int sweep = -RANGE; + +static bool +doTests(void) + { int i; + int end; char text[TXT_SIZE]=""; + + if (sweep >= RANGE) sweep = -RANGE; + end = sweep + CHUNK; + if (end > RANGE) end = RANGE; + + for(i=sweep; i Date: Tue, 8 Sep 2026 16:56:26 -0600 Subject: [PATCH 149/361] Increase test loop time to improve precision. --- centrallix-lib/tests/test_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 76a9bb9c8..ce2d734c5 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -27,7 +27,7 @@ *** time than this might not use enough cpu cycles, confusing the test suite *** performance tracking. ***/ -#define MIN_TEST_SECONDS 0.02 +#define MIN_TEST_SECONDS 0.1 /*** Expect two values to be equal. *** From e0a72f42b314d516f141194b4ee23bc29dcc227f Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 16:57:13 -0600 Subject: [PATCH 150/361] Refactor lockup time. --- centrallix-lib/tests/t_driver.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index ad8f41dd5..2293dace2 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -24,6 +24,16 @@ #include "mtask.h" #include "util.h" +/*** Valgrind instruments every memory access, so a test needs a good deal + *** longer to finish under it before it can fairly be called locked up. + ***/ +#ifdef USING_VALGRIND +#include "valgrind/valgrind.h" +#define LOCKUP_SECONDS (RUNNING_ON_VALGRIND ? 10u : 5u) +#else +#define LOCKUP_SECONDS 5u +#endif + long long test(char**); @@ -62,14 +72,8 @@ start(void* v) signal(SIGABRT, abort_handler); signal(SIGALRM, alarm_handler); - /*** Set a timer before Lockup is triggered, using a significantly - *** larger value if Valgrind appears to be enabled. - ***/ - #ifdef USING_VALGRIND - alarm(10); /* Valgrind detected. */ - #else - alarm(5); /* Normal timeout. */ - #endif + /** Set a timer before Lockup is triggered. **/ + alarm(LOCKUP_SECONDS); /** Run the test while tracking CPU time. **/ times(&t); From e5acd783dd13f1a8005d3a645967beb8fc351cfd Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 17:08:08 -0600 Subject: [PATCH 151/361] Implement parallel test runner. --- centrallix-lib/Makefile.in | 96 ++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 6252b5101..3988e3819 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -83,6 +83,14 @@ VALGRIND?=valgrind VALGRINDFLAGS?=-q VALGRINDLOG?= +## How many tests "make test" and "make valtest" run at once. Each test is a +## single-threaded process of its own, so this scales with the core count. +## Set TESTJOBS=1 to run them one at a time; the results are printed in the +## same order either way. Note that the driver's lockup timeout is wall clock, +## so a job count well above the core count risks reporting LOCKUP for a test +## that was merely waiting for a core. +TESTJOBS?=$(shell nproc 2>/dev/null || echo 1) + .SUFFIXES: .c .o .lo .PHONY: all @@ -167,15 +175,62 @@ install: all %.bin: %.c tests/t_driver.c libCentrallix.a $(CC) $(TCFLAGS) tests/t_driver.c $< libCentrallix.a -o $@ ${LIBS} +## Runs the selected tests $(TESTJOBS) at a time. Output is captured per test +## rather than written straight to the terminal, then printed in listing order +## as each test finishes, so parallel tests cannot interleave. +## +## TESTCMD runs once per test with the binary as "$$1" and its capture file as +## "$$2". It writes to "$$2.part" and renames that to "$$2" when done, since +## the rename is what marks the test finished, and it exits with the test's own +## status. The driver exits 0 even when reporting FAIL or CRASH (see +## tests/t_driver.c), so a nonzero status means the test did not complete +## normally and must fail the target; only valgrind's --error-exitcode is +## swallowed. A test whose output never appears is reported in its place and +## fails the target too. +RUN_TESTS = \ + RESULTS=$$(mktemp -d) || exit 1; \ + trap 'rm -rf "$$RESULTS"' EXIT INT TERM; \ + N=0; \ + for PROG in $$( ls $(TESTPROGS) ); do \ + case "$$PROG" in tests/test_$$TONLY*) \ + N=$$((N+1)); \ + printf '%s\n%s/%04d.out\n' "$$PROG" "$$RESULTS" "$$N";; \ + esac; \ + done > "$$RESULTS/list"; \ + if [ "$$N" -eq 0 ]; then exit 0; fi; \ + xargs -r -n 2 -P $(TESTJOBS) sh -c "$$TESTCMD" sh \ + < "$$RESULTS/list" 2> "$$RESULTS/stderr" & \ + XARGS=$$!; \ + RC=0; \ + I=0; \ + while [ "$$I" -lt "$$N" ]; do \ + I=$$((I+1)); \ + OUT=$$(printf '%s/%04d.out' "$$RESULTS" "$$I"); \ + while [ ! -e "$$OUT" ]; do \ + kill -0 "$$XARGS" 2>/dev/null || break; \ + sleep 0.05; \ + done; \ + if [ -e "$$OUT" ]; then \ + cat "$$OUT"; \ + else \ + printf '%-62.62s FAIL (produced no result)\n' \ + "$$(sed -n "$$((2 * I - 1))p" "$$RESULTS/list")"; \ + RC=1; \ + fi; \ + done; \ + wait "$$XARGS" || RC=$$?; \ + cat "$$RESULTS/stderr" >&2; \ + exit "$$RC" + .PHONY: test test: $(TESTPROGS) @printf "%-62.62s %s\n" "Test Name" "Stat Ops/sec" @printf "%-62.62s %s\n" "----------------------------------------------------------------" "--------------" - @for PROG in $$( ls $(TESTPROGS) ); do \ - if [ "$$TONLY" = "" -o "$${PROG#tests/test_$$TONLY}" != "$$PROG" ]; then \ - $$PROG; \ - fi \ - done + @TESTCMD='"$$1" > "$$2.part" 2>&1; \ + S=$$?; \ + mv "$$2.part" "$$2"; \ + exit $$S'; \ + $(RUN_TESTS) .PHONY: valtest valtest: $(TESTPROGS) @@ -183,21 +238,24 @@ valtest: $(TESTPROGS) @printf "%-62.62s %s\n" "Test Name" "Stat Ops/sec" @printf "%-62.62s %s\n" "----------------------------------------------------------------" "--------------" @if [ "$(VALGRINDLOG)" != "" ]; then mkdir -p "$(VALGRINDLOG)"; fi - @for PROG in $$( ls $(TESTPROGS) ); do \ - if [ "$$TONLY" = "" -o "$${PROG#tests/test_$$TONLY}" != "$$PROG" ]; then \ - NAME=$${PROG##*/}; \ - NAME=$${NAME%.bin}; \ - if [ "$(VALGRINDLOG)" = "" ]; then \ - $(VALGRIND) $(VALGRINDFLAGS) $$PROG; \ - else \ - LOG="$(VALGRINDLOG)/$$NAME.log"; \ - $(VALGRIND) $(VALGRINDFLAGS) --error-exitcode=125 --log-file="$$LOG" $$PROG; \ - if [ "$$?" = "125" ]; then \ - printf " valgrind: errors reported -- %s\n" "$$LOG"; \ - fi; \ + @if [ "$(VALGRINDLOG)" = "" ]; then \ + TESTCMD='$(VALGRIND) $(VALGRINDFLAGS) "$$1" > "$$2.part" 2>&1; \ + S=$$?; \ + mv "$$2.part" "$$2"; \ + exit $$S'; \ + else \ + TESTCMD='NAME=$${1##*/}; \ + LOG="$(VALGRINDLOG)/$${NAME%.bin}.log"; \ + $(VALGRIND) $(VALGRINDFLAGS) --error-exitcode=125 --log-file="$$LOG" "$$1" > "$$2.part" 2>&1; \ + S=$$?; \ + if [ "$$S" = "125" ]; then \ + printf " valgrind: errors reported -- %s\n" "$$LOG" >> "$$2.part"; \ + S=0; \ fi; \ - fi \ - done + mv "$$2.part" "$$2"; \ + exit $$S'; \ + fi; \ + $(RUN_TESTS) .PHONY: cov-clean cov-clean: From 97fdc1ad27b42036a7175a6d73443d7454606e23 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 17:30:40 -0600 Subject: [PATCH 152/361] Refactor lockup times again. --- centrallix-lib/tests/t_driver.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 2293dace2..66cf09e76 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -24,14 +24,18 @@ #include "mtask.h" #include "util.h" -/*** Valgrind instruments every memory access, so a test needs a good deal - *** longer to finish under it before it can fairly be called locked up. +/*** Define lockup times. Valgrind instruments every memory access, so tests + *** may need longer to finish when running under valgrind. ***/ +#define VALGRIND_LOCKUP_SECONDS 10u +#define NORMAL_LOCKUP_SECONDS 5u + +/** Detect valgrind. **/ #ifdef USING_VALGRIND #include "valgrind/valgrind.h" -#define LOCKUP_SECONDS (RUNNING_ON_VALGRIND ? 10u : 5u) +#define LOCKUP_SECONDS ((RUNNING_ON_VALGRIND) ? VALGRIND_LOCKUP_SECONDS : NORMAL_LOCKUP_SECONDS) #else -#define LOCKUP_SECONDS 5u +#define LOCKUP_SECONDS NORMAL_LOCKUP_SECONDS #endif @@ -67,12 +71,10 @@ start(void* v) clock_t start,end; long long rval; - /** Register handlers for signals that may occur during a test. **/ + /** Setup handlers for signals that may occur during a test. **/ signal(SIGSEGV, segv_handler); signal(SIGABRT, abort_handler); signal(SIGALRM, alarm_handler); - - /** Set a timer before Lockup is triggered. **/ alarm(LOCKUP_SECONDS); /** Run the test while tracking CPU time. **/ From fa89fb5566dda767b1c1b05e43f7855adc569802 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 17:39:53 -0600 Subject: [PATCH 153/361] Reduce lockup times to improve test performance. --- centrallix-lib/tests/t_driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 66cf09e76..8600ef9a8 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -27,8 +27,8 @@ /*** Define lockup times. Valgrind instruments every memory access, so tests *** may need longer to finish when running under valgrind. ***/ -#define VALGRIND_LOCKUP_SECONDS 10u -#define NORMAL_LOCKUP_SECONDS 5u +#define NORMAL_LOCKUP_SECONDS 4u +#define VALGRIND_LOCKUP_SECONDS NORMAL_LOCKUP_SECONDS /** Detect valgrind. **/ #ifdef USING_VALGRIND From 9f1bd00b2a013875752c0dab796e355d4a5dd3f2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 17:40:18 -0600 Subject: [PATCH 154/361] Trim comments. --- centrallix-lib/Makefile.in | 10 ++++------ centrallix-lib/tests/test_00baseline.c | 5 +---- centrallix-lib/tests/test_mtlexer_03.c | 6 +----- centrallix-lib/tests/test_mtlexer_09.c | 6 +----- centrallix-lib/tests/test_mtlexer_12.c | 6 +----- centrallix-lib/tests/test_mtlexer_13.c | 6 +----- centrallix-lib/tests/test_mtlexer_14.c | 6 +----- centrallix-lib/tests/test_util_00.c | 6 +----- centrallix-lib/tests/test_util_01.c | 6 +----- 9 files changed, 12 insertions(+), 45 deletions(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 3988e3819..55aa5b23a 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -83,12 +83,10 @@ VALGRIND?=valgrind VALGRINDFLAGS?=-q VALGRINDLOG?= -## How many tests "make test" and "make valtest" run at once. Each test is a -## single-threaded process of its own, so this scales with the core count. -## Set TESTJOBS=1 to run them one at a time; the results are printed in the -## same order either way. Note that the driver's lockup timeout is wall clock, -## so a job count well above the core count risks reporting LOCKUP for a test -## that was merely waiting for a core. +## How many tests "make test" and "make valtest" run at once (1 for one at a +## time). Each test is a single-threaded process, so this scales with the core +## count; the driver's lockup timeout is wall clock, so going well past the +## core count risks reporting LOCKUP for a test that was only waiting for one. TESTJOBS?=$(shell nproc 2>/dev/null || echo 1) .SUFFIXES: .c .o .lo diff --git a/centrallix-lib/tests/test_00baseline.c b/centrallix-lib/tests/test_00baseline.c index 3373c2050..06783ff06 100644 --- a/centrallix-lib/tests/test_00baseline.c +++ b/centrallix-lib/tests/test_00baseline.c @@ -6,10 +6,7 @@ #include #include "test_utils.h" -/*** Number of no-op operations performed per call to doTests(). This is large - *** enough that the loop, rather than loopTests()'s own timer check, dominates - *** the measurement. - ***/ +/** No-op operations per pass, enough that the loop dominates the timing. **/ #define OPS_PER_PASS (1000*1000) static bool diff --git a/centrallix-lib/tests/test_mtlexer_03.c b/centrallix-lib/tests/test_mtlexer_03.c index 3460f8a39..e007331e9 100644 --- a/centrallix-lib/tests/test_mtlexer_03.c +++ b/centrallix-lib/tests/test_mtlexer_03.c @@ -19,11 +19,7 @@ static char str[65536] = ""; static int toktype[9] = {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }; static char* tokstr[6]; -/*** Line length under test, advanced by one on each pass and wrapped once the - *** longest case has been reached. A native run walks the whole sweep many - *** times over; a slower run, such as one under Valgrind, covers a prefix of - *** it rather than taking proportionally longer. - ***/ +/** Line length under test, advanced each pass and wrapped at the longest case. **/ static int sweep = 0; static bool diff --git a/centrallix-lib/tests/test_mtlexer_09.c b/centrallix-lib/tests/test_mtlexer_09.c index 347eec35e..ec3447ad7 100644 --- a/centrallix-lib/tests/test_mtlexer_09.c +++ b/centrallix-lib/tests/test_mtlexer_09.c @@ -19,11 +19,7 @@ static char str[65536] = ""; static int toktype[N_TOK] = {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }; static char* tokstr[6]; -/*** Line length under test, advanced by one on each pass and wrapped once the - *** longest case has been reached. A native run walks the whole sweep many - *** times over; a slower run, such as one under Valgrind, covers a prefix of - *** it rather than taking proportionally longer. - ***/ +/** Line length under test, advanced each pass and wrapped at the longest case. **/ static int sweep = 0; static bool diff --git a/centrallix-lib/tests/test_mtlexer_12.c b/centrallix-lib/tests/test_mtlexer_12.c index ac7dd1529..87031b10c 100644 --- a/centrallix-lib/tests/test_mtlexer_12.c +++ b/centrallix-lib/tests/test_mtlexer_12.c @@ -15,11 +15,7 @@ static char str[65536] = ""; -/*** Length under test, advanced by one on each pass and wrapped once the - *** longest case has been reached. A native run walks the whole sweep many - *** times over; a slower run, such as one under Valgrind, covers a prefix of - *** it rather than taking proportionally longer. - ***/ +/** Length under test, advanced each pass and wrapped at the longest case. **/ static int sweep = 0; static bool diff --git a/centrallix-lib/tests/test_mtlexer_13.c b/centrallix-lib/tests/test_mtlexer_13.c index 838e32b53..8f746bff3 100644 --- a/centrallix-lib/tests/test_mtlexer_13.c +++ b/centrallix-lib/tests/test_mtlexer_13.c @@ -15,11 +15,7 @@ static char str[65536] = ""; -/*** Length under test, advanced by one on each pass and wrapped once the - *** longest case has been reached. A native run walks the whole sweep many - *** times over; a slower run, such as one under Valgrind, covers a prefix of - *** it rather than taking proportionally longer. - ***/ +/** Length under test, advanced each pass and wrapped at the longest case. **/ static int sweep = 0; /** The repunit str currently holds, grown alongside the sweep. **/ diff --git a/centrallix-lib/tests/test_mtlexer_14.c b/centrallix-lib/tests/test_mtlexer_14.c index 8ae37f2a6..a571de25c 100644 --- a/centrallix-lib/tests/test_mtlexer_14.c +++ b/centrallix-lib/tests/test_mtlexer_14.c @@ -15,11 +15,7 @@ static char str[65536] = ""; -/*** Length under test, advanced by one on each pass and wrapped once the - *** longest case has been reached. A native run walks the whole sweep many - *** times over; a slower run, such as one under Valgrind, covers a prefix of - *** it rather than taking proportionally longer. - ***/ +/** Length under test, advanced each pass and wrapped at the longest case. **/ static int sweep = 0; static bool diff --git a/centrallix-lib/tests/test_util_00.c b/centrallix-lib/tests/test_util_00.c index 9cea94b4e..7cf920985 100644 --- a/centrallix-lib/tests/test_util_00.c +++ b/centrallix-lib/tests/test_util_00.c @@ -31,11 +31,7 @@ /** Values from the sweep converted per call to doTests(). **/ #define CHUNK 10000 -/*** Next value in the sweep, advanced by CHUNK on each pass and wrapped once - *** the whole range has been covered. A native run walks the range many times - *** over; a slower run, such as one under Valgrind, covers a part of it rather - *** than taking proportionally longer. - ***/ +/** Next value in the sweep, advanced by CHUNK and wrapped at the end. **/ static int sweep = -RANGE; static bool diff --git a/centrallix-lib/tests/test_util_01.c b/centrallix-lib/tests/test_util_01.c index 2f5945bc1..be47524f6 100644 --- a/centrallix-lib/tests/test_util_01.c +++ b/centrallix-lib/tests/test_util_01.c @@ -31,11 +31,7 @@ /** Values from the sweep converted per call to doTests(). **/ #define CHUNK 10000 -/*** Next value in the sweep, advanced by CHUNK on each pass and wrapped once - *** the whole range has been covered. A native run walks the range many times - *** over; a slower run, such as one under Valgrind, covers a part of it rather - *** than taking proportionally longer. - ***/ +/** Next value in the sweep, advanced by CHUNK and wrapped at the end. **/ static int sweep = -RANGE; static bool From a718790d6aff764c5260a1731f501a3cca277c4e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 8 Sep 2026 17:55:39 -0600 Subject: [PATCH 155/361] Tweak lockup timeouts. --- centrallix-lib/tests/t_driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 8600ef9a8..cf7f2f9c5 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -27,8 +27,8 @@ /*** Define lockup times. Valgrind instruments every memory access, so tests *** may need longer to finish when running under valgrind. ***/ -#define NORMAL_LOCKUP_SECONDS 4u -#define VALGRIND_LOCKUP_SECONDS NORMAL_LOCKUP_SECONDS +#define NORMAL_LOCKUP_SECONDS 5u +#define VALGRIND_LOCKUP_SECONDS 10u /** Detect valgrind. **/ #ifdef USING_VALGRIND From f54378ba9d89814963990a6696601f9551965e2e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 12:59:27 -0600 Subject: [PATCH 156/361] Move the lockup time calculation to test_utils.h so that tests can use it. --- centrallix-lib/tests/t_driver.c | 15 +-------------- centrallix-lib/tests/test_utils.h | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index cf7f2f9c5..2d9648d73 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -22,22 +22,9 @@ #include "cxlibconfig-internal.h" #include "mtask.h" +#include "test_utils.h" #include "util.h" -/*** Define lockup times. Valgrind instruments every memory access, so tests - *** may need longer to finish when running under valgrind. - ***/ -#define NORMAL_LOCKUP_SECONDS 5u -#define VALGRIND_LOCKUP_SECONDS 10u - -/** Detect valgrind. **/ -#ifdef USING_VALGRIND -#include "valgrind/valgrind.h" -#define LOCKUP_SECONDS ((RUNNING_ON_VALGRIND) ? VALGRIND_LOCKUP_SECONDS : NORMAL_LOCKUP_SECONDS) -#else -#define LOCKUP_SECONDS NORMAL_LOCKUP_SECONDS -#endif - long long test(char**); diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index ce2d734c5..b473bf7bf 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -23,6 +23,20 @@ #include "timer.h" +/*** Define lockup times. Valgrind instruments every memory access, so tests + *** may need longer to finish when running under valgrind. + ***/ +#define NORMAL_LOCKUP_SECONDS 5u +#define VALGRIND_LOCKUP_SECONDS 10u + +/** Detect valgrind. **/ +#ifdef USING_VALGRIND +#include "valgrind/valgrind.h" +#define LOCKUP_SECONDS ((RUNNING_ON_VALGRIND) ? VALGRIND_LOCKUP_SECONDS : NORMAL_LOCKUP_SECONDS) +#else +#define LOCKUP_SECONDS NORMAL_LOCKUP_SECONDS +#endif + /*** The minimum number of seconds that a test can take. Tests that take less *** time than this might not use enough cpu cycles, confusing the test suite *** performance tracking. From ca5930cb7a3b046f09675a8dec8459926497192b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 13:13:21 -0600 Subject: [PATCH 157/361] Inline some variables in the newmalloc tests. --- centrallix-lib/tests/test_newmalloc_00.c | 30 ++++++++++-------------- centrallix-lib/tests/test_newmalloc_01.c | 24 +++++++------------ 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 83ebc564d..8af3af669 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -82,12 +82,6 @@ static bool doTests(void) { bool success = true; - /*** Evaluate the bulk data sizes once, since the Valgrind check behind - *** them is a client request rather than a plain constant. - ***/ - const size_t test_limit = TEST_LIMIT; - const size_t large_buf_size = LARGE_BUF_SIZE; - /** Set a consistent, distinct seed for each test iteration. **/ srand(seed_counter++); @@ -107,15 +101,15 @@ static bool doTests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ - void** data = checkPtr(malloc(test_limit * sizeof(void*))); - void** test = checkPtr(malloc(test_limit * sizeof(void*))); - for (size_t i = 1lu; i < test_limit; i++) + void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + for (size_t i = 1lu; i < TEST_LIMIT; i++) { success &= EXPECT_NOT_NULL(test[i] = nmSysMalloc(i)); data[i] = random_init(checkPtr(malloc(i)), i); memcpy(test[i], data[i], i); /* Write test data into test memory. */ } - for (size_t i = test_limit - 1lu; i > 0lu; i--) + for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); /** Basic string data is unharmed. **/ @@ -123,10 +117,10 @@ static bool doTests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Reallocate all variably sized memory to a different size. **/ - for (size_t i = test_limit - 1lu; i > 0lu; i--) - success &= EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], test_limit - i)); - for (size_t i = 1lu; i < test_limit; i++) - success &= EXPECT_EQL(memcmp(data[i], test[i], min(i, test_limit - i)), 0, "%d"); + for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) + success &= EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i)); + for (size_t i = 1lu; i < TEST_LIMIT; i++) + success &= EXPECT_EQL(memcmp(data[i], test[i], min(i, TEST_LIMIT - i)), 0, "%d"); /** Basic string data is unharmed. **/ success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); @@ -150,7 +144,7 @@ static bool doTests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ - for (size_t i = 1lu; i < test_limit; i++) + for (size_t i = 1lu; i < TEST_LIMIT; i++) { free(data[i]); nmSysFree(test[i]); @@ -172,12 +166,12 @@ static bool doTests(void) /** Large singular allocation. **/ void* large_buf; - success &= EXPECT_NOT_NULL(large_buf = nmSysMalloc(large_buf_size)); - for (size_t i = large_buf_size - 1lu; i > 0lu; i--) + success &= EXPECT_NOT_NULL(large_buf = nmSysMalloc(LARGE_BUF_SIZE)); + for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; size_t mismatches = 0lu; - for (size_t i = 0lu; i < large_buf_size; i++) + for (size_t i = 0lu; i < LARGE_BUF_SIZE; i++) if (*((unsigned char*)large_buf + i) != (unsigned char)(i % 255lu)) mismatches++; success &= EXPECT_EQL(mismatches, 0lu, "%zu"); diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index b09deb624..ee188a3dc 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -82,12 +82,6 @@ static bool doTests(void) { bool success = true; - /*** Evaluate the bulk data sizes once, since the Valgrind check behind - *** them is a client request rather than a plain constant. - ***/ - const size_t test_limit = TEST_LIMIT; - const size_t large_buf_size = LARGE_BUF_SIZE; - /** Set a consistent, distinct seed for each test iteration. **/ srand(seed_counter++); @@ -107,15 +101,15 @@ static bool doTests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ - void** data = checkPtr(malloc(test_limit * sizeof(void*))); - void** test = checkPtr(malloc(test_limit * sizeof(void*))); - for (size_t i = 1lu; i < test_limit; i++) + void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + for (size_t i = 1lu; i < TEST_LIMIT; i++) { success &= EXPECT_NOT_NULL(test[i] = nmMalloc(i)); data[i] = random_init(checkPtr(malloc(i)), i); memcpy(test[i], data[i], i); } - for (size_t i = test_limit - 1lu; i > 0lu; i--) + for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); /** Basic string data is unharmed. **/ @@ -124,12 +118,12 @@ static bool doTests(void) /** Large singular allocation. **/ void* large_buf; - success &= EXPECT_NOT_NULL(large_buf = nmMalloc(large_buf_size)); - for (size_t i = large_buf_size - 1lu; i > 0lu; i--) + success &= EXPECT_NOT_NULL(large_buf = nmMalloc(LARGE_BUF_SIZE)); + for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; size_t mismatches = 0lu; - for (size_t i = 0lu; i < large_buf_size; i++) + for (size_t i = 0lu; i < LARGE_BUF_SIZE; i++) if (*((unsigned char*)large_buf + i) != (unsigned char)(i % 255lu)) mismatches++; success &= EXPECT_EQL(mismatches, 0lu, "%zu"); @@ -138,7 +132,7 @@ static bool doTests(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ - for (size_t i = 1lu; i < test_limit; i++) + for (size_t i = 1lu; i < TEST_LIMIT; i++) { free(data[i]); nmFree(test[i], i); @@ -155,7 +149,7 @@ static bool doTests(void) nmFree(str2, 32); /** Free large allocation. **/ - nmFree(large_buf, large_buf_size); + nmFree(large_buf, LARGE_BUF_SIZE); /** Clear cache. **/ nmClear(); From 59631fe900fe9bd98db96ad9398eed645e91ca49 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 13:15:20 -0600 Subject: [PATCH 158/361] Update mtlexer_03 and 09 to use striped coverage. --- centrallix-lib/tests/test_mtlexer_03.c | 59 ++++++++++++-------- centrallix-lib/tests/test_mtlexer_09.c | 77 ++++++++++++++------------ 2 files changed, 77 insertions(+), 59 deletions(-) diff --git a/centrallix-lib/tests/test_mtlexer_03.c b/centrallix-lib/tests/test_mtlexer_03.c index e007331e9..4c4328aa1 100644 --- a/centrallix-lib/tests/test_mtlexer_03.c +++ b/centrallix-lib/tests/test_mtlexer_03.c @@ -19,7 +19,13 @@ static char str[65536] = ""; static int toktype[9] = {MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_STRING, MLX_TOK_EOL, MLX_TOK_EOF }; static char* tokstr[6]; -/** Line length under test, advanced each pass and wrapped at the longest case. **/ +/*** Number of passes needed to cover every line length. Each pass tests the + *** lengths congruent to it, spread across the whole sweep, so even a single + *** pass exercises the longest lines. + ***/ +#define N_PASS 250 + +/** Pass under test, advanced each time and wrapped once all passes are done. **/ static int sweep = 0; static bool @@ -33,33 +39,38 @@ doTests(void) char* strval; pLxSession lxs; - if (sweep >= MAX_LEN) sweep = 0; - i = sweep++; - - /** Both the input and the expected first token grow one 'a' per pass. **/ - strcpy(str+i, "a\r\nnextline\r\nthirdline"); - tokstr[0][i] = 'a'; - tokstr[0][i+1] = '\0'; - - lxs = mlxStringSession(str, MLX_F_EOL | MLX_F_EOF | MLX_F_IFSONLY); - assert(lxs != NULL); - strcnt = 0; - for(j=0;j= N_PASS) sweep = 0; + for(i=sweep++;i= MAX_LEN-20) sweep = 0; - i = sweep++; - - /** Put the line breaks in place for this pass. **/ - str[i+1] = '\r'; - str[i+2] = '\n'; - tokstr[0][i] = '\r'; - tokstr[0][i+1] = '\n'; - str[i+20] = '\n'; - tokstr[0][i+19] = '\n'; - - lxs = mlxStringSession(str, MLX_F_EOL | MLX_F_EOF); - assert(lxs != NULL); - strcnt = 0; - for(j=0;j= N_PASS) sweep = 0; + for(i=sweep++;i Date: Wed, 9 Sep 2026 13:19:44 -0600 Subject: [PATCH 159/361] Refactor the nmStats() test into to its own test file. --- centrallix-lib/tests/test_newmalloc_01.c | 23 ------- centrallix-lib/tests/test_newmalloc_02.c | 88 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 centrallix-lib/tests/test_newmalloc_02.c diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index ee188a3dc..5e2b19432 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -19,7 +19,6 @@ #include #include #include -#include /** Test dependencies. **/ #include "test_utils.h" @@ -154,28 +153,6 @@ static bool doTests(void) /** Clear cache. **/ nmClear(); - /*** Debug info, captured to verify that nmStats() prints something. - *** nmStats() prints via the library's own stdout, so capturing it - *** requires us to redirect that file descriptor into a pipe that - *** we flush into a buffer. This deadlocks if stats prints over - *** 64kb of data and fills the pipe, but that shouldn't happen. - ***/ - char stats_buf[2048]; - int stats_pipe[2]; - success &= EXPECT_EQL(pipe(stats_pipe), 0, "%d"); - fflush(stdout); - int saved_stdout = dup(STDOUT_FILENO); - dup2(stats_pipe[1], STDOUT_FILENO); - close(stats_pipe[1]); - nmStats(); /** Run target code. **/ - fflush(stdout); - dup2(saved_stdout, STDOUT_FILENO); - close(saved_stdout); - ssize_t stats_len = read(stats_pipe[0], stats_buf, sizeof(stats_buf) - 1lu); - close(stats_pipe[0]); - stats_buf[(stats_len > 0) ? stats_len : 0] = '\0'; - success &= EXPECT_RANGE(strlen(stats_buf), (size_t)32, sizeof(stats_buf) - 1lu, "%zu"); - /** Expect no captured errors. **/ success &= EXPECT_STR_EQL(err_buf, ""); diff --git a/centrallix-lib/tests/test_newmalloc_02.c b/centrallix-lib/tests/test_newmalloc_02.c new file mode 100644 index 000000000..0c6da8737 --- /dev/null +++ b/centrallix-lib/tests/test_newmalloc_02.c @@ -0,0 +1,88 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_newmalloc_02.c */ +/* Author: Israel Fuller */ +/* Creation: September 9th, 2026 */ +/* Description: Test the nmStats() function from the NewMalloc library. */ +/************************************************************************/ + +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "newmalloc.h" + +#define BLOCK_COUNT 64lu +#define BLOCK_SIZE 128 + +static bool doTests(void) + { + bool success = true; + + /** Give the allocator some activity to report on. **/ + void* blocks[BLOCK_COUNT]; + for (size_t i = 0lu; i < BLOCK_COUNT; i++) + success &= EXPECT_NOT_NULL(blocks[i] = nmMalloc(BLOCK_SIZE)); + for (size_t i = 0lu; i < BLOCK_COUNT; i++) + nmFree(blocks[i], BLOCK_SIZE); + + /*** Debug info, captured to verify that nmStats() prints the stats. + *** nmStats() prints via the library's own stdout, so capturing it + *** requires us to redirect that file descriptor into a pipe that + *** we flush into a buffer. This deadlocks if stats prints over + *** 64kb of data and fills the pipe, but that shouldn't happen. + ***/ + char stats_buf[2048]; + int stats_pipe[2]; + success &= EXPECT_EQL(pipe(stats_pipe), 0, "%d"); + fflush(stdout); + int saved_stdout = dup(STDOUT_FILENO); + dup2(stats_pipe[1], STDOUT_FILENO); + close(stats_pipe[1]); + nmStats(); /** Run target code. **/ + fflush(stdout); + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + ssize_t stats_len = read(stats_pipe[0], stats_buf, sizeof(stats_buf) - 1lu); + close(stats_pipe[0]); + stats_buf[(stats_len > 0) ? stats_len : 0] = '\0'; + + /*** Every counter line is reported. The counts themselves are only + *** tracked when the library is built with NMMALLOC_PROFILING, so + *** their values aren't checked here. + ***/ + success &= EXPECT_RANGE(strlen(stats_buf), (size_t)32, sizeof(stats_buf) - 1lu, "%zu"); + success &= EXPECT_NOT_NULL(strstr(stats_buf, "NewMalloc subsystem statistics:")); + success &= EXPECT_NOT_NULL(strstr(stats_buf, "nmMalloc:")); + success &= EXPECT_NOT_NULL(strstr(stats_buf, "nmFree:")); + success &= EXPECT_NOT_NULL(strstr(stats_buf, "bigblks:")); + + /** Clear cache. **/ + nmClear(); + + return success; + } + +long long test(char** tname) + { + *tname = "newmalloc-02 nmStats()"; + return loopTests(doTests); + } + +/** Scope cleanup. **/ +#undef BLOCK_COUNT +#undef BLOCK_SIZE From 2a8d75d68d9eebdb891fd33822f44eb43a66292d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 13:23:23 -0600 Subject: [PATCH 160/361] Rename functions to match naming conventions. --- centrallix-lib/tests/t_driver.c | 2 +- centrallix-lib/tests/test_00baseline.c | 4 ++-- centrallix-lib/tests/test_memstr_00.c | 4 ++-- centrallix-lib/tests/test_mtlexer_00.c | 4 ++-- centrallix-lib/tests/test_mtlexer_01.c | 4 ++-- centrallix-lib/tests/test_mtlexer_02.c | 4 ++-- centrallix-lib/tests/test_mtlexer_03.c | 4 ++-- centrallix-lib/tests/test_mtlexer_04.c | 4 ++-- centrallix-lib/tests/test_mtlexer_05.c | 4 ++-- centrallix-lib/tests/test_mtlexer_06.c | 4 ++-- centrallix-lib/tests/test_mtlexer_07.c | 4 ++-- centrallix-lib/tests/test_mtlexer_08.c | 4 ++-- centrallix-lib/tests/test_mtlexer_09.c | 4 ++-- centrallix-lib/tests/test_mtlexer_10.c | 4 ++-- centrallix-lib/tests/test_mtlexer_11.c | 4 ++-- centrallix-lib/tests/test_mtlexer_12.c | 4 ++-- centrallix-lib/tests/test_mtlexer_13.c | 4 ++-- centrallix-lib/tests/test_mtlexer_14.c | 4 ++-- centrallix-lib/tests/test_mtlexer_15.c | 4 ++-- centrallix-lib/tests/test_mtlexer_16.c | 4 ++-- centrallix-lib/tests/test_mtlexer_17.c | 4 ++-- centrallix-lib/tests/test_mtlexer_18.c | 4 ++-- centrallix-lib/tests/test_mtlexer_19.c | 4 ++-- centrallix-lib/tests/test_mtlexer_20.c | 4 ++-- centrallix-lib/tests/test_mtlexer_21.c | 4 ++-- centrallix-lib/tests/test_newmalloc_00.c | 12 ++++++------ centrallix-lib/tests/test_newmalloc_01.c | 12 ++++++------ centrallix-lib/tests/test_newmalloc_02.c | 4 ++-- centrallix-lib/tests/test_qprintf_00.c | 4 ++-- centrallix-lib/tests/test_qprintf_01.c | 4 ++-- centrallix-lib/tests/test_qprintf_02.c | 4 ++-- centrallix-lib/tests/test_qprintf_03.c | 4 ++-- centrallix-lib/tests/test_qprintf_04.c | 4 ++-- centrallix-lib/tests/test_qprintf_05.c | 4 ++-- centrallix-lib/tests/test_qprintf_06.c | 4 ++-- centrallix-lib/tests/test_qprintf_07.c | 4 ++-- centrallix-lib/tests/test_qprintf_08.c | 4 ++-- centrallix-lib/tests/test_qprintf_09.c | 4 ++-- centrallix-lib/tests/test_qprintf_10.c | 4 ++-- centrallix-lib/tests/test_qprintf_11.c | 4 ++-- centrallix-lib/tests/test_qprintf_12.c | 4 ++-- centrallix-lib/tests/test_qprintf_13.c | 4 ++-- centrallix-lib/tests/test_qprintf_14.c | 4 ++-- centrallix-lib/tests/test_qprintf_15.c | 4 ++-- centrallix-lib/tests/test_qprintf_16.c | 4 ++-- centrallix-lib/tests/test_qprintf_17.c | 4 ++-- centrallix-lib/tests/test_qprintf_18.c | 4 ++-- centrallix-lib/tests/test_qprintf_19.c | 4 ++-- centrallix-lib/tests/test_qprintf_20.c | 4 ++-- centrallix-lib/tests/test_qprintf_21.c | 4 ++-- centrallix-lib/tests/test_qprintf_22.c | 4 ++-- centrallix-lib/tests/test_qprintf_23.c | 4 ++-- centrallix-lib/tests/test_qprintf_24.c | 4 ++-- centrallix-lib/tests/test_qprintf_25.c | 4 ++-- centrallix-lib/tests/test_qprintf_26.c | 4 ++-- centrallix-lib/tests/test_qprintf_27.c | 4 ++-- centrallix-lib/tests/test_qprintf_28.c | 4 ++-- centrallix-lib/tests/test_qprintf_29.c | 4 ++-- centrallix-lib/tests/test_qprintf_30.c | 4 ++-- centrallix-lib/tests/test_qprintf_31.c | 4 ++-- centrallix-lib/tests/test_qprintf_32.c | 4 ++-- centrallix-lib/tests/test_qprintf_33.c | 4 ++-- centrallix-lib/tests/test_qprintf_34.c | 4 ++-- centrallix-lib/tests/test_qprintf_35.c | 4 ++-- centrallix-lib/tests/test_qprintf_36.c | 4 ++-- centrallix-lib/tests/test_qprintf_37.c | 4 ++-- centrallix-lib/tests/test_qprintf_38.c | 4 ++-- centrallix-lib/tests/test_qprintf_39.c | 4 ++-- centrallix-lib/tests/test_qprintf_40.c | 4 ++-- centrallix-lib/tests/test_qprintf_41.c | 4 ++-- centrallix-lib/tests/test_qprintf_42.c | 4 ++-- centrallix-lib/tests/test_qprintf_43.c | 4 ++-- centrallix-lib/tests/test_qprintf_44.c | 4 ++-- centrallix-lib/tests/test_qprintf_45.c | 4 ++-- centrallix-lib/tests/test_qprintf_46.c | 4 ++-- centrallix-lib/tests/test_qprintf_47.c | 4 ++-- centrallix-lib/tests/test_qprintf_48.c | 4 ++-- centrallix-lib/tests/test_qprintf_49.c | 4 ++-- centrallix-lib/tests/test_qprintf_50.c | 4 ++-- centrallix-lib/tests/test_qprintf_51.c | 4 ++-- centrallix-lib/tests/test_qprintf_52.c | 4 ++-- centrallix-lib/tests/test_qprintf_53.c | 4 ++-- centrallix-lib/tests/test_qprintf_54.c | 4 ++-- centrallix-lib/tests/test_qprintf_55.c | 4 ++-- centrallix-lib/tests/test_qprintf_56.c | 4 ++-- centrallix-lib/tests/test_qprintf_57.c | 4 ++-- centrallix-lib/tests/test_qprintf_58.c | 4 ++-- centrallix-lib/tests/test_qprintf_59.c | 4 ++-- centrallix-lib/tests/test_qprintf_60.c | 4 ++-- centrallix-lib/tests/test_qprintf_61.c | 4 ++-- centrallix-lib/tests/test_qprintf_62.c | 4 ++-- centrallix-lib/tests/test_qprintf_63.c | 4 ++-- centrallix-lib/tests/test_qprintf_64.c | 4 ++-- centrallix-lib/tests/test_qprintf_65.c | 4 ++-- centrallix-lib/tests/test_qprintf_66.c | 4 ++-- centrallix-lib/tests/test_qprintf_67.c | 4 ++-- centrallix-lib/tests/test_qprintf_68.c | 4 ++-- centrallix-lib/tests/test_range_00.c | 4 ++-- centrallix-lib/tests/test_range_01.c | 4 ++-- centrallix-lib/tests/test_smmalloc_01.c | 4 ++-- centrallix-lib/tests/test_smmalloc_02.c | 4 ++-- centrallix-lib/tests/test_smmalloc_03.c | 4 ++-- centrallix-lib/tests/test_smmalloc_04.c | 4 ++-- centrallix-lib/tests/test_smmalloc_05.c | 4 ++-- centrallix-lib/tests/test_smmalloc_06.c | 4 ++-- centrallix-lib/tests/test_smmalloc_07.c | 4 ++-- centrallix-lib/tests/test_smmalloc_08.c | 4 ++-- centrallix-lib/tests/test_strtcpy_00.c | 4 ++-- centrallix-lib/tests/test_strtcpy_01.c | 4 ++-- centrallix-lib/tests/test_strtcpy_02.c | 4 ++-- centrallix-lib/tests/test_strtcpy_03.c | 4 ++-- centrallix-lib/tests/test_strtcpy_04.c | 4 ++-- centrallix-lib/tests/test_strtcpy_05.c | 4 ++-- centrallix-lib/tests/test_strtcpy_06.c | 4 ++-- centrallix-lib/tests/test_strtcpy_07.c | 4 ++-- centrallix-lib/tests/test_strtcpy_08.c | 4 ++-- centrallix-lib/tests/test_strtcpy_09.c | 4 ++-- centrallix-lib/tests/test_strtcpy_10.c | 4 ++-- centrallix-lib/tests/test_strtcpy_11.c | 6 +++--- centrallix-lib/tests/test_strtcpy_12.c | 8 ++++---- centrallix-lib/tests/test_strtcpy_13.c | 6 +++--- centrallix-lib/tests/test_timer_00.c | 2 +- centrallix-lib/tests/test_util_00.c | 6 +++--- centrallix-lib/tests/test_util_01.c | 6 +++--- centrallix-lib/tests/test_util_02.c | 4 ++-- centrallix-lib/tests/test_utils.h | 2 +- centrallix-lib/tests/test_xarray_00.c | 4 ++-- 127 files changed, 265 insertions(+), 265 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 2d9648d73..7949b0231 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -80,7 +80,7 @@ start(void* v) if (duration == 0) { printf("%-62.62s PASS ???\n", tname); - printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loopTests() from test_utils.h.\n"); + printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loopTest() from test_utils.h.\n"); return; } double ops_per_second = rval * (100.0 / duration); diff --git a/centrallix-lib/tests/test_00baseline.c b/centrallix-lib/tests/test_00baseline.c index 06783ff06..72773400d 100644 --- a/centrallix-lib/tests/test_00baseline.c +++ b/centrallix-lib/tests/test_00baseline.c @@ -10,7 +10,7 @@ #define OPS_PER_PASS (1000*1000) static bool -doTests(void) +doTest(void) { int i; int array[2] = {0}; @@ -24,5 +24,5 @@ long long test(char** tname) { *tname = "BASELINE - should pass"; - return loopTests(doTests) * OPS_PER_PASS; + return loopTest(doTest) * OPS_PER_PASS; } diff --git a/centrallix-lib/tests/test_memstr_00.c b/centrallix-lib/tests/test_memstr_00.c index 6ea87da46..f161832c8 100755 --- a/centrallix-lib/tests/test_memstr_00.c +++ b/centrallix-lib/tests/test_memstr_00.c @@ -25,7 +25,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { char buffer1[40]; char buffer2[20]; @@ -57,5 +57,5 @@ long long test(char** tname) { *tname = "memstr-00 correct null ptr"; - return loopTests(doTests) * 5; + return loopTest(doTest) * 5; } diff --git a/centrallix-lib/tests/test_mtlexer_00.c b/centrallix-lib/tests/test_mtlexer_00.c index 2d87a0485..1aadd6bc4 100644 --- a/centrallix-lib/tests/test_mtlexer_00.c +++ b/centrallix-lib/tests/test_mtlexer_00.c @@ -21,7 +21,7 @@ static int toktype[N_FLAGTYPE][N_TOK] = { {MLX_TOK_ERROR, MLX_TOK_ERROR, MLX_TOK_ERROR} }; static bool -doTests(void) +doTest(void) { int f; int j; @@ -48,5 +48,5 @@ test(char** tname) { *tname = "mtlexer-00 empty string and eol/eof/error test"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_FLAGTYPE; + return loopTest(doTest) * N_FLAGTYPE; } diff --git a/centrallix-lib/tests/test_mtlexer_01.c b/centrallix-lib/tests/test_mtlexer_01.c index 8c06e32dc..a37ab9dbd 100644 --- a/centrallix-lib/tests/test_mtlexer_01.c +++ b/centrallix-lib/tests/test_mtlexer_01.c @@ -22,7 +22,7 @@ static int toktype[N_FLAGTYPE][N_TOK] = { static char* tokstr[N_TOK] = { "hello world", NULL, NULL, NULL }; static bool -doTests(void) +doTest(void) { int f; int j; @@ -56,5 +56,5 @@ test(char** tname) { *tname = "mtlexer-01 string token and eol/eof/error test"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_FLAGTYPE; + return loopTest(doTest) * N_FLAGTYPE; } diff --git a/centrallix-lib/tests/test_mtlexer_02.c b/centrallix-lib/tests/test_mtlexer_02.c index 1073bebe6..3d1b83854 100644 --- a/centrallix-lib/tests/test_mtlexer_02.c +++ b/centrallix-lib/tests/test_mtlexer_02.c @@ -23,7 +23,7 @@ static int toktype[N_FLAGTYPE][N_TOK] = { static char* tokstr[6] = { "string one", "string two", "string three", "string four", "string five", NULL }; static bool -doTests(void) +doTest(void) { int f; int j; @@ -60,5 +60,5 @@ test(char** tname) { *tname = "mtlexer-02 three lines of strings and eol/eof/error test"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_FLAGTYPE; + return loopTest(doTest) * N_FLAGTYPE; } diff --git a/centrallix-lib/tests/test_mtlexer_03.c b/centrallix-lib/tests/test_mtlexer_03.c index 4c4328aa1..fe997c303 100644 --- a/centrallix-lib/tests/test_mtlexer_03.c +++ b/centrallix-lib/tests/test_mtlexer_03.c @@ -29,7 +29,7 @@ static char* tokstr[6]; static int sweep = 0; static bool -doTests(void) +doTest(void) { int i; int j; @@ -93,7 +93,7 @@ test(char** tname) tokstr[1] = "nextline"; tokstr[2] = "thirdline"; - rval = loopTests(doTests); + rval = loopTest(doTest); nmSysFree(tokstr[0]); tokstr[0] = NULL; diff --git a/centrallix-lib/tests/test_mtlexer_04.c b/centrallix-lib/tests/test_mtlexer_04.c index cca05d28a..e647f0364 100644 --- a/centrallix-lib/tests/test_mtlexer_04.c +++ b/centrallix-lib/tests/test_mtlexer_04.c @@ -13,7 +13,7 @@ static char str[65536] = ""; static bool -doTests(void) +doTest(void) { pLxSession lxs; @@ -29,5 +29,5 @@ test(char** tname) { *tname = "mtlexer-04 open/close session"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_mtlexer_05.c b/centrallix-lib/tests/test_mtlexer_05.c index 8799f0808..1b956573e 100644 --- a/centrallix-lib/tests/test_mtlexer_05.c +++ b/centrallix-lib/tests/test_mtlexer_05.c @@ -14,7 +14,7 @@ #define N_INTS 60000 static bool -doTests(void) +doTest(void) { int j; int t; @@ -48,5 +48,5 @@ test(char** tname) { *tname = "mtlexer-05 integer data, one per line with eol/eof"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_INTS; + return loopTest(doTest) * N_INTS; } diff --git a/centrallix-lib/tests/test_mtlexer_06.c b/centrallix-lib/tests/test_mtlexer_06.c index 8a8111221..b81a907e1 100644 --- a/centrallix-lib/tests/test_mtlexer_06.c +++ b/centrallix-lib/tests/test_mtlexer_06.c @@ -14,7 +14,7 @@ #define N_INTS 60000 static bool -doTests(void) +doTest(void) { int j; int t; @@ -48,5 +48,5 @@ test(char** tname) { *tname = "mtlexer-06 integer data, all on one line with eol/eof"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_INTS; + return loopTest(doTest) * N_INTS; } diff --git a/centrallix-lib/tests/test_mtlexer_07.c b/centrallix-lib/tests/test_mtlexer_07.c index 4a8467493..3b4fcf477 100644 --- a/centrallix-lib/tests/test_mtlexer_07.c +++ b/centrallix-lib/tests/test_mtlexer_07.c @@ -17,7 +17,7 @@ static int n_tokens = 0; static int n_iter = 0; static bool -doTests(void) +doTest(void) { int j; int k; @@ -66,7 +66,7 @@ test(char** tname) mssInitialize("system", "", "", 0, "test"); - rval = loopTests(doTests); + rval = loopTest(doTest); if (rval > 0) rval *= (long long)n_tokens * n_iter; return rval; diff --git a/centrallix-lib/tests/test_mtlexer_08.c b/centrallix-lib/tests/test_mtlexer_08.c index 680c35947..d00f34a83 100644 --- a/centrallix-lib/tests/test_mtlexer_08.c +++ b/centrallix-lib/tests/test_mtlexer_08.c @@ -14,7 +14,7 @@ static char* teststr = "'string' 'test string' \"string\" \"test string\" 'strin static char* strs[] = {"string", "test string", "string", "test string", "string's", "\"string\"", "string\\string", "string\"string", "string'string", NULL}; static bool -doTests(void) +doTest(void) { int cnt; int t; @@ -43,5 +43,5 @@ test(char** tname) { *tname = "mtlexer-08 string quoting"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * 9; + return loopTest(doTest) * 9; } diff --git a/centrallix-lib/tests/test_mtlexer_09.c b/centrallix-lib/tests/test_mtlexer_09.c index 9fd21cb67..988d31a1d 100644 --- a/centrallix-lib/tests/test_mtlexer_09.c +++ b/centrallix-lib/tests/test_mtlexer_09.c @@ -29,7 +29,7 @@ static char* tokstr[6]; static int sweep = 0; static bool -doTests(void) +doTest(void) { int i; int j; @@ -100,7 +100,7 @@ test(char** tname) memset(tokstr[0], 'a', MAX_LEN+1); tokstr[0][MAX_LEN+1] = '\0'; - rval = loopTests(doTests); + rval = loopTest(doTest); nmSysFree(tokstr[0]); tokstr[0] = NULL; diff --git a/centrallix-lib/tests/test_mtlexer_10.c b/centrallix-lib/tests/test_mtlexer_10.c index c83412bf9..f2fb82509 100644 --- a/centrallix-lib/tests/test_mtlexer_10.c +++ b/centrallix-lib/tests/test_mtlexer_10.c @@ -19,7 +19,7 @@ static double doubles[] = {.0, .1, .10, .01, 0.0, 0.1, 0.10, 0.01, 1.0, 1.1, 1.1 #define N_DOUBLES (sizeof(doubles)/sizeof(doubles[0])) static bool -doTests(void) +doTest(void) { unsigned int j; int n; @@ -51,5 +51,5 @@ test(char** tname) { *tname = "mtlexer-10 integer and double parsing"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * (N_INTEGERS + N_DOUBLES); + return loopTest(doTest) * (N_INTEGERS + N_DOUBLES); } diff --git a/centrallix-lib/tests/test_mtlexer_11.c b/centrallix-lib/tests/test_mtlexer_11.c index 2f3310eba..c7e6876f1 100644 --- a/centrallix-lib/tests/test_mtlexer_11.c +++ b/centrallix-lib/tests/test_mtlexer_11.c @@ -29,7 +29,7 @@ static char* tokstr[N_FLAGTYPE][N_TOK] = { }; static bool -doTests(void) +doTest(void) { int f; int j; @@ -67,5 +67,5 @@ test(char** tname) { *tname = "mtlexer-11 case (in)sensitive keywords and reserved words"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_FLAGTYPE * N_TOK; + return loopTest(doTest) * N_FLAGTYPE * N_TOK; } diff --git a/centrallix-lib/tests/test_mtlexer_12.c b/centrallix-lib/tests/test_mtlexer_12.c index 87031b10c..6861146c4 100644 --- a/centrallix-lib/tests/test_mtlexer_12.c +++ b/centrallix-lib/tests/test_mtlexer_12.c @@ -19,7 +19,7 @@ static char str[65536] = ""; static int sweep = 0; static bool -doTests(void) +doTest(void) { int i; pLxSession lxs; @@ -63,5 +63,5 @@ test(char** tname) { *tname = "mtlexer-12 normal/oversized keywords"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_mtlexer_13.c b/centrallix-lib/tests/test_mtlexer_13.c index 8f746bff3..c768a8abf 100644 --- a/centrallix-lib/tests/test_mtlexer_13.c +++ b/centrallix-lib/tests/test_mtlexer_13.c @@ -22,7 +22,7 @@ static int sweep = 0; static int iv = 0; static bool -doTests(void) +doTest(void) { int i; int n; @@ -66,5 +66,5 @@ test(char** tname) { *tname = "mtlexer-13 normal/oversized integers"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_mtlexer_14.c b/centrallix-lib/tests/test_mtlexer_14.c index a571de25c..56665bed2 100644 --- a/centrallix-lib/tests/test_mtlexer_14.c +++ b/centrallix-lib/tests/test_mtlexer_14.c @@ -19,7 +19,7 @@ static char str[65536] = ""; static int sweep = 0; static bool -doTests(void) +doTest(void) { int i; double d; @@ -61,5 +61,5 @@ test(char** tname) { *tname = "mtlexer-14 normal/oversized double floating point"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_mtlexer_15.c b/centrallix-lib/tests/test_mtlexer_15.c index c7ef61606..b630b4cc6 100644 --- a/centrallix-lib/tests/test_mtlexer_15.c +++ b/centrallix-lib/tests/test_mtlexer_15.c @@ -18,7 +18,7 @@ static int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_ #define N_FLAGTYPES ((int)(sizeof(flagtypes)/sizeof(flagtypes[0]))) static bool -doTests(void) +doTest(void) { int i; int j; @@ -56,5 +56,5 @@ test(char** tname) { *tname = "mtlexer-15 comments // # ; -- /**/ long and short"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * 10; + return loopTest(doTest) * 10; } diff --git a/centrallix-lib/tests/test_mtlexer_16.c b/centrallix-lib/tests/test_mtlexer_16.c index 4c4c3f082..eaa627ec2 100644 --- a/centrallix-lib/tests/test_mtlexer_16.c +++ b/centrallix-lib/tests/test_mtlexer_16.c @@ -18,7 +18,7 @@ static int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_ #define N_FLAGTYPES ((int)(sizeof(flagtypes)/sizeof(flagtypes[0]))) static bool -doTests(void) +doTest(void) { int i; int j; @@ -56,5 +56,5 @@ test(char** tname) { *tname = "mtlexer-16 comments // # ; -- /**/ short only"; mssInitialize("system", "", "none", 0, "test"); - return loopTests(doTests) * 10; + return loopTest(doTest) * 10; } diff --git a/centrallix-lib/tests/test_mtlexer_17.c b/centrallix-lib/tests/test_mtlexer_17.c index c0908f033..0979953f4 100644 --- a/centrallix-lib/tests/test_mtlexer_17.c +++ b/centrallix-lib/tests/test_mtlexer_17.c @@ -23,7 +23,7 @@ static int toktype[N_FLAGTYPE][N_TOK] = { static char* tokstr[8] = { "\n", "\n", "'string one' 'string two'\n", "'string three' 'string four'\r\n", "'string five'\r\n", "\r\n", "String Six", NULL }; static bool -doTests(void) +doTest(void) { int f; int j; @@ -60,5 +60,5 @@ test(char** tname) { *tname = "mtlexer-17 LINEONLY mode test"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_FLAGTYPE * 7; + return loopTest(doTest) * N_FLAGTYPE * 7; } diff --git a/centrallix-lib/tests/test_mtlexer_18.c b/centrallix-lib/tests/test_mtlexer_18.c index e06157ab1..420440b1b 100644 --- a/centrallix-lib/tests/test_mtlexer_18.c +++ b/centrallix-lib/tests/test_mtlexer_18.c @@ -23,7 +23,7 @@ static int toktype[N_FLAGTYPE][N_TOK] = { static char* tokstr[8] = { "\n", "\n", "'string one' 'string two'\n", "'string three' 'string four'\r\n", "'string five'\r\n", "\r\n", "String Six\n", NULL }; static bool -doTests(void) +doTest(void) { int f; int j; @@ -60,5 +60,5 @@ test(char** tname) { *tname = "mtlexer-18 LINEONLY mode test - file ends in newline"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * N_FLAGTYPE * 7; + return loopTest(doTest) * N_FLAGTYPE * 7; } diff --git a/centrallix-lib/tests/test_mtlexer_19.c b/centrallix-lib/tests/test_mtlexer_19.c index f11a7e882..9854c38f9 100644 --- a/centrallix-lib/tests/test_mtlexer_19.c +++ b/centrallix-lib/tests/test_mtlexer_19.c @@ -18,7 +18,7 @@ static int setflags[N_TOK] = {0, 0, MLX_F_IFSONLY, 0, 0, 0, 0, 0, static int unsetflags[N_TOK] = {0, 0, 0, 0, 0, MLX_F_IFSONLY, 0, 0, 0, MLX_F_LINEONLY, 0, 0, 0, 0, 0, 0 }; static bool -doTests(void) +doTest(void) { int j; int t; @@ -53,5 +53,5 @@ test(char** tname) { *tname = "mtlexer-19 enabling/disabling LINEONLY/IFSONLY during parsing"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests) * 9; + return loopTest(doTest) * 9; } diff --git a/centrallix-lib/tests/test_mtlexer_20.c b/centrallix-lib/tests/test_mtlexer_20.c index c8bbfc194..9071701b3 100644 --- a/centrallix-lib/tests/test_mtlexer_20.c +++ b/centrallix-lib/tests/test_mtlexer_20.c @@ -11,7 +11,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int j; int t; @@ -52,5 +52,5 @@ test(char** tname) { *tname = "mtlexer-20 NODISCARD flag test"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_mtlexer_21.c b/centrallix-lib/tests/test_mtlexer_21.c index a8dc93fc4..ea34cad95 100644 --- a/centrallix-lib/tests/test_mtlexer_21.c +++ b/centrallix-lib/tests/test_mtlexer_21.c @@ -11,7 +11,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int j; int t; @@ -46,5 +46,5 @@ test(char** tname) { *tname = "mtlexer-21 NODISCARD test (LINEONLY + ~EOL)"; mssInitialize("system", "", "", 0, "test"); - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 8af3af669..dbb41d232 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -47,7 +47,7 @@ static char* err_buf; static unsigned int err_buf_i; static unsigned int err_buf_size; -static int mock_error_fn(char* error_msg) +static int mockErrorFn(char* error_msg) { const size_t len = strlen(error_msg) + 1lu; @@ -68,7 +68,7 @@ static int mock_error_fn(char* error_msg) } /** Initialize memory of a given size with random data. **/ -static void* random_init(void* ptr, size_t size) +static void* randomInit(void* ptr, size_t size) { if (ptr == NULL) return NULL; unsigned char* p = (unsigned char*)ptr; @@ -78,7 +78,7 @@ static void* random_init(void* ptr, size_t size) return ptr; } -static bool doTests(void) +static bool doTest(void) { bool success = true; @@ -88,7 +88,7 @@ static bool doTests(void) /** Initialize the mock error function. **/ err_buf = checkPtr(malloc(err_buf_size = 256)); err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); - nmSetErrFunction(mock_error_fn); + nmSetErrFunction(mockErrorFn); /** Basic string data. **/ char* str1; @@ -106,7 +106,7 @@ static bool doTests(void) for (size_t i = 1lu; i < TEST_LIMIT; i++) { success &= EXPECT_NOT_NULL(test[i] = nmSysMalloc(i)); - data[i] = random_init(checkPtr(malloc(i)), i); + data[i] = randomInit(checkPtr(malloc(i)), i); memcpy(test[i], data[i], i); /* Write test data into test memory. */ } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) @@ -198,7 +198,7 @@ static bool doTests(void) long long test(char** tname) { *tname = "newmalloc-00 nmSysMalloc(), nmSysFree(), nmSysRealloc(), & nmSysStrdup()"; - return loopTests(doTests); + return loopTest(doTest); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 5e2b19432..9bd173e7b 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -46,7 +46,7 @@ static char* err_buf; static unsigned int err_buf_i; static unsigned int err_buf_size; -static int mock_error_fn(char* error_msg) +static int mockErrorFn(char* error_msg) { const size_t len = strlen(error_msg) + 1lu; @@ -67,7 +67,7 @@ static int mock_error_fn(char* error_msg) } /** Initialize memory of a given size with random data. **/ -static void* random_init(void* ptr, size_t size) +static void* randomInit(void* ptr, size_t size) { if (ptr == NULL) return NULL; unsigned char* p = (unsigned char*)ptr; @@ -77,7 +77,7 @@ static void* random_init(void* ptr, size_t size) return ptr; } -static bool doTests(void) +static bool doTest(void) { bool success = true; @@ -87,7 +87,7 @@ static bool doTests(void) /** Initialize the mock error function. **/ err_buf = checkPtr(malloc(err_buf_size = 256)); err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); - nmSetErrFunction(mock_error_fn); + nmSetErrFunction(mockErrorFn); /** Basic string data. **/ char* str1; @@ -105,7 +105,7 @@ static bool doTests(void) for (size_t i = 1lu; i < TEST_LIMIT; i++) { success &= EXPECT_NOT_NULL(test[i] = nmMalloc(i)); - data[i] = random_init(checkPtr(malloc(i)), i); + data[i] = randomInit(checkPtr(malloc(i)), i); memcpy(test[i], data[i], i); } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) @@ -165,7 +165,7 @@ static bool doTests(void) long long test(char** tname) { *tname = "newmalloc-01 nmMalloc(), nmFree(), & nmClear()"; - return loopTests(doTests); + return loopTest(doTest); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_newmalloc_02.c b/centrallix-lib/tests/test_newmalloc_02.c index 0c6da8737..08fcd6345 100644 --- a/centrallix-lib/tests/test_newmalloc_02.c +++ b/centrallix-lib/tests/test_newmalloc_02.c @@ -29,7 +29,7 @@ #define BLOCK_COUNT 64lu #define BLOCK_SIZE 128 -static bool doTests(void) +static bool doTest(void) { bool success = true; @@ -80,7 +80,7 @@ static bool doTests(void) long long test(char** tname) { *tname = "newmalloc-02 nmStats()"; - return loopTests(doTests); + return loopTest(doTest); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_qprintf_00.c b/centrallix-lib/tests/test_qprintf_00.c index 841f40f1e..ceac9ef72 100644 --- a/centrallix-lib/tests/test_qprintf_00.c +++ b/centrallix-lib/tests/test_qprintf_00.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-00 constant string using snprintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_01.c b/centrallix-lib/tests/test_qprintf_01.c index 87bd9c35e..f252e51d6 100644 --- a/centrallix-lib/tests/test_qprintf_01.c +++ b/centrallix-lib/tests/test_qprintf_01.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-01 constant string using qpfPrintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_02.c b/centrallix-lib/tests/test_qprintf_02.c index 94cbfc030..2adaabee9 100644 --- a/centrallix-lib/tests/test_qprintf_02.c +++ b/centrallix-lib/tests/test_qprintf_02.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-02 constant string with truncation, qpfPrintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_03.c b/centrallix-lib/tests/test_qprintf_03.c index d81f9937b..b495c0b43 100644 --- a/centrallix-lib/tests/test_qprintf_03.c +++ b/centrallix-lib/tests/test_qprintf_03.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-03 constant string, 1char overflow, using qpfPrintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_04.c b/centrallix-lib/tests/test_qprintf_04.c index 2efddbdd4..7d424a1ae 100644 --- a/centrallix-lib/tests/test_qprintf_04.c +++ b/centrallix-lib/tests/test_qprintf_04.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -46,5 +46,5 @@ long long test(char** tname) { *tname = "qprintf-04 empty string using qpfPrintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_05.c b/centrallix-lib/tests/test_qprintf_05.c index 88ee18af2..83a6da21a 100644 --- a/centrallix-lib/tests/test_qprintf_05.c +++ b/centrallix-lib/tests/test_qprintf_05.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -47,5 +47,5 @@ long long test(char** tname) { *tname = "qprintf-05 constant string into 0-sized buf using qpfPrintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_06.c b/centrallix-lib/tests/test_qprintf_06.c index 37af3e9d3..811621b3b 100644 --- a/centrallix-lib/tests/test_qprintf_06.c +++ b/centrallix-lib/tests/test_qprintf_06.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-06 constant string into 1-sized buf using qpfPrintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_07.c b/centrallix-lib/tests/test_qprintf_07.c index 230012848..c6fa656c3 100644 --- a/centrallix-lib/tests/test_qprintf_07.c +++ b/centrallix-lib/tests/test_qprintf_07.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-07 %STR insertion in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_08.c b/centrallix-lib/tests/test_qprintf_08.c index 37e3d6a59..9dbf37f98 100644 --- a/centrallix-lib/tests/test_qprintf_08.c +++ b/centrallix-lib/tests/test_qprintf_08.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-08 %STR insertion at end without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_09.c b/centrallix-lib/tests/test_qprintf_09.c index 48070b38f..11bcc5178 100644 --- a/centrallix-lib/tests/test_qprintf_09.c +++ b/centrallix-lib/tests/test_qprintf_09.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-09 %STR insertion at beginning without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_10.c b/centrallix-lib/tests/test_qprintf_10.c index 98643fd34..07d57d337 100644 --- a/centrallix-lib/tests/test_qprintf_10.c +++ b/centrallix-lib/tests/test_qprintf_10.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-10 compare with snprintf(), %s inserted"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_11.c b/centrallix-lib/tests/test_qprintf_11.c index 04bfbb2cf..b96f5715b 100644 --- a/centrallix-lib/tests/test_qprintf_11.c +++ b/centrallix-lib/tests/test_qprintf_11.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-11 %STR insertion in middle with overflow after STR"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_12.c b/centrallix-lib/tests/test_qprintf_12.c index 702581e86..d8b663c2c 100644 --- a/centrallix-lib/tests/test_qprintf_12.c +++ b/centrallix-lib/tests/test_qprintf_12.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-12 %STR insertion in middle with overflow in STR"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_13.c b/centrallix-lib/tests/test_qprintf_13.c index bd65f4580..ea1e05c2f 100644 --- a/centrallix-lib/tests/test_qprintf_13.c +++ b/centrallix-lib/tests/test_qprintf_13.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-13 %STR insertion in middle with overflow before STR"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_14.c b/centrallix-lib/tests/test_qprintf_14.c index d88aa5cbf..6e4fa8a72 100644 --- a/centrallix-lib/tests/test_qprintf_14.c +++ b/centrallix-lib/tests/test_qprintf_14.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-14 %INT insertion in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_15.c b/centrallix-lib/tests/test_qprintf_15.c index 61bdb118c..75b2666ca 100644 --- a/centrallix-lib/tests/test_qprintf_15.c +++ b/centrallix-lib/tests/test_qprintf_15.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-15 %POS insertion in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_16.c b/centrallix-lib/tests/test_qprintf_16.c index de528edb6..0bef635b2 100644 --- a/centrallix-lib/tests/test_qprintf_16.c +++ b/centrallix-lib/tests/test_qprintf_16.c @@ -10,7 +10,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -45,5 +45,5 @@ long long test(char** tname) { *tname = "qprintf-16 %POS insertion without overflow, negative"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_17.c b/centrallix-lib/tests/test_qprintf_17.c index eac970332..922164029 100644 --- a/centrallix-lib/tests/test_qprintf_17.c +++ b/centrallix-lib/tests/test_qprintf_17.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-17 %NSTR insertion in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_18.c b/centrallix-lib/tests/test_qprintf_18.c index 04fa3a614..fd1534457 100644 --- a/centrallix-lib/tests/test_qprintf_18.c +++ b/centrallix-lib/tests/test_qprintf_18.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-18 %NSTR insertion in middle with insert overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_19.c b/centrallix-lib/tests/test_qprintf_19.c index d8eb66520..6713c152d 100644 --- a/centrallix-lib/tests/test_qprintf_19.c +++ b/centrallix-lib/tests/test_qprintf_19.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-19 %*STR insertion in middle with insert overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_20.c b/centrallix-lib/tests/test_qprintf_20.c index 9d1421717..323c4ef09 100644 --- a/centrallix-lib/tests/test_qprintf_20.c +++ b/centrallix-lib/tests/test_qprintf_20.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-20 %CHR insertion in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_21.c b/centrallix-lib/tests/test_qprintf_21.c index db4d02e8c..f13c1e3bd 100644 --- a/centrallix-lib/tests/test_qprintf_21.c +++ b/centrallix-lib/tests/test_qprintf_21.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-21 %DBL insertion in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_22.c b/centrallix-lib/tests/test_qprintf_22.c index dc7275a79..dcbbd4003 100644 --- a/centrallix-lib/tests/test_qprintf_22.c +++ b/centrallix-lib/tests/test_qprintf_22.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-22 %STR&NLEN in middle with insert overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_23.c b/centrallix-lib/tests/test_qprintf_23.c index 430544e84..c0f8d5dab 100644 --- a/centrallix-lib/tests/test_qprintf_23.c +++ b/centrallix-lib/tests/test_qprintf_23.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-23 %STR&NLEN in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_24.c b/centrallix-lib/tests/test_qprintf_24.c index 503a4082a..dde0bf616 100644 --- a/centrallix-lib/tests/test_qprintf_24.c +++ b/centrallix-lib/tests/test_qprintf_24.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-24 %STR&*LEN in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_25.c b/centrallix-lib/tests/test_qprintf_25.c index ecdfc90be..f03d70b84 100644 --- a/centrallix-lib/tests/test_qprintf_25.c +++ b/centrallix-lib/tests/test_qprintf_25.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-25 %STR&SYM in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_26.c b/centrallix-lib/tests/test_qprintf_26.c index 04d51fe34..ccd20d355 100644 --- a/centrallix-lib/tests/test_qprintf_26.c +++ b/centrallix-lib/tests/test_qprintf_26.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -43,5 +43,5 @@ long long test(char** tname) { *tname = "qprintf-26 %STR&SYM in middle with illegal symbol"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_27.c b/centrallix-lib/tests/test_qprintf_27.c index d8af19f2a..3856095ed 100644 --- a/centrallix-lib/tests/test_qprintf_27.c +++ b/centrallix-lib/tests/test_qprintf_27.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-27 %STR&ESCQ in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_28.c b/centrallix-lib/tests/test_qprintf_28.c index 5176de1c3..4e0bc7694 100644 --- a/centrallix-lib/tests/test_qprintf_28.c +++ b/centrallix-lib/tests/test_qprintf_28.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-28 %STR&ESCQ in middle with overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_29.c b/centrallix-lib/tests/test_qprintf_29.c index 375af2d27..2985d1c1f 100644 --- a/centrallix-lib/tests/test_qprintf_29.c +++ b/centrallix-lib/tests/test_qprintf_29.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-29 %STR&ESCQ in middle with overflow of insert"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_30.c b/centrallix-lib/tests/test_qprintf_30.c index 0808ebff7..e7db817a8 100644 --- a/centrallix-lib/tests/test_qprintf_30.c +++ b/centrallix-lib/tests/test_qprintf_30.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-30 %STR&ESCQ in middle with 2-overflow of insert"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_31.c b/centrallix-lib/tests/test_qprintf_31.c index 9c00654d4..d1ddddfff 100644 --- a/centrallix-lib/tests/test_qprintf_31.c +++ b/centrallix-lib/tests/test_qprintf_31.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-31 %STR&ESCQ&NLEN in middle less than LEN"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_32.c b/centrallix-lib/tests/test_qprintf_32.c index a12859076..4d5283521 100644 --- a/centrallix-lib/tests/test_qprintf_32.c +++ b/centrallix-lib/tests/test_qprintf_32.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-32 %STR&ESCQ&NLEN in middle eq to LEN"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_33.c b/centrallix-lib/tests/test_qprintf_33.c index a975594d4..1927f764a 100644 --- a/centrallix-lib/tests/test_qprintf_33.c +++ b/centrallix-lib/tests/test_qprintf_33.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-33 %STR&ESCQ&NLEN in middle 1 greater than LEN"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_34.c b/centrallix-lib/tests/test_qprintf_34.c index 96b65304d..bf0ab5b56 100644 --- a/centrallix-lib/tests/test_qprintf_34.c +++ b/centrallix-lib/tests/test_qprintf_34.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-34 %STR&ESCQ&NLEN in middle 2 greater than LEN"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_35.c b/centrallix-lib/tests/test_qprintf_35.c index 354e34e4f..3442eaffc 100644 --- a/centrallix-lib/tests/test_qprintf_35.c +++ b/centrallix-lib/tests/test_qprintf_35.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-35 %STR&HTE in middle, no overflows"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_36.c b/centrallix-lib/tests/test_qprintf_36.c index c48a4cf8b..cd5fb6f4b 100644 --- a/centrallix-lib/tests/test_qprintf_36.c +++ b/centrallix-lib/tests/test_qprintf_36.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -46,5 +46,5 @@ long long test(char** tname) { *tname = "qprintf-36 %STR&HTE in middle, overflow 1 char"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_37.c b/centrallix-lib/tests/test_qprintf_37.c index 550dcb0ec..de779340a 100644 --- a/centrallix-lib/tests/test_qprintf_37.c +++ b/centrallix-lib/tests/test_qprintf_37.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-37 %STR&HTE&NLEN in middle, len 1 greater"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_38.c b/centrallix-lib/tests/test_qprintf_38.c index 55c0b6c0f..f65a89df9 100644 --- a/centrallix-lib/tests/test_qprintf_38.c +++ b/centrallix-lib/tests/test_qprintf_38.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-38 %STR&HTE&NLEN in middle, len equal"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_39.c b/centrallix-lib/tests/test_qprintf_39.c index 479abbeed..199b7739f 100644 --- a/centrallix-lib/tests/test_qprintf_39.c +++ b/centrallix-lib/tests/test_qprintf_39.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-39 %STR&HTE&NLEN in middle, len 1 less"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_40.c b/centrallix-lib/tests/test_qprintf_40.c index 334fa4edf..9535925c5 100644 --- a/centrallix-lib/tests/test_qprintf_40.c +++ b/centrallix-lib/tests/test_qprintf_40.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-40 %STR&HTE&NLEN in middle, len 2 less"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_41.c b/centrallix-lib/tests/test_qprintf_41.c index aabad7ac1..72eb100cd 100644 --- a/centrallix-lib/tests/test_qprintf_41.c +++ b/centrallix-lib/tests/test_qprintf_41.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-41 %STR&HEX in middle, no overflows"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_42.c b/centrallix-lib/tests/test_qprintf_42.c index cc2abe93d..b17a80e72 100644 --- a/centrallix-lib/tests/test_qprintf_42.c +++ b/centrallix-lib/tests/test_qprintf_42.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-42 %STR&HEX at end, overflow(1) in insert"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_43.c b/centrallix-lib/tests/test_qprintf_43.c index f52d1efdf..9076b55d1 100644 --- a/centrallix-lib/tests/test_qprintf_43.c +++ b/centrallix-lib/tests/test_qprintf_43.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-43 %STR&HEX at end, overflow(2) in insert"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_44.c b/centrallix-lib/tests/test_qprintf_44.c index 5e17a3bd8..eed57c627 100644 --- a/centrallix-lib/tests/test_qprintf_44.c +++ b/centrallix-lib/tests/test_qprintf_44.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-44 %STR&HEX in middle, overflow(1) post-insert"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_45.c b/centrallix-lib/tests/test_qprintf_45.c index 38bde9b67..ed2815dc1 100644 --- a/centrallix-lib/tests/test_qprintf_45.c +++ b/centrallix-lib/tests/test_qprintf_45.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-45 %STR&HEX&NLEN in middle, no overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_46.c b/centrallix-lib/tests/test_qprintf_46.c index 86c7d7e18..79a2f48cb 100644 --- a/centrallix-lib/tests/test_qprintf_46.c +++ b/centrallix-lib/tests/test_qprintf_46.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-46 %STR&HEX&NLEN in middle, insert overflow(1)"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_47.c b/centrallix-lib/tests/test_qprintf_47.c index e73e64298..2e5d73163 100644 --- a/centrallix-lib/tests/test_qprintf_47.c +++ b/centrallix-lib/tests/test_qprintf_47.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-47 %STR&HEX&NLEN in middle, insert overflow(2)"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_48.c b/centrallix-lib/tests/test_qprintf_48.c index 4227fb201..99d25930b 100644 --- a/centrallix-lib/tests/test_qprintf_48.c +++ b/centrallix-lib/tests/test_qprintf_48.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-48 %STR" in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_49.c b/centrallix-lib/tests/test_qprintf_49.c index 11948fd88..93cc4cf98 100644 --- a/centrallix-lib/tests/test_qprintf_49.c +++ b/centrallix-lib/tests/test_qprintf_49.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-49 %STR&DQUOT in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_50.c b/centrallix-lib/tests/test_qprintf_50.c index 9b18fa35c..44448d093 100644 --- a/centrallix-lib/tests/test_qprintf_50.c +++ b/centrallix-lib/tests/test_qprintf_50.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-50 %STR" at end without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_51.c b/centrallix-lib/tests/test_qprintf_51.c index ad77a6d6b..3daefae69 100644 --- a/centrallix-lib/tests/test_qprintf_51.c +++ b/centrallix-lib/tests/test_qprintf_51.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-51 %STR" at end with overflow(1)"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_52.c b/centrallix-lib/tests/test_qprintf_52.c index 07b45aec7..f9b03f70b 100644 --- a/centrallix-lib/tests/test_qprintf_52.c +++ b/centrallix-lib/tests/test_qprintf_52.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-52 %STR" at end with overflow(2)"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_53.c b/centrallix-lib/tests/test_qprintf_53.c index 96f44cddd..d2a25f6ed 100644 --- a/centrallix-lib/tests/test_qprintf_53.c +++ b/centrallix-lib/tests/test_qprintf_53.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-53 Bugtest:   following %STR&HTE"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_54.c b/centrallix-lib/tests/test_qprintf_54.c index 125aead74..757ccc8cd 100644 --- a/centrallix-lib/tests/test_qprintf_54.c +++ b/centrallix-lib/tests/test_qprintf_54.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-54 Bugtest: %[ %] with static data"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_55.c b/centrallix-lib/tests/test_qprintf_55.c index 21d7d7b2b..2af189785 100644 --- a/centrallix-lib/tests/test_qprintf_55.c +++ b/centrallix-lib/tests/test_qprintf_55.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-55 %STR&FILE valid filename"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_56.c b/centrallix-lib/tests/test_qprintf_56.c index f103de55b..9667f0ab9 100644 --- a/centrallix-lib/tests/test_qprintf_56.c +++ b/centrallix-lib/tests/test_qprintf_56.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -52,5 +52,5 @@ long long test(char** tname) { *tname = "qprintf-56 %STR&FILE various invalid filenames"; - return loopTests(doTests) * 7; + return loopTest(doTest) * 7; } diff --git a/centrallix-lib/tests/test_qprintf_57.c b/centrallix-lib/tests/test_qprintf_57.c index e683db5f0..9d8146376 100644 --- a/centrallix-lib/tests/test_qprintf_57.c +++ b/centrallix-lib/tests/test_qprintf_57.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "qprintf-57 %nSTR&FILE tests with fixed length insert"; - return loopTests(doTests) * 5; + return loopTest(doTest) * 5; } diff --git a/centrallix-lib/tests/test_qprintf_58.c b/centrallix-lib/tests/test_qprintf_58.c index a9d61c4b6..6a0bec542 100644 --- a/centrallix-lib/tests/test_qprintf_58.c +++ b/centrallix-lib/tests/test_qprintf_58.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "qprintf-58 %STR&PATH valid pathname"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_59.c b/centrallix-lib/tests/test_qprintf_59.c index 8e65c6470..42ec7a392 100644 --- a/centrallix-lib/tests/test_qprintf_59.c +++ b/centrallix-lib/tests/test_qprintf_59.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -52,5 +52,5 @@ long long test(char** tname) { *tname = "qprintf-59 %STR&PATH various invalid pathnames"; - return loopTests(doTests) * 7; + return loopTest(doTest) * 7; } diff --git a/centrallix-lib/tests/test_qprintf_60.c b/centrallix-lib/tests/test_qprintf_60.c index 98c2dc32d..6fe6d86ca 100644 --- a/centrallix-lib/tests/test_qprintf_60.c +++ b/centrallix-lib/tests/test_qprintf_60.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -49,5 +49,5 @@ long long test(char** tname) { *tname = "qprintf-60 %nSTR&PATH fixed-length insert tests"; - return loopTests(doTests) * 5; + return loopTest(doTest) * 5; } diff --git a/centrallix-lib/tests/test_qprintf_61.c b/centrallix-lib/tests/test_qprintf_61.c index cb6b67683..f2e1f64d4 100644 --- a/centrallix-lib/tests/test_qprintf_61.c +++ b/centrallix-lib/tests/test_qprintf_61.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -52,5 +52,5 @@ long long test(char** tname) { *tname = "qprintf-61 %STR&PATH&nLEN length-limited insert tests"; - return loopTests(doTests) * 6; + return loopTest(doTest) * 6; } diff --git a/centrallix-lib/tests/test_qprintf_62.c b/centrallix-lib/tests/test_qprintf_62.c index 3278976ba..aeecde0c7 100644 --- a/centrallix-lib/tests/test_qprintf_62.c +++ b/centrallix-lib/tests/test_qprintf_62.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -49,5 +49,5 @@ long long test(char** tname) { *tname = "qprintf-62 %STR&DB64 integrity test"; - return loopTests(doTests) * 6; + return loopTest(doTest) * 6; } diff --git a/centrallix-lib/tests/test_qprintf_63.c b/centrallix-lib/tests/test_qprintf_63.c index 0031e6c4a..63cc9bb0d 100644 --- a/centrallix-lib/tests/test_qprintf_63.c +++ b/centrallix-lib/tests/test_qprintf_63.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -42,5 +42,5 @@ long long test(char** tname) { *tname = "qprintf-63 %STR&DB64 overflow test"; - return loopTests(doTests) * 2; + return loopTest(doTest) * 2; } diff --git a/centrallix-lib/tests/test_qprintf_64.c b/centrallix-lib/tests/test_qprintf_64.c index f7747a7d0..00202033c 100644 --- a/centrallix-lib/tests/test_qprintf_64.c +++ b/centrallix-lib/tests/test_qprintf_64.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -41,5 +41,5 @@ long long test(char** tname) { *tname = "qprintf-64 %STR&B64 integrity test"; - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_qprintf_65.c b/centrallix-lib/tests/test_qprintf_65.c index 5fcff11e8..88f72ef12 100644 --- a/centrallix-lib/tests/test_qprintf_65.c +++ b/centrallix-lib/tests/test_qprintf_65.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -42,5 +42,5 @@ long long test(char** tname) { *tname = "qprintf-65 %STR&B64 overflow test"; - return loopTests(doTests) * 2; + return loopTest(doTest) * 2; } diff --git a/centrallix-lib/tests/test_qprintf_66.c b/centrallix-lib/tests/test_qprintf_66.c index a24651959..e48d7d1dc 100644 --- a/centrallix-lib/tests/test_qprintf_66.c +++ b/centrallix-lib/tests/test_qprintf_66.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[39]; @@ -55,5 +55,5 @@ long long test(char** tname) { *tname = "qprintf-66 %LL insertion in middle without overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_qprintf_67.c b/centrallix-lib/tests/test_qprintf_67.c index a9b8a334f..13dcd8b4e 100644 --- a/centrallix-lib/tests/test_qprintf_67.c +++ b/centrallix-lib/tests/test_qprintf_67.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -54,5 +54,5 @@ long long test(char** tname) { *tname = "qprintf-67 %STR&DHEX integrity test"; - return loopTests(doTests) * 7; + return loopTest(doTest) * 7; } diff --git a/centrallix-lib/tests/test_qprintf_68.c b/centrallix-lib/tests/test_qprintf_68.c index db462ef41..68c0ca3d6 100644 --- a/centrallix-lib/tests/test_qprintf_68.c +++ b/centrallix-lib/tests/test_qprintf_68.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -42,5 +42,5 @@ long long test(char** tname) { *tname = "qprintf-68 %STR&DHEX overflow test"; - return loopTests(doTests) * 2; + return loopTest(doTest) * 2; } diff --git a/centrallix-lib/tests/test_range_00.c b/centrallix-lib/tests/test_range_00.c index 9cc767033..b649441a3 100644 --- a/centrallix-lib/tests/test_range_00.c +++ b/centrallix-lib/tests/test_range_00.c @@ -27,7 +27,7 @@ /** Tested module. **/ #include "range.h" -static bool doTests(void) +static bool doTest(void) { bool success = true; @@ -75,5 +75,5 @@ static bool doTests(void) long long test(char** tname) { *tname = "range-00 min() & max()"; - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_range_01.c b/centrallix-lib/tests/test_range_01.c index 0b199da9e..41a1334ed 100644 --- a/centrallix-lib/tests/test_range_01.c +++ b/centrallix-lib/tests/test_range_01.c @@ -26,7 +26,7 @@ /** Tested module. **/ #include "range.h" -static bool doTests(void) +static bool doTest(void) { bool success = true; @@ -68,5 +68,5 @@ static bool doTests(void) long long test(char** tname) { *tname = "range-01 roundTo()"; - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_smmalloc_01.c b/centrallix-lib/tests/test_smmalloc_01.c index a6354ed99..f5bf47a63 100644 --- a/centrallix-lib/tests/test_smmalloc_01.c +++ b/centrallix-lib/tests/test_smmalloc_01.c @@ -8,7 +8,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { pSmRegion r; @@ -24,5 +24,5 @@ test(char** tname) { *tname = "smmalloc-01 create/destroy region"; smInitialize(); - return loopTests(doTests); + return loopTest(doTest); } diff --git a/centrallix-lib/tests/test_smmalloc_02.c b/centrallix-lib/tests/test_smmalloc_02.c index 07933b9cf..9f7ec72ed 100644 --- a/centrallix-lib/tests/test_smmalloc_02.c +++ b/centrallix-lib/tests/test_smmalloc_02.c @@ -11,7 +11,7 @@ static pSmRegion region = NULL; static bool -doTests(void) +doTest(void) { void* ptr; @@ -33,7 +33,7 @@ test(char** tname) region = smCreate(1024*1024); if (!region) return -1; - rval = loopTests(doTests); + rval = loopTest(doTest); smDestroy(region); region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_03.c b/centrallix-lib/tests/test_smmalloc_03.c index 7c7e38410..f3c45e572 100644 --- a/centrallix-lib/tests/test_smmalloc_03.c +++ b/centrallix-lib/tests/test_smmalloc_03.c @@ -15,7 +15,7 @@ static pSmRegion region = NULL; static int blocks = 0; static bool -doTests(void) +doTest(void) { int j,k; void* alloc[1024]; @@ -53,7 +53,7 @@ test(char** tname) region = smCreate(1024*1024); if (!region) return -1; - rval = loopTests(doTests); + rval = loopTest(doTest); if (rval > 0) rval *= blocks; diff --git a/centrallix-lib/tests/test_smmalloc_04.c b/centrallix-lib/tests/test_smmalloc_04.c index b75993f29..bfd1665a8 100644 --- a/centrallix-lib/tests/test_smmalloc_04.c +++ b/centrallix-lib/tests/test_smmalloc_04.c @@ -15,7 +15,7 @@ static pSmRegion region = NULL; static int blocks = 0; static bool -doTests(void) +doTest(void) { int j,k; void* alloc[1024]; @@ -51,7 +51,7 @@ test(char** tname) region = smCreate(1024*1024); if (!region) return -1; - rval = loopTests(doTests); + rval = loopTest(doTest); if (rval > 0) rval *= blocks; diff --git a/centrallix-lib/tests/test_smmalloc_05.c b/centrallix-lib/tests/test_smmalloc_05.c index 1690a0478..ec6e2ef19 100644 --- a/centrallix-lib/tests/test_smmalloc_05.c +++ b/centrallix-lib/tests/test_smmalloc_05.c @@ -16,7 +16,7 @@ static pSmRegion region = NULL; static int blocks = 0; static bool -doTests(void) +doTest(void) { int j,k,l; void* alloc[1024]; @@ -61,7 +61,7 @@ test(char** tname) region = smCreate(1024*1024); if (!region) return -1; - rval = loopTests(doTests); + rval = loopTest(doTest); if (rval > 0) rval *= blocks; diff --git a/centrallix-lib/tests/test_smmalloc_06.c b/centrallix-lib/tests/test_smmalloc_06.c index 8afe32394..8b3aec81a 100644 --- a/centrallix-lib/tests/test_smmalloc_06.c +++ b/centrallix-lib/tests/test_smmalloc_06.c @@ -15,7 +15,7 @@ static pSmRegion region = NULL; static int blocks = 0; static bool -doTests(void) +doTest(void) { int j,k,l; void* alloc[1024]; @@ -53,7 +53,7 @@ test(char** tname) region = smCreate(1024*1024); if (!region) return -1; - rval = loopTests(doTests); + rval = loopTest(doTest); if (rval > 0) rval *= blocks; diff --git a/centrallix-lib/tests/test_smmalloc_07.c b/centrallix-lib/tests/test_smmalloc_07.c index 8e82e265b..7c330b65c 100644 --- a/centrallix-lib/tests/test_smmalloc_07.c +++ b/centrallix-lib/tests/test_smmalloc_07.c @@ -15,7 +15,7 @@ static pSmRegion region = NULL; static int refs = 0; static bool -doTests(void) +doTest(void) { int j,k,l,t; void* alloc[1024]; @@ -72,7 +72,7 @@ test(char** tname) region = smCreate(1024*1024); if (!region) return -1; - rval = loopTests(doTests); + rval = loopTest(doTest); if (rval > 0) rval *= refs; diff --git a/centrallix-lib/tests/test_smmalloc_08.c b/centrallix-lib/tests/test_smmalloc_08.c index 0c0a8f22a..6cb52626f 100644 --- a/centrallix-lib/tests/test_smmalloc_08.c +++ b/centrallix-lib/tests/test_smmalloc_08.c @@ -12,7 +12,7 @@ static pSmRegion region = NULL; static bool -doTests(void) +doTest(void) { int j,l,s; void* alloc[128]; @@ -61,7 +61,7 @@ test(char** tname) region = smCreate(1024*1024); if (!region) return -1; - rval = loopTests(doTests); + rval = loopTest(doTest); if (rval > 0) rval *= 128; diff --git a/centrallix-lib/tests/test_strtcpy_00.c b/centrallix-lib/tests/test_strtcpy_00.c index c6e7ef4d8..1ee0f1748 100644 --- a/centrallix-lib/tests/test_strtcpy_00.c +++ b/centrallix-lib/tests/test_strtcpy_00.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "strtcpy-00 compare to strcpy()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_01.c b/centrallix-lib/tests/test_strtcpy_01.c index 678631d56..3a8694f82 100644 --- a/centrallix-lib/tests/test_strtcpy_01.c +++ b/centrallix-lib/tests/test_strtcpy_01.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "strtcpy-01 compare to memccpy() and arr[n-1] = '\\0'"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_02.c b/centrallix-lib/tests/test_strtcpy_02.c index be90530be..be6bf4a99 100644 --- a/centrallix-lib/tests/test_strtcpy_02.c +++ b/centrallix-lib/tests/test_strtcpy_02.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { unsigned char buf[44]; @@ -44,5 +44,5 @@ long long test(char** tname) { *tname = "strtcpy-02 compare to snprintf()"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_03.c b/centrallix-lib/tests/test_strtcpy_03.c index 1830bc9b3..7bcb95bbb 100644 --- a/centrallix-lib/tests/test_strtcpy_03.c +++ b/centrallix-lib/tests/test_strtcpy_03.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -46,5 +46,5 @@ long long test(char** tname) { *tname = "strtcpy-03 strtcpy() full-size non-overflow"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_04.c b/centrallix-lib/tests/test_strtcpy_04.c index fd8fcfbb5..470979bb0 100644 --- a/centrallix-lib/tests/test_strtcpy_04.c +++ b/centrallix-lib/tests/test_strtcpy_04.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -46,5 +46,5 @@ long long test(char** tname) { *tname = "strtcpy-04 strtcpy() overflow by 1 char"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_05.c b/centrallix-lib/tests/test_strtcpy_05.c index 920b24dcb..9bc26b1fd 100644 --- a/centrallix-lib/tests/test_strtcpy_05.c +++ b/centrallix-lib/tests/test_strtcpy_05.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -46,5 +46,5 @@ long long test(char** tname) { *tname = "strtcpy-05 strtcpy() overflow / truncation"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_06.c b/centrallix-lib/tests/test_strtcpy_06.c index 50e892de4..d5d81e6f2 100644 --- a/centrallix-lib/tests/test_strtcpy_06.c +++ b/centrallix-lib/tests/test_strtcpy_06.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -49,5 +49,5 @@ long long test(char** tname) { *tname = "strtcpy-06 strtcpy() copy to 0-length buffer"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_07.c b/centrallix-lib/tests/test_strtcpy_07.c index 82a7597e4..d99cc0483 100644 --- a/centrallix-lib/tests/test_strtcpy_07.c +++ b/centrallix-lib/tests/test_strtcpy_07.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -51,5 +51,5 @@ long long test(char** tname) { *tname = "strtcpy-07 strtcpy() copy to 1 char buffer"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_08.c b/centrallix-lib/tests/test_strtcpy_08.c index a751f8745..ea7304c6b 100644 --- a/centrallix-lib/tests/test_strtcpy_08.c +++ b/centrallix-lib/tests/test_strtcpy_08.c @@ -14,7 +14,7 @@ *** offsets). ***/ static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -56,5 +56,5 @@ long long test(char** tname) { *tname = "strtcpy-08 strtcpy() copy to 2 char buffer"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_09.c b/centrallix-lib/tests/test_strtcpy_09.c index 32b691604..883beea73 100644 --- a/centrallix-lib/tests/test_strtcpy_09.c +++ b/centrallix-lib/tests/test_strtcpy_09.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "strtcpy-09 strtcpy() use less than entire buffer"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_10.c b/centrallix-lib/tests/test_strtcpy_10.c index ee1707cb3..4d07bb2cd 100644 --- a/centrallix-lib/tests/test_strtcpy_10.c +++ b/centrallix-lib/tests/test_strtcpy_10.c @@ -9,7 +9,7 @@ #include "test_utils.h" static bool -doTests(void) +doTest(void) { int rval; unsigned char buf[44]; @@ -48,5 +48,5 @@ long long test(char** tname) { *tname = "strtcpy-10 strtcpy() copy empty string into nonempty buffer"; - return loopTests(doTests) * 4; + return loopTest(doTest) * 4; } diff --git a/centrallix-lib/tests/test_strtcpy_11.c b/centrallix-lib/tests/test_strtcpy_11.c index dfa551dff..a007d440f 100644 --- a/centrallix-lib/tests/test_strtcpy_11.c +++ b/centrallix-lib/tests/test_strtcpy_11.c @@ -65,7 +65,7 @@ static Case cases[] = { "", "ab", 2, -2, "a", 1 }, }; -/** Number of cases run per call to doTests(). **/ +/** Number of cases run per call to doTest(). **/ #define NCASES ((int)(sizeof(cases) / sizeof(Case))) /*** This test verifies strtcatf() over a table of buffer sizes and append @@ -76,7 +76,7 @@ static Case cases[] = *** outside the caller's declared DstLen is ever touched. ***/ static bool -doTests(void) +doTest(void) { int c, rval; unsigned char raw[RAW]; @@ -122,5 +122,5 @@ long long test(char** tname) { *tname = "strtcpy-11 strtcatf() return values and buffer bounds"; - return loopTests(doTests) * NCASES; + return loopTest(doTest) * NCASES; } diff --git a/centrallix-lib/tests/test_strtcpy_12.c b/centrallix-lib/tests/test_strtcpy_12.c index 765457aca..5b6d22588 100644 --- a/centrallix-lib/tests/test_strtcpy_12.c +++ b/centrallix-lib/tests/test_strtcpy_12.c @@ -106,7 +106,7 @@ wrapper(char* dst, size_t dstlen, size_t* pos, const char* fmt, ...) return rval; } -/** Number of chains run per call to doTests(). **/ +/** Number of chains run per call to doTest(). **/ #define NCHAINS ((int)(sizeof(chains) / sizeof(Chain))) /*** This test verifies strtcatf_va() reached through a caller's own varargs @@ -120,7 +120,7 @@ wrapper(char* dst, size_t dstlen, size_t* pos, const char* fmt, ...) *** contributing no text, and empty appends at the front and back of a chain. ***/ static bool -doTests(void) +doTest(void) { int c, a; size_t prefixlen; @@ -192,10 +192,10 @@ test(char** tname) *tname = "strtcpy-12 strtcatf_va() parity and chained appends"; - /** Count the appends one pass of doTests() performs. **/ + /** Count the appends one pass of doTest() performs. **/ for(c=0;c Date: Wed, 9 Sep 2026 13:29:17 -0600 Subject: [PATCH 161/361] Multiply operation counts properly. --- centrallix-lib/tests/test_newmalloc_00.c | 2 +- centrallix-lib/tests/test_newmalloc_01.c | 2 +- centrallix-lib/tests/test_range_00.c | 2 +- centrallix-lib/tests/test_range_01.c | 2 +- centrallix-lib/tests/test_timer_00.c | 2 +- centrallix-lib/tests/test_util_02.c | 2 +- centrallix-lib/tests/test_xarray_00.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index dbb41d232..33039bac5 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -198,7 +198,7 @@ static bool doTest(void) long long test(char** tname) { *tname = "newmalloc-00 nmSysMalloc(), nmSysFree(), nmSysRealloc(), & nmSysStrdup()"; - return loopTest(doTest); + return loopTest(doTest) * ((long long)TEST_LIMIT + 3ll); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 9bd173e7b..2dd707178 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -165,7 +165,7 @@ static bool doTest(void) long long test(char** tname) { *tname = "newmalloc-01 nmMalloc(), nmFree(), & nmClear()"; - return loopTest(doTest); + return loopTest(doTest) * ((long long)TEST_LIMIT + 3ll); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_range_00.c b/centrallix-lib/tests/test_range_00.c index b649441a3..c7ec952c6 100644 --- a/centrallix-lib/tests/test_range_00.c +++ b/centrallix-lib/tests/test_range_00.c @@ -75,5 +75,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "range-00 min() & max()"; - return loopTest(doTest); + return loopTest(doTest) * (15 * 2); } diff --git a/centrallix-lib/tests/test_range_01.c b/centrallix-lib/tests/test_range_01.c index 41a1334ed..6479e966d 100644 --- a/centrallix-lib/tests/test_range_01.c +++ b/centrallix-lib/tests/test_range_01.c @@ -68,5 +68,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "range-01 roundTo()"; - return loopTest(doTest); + return loopTest(doTest) * 22; } diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c index 778d49071..ae5951761 100644 --- a/centrallix-lib/tests/test_timer_00.c +++ b/centrallix-lib/tests/test_timer_00.c @@ -109,7 +109,7 @@ long long test(char** tname) *** CPU cycles so that the test runner doesn't crash because the *** CPU clock time was too low. ***/ - long long i = loopTest(doNothing); + const long long i = loopTest(doNothing); /** Return success. **/ return i; diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index b55b5f7ea..38e2d5f97 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -121,5 +121,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "util-02 Printing"; - return loopTest(doTest); + return loopTest(doTest) * (17 + 43); } diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index aa08d4c1d..3cb5ac24e 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -157,5 +157,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "xarray-00 Full Test"; - return loopTest(doTest); + return loopTest(doTest) * 34; } From 1fa7e0cdc1a4255bf55794a1b05e29c10ca5496e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 14:01:45 -0600 Subject: [PATCH 162/361] Improve comment clarity. --- centrallix-lib/Makefile.in | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 55aa5b23a..323b2ba5c 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -178,13 +178,12 @@ install: all ## as each test finishes, so parallel tests cannot interleave. ## ## TESTCMD runs once per test with the binary as "$$1" and its capture file as -## "$$2". It writes to "$$2.part" and renames that to "$$2" when done, since -## the rename is what marks the test finished, and it exits with the test's own +## "$$2". It writes to "$$2.part" and renames that to "$$2" when done so that +## the rename is what marks the test as finished, then it exits with the test's ## status. The driver exits 0 even when reporting FAIL or CRASH (see ## tests/t_driver.c), so a nonzero status means the test did not complete ## normally and must fail the target; only valgrind's --error-exitcode is -## swallowed. A test whose output never appears is reported in its place and -## fails the target too. +## dropped. A test whose output never appears is reported and also fails. RUN_TESTS = \ RESULTS=$$(mktemp -d) || exit 1; \ trap 'rm -rf "$$RESULTS"' EXIT INT TERM; \ From 374465b088dc020182477abbd5f56a6342d24057 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 14:31:39 -0600 Subject: [PATCH 163/361] Add tests for the xhash module. --- centrallix-lib/tests/test_xhash_00.c | 136 +++++++++++++++++++++++ centrallix-lib/tests/test_xhash_01.c | 114 ++++++++++++++++++++ centrallix-lib/tests/test_xhash_02.c | 154 +++++++++++++++++++++++++++ centrallix-lib/tests/test_xhash_03.c | 135 +++++++++++++++++++++++ centrallix-lib/tests/test_xhash_04.c | 80 ++++++++++++++ centrallix-lib/tests/test_xhash_05.c | 106 ++++++++++++++++++ 6 files changed, 725 insertions(+) create mode 100644 centrallix-lib/tests/test_xhash_00.c create mode 100644 centrallix-lib/tests/test_xhash_01.c create mode 100644 centrallix-lib/tests/test_xhash_02.c create mode 100644 centrallix-lib/tests/test_xhash_03.c create mode 100644 centrallix-lib/tests/test_xhash_04.c create mode 100644 centrallix-lib/tests/test_xhash_05.c diff --git a/centrallix-lib/tests/test_xhash_00.c b/centrallix-lib/tests/test_xhash_00.c new file mode 100644 index 000000000..651d75fab --- /dev/null +++ b/centrallix-lib/tests/test_xhash_00.c @@ -0,0 +1,136 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_xhash_00.c */ +/* Author: Israel Fuller */ +/* Creation: September 9th, 2026 */ +/* Description: Test xhInit(), xhAdd(), xhLookup(), xhRemove(), and */ +/* xhDeInit() on a table keyed by NUL terminated strings. */ +/* */ +/* Unsupported cases skipped by the test suite: */ +/* - a table with zero rows: divides by zero. */ +/* - a key shorter nonzero key length: reads past the end. */ +/* - xhDeInit() on a table with entries: leaks them. */ +/* - xhAdd() allocation failure: needs an inject to test. */ +/************************************************************************/ + +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "xhash.h" + +#define HASH_ROWS 17 +#define KEY_COUNT 5 + +static char* keys[KEY_COUNT] = {"alpha", "beta", "gamma", "delta", "epsilon"}; +static char* data[KEY_COUNT] = {"one", "two", "three", "four", "five"}; + +static bool doTest(void) + { + bool success = true; + XHashTable hash; + + /** A key length of zero selects NUL-terminated string keys. **/ + success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, 0)), 0, "%d"); + success &= EXPECT_EQL(hash.nRows, HASH_ROWS, "%d"); + success &= EXPECT_EQL(hash.KeyLen, 0, "%d"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + + /** Every row starts out empty. **/ + success &= EXPECT_EQL(xaCount(&hash.Rows), HASH_ROWS, "%d"); + int used_rows = 0; + for (int i = 0; i < HASH_ROWS; i++) + if (hash.Rows.Items[i] != NULL) used_rows++; + success &= EXPECT_EQL(used_rows, 0, "%d"); + + /** An empty table has nothing to find or remove. **/ + success &= EXPECT_EQL(xhLookup(&hash, keys[0]), NULL, "%p"); + success &= EXPECT_EQL(xhRemove(&hash, keys[0]), -1, "%d"); + + /** Add each pair. The table stores the pointers it is given. **/ + for (int i = 0; i < KEY_COUNT; i++) + { + success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], data[i])), 0, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); + } + + /** Every entry survives the additions that follow it. **/ + for (int i = 0; i < KEY_COUNT; i++) + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + + /** Keys are matched by content rather than by pointer. **/ + char key_copy[16]; + strcpy(key_copy, keys[0]); + success &= EXPECT_EQL(xhLookup(&hash, key_copy), data[0], "%p"); + + /** Neither a prefix nor an extension of a key matches it. **/ + success &= EXPECT_EQL(xhLookup(&hash, "alph"), NULL, "%p"); + success &= EXPECT_EQL(xhLookup(&hash, "alphabet"), NULL, "%p"); + + /** A duplicate key is rejected and leaves the existing entry alone. **/ + success &= EXPECT_EQL(xhAdd(&hash, key_copy, "duplicate"), -1, "%d"); + success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + + /** Removing a key that was never added does not disturb the table. **/ + success &= EXPECT_EQL(xhRemove(&hash, "alphabet"), -1, "%d"); + success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + + /** The empty string is a usable key. **/ + success &= EXPECT_EQL(check(xhAdd(&hash, "", data[0])), 0, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, ""), data[0], "%p"); + success &= EXPECT_EQL(check(xhRemove(&hash, "")), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + + /** Remove each key. A removed key cannot be found or removed again. **/ + for (int i = 0; i < KEY_COUNT; i++) + { + success &= EXPECT_EQL(check(xhRemove(&hash, keys[i])), 0, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), NULL, "%p"); + success &= EXPECT_EQL(xhRemove(&hash, keys[i]), -1, "%d"); + success &= EXPECT_EQL(hash.nItems, KEY_COUNT - i - 1, "%d"); + } + + /** Two tables are independent, even when given the same keys. **/ + XHashTable other; + success &= EXPECT_EQL(check(xhInit(&other, HASH_ROWS, 0)), 0, "%d"); + success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], data[0])), 0, "%d"); + success &= EXPECT_EQL(check(xhAdd(&other, keys[0], data[1])), 0, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + success &= EXPECT_EQL(xhLookup(&other, keys[0]), data[1], "%p"); + success &= EXPECT_EQL(check(xhRemove(&other, keys[0])), 0, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + success &= EXPECT_EQL(hash.nItems, 1, "%d"); + success &= EXPECT_EQL(other.nItems, 0, "%d"); + success &= EXPECT_EQL(check(xhDeInit(&other)), 0, "%d"); + + /** Clean up. **/ + success &= EXPECT_EQL(check(xhRemove(&hash, keys[0])), 0, "%d"); + success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "xhash-00 String Keys"; + return loopTest(doTest) * (32ll + 8ll * KEY_COUNT); + } + +/** Scope cleanup. **/ +#undef HASH_ROWS +#undef KEY_COUNT diff --git a/centrallix-lib/tests/test_xhash_01.c b/centrallix-lib/tests/test_xhash_01.c new file mode 100644 index 000000000..882ee320a --- /dev/null +++ b/centrallix-lib/tests/test_xhash_01.c @@ -0,0 +1,114 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_xhash_01.c */ +/* Author: Israel Fuller */ +/* Creation: September 9th, 2026 */ +/* Description: Test a hash table keyed by fixed length binary keys, */ +/* which may contain NUL bytes and need not be */ +/* NUL terminated. */ +/************************************************************************/ + +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "xhash.h" + +#define HASH_ROWS 7 +#define KEY_LEN 4 +#define KEY_COUNT 8 + +/*** Keys deliberately include embedded NUL bytes, high bytes, and pairs that + *** a NUL terminated comparison would consider equal. + ***/ +static char keys[KEY_COUNT][KEY_LEN] = + { + {'k', 'e', 'y', '0'}, + {'k', 'e', 'y', '1'}, + {'\0', '\0', '\0', '\0'}, + {'\0', '\0', '\0', '\1'}, + {'a', '\0', 'b', '\0'}, + {'a', '\0', 'b', '\1'}, + {'\xff', '\x80', '\xfe', '\x7f'}, + {'\xff', '\x80', '\xfe', '\xff'}, + }; + +static char* data[KEY_COUNT] = + {"zero", "one", "two", "three", "four", "five", "six", "seven"}; + +static bool doTest(void) + { + bool success = true; + XHashTable hash; + + /** A nonzero key length selects fixed length binary keys. **/ + success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, KEY_LEN)), 0, "%d"); + success &= EXPECT_EQL(hash.KeyLen, KEY_LEN, "%d"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + + /** Add each pair. **/ + for (int i = 0; i < KEY_COUNT; i++) + { + success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], data[i])), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); + } + + /** Keys that differ only past a NUL byte are separate entries. **/ + for (int i = 0; i < KEY_COUNT; i++) + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + + /** Only the first KEY_LEN bytes of a key are examined. **/ + char long_key[KEY_LEN * 2]; + memcpy(long_key, keys[0], KEY_LEN); + memset(long_key + KEY_LEN, 'x', KEY_LEN); + success &= EXPECT_EQL(xhLookup(&hash, long_key), data[0], "%p"); + success &= EXPECT_EQL(xhAdd(&hash, long_key, "duplicate"), -1, "%d"); + success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + + /** A key differing in its last examined byte is a different key. **/ + char other_key[KEY_LEN]; + memcpy(other_key, keys[0], KEY_LEN); + other_key[KEY_LEN - 1] = 'Z'; + success &= EXPECT_EQL(xhLookup(&hash, other_key), NULL, "%p"); + success &= EXPECT_EQL(xhRemove(&hash, other_key), -1, "%d"); + + /** Removing by an equal copy of a key removes the original entry. **/ + success &= EXPECT_EQL(check(xhRemove(&hash, long_key)), 0, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, keys[0]), NULL, "%p"); + success &= EXPECT_EQL(hash.nItems, KEY_COUNT - 1, "%d"); + + /** The other entries are untouched by that removal. **/ + for (int i = 1; i < KEY_COUNT; i++) + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + + /** Clean up. **/ + success &= EXPECT_EQL(check(xhClear(&hash, NULL, NULL)), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "xhash-01 Fixed Length Keys"; + return loopTest(doTest) * (13ll + 4ll * KEY_COUNT); + } + +/** Scope cleanup. **/ +#undef HASH_ROWS +#undef KEY_LEN +#undef KEY_COUNT diff --git a/centrallix-lib/tests/test_xhash_02.c b/centrallix-lib/tests/test_xhash_02.c new file mode 100644 index 000000000..827ae4f28 --- /dev/null +++ b/centrallix-lib/tests/test_xhash_02.c @@ -0,0 +1,154 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_xhash_02.c */ +/* Author: Israel Fuller */ +/* Creation: September 9th, 2026 */ +/* Description: Test how a hash table handles keys that land in the */ +/* same row, using a single row table to force every key */ +/* into one chain. */ +/************************************************************************/ + +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "xhash.h" + +#define CHAIN_COUNT 8 + +static char* keys[CHAIN_COUNT] = {"k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"}; +static char* data[CHAIN_COUNT] = {"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"}; + +/*** The keys removed below: the head, the middle, and the tail. Keep + *** REMOVED_COUNT equal to the number of indices isRemoved() accepts; the + *** test checks that it is. + ***/ +#define REMOVED_COUNT 3 +static bool isRemoved(int i) + { + return (i == 0 || i == CHAIN_COUNT / 2 || i == CHAIN_COUNT - 1); + } + +/*** Walk the chain of a row, checking that it holds the expected keys in + *** order. xhAdd() appends to the end of the chain, so the keys come back + *** in the order they were added. + *** + *** @param hash The hash table, which must have exactly one row. + *** @param expect_keys The keys expected in the chain, in order. + *** @param expect_count The number of expected keys. + *** @returns true if the chain matched, false otherwise. + ***/ +static bool checkChain(pXHashTable hash, char** expect_keys, int expect_count) + { + bool success = true; + pXHashEntry entry = XHE(hash->Rows.Items[0]); + + for (int i = 0; i < expect_count; i++) + { + if (!EXPECT_NOT_NULL(entry)) return false; + success &= EXPECT_EQL(entry->Key, expect_keys[i], "%p"); + entry = entry->Next; + } + success &= EXPECT_EQL(entry, NULL, "%p"); + + return success; + } + +static bool doTest(void) + { + bool success = true; + XHashTable hash; + char* remaining[CHAIN_COUNT + 1]; + int remaining_count = 0; + + /** One row means every key collides. **/ + success &= EXPECT_EQL(check(xhInit(&hash, 1, 0)), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + + /** Build the chain. **/ + for (int i = 0; i < CHAIN_COUNT; i++) + { + success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], data[i])), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); + } + success &= checkChain(&hash, keys, CHAIN_COUNT); + + /** Every key in the chain is reachable. **/ + for (int i = 0; i < CHAIN_COUNT; i++) + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + + /** A key absent from a populated chain is neither found nor removed. **/ + success &= EXPECT_EQL(xhLookup(&hash, "absent"), NULL, "%p"); + success &= EXPECT_EQL(xhRemove(&hash, "absent"), -1, "%d"); + success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT, "%d"); + + /** A duplicate is caught no matter how deep in the chain it sits. **/ + success &= EXPECT_EQL(xhAdd(&hash, keys[CHAIN_COUNT / 2], "duplicate"), -1, "%d"); + success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT, "%d"); + + /** Unlink the head, the middle, and the tail of the chain. **/ + int removed_count = 0; + for (int i = 0; i < CHAIN_COUNT; i++) + { + if (isRemoved(i)) + { + success &= EXPECT_EQL(check(xhRemove(&hash, keys[i])), 0, "%d"); + removed_count++; + } + else + remaining[remaining_count++] = keys[i]; + } + success &= EXPECT_EQL(removed_count, REMOVED_COUNT, "%d"); + + /*** The chain kept the other entries, in order, and lost the removed + *** ones. + ***/ + for (int i = 0; i < CHAIN_COUNT; i++) + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), + isRemoved(i) ? NULL : data[i], "%p"); + success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT, "%d"); + success &= checkChain(&hash, remaining, remaining_count); + + /** A removed key can be added back, landing at the end of the chain. **/ + success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], data[0])), 0, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT + 1, "%d"); + remaining[remaining_count++] = keys[0]; + success &= checkChain(&hash, remaining, remaining_count); + + /** Clean up. **/ + success &= EXPECT_EQL(check(xhClear(&hash, NULL, NULL)), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= EXPECT_EQL(hash.Rows.Items[0], NULL, "%p"); + success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "xhash-02 Collisions"; + + /*** The three chain walks check two things per entry plus the end of the + *** chain, over chains of CHAIN_COUNT, CHAIN_COUNT - REMOVED_COUNT, and + *** one more than that. + ***/ + return loopTest(doTest) * (21ll - 3ll * REMOVED_COUNT + 10ll * CHAIN_COUNT); + } + +/** Scope cleanup. **/ +#undef CHAIN_COUNT +#undef REMOVED_COUNT diff --git a/centrallix-lib/tests/test_xhash_03.c b/centrallix-lib/tests/test_xhash_03.c new file mode 100644 index 000000000..3b2604630 --- /dev/null +++ b/centrallix-lib/tests/test_xhash_03.c @@ -0,0 +1,135 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_xhash_03.c */ +/* Author: Israel Fuller */ +/* Creation: September 9th, 2026 */ +/* Description: Test xhClear(), including the free function it calls */ +/* for the data of each entry, and how a hash table */ +/* handles entries whose data is NULL. */ +/************************************************************************/ + +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "newmalloc.h" +#include "check.h" + +/** Tested module. **/ +#include "xhash.h" + +#define HASH_ROWS 11 +#define ITEM_COUNT 6 +#define DATA_SIZE ((int)sizeof(int)) + +static char* keys[ITEM_COUNT] = {"one", "two", "three", "four", "five", "six"}; + +/** Free the data of an entry and count the entries xhClear() handed over. **/ +static int test_free(void* data, void* arg) + { + if (data != NULL) nmFree(data, DATA_SIZE); + if (arg != NULL) (*(unsigned int*)arg)++; + + return 0; + } + +static unsigned int seed_counter = 0; + +static bool doTest(void) + { + bool success = true; + XHashTable hash; + unsigned int freed = 0; + + /** Use a distinct seed each pass so the data varies. **/ + srand(seed_counter++); + + success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, 0)), 0, "%d"); + + /** Fill the table with data that xhClear() has to free. **/ + for (int i = 0; i < ITEM_COUNT; i++) + { + int* value = checkPtr(nmMalloc(DATA_SIZE)); + if (value == NULL) return false; + *value = rand(); + success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], (char*)value)), 0, "%d"); + char* found = xhLookup(&hash, keys[i]); + success &= EXPECT_EQL(found, (char*)value, "%p"); + if (found != NULL) success &= EXPECT_EQL(*(int*)found, *value, "%d"); + } + success &= EXPECT_EQL(hash.nItems, ITEM_COUNT, "%d"); + + /** An entry may carry NULL data; only its key marks its presence. **/ + success &= EXPECT_EQL(check(xhAdd(&hash, "null-data", NULL)), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, ITEM_COUNT + 1, "%d"); + success &= EXPECT_EQL(xhLookup(&hash, "null-data"), NULL, "%p"); + + /** Such an entry is still found by a removal, and by a duplicate add. **/ + success &= EXPECT_EQL(xhAdd(&hash, "null-data", NULL), -1, "%d"); + success &= EXPECT_EQL(check(xhRemove(&hash, "null-data")), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, ITEM_COUNT, "%d"); + + /*** Clear the table. Every entry is handed to the free function, + *** including the one whose data is NULL. + ***/ + success &= EXPECT_EQL(check(xhAdd(&hash, "null-data", NULL)), 0, "%d"); + success &= EXPECT_EQL(check(xhClear(&hash, test_free, &freed)), 0, "%d"); + success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + + /** The cleared table holds nothing and every row is empty. **/ + for (int i = 0; i < ITEM_COUNT; i++) + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), NULL, "%p"); + int used_rows = 0; + for (int i = 0; i < HASH_ROWS; i++) + if (hash.Rows.Items[i] != NULL) used_rows++; + success &= EXPECT_EQL(used_rows, 0, "%d"); + + /** Clearing an already cleared table frees nothing. **/ + success &= EXPECT_EQL(check(xhClear(&hash, test_free, &freed)), 0, "%d"); + success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); + + /** The table is usable again after being cleared. **/ + success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], "reused")), 0, "%d"); + success &= EXPECT_STR_EQL(xhLookup(&hash, keys[0]), "reused"); + success &= EXPECT_EQL(hash.nItems, 1, "%d"); + + /** A NULL free function leaves the data alone. **/ + success &= EXPECT_EQL(check(xhClear(&hash, NULL, NULL)), 0, "%d"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + + /** A free function may be given a NULL argument. **/ + int* value = checkPtr(nmMalloc(DATA_SIZE)); + if (value == NULL) return false; + success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], (char*)value)), 0, "%d"); + success &= EXPECT_EQL(check(xhClear(&hash, test_free, NULL)), 0, "%d"); + success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + + /** Clean up. **/ + success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "xhash-03 Clearing"; + return loopTest(doTest) * (25ll + 4ll * ITEM_COUNT); + } + +/** Scope cleanup. **/ +#undef HASH_ROWS +#undef ITEM_COUNT +#undef DATA_SIZE diff --git a/centrallix-lib/tests/test_xhash_04.c b/centrallix-lib/tests/test_xhash_04.c new file mode 100644 index 000000000..7f1958bd0 --- /dev/null +++ b/centrallix-lib/tests/test_xhash_04.c @@ -0,0 +1,80 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_xhash_04.c */ +/* Author: Israel Fuller */ +/* Creation: September 9th, 2026 */ +/* Description: Test that a hash table holding far more keys than it */ +/* has rows still finds and removes every one of them. */ +/************************************************************************/ + +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "xhash.h" + +#define HASH_ROWS 64 +#define KEY_COUNT 256 +#define KEY_SIZE 16 + +static bool doTest(void) + { + bool success = true; + XHashTable hash; + char keys[KEY_COUNT][KEY_SIZE]; + int used_rows = 0; + int i; + + success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, 0)), 0, "%d"); + + /*** Each key doubles as its own data, giving every entry a unique + *** pointer to look up. The keys share the stack of an mtask thread, + *** which is far smaller than a process stack. + ***/ + for (int i = 0; i < KEY_COUNT; i++) + { + snprintf(keys[i], sizeof(keys[i]), "spread-key-%d", i); + success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], keys[i])), 0, "%d"); + } + success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + + /** Nothing was lost or confused with another key. **/ + for (int i = 0; i < KEY_COUNT; i++) + success &= EXPECT_EQL(xhLookup(&hash, keys[i]), (char*)keys[i], "%p"); + + /** Removing every key empties every row. **/ + for (int i = 0; i < KEY_COUNT; i++) + success &= EXPECT_EQL(check(xhRemove(&hash, keys[i])), 0, "%d"); + for (int i = 0; i < HASH_ROWS; i++) + if (hash.Rows.Items[i] != NULL) used_rows++; + success &= EXPECT_EQL(used_rows, 0, "%d"); + success &= EXPECT_EQL(hash.nItems, 0, "%d"); + + /** Clean up. **/ + success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "xhash-04 Many Keys"; + return loopTest(doTest) * (5ll + 3ll * KEY_COUNT); + } + +/** Scope cleanup. **/ +#undef HASH_ROWS +#undef KEY_COUNT +#undef KEY_SIZE diff --git a/centrallix-lib/tests/test_xhash_05.c b/centrallix-lib/tests/test_xhash_05.c new file mode 100644 index 000000000..ab8e7b34b --- /dev/null +++ b/centrallix-lib/tests/test_xhash_05.c @@ -0,0 +1,106 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_xhash_05.c */ +/* Author: Israel Fuller */ +/* Creation: September 9th, 2026 */ +/* Description: Test xhInitialize() and the hash computation itself. */ +/************************************************************************/ + +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "xhash.h" + +/*** These are internal functions, so we use forward forward declarations to + *** make them available for testing. + ***/ +int xhInitialize(); +int xh_internal_ComputeHash(char* key, int keylen, int rows); + +#define KEY_LEN 8 +#define ROW_COUNT 5 + +/** Row counts to hash against, including the degenerate single row table. **/ +static int rows[ROW_COUNT] = {1, 2, 17, 64, 1000003}; + +/*** Enough rows that two keys landing on the same row would mean the hash + *** ignored the difference between them rather than that the rows ran out. + ***/ +#define WIDE_ROWS 1000003 + +static char key[KEY_LEN] = {'h', 'a', 's', 'h', 'k', 'e', 'y', '\xf0'}; + +static bool doTest(void) + { + bool success = true; + + /*** The hash never leaves the rows it was given, and survives + *** xhInitialize(), which once rebuilt the table the hash reads from. + ***/ + for (int r = 0; r < ROW_COUNT; r++) + { + int hash = xh_internal_ComputeHash(key, KEY_LEN, rows[r]); + success &= EXPECT_RANGE(hash, 0, rows[r] - 1, "%d"); + success &= EXPECT_EQL(check(xhInitialize()), 0, "%d"); + success &= EXPECT_EQL(xh_internal_ComputeHash(key, KEY_LEN, rows[r]), hash, "%d"); + + /** An empty key hashes to the first row of any table. **/ + success &= EXPECT_EQL(xh_internal_ComputeHash(key, 0, rows[r]), 0, "%d"); + } + + /*** The hash is in memory only, so it may be changed, but only on + *** purpose: update this value when the hash or the key above changes. + ***/ + success &= EXPECT_EQL(xh_internal_ComputeHash(key, KEY_LEN, WIDE_ROWS), 609235, "%d"); + + /** Where a byte sits matters, not just which bytes are present. **/ + success &= EXPECT_EQL(xh_internal_ComputeHash("ab", 2, WIDE_ROWS) + != xh_internal_ComputeHash("ba", 2, WIDE_ROWS), true, "%d"); + + /** Only the first keylen bytes of the key are hashed. **/ + char altered[KEY_LEN]; + memcpy(altered, key, KEY_LEN); + altered[KEY_LEN - 1] ^= 0x5A; + success &= EXPECT_EQL( + xh_internal_ComputeHash(altered, KEY_LEN - 1, WIDE_ROWS), + xh_internal_ComputeHash(key, KEY_LEN - 1, WIDE_ROWS), "%d"); + + /** Every byte of the key changes the hash. **/ + const int base = xh_internal_ComputeHash(key, KEY_LEN, WIDE_ROWS); + for (int i = 0; i < KEY_LEN; i++) + { + memcpy(altered, key, KEY_LEN); + altered[i] ^= 0x5A; + bool differs = EXPECT_EQL( + xh_internal_ComputeHash(altered, KEY_LEN, WIDE_ROWS) != base, true, "%d"); + if (!differs) fprintf(stderr, " > Byte %d of the key did not affect the hash.\n", i); + success &= differs; + } + + return success; + } + +long long test(char** tname) + { + *tname = "xhash-05 Hash Computation"; + return loopTest(doTest) * (3ll + 4ll * ROW_COUNT + KEY_LEN); + } + +/** Scope cleanup. **/ +#undef KEY_LEN +#undef ROW_COUNT +#undef WIDE_ROWS From ce94e0074aca1e0201be6c68019cad1b425158fa Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 15:58:51 -0600 Subject: [PATCH 164/361] Revert changes to mssError() which are better done elsewhere. --- centrallix-lib/include/mtsession.h | 4 +- centrallix-lib/src/mtsession.c | 153 +++++++++++++++-------------- 2 files changed, 82 insertions(+), 75 deletions(-) diff --git a/centrallix-lib/include/mtsession.h b/centrallix-lib/include/mtsession.h index cff7101d5..29adaff59 100644 --- a/centrallix-lib/include/mtsession.h +++ b/centrallix-lib/include/mtsession.h @@ -81,9 +81,7 @@ void* mssGetParam(char* paramname); /** Error handling functions **/ int mssLog(int level, char* msg); -void mssError_internal(int clr, char* module, char* file, int line, char* message, ...); -#define mssError(clear, module, message, ...) \ - mssError_internal(clear, module, __FILE__, __LINE__, message, ##__VA_ARGS__) +int mssError(int clr, char* module, char* message, ...); int mssErrorErrno(int clr, char* module, char* message, ...); int mssClearError(); int mssPrintError(pFile fd); diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index b300598f9..f3b378811 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -3,7 +3,6 @@ #include "cxlibconfig-internal.h" #endif #include -#include #include #include #include @@ -24,7 +23,6 @@ #include "xstring.h" #include "xhash.h" #include "strtcpy.h" -#include "check.h" #include "cxsec.h" /************************************************************************/ @@ -455,88 +453,99 @@ mssEndSession(pMtSession s) } -/*** Displays error text to the user (but no stack trace). Does not exit the - *** program, allowing the calling function to fail, generating a cascade of - *** error messages which may provide useful info. - *** - *** @param clr Whether to clear the current error stack. As a rule of thumb, - *** if you are the first one to detect the error, clear the stack so that - *** other unrelated messages are not shown. If you are detecting an error - *** from another function that may also call an mssError() function, do - *** not clear the stack. - *** @param module The name or abbreviation of the module in which this - *** function is being called, to help developers narrow down the location - *** of the error. - *** @param format The format text for the error, which accepts any format - *** specifier that would be accepted by printf(). - *** @param ... Variables matching format specifiers in the format. +/*** mssError - Add an error message to the error stack, optionally + *** clearing the existing contents thereof. ***/ -void -mssError_internal(int clr, char* module, char* file, int line, char* message, ...) +int +mssError(int clr, char* module, char* message, ...) { - char err_msg[BUFSIZ]; - size_t i = 0; - - /** Prevent issues from interlacing this function with prints to stdout. **/ - check(fflush(stdout)); /* Failure ignored. */ - - /** Add line number to error message. **/ - err_msg[0] = '\0'; - strtcatf(err_msg, sizeof(err_msg), &i, "%s:%d: ", file, line); - - /** Write the module to the start of the error message. */ - strtcatf(err_msg, sizeof(err_msg), &i, "%s: ", module); - - /** Process the message format with all the same rules as printf(). **/ - va_list args; - va_start(args, message); - strtcatf_va(err_msg, sizeof(err_msg), &i, message, args); - va_end(args); - + va_list vl; + char* msg; + pMtSession s; + XString xs; + char* ptr; + char* cur_pos; + char* str; + int i; + char nbuf[16]; + char ch; + + /** Build the real error msg. **/ + xsInit(&xs); + cur_pos = message; + va_start(vl, message); + while((ptr = strchr(cur_pos, '%'))) + { + xsConcatenate(&xs, cur_pos, ptr - cur_pos); + switch(ptr[1]) + { + case '\0': + xsConcatenate(&xs, "%", 1); + cur_pos = ptr+1; + break; + case '%': + xsConcatenate(&xs, "%", 1); + cur_pos = ptr+2; + break; + case 's': + str = va_arg(vl, char*); + xsConcatenate(&xs, str?str:"(NULL)", -1); + cur_pos = ptr + 2; + break; + case 'c': + ch = va_arg(vl, int); + xsConcatenate(&xs, &ch, 1); + cur_pos = ptr + 2; + break; + case 'd': + i = va_arg(vl, int); + sprintf(nbuf,"%d",i); + xsConcatenate(&xs, nbuf, -1); + cur_pos = ptr + 2; + break; + default: + cur_pos = ptr + 2; + break; + } + } + va_end(vl); + if (*cur_pos) xsConcatenate(&xs, cur_pos, -1); + /** Get current session **/ - pMtSession s = thGetParam(NULL, "mss"); - const bool log_error = (s == NULL || MSS.LogAllErrors); - - /** Use standard logging without a session context, if needed. **/ - if (log_error) + s = (pMtSession)thGetParam(NULL,"mss"); + if (!s || MSS.LogAllErrors) { - /** Use the requested logging method. **/ - if (strcmp(MSS.LogMethod, "syslog") == 0) + /*printf("mssError: Error occurred outside of session context.\n");*/ + if (!strcmp(MSS.LogMethod,"syslog")) { - if (s == NULL) - syslog(LOG_ERR, "System: %.256s\n", err_msg); + if (!s) + syslog(LOG_ERR, "System: %s: %.256s\n", module, xs.String); else - syslog(LOG_WARNING, "User '%s': %.256s\n", s->UserName, err_msg); + syslog(LOG_WARNING, "User '%s': %s: %.256s\n", s->UserName, module, xs.String); } - else if (strcmp(MSS.LogMethod, "stdout") == 0) + else if (!strcmp(MSS.LogMethod, "stdout")) { - printf("%s: %s\n", (MSS.AppName[0]) ? MSS.AppName : "error", err_msg); + printf("%s: %s: %s\n",MSS.AppName[0]?MSS.AppName:"error",module,xs.String); } + if (!s) return -1; } - - /** If a session is available, try to add the error to the error list. **/ - if (s != NULL) + + /** Need to clear? **/ + if (clr) mssClearError(); + + /** Allocate space and construct the error text. **/ + msg = (char*)nmSysMalloc(strlen(module)+strlen(xs.String)+3); + if (!msg) { - /** Clear the error context, if requested. **/ - if (clr) check(mssClearError()); /* Failure ignored. */ - - /** Allocate space and construct the error text. **/ - const char* allocated_err_msg = checkPtr(nmSysStrdup(err_msg)); - if (allocated_err_msg == NULL) - { - fprintf(stderr, "Failed to store error message: %s\n", err_msg); - return; /* Give up. */ - } - - /** Store the error. **/ - if (checkNeg(xaAddItem(&(s->ErrList), (void*)allocated_err_msg)) < 0) - { - fprintf(stderr, "Failed to add error message to session error list: %s\n", err_msg); - return; /* Give up. */ - } + perror("mssError: Could not allocate error"); + printf("mssError: %s: %s\n",module,xs.String); + return -1; } - - return; + sprintf(msg,"%s: %s",module,xs.String); + xaAddItem(&(s->ErrList),(void*)msg); + xsDeInit(&xs); + + return 0; } From 58178208989a17a5706a65687728ebf3e0809d64 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 17:38:58 -0600 Subject: [PATCH 165/361] Add new test util functions. --- centrallix-lib/tests/test_utils.h | 62 ++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 73e0bccdf..2d89a3a09 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -43,6 +43,22 @@ ***/ #define MIN_TEST_SECONDS 0.1 +/*** Expect a value to be true. + *** + *** @param v1 The value. + *** @param v2 The second value. + *** @returns true if successful, false otherwise. + ***/ +#define EXPECT_TRUE(v) \ + ({ \ + const int success = !!(v); \ + if (!success) fprintf(stderr, \ + " > Expected %s to be true at %s:%d\n", \ + #v, __FILE__, __LINE__ \ + ); \ + success; \ + }) + /*** Expect two values to be equal. *** *** @param v1 The first value. @@ -81,7 +97,33 @@ ); \ success; \ }) - + +/*** Expect two strings to be equal, including null characters. + *** Warning: Can read off the end of strings. + *** + *** @param str1 The first string. + *** @param str2 The second string. + *** @param len The length of strings to check (aka. N). + *** @returns true if successful, false otherwise. + ***/ +#define EXPECT_STR_EQL_N(str1, str2, len) \ + ({ \ + const char* _str1 = (str1); \ + const char* _str2 = (str2); \ + const size_t _len = (size_t)(len); \ + const int success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ + if (!success)\ + { \ + char _tmp1[_len + 1]; STR_COPY_REPLACE_NULLS(_tmp1, _str1, _len); \ + char _tmp2[_len + 1]; STR_COPY_REPLACE_NULLS(_tmp2, _str2, _len); \ + fprintf(stderr, \ + " > Expected %s (\"%s\") to equal %s (\"%s\") where '_' is a null-terminator, at %s:%d\n", \ + #str1, _tmp1, #str2, _tmp2, __FILE__, __LINE__ \ + ); \ + } \ + success; \ + }) + /*** Expect two cosine vectors from `cluster.c` to be equal. *** *** @param v1 The first vector. @@ -143,6 +185,24 @@ success; \ }) +/*** Copy n characters from src to dest, including null-terminators, which are + *** replaced with the '_' character. + *** + *** @param dest The destination string buffer (must be at least `n+1` bytes). + *** @param src The source string (must be at least `n+1` bytes), which can + *** include null-terminator characters ('\0'). + *** @param n The number of characters to copy. + ***/ +#define STR_COPY_REPLACE_NULLS(dest, src, n) \ + { \ + char* _dest = (dest); \ + const char* _src = (src); \ + const size_t _n = (size_t)(n); \ + for (size_t i = 0lu; i < _n; i++) \ + _dest[i] = (_src[i] == '\0') ? '_' : _src[i]; \ + _dest[_n] = '\0'; \ + } + /** Repeat the test as many times as possible within a set time window. **/ #define loopTest(test_fn) \ ({ \ From fd58f4cd9e72e1575caa654f33b7470be2367945 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 9 Sep 2026 17:39:28 -0600 Subject: [PATCH 166/361] Fix a warning. --- centrallix-lib/tests/test_xhash_04.c | 1 - 1 file changed, 1 deletion(-) diff --git a/centrallix-lib/tests/test_xhash_04.c b/centrallix-lib/tests/test_xhash_04.c index 7f1958bd0..a71946708 100644 --- a/centrallix-lib/tests/test_xhash_04.c +++ b/centrallix-lib/tests/test_xhash_04.c @@ -35,7 +35,6 @@ static bool doTest(void) XHashTable hash; char keys[KEY_COUNT][KEY_SIZE]; int used_rows = 0; - int i; success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, 0)), 0, "%d"); From 76e5dddd47e3e0567a8652ba403ae81f6d351571 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 12:44:47 -0600 Subject: [PATCH 167/361] Add old cluster tests that I haven't finished fixing yet. --- centrallix-lib/tests/test_clusters_00.c | 95 ++++++++++++ centrallix-lib/tests/test_clusters_01.c | 51 +++++++ centrallix-lib/tests/test_clusters_02.c | 85 +++++++++++ centrallix-lib/tests/test_clusters_03.c | 91 ++++++++++++ centrallix-lib/tests/test_clusters_04.c | 70 +++++++++ centrallix-lib/tests/test_clusters_05.c | 185 ++++++++++++++++++++++++ centrallix-lib/tests/test_clusters_06.c | 149 +++++++++++++++++++ 7 files changed, 726 insertions(+) create mode 100644 centrallix-lib/tests/test_clusters_00.c create mode 100644 centrallix-lib/tests/test_clusters_01.c create mode 100644 centrallix-lib/tests/test_clusters_02.c create mode 100644 centrallix-lib/tests/test_clusters_03.c create mode 100644 centrallix-lib/tests/test_clusters_04.c create mode 100644 centrallix-lib/tests/test_clusters_05.c create mode 100644 centrallix-lib/tests/test_clusters_06.c diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c new file mode 100644 index 000000000..d900881e5 --- /dev/null +++ b/centrallix-lib/tests/test_clusters_00.c @@ -0,0 +1,95 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_clusters_00.c */ +/* Author: Israel Fuller */ +/* Creation: November 25th, 2025 */ +/* Description: Test the ca_edit_dist() function from clusters.h. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "clusters.h" + +static bool doTest(void) + { + bool success = true; + + /** Kitten tests. **/ + success &= EXPECT_EQL(ca_edit_dist("kitten", "kitten", 0, 0), 0, "%d"); /* 0 edits. */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "skitten", 0, 0), 1, "%d"); /* 1 insert. */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "itten", 0, 0), 1, "%d"); /* 1 delete. */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "mitten", 0, 0), 1, "%d"); /* 1 replace. */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "smitten", 0, 0), 2, "%d"); /* 1 insert and 1 replace. */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "iktten", 0, 0), 1, "%d"); /* 1 transpose. */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "kittens", 0, 0), 1, "%d"); /* 1 insert (end). */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "kitte", 0, 0), 1, "%d"); /* 1 delete (end). */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "kittem", 0, 0), 1, "%d"); /* 1 replace (end). */ + success &= EXPECT_EQL(ca_edit_dist("kitten", "kittne", 0, 0), 1, "%d"); /* 1 transpose (end). */ + + /** Alternate words. **/ + success &= EXPECT_EQL(ca_edit_dist("lawn", "flown", 0, 0), 2, "%d"); /* 1 insert and one replace. */ + success &= EXPECT_EQL(ca_edit_dist("hello", "hello!", 0, 0), 1, "%d"); /* 1 insert (end). */ + success &= EXPECT_EQL(ca_edit_dist("zert", "zerf", 0, 0), 1, "%d"); /* 1 replace (end). */ + success &= EXPECT_EQL(ca_edit_dist("llearr", "lear", 0, 0), 2, "%d"); /* 2 deletes (start & end). */ + + /** Long strings for testing edge cases. **/ + char* str1 = "This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string..."; + const size_t str_size = (strlen(str1) + 1) * sizeof(char); + char* str2 = memcpy(malloc(str_size), str1, str_size); + char* str3 = "This is quite a lengthy string. I do not expect the function to compute any longer string since this one is a full 254 characters. That is plenty, even if someone adds many contact details to their record!! Thus, this test should cover most cases we see."; + + /** Test edge cases. **/ + success &= EXPECT_EQL(ca_edit_dist("", "", 0, 0), 0, "%d"); /* Identical, empty string: 0 edits. */ + success &= EXPECT_EQL(ca_edit_dist(str1, str1, 0, 0), 0, "%d"); /* Identical, very long strings. */ + success &= EXPECT_EQL(ca_edit_dist(str1, str2, 0, 0), 0, "%d"); /* Identical, very long strings (different pointers). */ + success &= EXPECT_EQL(ca_edit_dist(str2, str3, 0, 0), 133, "%d"); /* 133 edits. */ + + /** Empty string comparsions. **/ + success &= EXPECT_EQL(ca_edit_dist(str1, "", 0, 0), (int)strlen(str1), "%d"); + success &= EXPECT_EQL(ca_edit_dist(str2, "", 0, 0), (int)strlen(str2), "%d"); + success &= EXPECT_EQL(ca_edit_dist(str3, "", 0, 0), (int)strlen(str3), "%d"); + + /** Specifying lengths with overflows. **/ + success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 13, 13), 0, "%d"); + success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 13, 0), 9, "%d"); + success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 0, 13), 6, "%d"); + success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 0, 0), 3, "%d"); + success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 19, 0), 3, "%d"); + success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 0, 19), 6, "%d"); + + /** Test for errors in identical pointer optimizations with specified lengths. **/ + char identical_string[] = "Identical String!"; + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 0), 0, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 17), 0, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 17, 0), 0, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 16), 1, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 8), 9, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 5, 6), 1, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 16, 13), 3, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 1), 16, "%d"); + success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 1, 0), 16, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "cluster-00 ca_edit_dist()"; + return loopTest(doTest); + } diff --git a/centrallix-lib/tests/test_clusters_01.c b/centrallix-lib/tests/test_clusters_01.c new file mode 100644 index 000000000..409219291 --- /dev/null +++ b/centrallix-lib/tests/test_clusters_01.c @@ -0,0 +1,51 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_clusters_01.c */ +/* Author: Israel Fuller */ +/* Creation: November 25th, 2025 */ +/* Description: Test the ca_edit_dist() function from clusters.h. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "clusters.h" + + +static bool doTest(void) + { + bool success = true; + + /** Many, many replace edits. **/ + const static unsigned short num_edits = 254;//15827; + char AAA[num_edits + 1], BBB[num_edits + 1]; + memset(AAA, 'A', num_edits); + memset(BBB, 'B', num_edits); + AAA[num_edits] = BBB[num_edits] = '\0'; + success &= EXPECT_EQL(ca_edit_dist(AAA, "", 0, 0), num_edits, "%d"); + success &= EXPECT_EQL(ca_edit_dist("", BBB, 0, 0), num_edits, "%d"); + success &= EXPECT_EQL(ca_edit_dist(AAA, BBB, 0, 0), num_edits, "%d"); + + return success; + } + +long long test(char** tname) + { + *tname = "cluster-01 ca_edit_dist(): Stress test"; + return loopTest(doTest); + } diff --git a/centrallix-lib/tests/test_clusters_02.c b/centrallix-lib/tests/test_clusters_02.c new file mode 100644 index 000000000..f16300340 --- /dev/null +++ b/centrallix-lib/tests/test_clusters_02.c @@ -0,0 +1,85 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_clusters_02.c */ +/* Author: Israel Fuller */ +/* Creation: November 25th, 2025 */ +/* Description: Test the ca_build_vector() function from clusters.h. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "clusters.h" + + +static bool doTest(void) + { + bool success = true; + + /** Make an array to STORE() pointers to vectors so we can free them. **/ + const unsigned int max_index = 16u; + unsigned int index = 0u; + pVector free_list[max_index]; + #define STORE(v) (free_list[index++] = (v)) + #define vec(s) STORE(ca_build_vector(s)) + + /** Edge case: Null string. **/ + success &= EXPECT_EQL(ca_build_vector(NULL), NULL, "%p"); + + /** Edge case: Empty string. **/ + success &= EXPECT_VEC_EQL(vec(""), ((int[]){-172, 11, -78})); + + /** Single letter cases. **/ + success &= EXPECT_VEC_EQL(vec("a"), ((int[]){-204, 12, -25, 12, -20})); + success &= EXPECT_VEC_EQL(vec("b"), ((int[]){-151, 13, -11, 13, -87})); + success &= EXPECT_VEC_EQL(vec("v"), ((int[]){-221, 7, -19, 7, -9})); + + /** Multi-letter cases. **/ + success &= EXPECT_VEC_EQL(vec("def"), ((int[]){-79, 4, -51, 2, -4, 7, -64, 9, -49})); + success &= EXPECT_VEC_EQL(vec("vec"), ((int[]){-37, 1, -175, 12, -18, 6, -8, 7, -9})); + + /** White space and punctuation should be ignored. **/ + success &= EXPECT_VEC_EQL(vec("Yippee!!!"), vec(">>->y i!&P^^_pe$/\n?e")); + + /** Clean up using the free list. **/ + if (index >= max_index) + { + printf(" > MEMORY ERROR!!\n"); + printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index + 1, max_index); + printf(" > Increase the size of the free list (aka. max_index) to %u or more.\n", index + 1); + return false; + } + while (index > 0u) + { + pVector cur_vector = free_list[--index]; + if (cur_vector == NULL) continue; + else ca_free_vector(cur_vector); + } + + return success; + } + +long long test(char** tname) + { + *tname = "cluster-02 ca_build_vector()"; + return loopTest(doTest); + } + +/** Clean up scope. **/ +#undef STORE +#undef vec diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c new file mode 100644 index 000000000..6a4da0890 --- /dev/null +++ b/centrallix-lib/tests/test_clusters_03.c @@ -0,0 +1,91 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_clusters_03.c */ +/* Author: Israel Fuller */ +/* Creation: November 26th, 2025 */ +/* Description: Test the ca_cos_compare() function from clusters.h. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "clusters.h" + + +static bool doTest(void) + { + bool success = true; + + /** Make an array to STORE() pointers to vectors so we can free them. **/ + const unsigned int max_index = 32u; + unsigned int index = 0u; + pVector free_list[max_index]; + #define STORE(v) (free_list[index++] = (v)) + + /** ca_cos_compare() shortcut macro. **/ + #define cos_cmp(str1, str2) ({ \ + pVector _v1 = STORE(ca_build_vector(str1)); \ + pVector _v2 = STORE(ca_build_vector(str2)); \ + ca_cos_compare(_v1, _v2); \ + }) + + /** Basic tests of cosine similarity. **/ + success &= EXPECT_RANGE(cos_cmp("hello", "hello"), 0.999, 1.0, "%g"); + success &= EXPECT_RANGE(cos_cmp("hello", "zephora"), 0.0, 0.001, "%g"); + success &= EXPECT_RANGE(cos_cmp("hello", "hello world"), 0.6, 0.7, "%g"); + success &= EXPECT_RANGE(cos_cmp("hello there", "hellow there"), 0.9, 1.0, "%g"); + + /** Tests on fabricated contact information. */ + /*** All email addresses and phone numbers are imaginary and were + *** fabricated for the purposes of this test. + ***/ + success &= EXPECT_RANGE(cos_cmp("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470"), 0.49, 0.54, "%g"); + success &= EXPECT_RANGE(cos_cmp("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189"), 0.45, 0.50, "%g"); + success &= EXPECT_RANGE(cos_cmp("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791"), 0.425, 0.475, "%g"); + success &= EXPECT_RANGE(cos_cmp("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791"), 0.94, 0.99, "%g"); + success &= EXPECT_RANGE(cos_cmp("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149"), 0.575, 0.625, "%g"); + success &= EXPECT_RANGE(cos_cmp("This is an identical case", "This is an identical case"), 0.975, 1.00, "%g"); + success &= EXPECT_RANGE(cos_cmp("Samuel", "Alex"), 0.00, 0.025, "%g"); + + /** Clean up scope. **/ + #undef STORE + #undef cos_cmp + + /** Clean up using the free list. **/ + if (index >= max_index) + { + printf(" > MEMORY ERROR!!\n"); + printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index + 1, max_index); + printf(" > Increase the size of the free list (aka. max_index) to %u or more.\n", index + 1); + return false; + } + while (index > 0u) + { + pVector cur_vector = free_list[--index]; + if (cur_vector == NULL) continue; + else ca_free_vector(cur_vector); + } + + return success; + } + +long long test(char** tname) + { + *tname = "cluster-03 ca_cos_compare()"; + return loopTest(doTest); + } diff --git a/centrallix-lib/tests/test_clusters_04.c b/centrallix-lib/tests/test_clusters_04.c new file mode 100644 index 000000000..e007f99f3 --- /dev/null +++ b/centrallix-lib/tests/test_clusters_04.c @@ -0,0 +1,70 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_clusters_04.c */ +/* Author: Israel Fuller */ +/* Creation: November 26th, 2025 */ +/* Description: Test the ca_lev_compare() function from clusters.h. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" + +/** Tested module. **/ +#include "clusters.h" + + +static bool doTest(void) + { + bool success = true; + + /** Basic tests of Levenshtein edit distance similarity. **/ + success &= EXPECT_RANGE(ca_lev_compare("hello", "hello"), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hello", "hello!"), 0.8, 1.0, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hello", "asdfkh"), 0.0, 0.1, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hello", "aaaaaaaaaaaaaaaaa"), 0.0, 0.1, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hello", "nope"), 0.0, 0.2, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hello", "noepo"), 0.15, 0.25, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("below", "hello!"), 0.4, 0.6, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "smitten"), 0.65, 0.85, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hello", "bobbobbobbob"), 0.0, 0.1, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hello", ""), 0.0, 0.05, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("", ""), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("blooooop", "blob"), 0.3, 0.5, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("", "!"), 0.0, 0.01, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("h", "h"), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("hi", "hi"), 0.99, 1.0, "%g"); + + /** Kitten tests with specific edit operations. **/ + success &= EXPECT_RANGE(ca_lev_compare("kitten", "kitten"), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "skitten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "itten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "mitten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "smitten"), 0.7, 0.8, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "iktten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "kittens"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "kitte"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "kittem"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(ca_lev_compare("kitten", "kittne"), 0.8, 0.9, "%g"); + + return success; + } + +long long test(char** tname) + { + *tname = "cluster-04 ca_lev_compare()"; + return loopTest(doTest); + } diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c new file mode 100644 index 000000000..3052c1a33 --- /dev/null +++ b/centrallix-lib/tests/test_clusters_05.c @@ -0,0 +1,185 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_clusters_05.c */ +/* Author: Israel Fuller */ +/* Creation: November 26th, 2025 */ +/* Description: Test the ca_most_similar() function from clusters.h. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" +#include "xhash.h" + +/** Tested module. **/ +#include "clusters.h" + + +/** Mock similarity function. **/ +static const unsigned int key_length = 64u; +static pXHashTable mock_sims = NULL; +static bool* success_ptr = NULL; +static double get_mock_sim(void* v1, void* v2) + { + char key[key_length]; + char* str1 = v1; + char* str2 = v2; + + /** Try key1, key2. **/ + snprintf(key, sizeof(key), "%s|%s", str1, str2); + double* sim = (double*)xhLookup(mock_sims, key); + if (sim != NULL) goto found; + + /** Try key2, key1. **/ + snprintf(key, sizeof(key), "%s|%s", str2, str1); + sim = (double*)xhLookup(mock_sims, key); + if (sim != NULL) goto found; + + /** Key not found. **/ + fprintf(stderr, " > get_mock_sim(\"%s\", \"%s\"): No sim provided!\n", str1, str2); + *success_ptr = false; + return NAN; + + found: + /** Key found. **/ + return *sim; + } + +static int do_nothing() { return 0; } + +static bool doTest(void) + { + bool success = true; + + /** Check error cases. **/ + success &= EXPECT_STR_EQL(ca_most_similar(NULL, (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", NULL, 2, ca_lev_compare, 0.0), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 0, ca_lev_compare, 0.0), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, NULL, 0.0), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 1.1), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, -0.1), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, INFINITY), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, -INFINITY), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, NAN), NULL); + + /** Simple test cases. **/ + success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), "str1"); + success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), "str1"); + success &= EXPECT_STR_EQL(ca_most_similar("kitten", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), "str1"); + success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, ca_lev_compare, 0.0), "str2"); + + /** Many, identically similar options. */ + success &= EXPECT_STR_EQL(ca_most_similar("kitten", + (void*[]){"skitten", "itten", "mitten", "iktten", "kittens", "kitte", "kittem", "kittne"}, 8, + ca_lev_compare, 0.0), "skitten"); + + /** Pointer-perfect handling. **/ + char* target = "string"; + success &= EXPECT_EQL((char*)ca_most_similar(target, (void*[]){"str", target}, 2, ca_lev_compare, 0.0), target, "%s"); + + /** List overflow. **/ + success &= EXPECT_STR_EQL(ca_most_similar("target", (void*[]){"str1", "targets", "target", "walmart"}, 2, ca_lev_compare, 0.0), "targets"); + + /** Threshold exceeded. **/ + success &= EXPECT_STR_EQL(ca_most_similar("blob", (void*[]){"blooooop", "targets", "string"}, 3, ca_lev_compare, 0.0), "blooooop"); + success &= EXPECT_STR_EQL(ca_most_similar("blob", (void*[]){"blooooop", "targets", "string"}, 3, ca_lev_compare, 0.5), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("hello", (void*[]){"bane", "noepo", "stars"}, 3, ca_lev_compare, 0.0), "noepo"); + success &= EXPECT_STR_EQL(ca_most_similar("hello", (void*[]){"bane", "noepo", "stars"}, 3, ca_lev_compare, 0.25), NULL); + success &= EXPECT_STR_EQL(ca_most_similar("kitten", (void*[]){"skitten", "fit"}, 2, ca_lev_compare, 0.0), "skitten"); + success &= EXPECT_STR_EQL(ca_most_similar("kitten", (void*[]){"skitten", "fit"}, 2, ca_lev_compare, 0.9), NULL); + + /** Make an array to STORE() pointers to vectors so we can free them. **/ + const unsigned int max_index = 32u; + unsigned int index = 0u; + pVector free_list[max_index]; + #define STORE(v) (free_list[index++] = (v)) + #define vec(s) STORE(ca_build_vector(s)) + + /** Alternative similarity function. **/ + pVector hello = vec("hello"), fellow = vec("fellow"), felon = vec("felon"); + pVector held = vec("held"), zephora = vec("zephora"), hexza = vec("hexza"); + pVector hello_there = vec("hello there"), hello_world = vec("hello world"); + pVector hellow_there = vec("hellow there"); + success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){fellow, felon, hello, held}, 4, ca_cos_compare, 0.0), hello); + success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){zephora, hello_world, hexza}, 3, ca_cos_compare, 0.0), hello_world); + success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){zephora, hello_world}, 1, ca_cos_compare, 0.0), zephora); + success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){zephora}, 1, ca_cos_compare, 0.0), zephora); + success &= EXPECT_VEC_EQL(ca_most_similar(hello_there, (void*[]){hello_world, zephora, hellow_there, hexza}, 4, ca_cos_compare, 0.0), hellow_there); + success &= EXPECT_VEC_EQL(ca_most_similar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, ca_cos_compare, 0.0), hello_world); + success &= EXPECT_VEC_EQL(ca_most_similar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, ca_cos_compare, 0.8), NULL); + + /** Special characters (ignored by the similarity function). **/ + pVector yip = vec("Yippee!!!"); + pVector str1 = vec("@*#((%^!&@*-+!"), str2 = vec(">>->y i!&P^^_pe$/\n?e"), str3 = vec("yip"); + success &= EXPECT_VEC_EQL(yip, str2); + success &= EXPECT_VEC_EQL(ca_most_similar(yip, (void*[]){str1, str2, str3}, 3, ca_cos_compare, 0.0), str2); + success &= EXPECT_VEC_EQL(ca_most_similar(yip, (void*[]){str1, str2, str3}, 3, ca_cos_compare, 1.0), str2); + + /** Clean up scope. **/ + #undef STORE + #undef vec + + /** Clean up using the free list. **/ + if (index >= max_index) + { + printf(" > MEMORY ERROR!!\n"); + printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index + 1, max_index); + printf(" > Increase the size of the free list (aka. max_index) to %u or more.\n", index + 1); + return false; + } + while (index > 0u) + { + pVector cur_vector = free_list[--index]; + if (cur_vector == NULL) continue; + else ca_free_vector(cur_vector); + } + + /** Set up the mock similarity function. **/ + XHashTable sim_table; + if (!check(xhInit(&sim_table, 64, 0))) return false; + mock_sims = &sim_table; + success_ptr = &success; + + /** Completely different strings are similar. **/ + double str1_str = 0.2, str1_str2 = 0.1, str1_eight = 0.8; + if (!check(xhAdd(&sim_table, "str1|str", (void*)&str1_str))) return false; + if (!check(xhAdd(&sim_table, "str1|str2", (void*)&str1_str2))) return false; + if (!check(xhAdd(&sim_table, "str1|eight", (void*)&str1_eight))) return false; + success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, get_mock_sim, 0.0), "eight"); + success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, get_mock_sim, 0.9), NULL); + if (!check(xhClear(&sim_table, do_nothing, NULL))) return false; + + /** Nans are skipped. **/ + double val_nan = 0.8, val_vals = NAN, val_val = 0.2; + if (!check(xhAdd(&sim_table, "val|nan", (void*)&val_nan))) return false; + if (!check(xhAdd(&sim_table, "val|vals", (void*)&val_vals))) return false; + if (!check(xhAdd(&sim_table, "val|val", (void*)&val_val))) return false; + success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, get_mock_sim, 0.0), "nan"); + success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, get_mock_sim, 0.9), NULL); + if (!check(xhClear(&sim_table, do_nothing, NULL))) return false; + + /** Clean up. **/ + if (!check(xhDeInit(&sim_table))) return false; + + return success; + } + +long long test(char** tname) + { + *tname = "cluster-05 ca_most_similar()"; + return loopTest(doTest); + } diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c new file mode 100644 index 000000000..e7be9d745 --- /dev/null +++ b/centrallix-lib/tests/test_clusters_06.c @@ -0,0 +1,149 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_clusters_06.c */ +/* Author: Israel Fuller */ +/* Creation: November 26th, 2025 */ +/* Description: Test the searching functions from clusters.h. */ +/************************************************************************/ + +#include +#include +#include +#include +#include + +/** Test dependencies. **/ +#include "test_utils.h" +#include "check.h" + +/** Tested module. **/ +#include "clusters.h" + + +static int cmp_Pairs(const void* v1, const void* v2) + { + const Pair* Pair1 = v1; + const Pair* Pair2 = v2; + return Pair1->i - Pair2->i; + } + +#define EXPECT_PAIR(Pair, k1, k2, sim_min, sim_max) \ + ({ \ + bool success = true; \ + pPair d = (Pair); \ + success &= EXPECT_EQL(d->i, k1, "%u"); \ + success &= EXPECT_EQL(d->j, k2, "%u"); \ + success &= EXPECT_RANGE(d->similarity, sim_min, sim_max, "%g"); \ + success; \ + }) + + +static bool doTest(void) + { + bool success = true; + + /** Allocate some test data. **/ + void* data[] = { + "string", + "string2", + "str", + "hello world", + "data", + "string3", + }; + + /** Check error cases. **/ + success &= EXPECT_EQL(ca_complete_search(NULL, 6, ca_lev_compare, 0.8, NULL), NULL, "%p"); + success &= EXPECT_EQL(ca_complete_search(data, 0, ca_lev_compare, 0.8, NULL), NULL, "%p"); + success &= EXPECT_EQL(ca_complete_search(data, 6, NULL, 0.8, NULL), NULL, "%p"); + success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, 1.1, NULL), NULL, "%p"); + success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, -0.1, NULL), NULL, "%p"); + success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, INFINITY, NULL), NULL, "%p"); + success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, -INFINITY, NULL), NULL, "%p"); + success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, NAN, NULL), NULL, "%p"); + + /** Test complete search. **/ + { + XArray xPairs; + if (!check(xaInit(&xPairs, 4))) return false; + success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); + pPair* Pairs = (pPair*)xPairs.Items; + for (unsigned int i = 0u; i < xPairs.nItems; i++) + { + pPair cur = Pairs[i]; + if (cur->i > cur->j) + { + unsigned int temp = cur->i; + cur->i = cur->j; + cur->j = temp; + } + } + qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); + success &= EXPECT_EQL(xPairs.nItems, 3, "%d"); + success &= EXPECT_PAIR(Pairs[0], 1, 2, 0.8, 1.0); + success &= EXPECT_PAIR(Pairs[1], 1, 6, 0.8, 1.0); + success &= EXPECT_PAIR(Pairs[2], 2, 6, 0.8, 1.0); + } + + /** Test sliding search: Large window. **/ + { + XArray xPairs; + if (!check(xaInit(&xPairs, 4))) return false; + success &= EXPECT_EQL(ca_sliding_search(data, 6, 5, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); + pPair* Pairs = (pPair*)xPairs.Items; + for (unsigned int i = 0u; i < xPairs.nItems; i++) + { + pPair cur = Pairs[i]; + if (cur->i > cur->j) + { + unsigned int temp = cur->i; + cur->i = cur->j; + cur->j = temp; + } + } + qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); + success &= EXPECT_EQL(xPairs.nItems, 2, "%d"); + success &= EXPECT_PAIR(Pairs[0], 1, 2, 0.8, 1.0); + // success &= EXPECT_PAIR(Pairs[1], 1, 6, 0.8, 1.0); /* Missed. */ + success &= EXPECT_PAIR(Pairs[1], 2, 6, 0.8, 1.0); + } + + /** Test sliding search: Small window. **/ + { + XArray xPairs; + if (!check(xaInit(&xPairs, 4))) return false; + success &= EXPECT_EQL(ca_sliding_search(data, 6, 2, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); + pPair* Pairs = (pPair*)xPairs.Items; + for (unsigned int i = 0u; i < xPairs.nItems; i++) + { + pPair cur = Pairs[i]; + if (cur->i > cur->j) + { + unsigned int temp = cur->i; + cur->i = cur->j; + cur->j = temp; + } + } + qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); + success &= EXPECT_EQL(xPairs.nItems, 1, "%d"); + success &= EXPECT_PAIR(Pairs[0], 1, 2, 0.8, 1.0); + // success &= EXPECT_PAIR(Pairs[1], 1, 6, 0.8, 1.0); /* Missed. */ + // success &= EXPECT_PAIR(Pairs[2], 2, 6, 0.8, 1.0); /* Missed. */ + } + + return success; + } + +long long test(char** tname) + { + *tname = "cluster-06 Searching"; + return loopTest(doTest); + } From 07331c4b577daf0c9a8dd18e89adbc3a2a3eadea Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 13:23:42 -0600 Subject: [PATCH 168/361] Fix test op count. --- centrallix-lib/tests/test_clusters_00.c | 2 +- centrallix-lib/tests/test_clusters_01.c | 2 +- centrallix-lib/tests/test_clusters_02.c | 2 +- centrallix-lib/tests/test_clusters_03.c | 2 +- centrallix-lib/tests/test_clusters_04.c | 2 +- centrallix-lib/tests/test_clusters_05.c | 2 +- centrallix-lib/tests/test_clusters_06.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c index d900881e5..141ee7b7f 100644 --- a/centrallix-lib/tests/test_clusters_00.c +++ b/centrallix-lib/tests/test_clusters_00.c @@ -91,5 +91,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-00 ca_edit_dist()"; - return loopTest(doTest); + return loopTest(doTest) * 36; } diff --git a/centrallix-lib/tests/test_clusters_01.c b/centrallix-lib/tests/test_clusters_01.c index 409219291..ad78d9324 100644 --- a/centrallix-lib/tests/test_clusters_01.c +++ b/centrallix-lib/tests/test_clusters_01.c @@ -47,5 +47,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-01 ca_edit_dist(): Stress test"; - return loopTest(doTest); + return loopTest(doTest) * 3; } diff --git a/centrallix-lib/tests/test_clusters_02.c b/centrallix-lib/tests/test_clusters_02.c index f16300340..1ec6bc2b8 100644 --- a/centrallix-lib/tests/test_clusters_02.c +++ b/centrallix-lib/tests/test_clusters_02.c @@ -77,7 +77,7 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-02 ca_build_vector()"; - return loopTest(doTest); + return loopTest(doTest) * 9; } /** Clean up scope. **/ diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c index 6a4da0890..2eef4d240 100644 --- a/centrallix-lib/tests/test_clusters_03.c +++ b/centrallix-lib/tests/test_clusters_03.c @@ -87,5 +87,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-03 ca_cos_compare()"; - return loopTest(doTest); + return loopTest(doTest) * 11; } diff --git a/centrallix-lib/tests/test_clusters_04.c b/centrallix-lib/tests/test_clusters_04.c index e007f99f3..110d512e5 100644 --- a/centrallix-lib/tests/test_clusters_04.c +++ b/centrallix-lib/tests/test_clusters_04.c @@ -66,5 +66,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-04 ca_lev_compare()"; - return loopTest(doTest); + return loopTest(doTest) * 25; } diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index 3052c1a33..f43417e1b 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -181,5 +181,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-05 ca_most_similar()"; - return loopTest(doTest); + return loopTest(doTest) * 35; } diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index e7be9d745..9beceae68 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -145,5 +145,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-06 Searching"; - return loopTest(doTest); + return loopTest(doTest) * 3; } From ee216a82e0314399bbb3621093b6b43403260c93 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 13:24:04 -0600 Subject: [PATCH 169/361] Reduce number of edits in test_clusters_01.c to a sane number. --- centrallix-lib/tests/test_clusters_01.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_clusters_01.c b/centrallix-lib/tests/test_clusters_01.c index ad78d9324..dbf5ef585 100644 --- a/centrallix-lib/tests/test_clusters_01.c +++ b/centrallix-lib/tests/test_clusters_01.c @@ -32,7 +32,7 @@ static bool doTest(void) bool success = true; /** Many, many replace edits. **/ - const static unsigned short num_edits = 254;//15827; + const static unsigned short num_edits = 2039; char AAA[num_edits + 1], BBB[num_edits + 1]; memset(AAA, 'A', num_edits); memset(BBB, 'B', num_edits); From 237ed02ce3ccddad312a0fbf45555c785a3f6e24 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 13:24:32 -0600 Subject: [PATCH 170/361] Fix naming conventions and error handling. --- centrallix-lib/tests/test_clusters_05.c | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index f43417e1b..84af473e3 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -33,7 +33,7 @@ static const unsigned int key_length = 64u; static pXHashTable mock_sims = NULL; static bool* success_ptr = NULL; -static double get_mock_sim(void* v1, void* v2) +static double getSimMock(void* v1, void* v2) { char key[key_length]; char* str1 = v1; @@ -50,7 +50,7 @@ static double get_mock_sim(void* v1, void* v2) if (sim != NULL) goto found; /** Key not found. **/ - fprintf(stderr, " > get_mock_sim(\"%s\", \"%s\"): No sim provided!\n", str1, str2); + fprintf(stderr, " > getSimMock(\"%s\", \"%s\"): No sim provided!\n", str1, str2); *success_ptr = false; return NAN; @@ -150,30 +150,30 @@ static bool doTest(void) /** Set up the mock similarity function. **/ XHashTable sim_table; - if (!check(xhInit(&sim_table, 64, 0))) return false; + if (check(xhInit(&sim_table, 64, 0)) != 0) return false; mock_sims = &sim_table; success_ptr = &success; /** Completely different strings are similar. **/ double str1_str = 0.2, str1_str2 = 0.1, str1_eight = 0.8; - if (!check(xhAdd(&sim_table, "str1|str", (void*)&str1_str))) return false; - if (!check(xhAdd(&sim_table, "str1|str2", (void*)&str1_str2))) return false; - if (!check(xhAdd(&sim_table, "str1|eight", (void*)&str1_eight))) return false; - success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, get_mock_sim, 0.0), "eight"); - success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, get_mock_sim, 0.9), NULL); - if (!check(xhClear(&sim_table, do_nothing, NULL))) return false; + if (check(xhAdd(&sim_table, "str1|str", (void*)&str1_str)) != 0) return false; + if (check(xhAdd(&sim_table, "str1|str2", (void*)&str1_str2)) != 0) return false; + if (check(xhAdd(&sim_table, "str1|eight", (void*)&str1_eight)) != 0) return false; + success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.0), "eight"); + success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.9), NULL); + if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; /** Nans are skipped. **/ double val_nan = 0.8, val_vals = NAN, val_val = 0.2; - if (!check(xhAdd(&sim_table, "val|nan", (void*)&val_nan))) return false; - if (!check(xhAdd(&sim_table, "val|vals", (void*)&val_vals))) return false; - if (!check(xhAdd(&sim_table, "val|val", (void*)&val_val))) return false; - success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, get_mock_sim, 0.0), "nan"); - success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, get_mock_sim, 0.9), NULL); - if (!check(xhClear(&sim_table, do_nothing, NULL))) return false; + if (check(xhAdd(&sim_table, "val|nan", (void*)&val_nan)) != 0) return false; + if (check(xhAdd(&sim_table, "val|vals", (void*)&val_vals)) != 0) return false; + if (check(xhAdd(&sim_table, "val|val", (void*)&val_val)) != 0) return false; + success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.0), "nan"); + success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.9), NULL); + if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; /** Clean up. **/ - if (!check(xhDeInit(&sim_table))) return false; + if (check(xhDeInit(&sim_table)) != 0) return false; return success; } From be8a28e96c86d4fb594283411c4cb37ccc262bd5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 13:24:45 -0600 Subject: [PATCH 171/361] Fix incorrect expected values and memory leaks. --- centrallix-lib/tests/test_clusters_06.c | 38 ++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index 9beceae68..c4bcb20df 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -21,8 +21,9 @@ #include /** Test dependencies. **/ -#include "test_utils.h" #include "check.h" +#include "newmalloc.h" +#include "test_utils.h" /** Tested module. **/ #include "clusters.h" @@ -35,6 +36,14 @@ static int cmp_Pairs(const void* v1, const void* v2) return Pair1->i - Pair2->i; } +static void freePairs(pXArray xPairs) + { + /** The search functions hand ownership of each pair to the caller. **/ + while (xPairs->nItems > 0) + nmFree(xPairs->Items[--xPairs->nItems], sizeof(Pair)); + check(xaDeInit(xPairs)); + } + #define EXPECT_PAIR(Pair, k1, k2, sim_min, sim_max) \ ({ \ bool success = true; \ @@ -73,7 +82,7 @@ static bool doTest(void) /** Test complete search. **/ { XArray xPairs; - if (!check(xaInit(&xPairs, 4))) return false; + if (check(xaInit(&xPairs, 4)) != 0) return false; success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) @@ -88,15 +97,16 @@ static bool doTest(void) } qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); success &= EXPECT_EQL(xPairs.nItems, 3, "%d"); - success &= EXPECT_PAIR(Pairs[0], 1, 2, 0.8, 1.0); - success &= EXPECT_PAIR(Pairs[1], 1, 6, 0.8, 1.0); - success &= EXPECT_PAIR(Pairs[2], 2, 6, 0.8, 1.0); + success &= EXPECT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); + success &= EXPECT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); + success &= EXPECT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); + freePairs(&xPairs); } /** Test sliding search: Large window. **/ { XArray xPairs; - if (!check(xaInit(&xPairs, 4))) return false; + if (check(xaInit(&xPairs, 4)) != 0) return false; success &= EXPECT_EQL(ca_sliding_search(data, 6, 5, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) @@ -111,15 +121,16 @@ static bool doTest(void) } qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); success &= EXPECT_EQL(xPairs.nItems, 2, "%d"); - success &= EXPECT_PAIR(Pairs[0], 1, 2, 0.8, 1.0); - // success &= EXPECT_PAIR(Pairs[1], 1, 6, 0.8, 1.0); /* Missed. */ - success &= EXPECT_PAIR(Pairs[1], 2, 6, 0.8, 1.0); + success &= EXPECT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); + // success &= EXPECT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ + success &= EXPECT_PAIR(Pairs[1], 1, 5, 0.8, 1.0); + freePairs(&xPairs); } /** Test sliding search: Small window. **/ { XArray xPairs; - if (!check(xaInit(&xPairs, 4))) return false; + if (check(xaInit(&xPairs, 4)) != 0) return false; success &= EXPECT_EQL(ca_sliding_search(data, 6, 2, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) @@ -134,9 +145,10 @@ static bool doTest(void) } qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); success &= EXPECT_EQL(xPairs.nItems, 1, "%d"); - success &= EXPECT_PAIR(Pairs[0], 1, 2, 0.8, 1.0); - // success &= EXPECT_PAIR(Pairs[1], 1, 6, 0.8, 1.0); /* Missed. */ - // success &= EXPECT_PAIR(Pairs[2], 2, 6, 0.8, 1.0); /* Missed. */ + success &= EXPECT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); + // success &= EXPECT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ + // success &= EXPECT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); /* Sliding search misses this pair. */ + freePairs(&xPairs); } return success; From a75d28e7c0314bdb24a5a5a41acc7351d0193561 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 13:25:06 -0600 Subject: [PATCH 172/361] Fix edge cases in clusters.c. --- centrallix-lib/src/clusters.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 8cd6748a8..e3fefd3ca 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -154,6 +154,9 @@ ca_build_vector(const char* str) pVector sparse_vector = NULL; pVector trimmed_sparse_vector = NULL; + /** Guard null. **/ + if (str == NULL) return NULL; + /** Allocate memory to store the characters. **/ unsigned int num_chars = 0u; chars = checkPtr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); @@ -507,9 +510,6 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const int result = -1; unsigned int** lev_matrix = NULL; - /** Optimization: Identical string pointers. **/ - if (str1 == str2) return 0; - /*** lev_matrix: *** For all i and j, d[i][j] will hold the Levenshtein distance between *** the first i characters of s and the first j characters of t. @@ -1006,9 +1006,9 @@ ca_most_similar( /** Iterate over all data options to find the one with the highest similarity. **/ for (unsigned int i = 0u; (num_data == 0u && data[i] != NULL) || (i < num_data); i++) { - const double sim = checkDouble(similarity(target, data[i])); + const double sim = similarity(target, data[i]); if (isnan(sim)) continue; /* Skip failed comparison. */ - if (sim > best_sim && sim > threshold) + if (sim > best_sim && sim >= threshold) { most_similar = data[i]; best_sim = sim; From 50129f8bc0ccd00a7f49f0c0b1277413e56cd82c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 13:29:00 -0600 Subject: [PATCH 173/361] Remove bad registrations. --- centrallix-lib/src/clusters.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index e3fefd3ca..56ccd1b6a 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -1131,8 +1131,6 @@ ca_complete_search( void ca_init(void) { - nmRegister(sizeof(pVector), "pVector"); - nmRegister(sizeof(pCentroid), "pCentroid"); nmRegister(CENTROID_SIZE, "Centroid"); nmRegister(sizeof(Pair), "Pair"); From d686af8600f2c7a96ba0fd35ef242135721b0b8d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 14:19:28 -0600 Subject: [PATCH 174/361] Remove redundant markdown lines about licensing. --- centrallix-sysdoc/Libraries/mtask.md | 2 -- centrallix-sysdoc/Libraries/mtsession.md | 2 -- centrallix-sysdoc/Libraries/newmalloc.md | 2 -- centrallix-sysdoc/Libraries/xarray.md | 2 -- centrallix-sysdoc/Libraries/xhash.md | 2 -- centrallix-sysdoc/Libraries/xstring.md | 2 -- 6 files changed, 12 deletions(-) diff --git a/centrallix-sysdoc/Libraries/mtask.md b/centrallix-sysdoc/Libraries/mtask.md index 0cbadf1cd..47510ca04 100644 --- a/centrallix-sysdoc/Libraries/mtask.md +++ b/centrallix-sysdoc/Libraries/mtask.md @@ -39,8 +39,6 @@ **Updated**: December 11, 2025 -**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. - ## Table of Contents - [Handling Network Connection](#handling-network-connection) diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md index 84990ab32..33f886ed6 100644 --- a/centrallix-sysdoc/Libraries/mtsession.md +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -38,8 +38,6 @@ **Updated**: December 11, 2025 -**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. - ## Table of Contents - [The MTSession Library](#the-mtsession-library) diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index 9183ca02a..3e4b7541a 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -40,8 +40,6 @@ **Updated**: December 11, 2025 -**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. - ## Table of Contents - [Memory Management in Centrallix](#memory-management-in-centrallix) diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 7275cee2f..110ded79e 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -38,8 +38,6 @@ **Updated**: December 11, 2025 -**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. - ## Table of Contents - [The XArray Library](#the-xarray-library) diff --git a/centrallix-sysdoc/Libraries/xhash.md b/centrallix-sysdoc/Libraries/xhash.md index daf16d186..65ab27b8b 100644 --- a/centrallix-sysdoc/Libraries/xhash.md +++ b/centrallix-sysdoc/Libraries/xhash.md @@ -38,8 +38,6 @@ **Updated**: December 11, 2025 -**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. - ## Table of Contents - [The XHash Library](#the-xhash-library) diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index d6e0d0b90..76531b2cb 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -38,8 +38,6 @@ **Updated**: December 11, 2025 -**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. - ## Table of Contents - [The XString Library](#the-xstring-library) From ac025731febde4e2d8c82a522900adf84857ca66 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 14:20:47 -0600 Subject: [PATCH 175/361] Fix outdated info in the docs. --- centrallix-sysdoc/OSDriver_Authoring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 8ee3a6693..fddbd7dee 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -489,7 +489,7 @@ The parameters passed are intentionally similar to the `fdRead()` function in `m | buffer | char* | The buffer where read data should be stored. | | max_cnt | int | The maximum number of bytes to read into the buffer. | | offset | int | An optional seek offset. | -| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 4th argument (`arg`). | +| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 4th argument (`offset`). | | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | - 📖 **Note**: Not all objects can be seekable and some of the objects handled by the driver may have limited seek functionality, even if others do not. From d1e8458f3bfe0030ad85986579e1732ff30d1208 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 14:33:04 -0600 Subject: [PATCH 176/361] Fix xaTrim() on size-0 XArrays. --- centrallix-lib/src/xarray.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/centrallix-lib/src/xarray.c b/centrallix-lib/src/xarray.c index e91d8a69b..a1e1155cb 100644 --- a/centrallix-lib/src/xarray.c +++ b/centrallix-lib/src/xarray.c @@ -418,6 +418,10 @@ int xaInsertAfter(pXArray this, int index, void* item) int xaTrim(pXArray this) { + /** Reallocating to size 0 is undefined behavior, so skip. **/ + if (this->nItems == 0) + return 0; + /** Allocate the new internal items array. **/ const size_t new_size = this->nItems * sizeof(void*); void* new_items = checkPtr(nmSysRealloc(this->Items, new_size)); From 95265a89e1fe60be0a50631744e17ecd9f5b4535 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 14:43:12 -0600 Subject: [PATCH 177/361] Add a #define to explicitly denote the magic number null marker. --- centrallix-lib/include/magic.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index 92679e9c3..803f650a5 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -30,11 +30,14 @@ #include #include "expect.h" +/** Define the value used as a placeholder to mark null data. **/ +#define MGK_NULL_MARK 0xEE1EE100 + #define ASSERTMAGIC(data, expect) \ ({ \ const pMagicHdr _data = (pMagicHdr)(data); \ const Magic_t _expect = (expect); \ - const Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + const Magic_t _actual = (_data == NULL) ? MGK_NULL_MARK : _data->Magic; \ if (UNLIKELY(_data != NULL && _actual != _expect)) \ { \ fprintf(stderr, \ @@ -50,7 +53,7 @@ ({ \ const pMagicHdr _data = (pMagicHdr)(data); \ const Magic_t _expect = (expect); \ - const Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + const Magic_t _actual = (_data == NULL) ? MGK_NULL_MARK : _data->Magic; \ if (UNLIKELY(_data != NULL && _actual == _expect)) \ { \ fprintf(stderr, \ @@ -80,7 +83,7 @@ typedef struct Magic_t Magic; } MagicHdr, *pMagicHdr; - + #define MGK_FILE 0x12340001 /* mtask.h::File */ #define MGK_OBJECT 0x12340102 /* obj.h::Object */ #define MGK_OBJQUERY 0x1234015a /* obj.h::ObjQuery */ From e74e856c8bc79b3e3ea0ade27ab42c33c98ce092 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 14:46:43 -0600 Subject: [PATCH 178/361] Update copyright headers. --- centrallix-lib/include/xarray.h | 2 +- centrallix-lib/src/xarray.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/include/xarray.h b/centrallix-lib/include/xarray.h index 9773767fa..7cad0d523 100644 --- a/centrallix-lib/include/xarray.h +++ b/centrallix-lib/include/xarray.h @@ -6,7 +6,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ diff --git a/centrallix-lib/src/xarray.c b/centrallix-lib/src/xarray.c index a1e1155cb..9fb94dba0 100644 --- a/centrallix-lib/src/xarray.c +++ b/centrallix-lib/src/xarray.c @@ -13,7 +13,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ From bd35642670601919d6f6af209c419c3cbb6601e8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 14:49:18 -0600 Subject: [PATCH 179/361] Fix markdown links. --- centrallix-sysdoc/GCC_Dependencies.md | 1 + centrallix-sysdoc/Libraries/mtsession.md | 3 ++- centrallix-sysdoc/OSDriver_Authoring.md | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/centrallix-sysdoc/GCC_Dependencies.md b/centrallix-sysdoc/GCC_Dependencies.md index 778a6fb68..fde389c90 100644 --- a/centrallix-sysdoc/GCC_Dependencies.md +++ b/centrallix-sysdoc/GCC_Dependencies.md @@ -9,6 +9,7 @@ Date: Descember 4, 2025 - [Table of Contents](#table-of-contents) - [Introduction](#introduction) - [List of Dependencies](#list-of-dependencies) + - [Notes](#notes) ## Introduction This document tracks dependencies on the GCC toolchain in the centrallix codebase. As code is added which relies on GCC specific behavior, such additions should be noted here to make possible use of a different toolchain (e.g. LLVM) in the future less painful. diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md index 33f886ed6..ca34b2cba 100644 --- a/centrallix-sysdoc/Libraries/mtsession.md +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -42,6 +42,7 @@ ## Table of Contents - [The MTSession Library](#the-mtsession-library) - [Introduction](#introduction) + - [mssInitialize()](#mssinitialize) - [mssUserName()](#mssusername) - [mssPassword()](#msspassword) - [mssSetParam()](#msssetparam) @@ -136,4 +137,4 @@ The `mssError()` function is not required to be called at every function nesting ```c int mssErrorErrno(int clr, char* module, char* message, ...); ``` -This function works the same way as [`mssError`](#mssError), except checks the current value of `errno` and includes a description of any error stored there. This is useful if a system call or other library function is responsible for this error. +This function works the same way as [`mssError`](#msserror), except checks the current value of `errno` and includes a description of any error stored there. This is useful if a system call or other library function is responsible for this error. diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index fddbd7dee..1aa11413b 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -66,6 +66,7 @@ - [Function: QueryFetch()](#function-queryfetch) - [Function: QueryCreate()](#function-querycreate) - [Function: QueryClose()](#function-queryclose) + - [Object Attributes](#object-attributes) - [Function: GetAttrType()](#function-getattrtype) - [Function: GetAttrValue()](#function-getattrvalue) - [Function: GetFirstAttr()](#function-getfirstattr--getnextattr) @@ -101,7 +102,7 @@ - [stparse: stFreeInf()](#stparse-stfreeinf) - [stparse: Using Fields Directly](#stparse-using-fields-directly) - [IV Module: Expression](#iv-module-expression) - - [expAllocExpression())](#expallocexpression) + - [expAllocExpression()](#expallocexpression) - [expFreeExpression()](#expfreeexpression) - [expCompileExpression()](#expcompileexpression) - [expCompileExpressionFromLxs()](#expcompileexpressionfromlxs) @@ -120,7 +121,7 @@ - [V Path Handling Functions](#v-path-handling-functions) - [obj_internal_PathPart()](#obj_internal_pathpart) - [obj_internal_AddToPath()](#obj_internal_addtopath) - - [obj_internal_CopyPath](#obj_internal_copypath) + - [obj_internal_CopyPath()](#obj_internal_copypath) - [obj_internal_FreePathStruct()](#obj_internal_freepathstruct) - [VI Parsing Data](#vi-parsing-data) - [mlxOpenSession()](#mlxopensession) From d14403dbf1e5a2e9b3c00699061aeadcab1438d3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 14:54:10 -0600 Subject: [PATCH 180/361] Remove glyph.h. --- centrallix-lib/include/glyph.h | 79 ---------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 centrallix-lib/include/glyph.h diff --git a/centrallix-lib/include/glyph.h b/centrallix-lib/include/glyph.h deleted file mode 100644 index 6a5b0a74e..000000000 --- a/centrallix-lib/include/glyph.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef GLYPH_H -#define GLYPH_H - -/************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Core */ -/* */ -/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ -/* */ -/* This program is free software; you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation; either version 2 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program; if not, write to the Free Software */ -/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ -/* 02111-1307 USA */ -/* */ -/* A copy of the GNU General Public License has been included in this */ -/* distribution in the file "COPYING". */ -/* */ -/* Module: glyph.h */ -/* Author: Israel Fuller */ -/* Creation: October 27, 2025 */ -/* Description: A simple debug visualizer to make pretty patterns in */ -/* developer's terminal which can be surprisingly useful */ -/* for debugging algorithms. */ -/************************************************************************/ - -#include - -/** Uncomment to activate glyphs. **/ -/** Should not be enabled in production code on the master branch. */ -// #define ENABLE_GLYPHS - -#ifdef ENABLE_GLYPHS -#define glyph_print(s) printf("%s", s); - -/*** Initialize a simple debug visualizer to make pretty patterns in the - *** developer's terminal. Great for when you need to run a long task and - *** want a super simple way to make sure it's still working. - *** - *** @attention - Relies on storing data in variables in scope, so calling - *** glyph() requires a call to glyph_init() previously in the same scope. - *** - *** @param name The symbol name of the visualizer. - *** @param str The string printed for the visualization. - *** @param interval The number of invocations of glyph() required to print. - *** @param flush Whether to flush on output. - ***/ -#define glyph_init(name, str, interval, flush) \ - const char* vis_##name##_str = str; \ - const unsigned int vis_##name##_interval = interval; \ - const bool vis_##name##_flush = flush; \ - unsigned int vis_##name##_i = 0u; - -/*** Invoke a visualizer. - *** - *** @param name The name of the visualizer to invoke. - ***/ -#define glyph(name) \ - if (++vis_##name##_i % vis_##name##_interval == 0) \ - { \ - glyph_print(vis_##name##_str); \ - if (vis_##name##_flush) fflush(stdout); \ - } -#else -#define glyph_print(str) -#define glyph_init(name, str, interval, flush) -#define glyph(name) -#endif - -#endif /* End of .h file. */ From 3b5e62091826eb97f6a088ece3f837c9751af28e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 15:08:25 -0600 Subject: [PATCH 181/361] Improve doc comment clarity. --- centrallix-lib/src/clusters.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 56ccd1b6a..72e40c170 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -496,13 +496,11 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c1) *** *** @param str1 The first string. *** @param str2 The second string. - *** @param str1_length The length of the first string. - *** @param str2_length The length of the second string. + *** @param str1_length The length of the first string, or 0 to detect from + *** the length from the null-terminator. + *** @param str2_length The length of the second string, or 0 to detect from + *** the length from the null-terminator. *** @returns The edit distance between the two strings, or a negative value on error. - *** - *** @attention - `Tip`: Pass 0 for the length of either string to infer it - *** using the null terminating character. Conversely, character arrays - *** with no null terminator are allowed if an explicit length is passed. ***/ int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) From b7eb044831b5b845eeab34f2c9eca7c8a31abc71 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 15:12:31 -0600 Subject: [PATCH 182/361] Remove unused requirements file. --- .../ClusterDriverRequirements-old.md | 186 ------------------ 1 file changed, 186 deletions(-) delete mode 100644 centrallix-sysdoc/ClusterDriverRequirements-old.md diff --git a/centrallix-sysdoc/ClusterDriverRequirements-old.md b/centrallix-sysdoc/ClusterDriverRequirements-old.md deleted file mode 100644 index d5891304d..000000000 --- a/centrallix-sysdoc/ClusterDriverRequirements-old.md +++ /dev/null @@ -1,186 +0,0 @@ - -## Cluster Driver Specifications -### Cluster Open -```c -void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); -``` -`clusterOpen()` shall... -- Create or read a node, as indicated by passed flags. - - Read flags from `obj->Mode`. - - If `O_EXCL` is specified, `O_CREAT` is specified, and there are no other elements in the path, create a new node. - - Otherwise attempt to read the previous object (in `obj->Prev`). - - If this fails and `O_CREAT` is specified, create a new node. - - If there is still no node, fail. -- Parse the provided path. - - Use `obj_internal_PathPart()` with the pathname in `obj->Pathname`. - - Not parse previous parts of the path already parsed by other drivers. - - Start at the `obj->SubPtr`-th path element (skipping `obj->SubPtr - 1` elements). - - Consume elements in the path until `obj_internal_PathPart()` returns `NULL`. - - Store the number of elements consumed in `obj->SubCnt`. -- Determine what data is being targeted from the parsed path. - - If the relevant part of the path contains only the name of the file, the driver shall set the target to root. - - If it contains the name of a valid (sub)cluster or search, the driver shall set the target to that (sub)cluster or search. - - Otherwise, the driver shall produce a descriptive error. -- Parse the provided structure file. - - Follow the spec given in `cluster-schema.cluster`. - - Produce descriptive errors when issues are detected. -- Return a new struct containing necessary information, including: - - The name, source path, and attribute name. - - All parameters (and a param list for scope), clusters, and searches. - - Each parameter shall be represented by a `pParam` object (see `params.h`). - - Each cluster shall be represented by a struct with information including: - - Its name, clustering algorithm, and similarity measure. - - The number of clusters to generate. - - If a k-means algorithm is specified, the improvement threshold. - - The maximum number of iterations to run. - - A list of subclusters with at least this information for each. - - Each search shall be represented by a struct with information including: - - Its name, threshold, and similarity measure. - - Its source, which is a valid cluster name of a cluster in the clusters list. - - Information about targets, derived from the path. - -### Cluster Close -```c -int clusterClose(void* inf_v, pObjTrxTree* oxt); -``` -`clusterClose()` shall... -- Free all allocated data in the driver struct. -- Close any open files or the like in the driver struct. -- Return 0. - -### Cluster Open Query -```c -void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); -``` -`clusterOpenQuery()` shall... -- Return a query struct that can be passed to `clusterQueryFetch()`. - - This struct shall contain an index to the last row accessed (starting at 0). - - This struct shall contain a pointer to the driver data. - -### Cluster Query Fetch -```c -void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) -``` -`clusterQueryFetch()` shall... -- If the driver struct targets the root node, this function shall produce an error. -- If the driver struct targets an entry, this function shall produce a different error. -- If the driver targets a cluster or search, this function shall return a driver struct targeting the cluster or search *entry* (respectively) indicated by the query struct's row pointer, and increment the pointer. - - Exception: If no data remains, this function shall return `NULL` instead. - - This request shall cause clustering / searching to execute, if it has not executed already. - -### Cluster Query Close -```c -int clusterQueryClose(void* qy_v, pObjTrxTree* oxt); -``` -`clusterQueryClose()` shall... -- Free all allocated data in the query struct. -- Close any open files or the like in the query struct. -- Return 0. - -### Cluster Get Attribute Type -```c -int clusterGetAttrType(void* qy_v, pObjTrxTree* oxt); -``` -`clusterGetAttrType()` shall... -- Return the `DATA_T_...` type of the requested attribute, or `DATA_T_UNAVAILABLE` if the attribute does not exist. -- The name, content_type, inner_type, and outer_type attributes shall be of type `DATA_T_STRING`. -- The last_modification attribute shall be of type `DATA_T_DATETIME`. -- If the target is root... - - The source and attr_name attributes shall be of type `DATA_T_STRING`. -- If the target is a cluster... - - The algorithm and similarity_measure attributes shall be of type `DATA_T_STRING`. - - The num_clusters and max_iterations attributes shall be of type `DATA_T_INTEGER`. - - The improvement_threshold and average_similarity attributes shall be of type `DATA_T_DOUBLE`. -- If the target is a search... - - The source and similarity_measure attribute shall be of type `DATA_T_STRING`. - - The threshold attribute shall be of type `DATA_T_DOUBLE`. -- If the target is a cluster entry... - - The val attribute shall be of type `DATA_T_INTEGER`. - - The sim attribute shall be of type `DATA_T_DOUBLE`. -- If the target is a search entry... - - The val1 and val2 attribute shall be of type `DATA_T_INTEGER`. - - The sim attribute shall be of type `DATA_T_DOUBLE`. - -### Cluster Get Attribute Value -```c -int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* _); -``` -`clusterGetAttrValue()` shall... -- If the given datatype does not match that returned from `clusterGetAttrType()`, the function shall produce an error. -- Requesting the name attribute shall produce the following values, depending on the target: - - If the target is root, the name in the driver struct (aka. the one specified in the .cluster file) shall be produced. - - If the target is a cluster or cluster entry, the name of the cluster shall be produced. - - If the target is a search or search entry, the name of the search shall be produced. -- Requesting the annotation shall produce some string describing the driver. -- Requesting the outer_type shall produce "system/row". -- Requesting the inner_type or content_type shall produce "system/void". (All path elements are consumed.) -- If the target is root... - - Requesting source shall produce the source path. - - Requesting attr_name shall produce the attribute name. -- If the target is a cluster... - - Requesting algorithm shall produce the name of the clustering algorithm. - - Requesting similarity_measure shall produce the name of the similarity measure. - - Requesting num_clusters shall produce the number of clusters. - - Requesting max_iterations shall produce the maximum number of iterations. - - Requesting improvement_threshold shall produce the minimum improvement threshold. - - Requesting average_similarity shall produce the average size of clusters, running clustering / searching algorithms, if necessary. -- If the target is a search... - - Requesting source shall produce the name of the source cluster for the search. - - Requesting similarity_measure shall produce the name of the similarity measure. - - Requesting threshold shall produce the filtering threshold. -- If the target is a cluster entry... - - Requesting val shall produce the value of the data point in this cluster. - - Requesting sim shall produce the similarity of the data point to the center of the cluster. -- If the target is a cluster entry... - - Requesting val1 or val2 shall produce the first and second value (respectively)detected in this search. - - Requesting sim shall produce the similarity of these two data points. - - -### Cluster Get First Attribute -```c -char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt); -``` -`clusterGetFirstAttr()` shall... -- Reset the current attribute index on the driver struct to 0. -- Return the value of invoking `clusterGetNextAttr()`. - -### Cluster Get Next Attribute -```c -char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt); -``` -`clusterGetNextAttr()` shall... -- Return the attribute name at the attribute index given by the driver struct in the list of attributes based on the target type. -- Return `NULL` if the end of the list has been reached. -- Increase the attribute index on the driver struct by 1. - -- The attribute name list for a targeting root shall include "source" and "attr_name". -- The attribute name list for a targeting cluster shall include "algorithm", "similarity_measure", "num_clusters", "improvement_threshold", and "max_iterations". -- The attribute name list for a targeting search shall include "source", "threshold", and "similarity_measure". -- The attribute name list for a targeting cluster entry shall include "val" and "sim". -- The attribute name list for a targeting search entry shall include "val1", "val2", and "sim". - -### Cluster Get Next Attribute -```c -int clusterInfo(void* inf_v, pObjectInfo info); -``` -`clusterInfo()` shall... -- Provide the OBJ_INFO_F_CANT_ADD_ATTR flag. -- Provide the OBJ_INFO_F_CANT_HAVE_CONTENT flag. -- Provide the OBJ_INFO_F_NO_CONTENT flag. -- If the target is a root... - - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. - - Provide the OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag. - - Provide the OBJ_INFO_F_HAS_SUBOBJ flag if there is at least one cluster or search. - - Provide the OBJ_INFO_F_NO_SUBOBJ flag otherwise. - - Provide the total number of clusters and searches as the number of subobjects. -- If the target is a cluster... - - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. - - Provide the OBJ_INFO_F_HAS_SUBOBJ flag. - - If the algorithm has been run, provide OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag and the number of data points clustered as the number of subobjects. -- If the target is a search... - - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. - - If the algorithm has been run... - - Provide OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag and the number of elements found by the search as the number of subobjects. - - Provide the OBJ_INFO_F_HAS_SUBOBJ flag if at least one element was found. -- If the target is a cluster entry or a search entry... - - Provide the OBJ_INFO_F_CANT_HAVE_SUBOBJ flag. \ No newline at end of file From 53b726f7138ef251f1a00de1b120a919acf9fde7 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 15:36:23 -0600 Subject: [PATCH 183/361] Fix typos and mistakes. --- centrallix-lib/include/clusters.h | 6 ++--- centrallix-lib/include/magic.h | 7 ++--- centrallix-lib/src/clusters.c | 21 +++++++-------- centrallix-lib/src/xhash.c | 4 +-- centrallix-sysdoc/Libraries/newmalloc.md | 6 ++--- centrallix-sysdoc/OSDriver_Authoring.md | 2 +- centrallix/expression/exp_compiler.c | 3 ++- centrallix/expression/exp_evaluate.c | 4 +-- centrallix/expression/exp_functions.c | 34 ++++++++++-------------- centrallix/multiquery/multiquery.c | 2 +- centrallix/objectsystem/obj_datatypes.c | 4 +-- centrallix/osdrivers/objdrv_cluster.c | 2 +- centrallix/test_obj.c | 4 +-- 13 files changed, 47 insertions(+), 52 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 403fcc227..6d366d9f6 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -25,14 +25,14 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: lib_cluster.c, lib_cluster.h */ +/* Module: clusters.c, clusters.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description Clustering library used to cluster and search data with */ /* cosine or Levenshtein (aka. edit distance) similarity */ /* measures. Used by the "clustering driver". */ /* For more information on how to use this library, see */ -/* string-similarity.md in the centrallix-sysdoc folder. */ +/* string_similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ #include @@ -60,7 +60,7 @@ *** Unfortunately, we can't use a number this large yet because *** kmeans algorithm creates densely allocated centroids with *** `CA_NUM_DIMS` dimensions, so a large number causes it to fail. - *** This, we use 251 as the largest prime number less than 256, + *** Thus, we use 251 as the largest prime number less than 256, *** giving us a decent balance between collision reduction and *** kmeans centroid performance/memory overhead. ***/ diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index 803f650a5..5c80d3cb1 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -25,9 +25,10 @@ /************************************************************************/ - #ifdef DBMAGIC +#include #include + #include "expect.h" /** Define the value used as a placeholder to mark null data. **/ @@ -41,7 +42,7 @@ if (UNLIKELY(_data != NULL && _actual != _expect)) \ { \ fprintf(stderr, \ - "%s:%d: Magic assertion failed, unexpected %u != %d for %8.8lX.\n", \ + "%s:%d: Magic assertion failed, unexpected %X != %X for %8.8lX.\n", \ __FILE__, __LINE__, _actual, _expect, (long)_data \ ); \ abort(); \ @@ -57,7 +58,7 @@ if (UNLIKELY(_data != NULL && _actual == _expect)) \ { \ fprintf(stderr, \ - "%s:%d: Magic assertion failed, unexpected %d.\n", \ + "%s:%d: Magic assertion failed, unexpected %X.\n", \ __FILE__, __LINE__, _expect \ ); \ abort(); \ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 72e40c170..8460f948e 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -22,14 +22,14 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: lib_cluster.c, lib_cluster.h */ +/* Module: clusters.c, clusters.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ /* Description Clustering library used to cluster and search data with */ /* cosine or Levenshtein (aka. edit distance) similarity */ /* measures. Used by the "clustering driver". */ /* For more information on how to use this library, see */ -/* string-similarity.md in the centrallix-sysdoc folder. */ +/* string_similarity.md in the centrallix-sysdoc folder. */ /************************************************************************/ #include @@ -613,7 +613,7 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const *** @attention - This function takes `void*` instead of `pVector` so that it *** can be used as the similarity function in the ca_search() function *** family without needing a messy typecast to avoid the compiler warning. - *** However, behavior is undefined if `v1` and `v2` do are not `pVector`s. + *** However, behavior is undefined if `v1` and `v2` are not `pVector`s. *** *** @param v1 A `pVector` to the first string to compare. *** @param v2 A `pVector` to the second string to compare. @@ -651,7 +651,7 @@ ca_cos_compare(void* v1, void* v2) *** @attention - This function takes `void*` instead of `char*` so that it *** can be used as the similarity function in the ca_search() function *** family without needing a messy typecast to avoid the compiler warning. - *** However, behavior is undefined if `v1` and `v2` do are not `char*`s. + *** However, behavior is undefined if `v1` and `v2` are not `char*`s. *** *** @param str1 A `char*` to the first string to compare. *** @param str2 A `char*` to the second string to compare. @@ -717,7 +717,7 @@ ca_eql(pVector v1, pVector v2) *** @returns The average cluster size. ***/ static double -get_cluster_size( +ca_i_get_cluster_size( pVector* vectors, const unsigned int num_vectors, unsigned int* labels, @@ -776,14 +776,13 @@ get_cluster_size( /*** Executes the k-means clustering algorithm. Selects `num_clusters` random *** vectors as initial centroids, using `rand()` (to set a seed, call srand() - *** pass false for `auto_seed`). Each iteration, points are assigned to the + *** and pass false for `auto_seed`). Each iteration, points are assigned to the *** nearest centroid using cosine similarity on the provided sparse vectors. *** After this, centroids are moved to the center of their points. This *** process repeats until the `min_improvement` threshold is not met, or *** `max_iter` is reached (whichever happens first). *** - *** @attention - `num_vectors` must be the length of `vectors`. - *** @attention - `num_clusters` must be the length of `labels`. + *** @attention - `num_vectors` must be the length of both `vectors` and `labels`. *** *** @param vectors The sparse cosine similarity vectors representing the data *** to cluster. @@ -925,7 +924,7 @@ ca_kmeans( /** Is there enough improvement? **/ if (min_improvement <= -1.0) continue; /** Skip check if it will never end the loop. **/ - const double average_cluster_size = checkDouble(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); + const double average_cluster_size = checkDouble(ca_i_get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); if (isnan(average_cluster_size)) goto end; const double improvement = old_average_cluster_size - average_cluster_size; if (improvement < min_improvement) break; @@ -1072,7 +1071,7 @@ ca_sliding_search( const double sim = checkDouble(similarity(data[i], data[j])); if (isnan(sim) || sim < 0.0 || 1.0 < sim) { - fprintf(stderr, "Invalid similarity %g %lf.\n", sim, sim); + fprintf(stderr, "Invalid similarity %g.\n", sim); goto err_free; } if (sim > threshold) /* Pair found! */ @@ -1094,7 +1093,7 @@ ca_sliding_search( /** Error cleanup: Free the pairs that we added to the XArray. **/ while (pairs->nItems > num_starting_pairs) nmFree(pairs->Items[--pairs->nItems], sizeof(Pair)); - if (maybe_pairs == NULL) check(xaDeInit(pairs)); /* Failure ignored. */ + if (maybe_pairs == NULL) check(xaFree(pairs)); /* Failure ignored. */ err: return NULL; diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index 58adc7e18..aad795cd4 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -290,7 +290,7 @@ xhClear(pXHashTable this, int (*free_fn)(), void* free_arg) return 0; } -/*** Executes an operation on each entry of the hash table entry. +/*** Executes an operation on each entry of the hash table. *** *** @param this The affected hash table (passing NULL causes undefined *** behavior). @@ -347,7 +347,7 @@ xh_i_FreeEntry(pXHashEntry entry, void* arg) *** *** Interestingly, you can write this code in one line like this: *** ((void (*)(pXHashEntry, void*))((void**)arg)[0])(entry, ((void**)arg)[1]); - *** But I value code readability, so fortunately, I can't be THAT cleaver... + *** But I value code readability, so fortunately, I can't be THAT clever... ***/ void** args = (void**)arg; void (*free_fn)(pXHashEntry, void*) = args[0]; diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index 3e4b7541a..75f4c32bd 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -92,7 +92,7 @@ This function frees the block of memory. ## nmStats() ```c -void nmStats(void); +void nmStats(); ``` Prints statistics about the memory manager, for debugging and optimizing. @@ -118,7 +118,7 @@ This function is usually called in a module's `Initialize()` function on each of ## nmDebug() ```c -void nmDebug(void); +void nmDebug(); ``` Prints a listing of block allocation counts, giving (by size): - The number of blocks allocated but not yet freed. @@ -129,7 +129,7 @@ Prints a listing of block allocation counts, giving (by size): ## nmDeltas() ```c -void nmDeltas(void); +void nmDeltas(); ``` Prints a listing of all blocks whose allocation count has changed, and by how much, since the last `nmDeltas()` call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 1aa11413b..ae812ab7e 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -259,7 +259,7 @@ Each OS Driver will have an abbreviation prefix, such as `qy` for the query driv This document uses `xxx` to refer to an unspecified abbreviation prefix. -- 📖 **Note**: Once an abbreviation prefix has been selected, the driver author should add it to the [Prefixes.md](Prefixes.md) file. It is advised never to pick a prefix longer than 7 characters, as doing so will this file's table style. +- 📖 **Note**: Once an abbreviation prefix has been selected, the driver author should add it to the [Prefixes.md](Prefixes.md) file. ### Internal Functions diff --git a/centrallix/expression/exp_compiler.c b/centrallix/expression/exp_compiler.c index 84753e5b8..afab4adb0 100644 --- a/centrallix/expression/exp_compiler.c +++ b/centrallix/expression/exp_compiler.c @@ -1072,7 +1072,8 @@ expBindExpression(pExpression exp, pParamObjects objlist, int flags) break; } } - cm |= EXPR_MASK_EXTREF; + if (exp->ObjID == -1) + cm |= EXPR_MASK_EXTREF; } else if (exp->ObjID == EXPR_OBJID_CURRENT) cm |= (1<<(objlist->CurrentID)); else if (exp->ObjID == EXPR_OBJID_PARENT) cm |= (1<<(objlist->ParentID)); diff --git a/centrallix/expression/exp_evaluate.c b/centrallix/expression/exp_evaluate.c index 2413a0d00..2fba38b1a 100644 --- a/centrallix/expression/exp_evaluate.c +++ b/centrallix/expression/exp_evaluate.c @@ -1466,7 +1466,7 @@ expEvalProperty(pExpression tree, pParamObjects objlist) case DATA_T_INTVEC: v = getfn(obj, tree->Name, DATA_T_INTVEC, &vptr); if (v != 0) break; - memcpy(&(tree->Types.StrVec), vptr, sizeof(IntVec)); + memcpy(&(tree->Types.IntVec), vptr, sizeof(IntVec)); break; /*** FIX: Shallow copy violates contract, but objdrv_cluster.c is @@ -1476,7 +1476,7 @@ expEvalProperty(pExpression tree, pParamObjects objlist) case DATA_T_STRINGVEC: v = getfn(obj, tree->Name, DATA_T_STRINGVEC, &vptr); if (v != 0) break; - memcpy(&(tree->Types.IntVec), vptr, sizeof(StringVec)); + memcpy(&(tree->Types.StrVec), vptr, sizeof(StringVec)); break; case DATA_T_MONEY: diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 6e5cb2245..0a12898fd 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -329,7 +329,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e if (arg_expect->Flags & EXP_ARG_NON_EMPTY && int_vec->nIntegers == 0) { mssError(1, "EXP", - "%s(...): Expects IntVec to contain strings, but got [].", + "%s(...): Expects IntVec to contain integers, but got [].", fn_name ); return -1; @@ -346,10 +346,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e *** @param arg_expects A pointer to an array of ArgExpect structs, each *** representing expectations for a single argument, in the order they *** are passed to the function. - *** @param num_args The number of arguments to expect to be passed to the - *** function (and the length of arg_expects). *** @param tree The tree containing the actual arguments passed. - *** @param obj_list The object list scope which was passed to the function. *** @returns 0 if verification passes, or *** -1 if an error occurs or arguments are incorrect. *** @@ -1737,7 +1734,7 @@ exp_fn_trim(pExpression tree) /** Temporarily override the arg0 str pointer with the result from ltrim(). **/ pExpression arg0 = tree->Children.Items[0]; - char* arg1_str = arg0->String; + char* arg0_str = arg0->String; arg0->String = tree->String; tree->Alloc = 0; @@ -1745,7 +1742,7 @@ exp_fn_trim(pExpression tree) exp_fn_rtrim(tree); /** Restore the arg0 tree. **/ - arg0->String = arg1_str; + arg0->String = arg0_str; return 0; } @@ -2887,6 +2884,13 @@ int exp_fn_truncate(pExpression tree, pParamObjects objlist, pExpression i0, pEx /*** constrain(value, min, max) ***/ int exp_fn_constrain(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { + /** Verify parameters. **/ + if (i0 == NULL || i1 == NULL || i2 == NULL) + { + mssError(1, "EXP", "constrain() expects three parameters."); + return -1; + } + /** Skip null value. **/ tree->DataType = i0->DataType; if ((i0->Flags & EXPR_F_NULL)) @@ -2894,20 +2898,12 @@ int exp_fn_constrain(pExpression tree, pParamObjects objlist, pExpression i0, pE tree->Flags |= EXPR_F_NULL; return 0; } - - /** Verify parameters. **/ - if (i0 == NULL || i1 == NULL || i2 == NULL) - { - mssError(1, "EXP", "constrain() expects three parameters."); - return -1; - } if (i0->DataType != DATA_T_INTEGER && i0->DataType != DATA_T_DOUBLE && i0->DataType != DATA_T_MONEY) { mssError(1, "EXP", "constrain() expects three numeric parameters: %s is not numeric.", objTypeToStr(i0->DataType) ); - if (i0->DataType == DATA_T_STRING) printf("Value: '%s'\n", i0->String); return -1; } if (i0->DataType != i1->DataType || i1->DataType != i2->DataType) @@ -4556,6 +4552,8 @@ exp_fn_metaphone(pExpression tree) { int ret = -1; bool free_strs = true; + char* primary = NULL; + char* secondary = NULL; /** Verify function schema. **/ if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ @@ -4567,10 +4565,6 @@ exp_fn_metaphone(pExpression tree) goto end_free; } - /** Allocate space to store metaphone pointers. **/ - char* primary = NULL; - char* secondary = NULL; - /** Extract string param. **/ pExpression maybe_str = checkPtr(tree->Children.Items[0]); if (UNLIKELY(maybe_str == NULL)) goto end_free; @@ -4704,7 +4698,7 @@ exp_fn_compare(pExpression tree) } err: - mssError(0, "EXP", "%s(): Failed to compute Levenshtein edit distance.", tree->Name); + mssError(0, "EXP", "%s(): Failed to compute the similarity.", tree->Name); return -1; } @@ -4741,7 +4735,7 @@ exp_fn_levenshtein(pExpression tree) const int edit_dist = checkNeg(ca_edit_dist(str1, str2, 0lu, 0lu)); if (UNLIKELY(edit_dist < 0)) { - mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); + mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.", tree->Name, str1, str2); return -1; } diff --git a/centrallix/multiquery/multiquery.c b/centrallix/multiquery/multiquery.c index 42dc09934..8cb722e72 100644 --- a/centrallix/multiquery/multiquery.c +++ b/centrallix/multiquery/multiquery.c @@ -1198,7 +1198,7 @@ mq_internal_ParseSelectItem(pQueryStructure item_qs, pLxSession lxs) /** Treat "log" as a keyword to allow the log function to be handled properly. **/ if (strcmp(ptr, "log") == 0) t = MLX_TOK_KEYWORD; else break; - }; + } /** Copy the token string into item_qs->RawData. **/ if (t == MLX_TOK_STRING) diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index 05c7c4c72..23b8b74f1 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -206,9 +206,9 @@ objTypeToStr(const int type) if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) { /** Invalid type. **/ - mssError(1, "Cluster", "Invalid type %d.\n", type); + mssError(1, "OBJ", "Invalid type %d.", type); - return "invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ + return "invalid"; /* Shall not parse to a valid type in objTypeFromStr(). */ } return obj_type_names[type]; diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 1ac216bd4..2d7b900ab 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3646,7 +3646,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Guard possible segfault. **/ if (UNLIKELY(attr_name == NULL)) { - fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); + fprintf(stderr, "Warning: Call to clusterGetAttrValue() with NULL attribute name.\n"); goto err; } diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index 2d4f4b4fb..f1d1e1ec0 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -914,11 +914,11 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) break; case DATA_T_STRINGVEC: - fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.StringVec, DATA_F_QUOTED & DATA_F_BRACKETS)); + fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.StringVec, DATA_F_QUOTED | DATA_F_BRACKETS)); break; case DATA_T_INTVEC: - fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.IntVec, DATA_F_QUOTED & DATA_F_BRACKETS)); + fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.IntVec, DATA_F_QUOTED | DATA_F_BRACKETS)); break; case DATA_T_STRING: From 1a8780a9f6e1dde3f2458e870c0e1c49cd7b7529 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 15:41:04 -0600 Subject: [PATCH 184/361] Fix a ca_most_similar() guard case blocking a correct value. --- centrallix-lib/src/clusters.c | 1 - 1 file changed, 1 deletion(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 8460f948e..d86425ce1 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -995,7 +995,6 @@ ca_most_similar( if (target == NULL || data == NULL || similarity == NULL - || num_data == 0u || (threshold < 0.0 || 1.0 < threshold) || isnan(threshold) ) return NULL; From 66bed2966d4f902831740e91f01506cd1ffe56f9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 15:41:19 -0600 Subject: [PATCH 185/361] Improve error handling in exp_fn_trim(). --- centrallix/expression/exp_functions.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 0a12898fd..4149cec8b 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1730,7 +1730,7 @@ int exp_fn_trim(pExpression tree) { /** Left trim the expression. **/ - exp_fn_ltrim(tree); + if (exp_fn_ltrim(tree) != 0) return -1; /** Temporarily override the arg0 str pointer with the result from ltrim(). **/ pExpression arg0 = tree->Children.Items[0]; @@ -1739,12 +1739,12 @@ exp_fn_trim(pExpression tree) tree->Alloc = 0; /** Right trim the expression, which will use the overridden string above. **/ - exp_fn_rtrim(tree); + const int rval = exp_fn_rtrim(tree); /** Restore the arg0 tree. **/ arg0->String = arg0_str; - return 0; + return rval; } From 3814676f7f4265181e635ecbf29783549c2c61b8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 15:51:05 -0600 Subject: [PATCH 186/361] Add detail to mlxStringVal() comment. --- centrallix-sysdoc/OSDriver_Authoring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index ae812ab7e..74d9563ca 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -1391,7 +1391,7 @@ Returns the type of the next token in the token stream. Valid token types are: ```c char* mlxStringVal(pLxSession this, int* alloc); ``` -This function gets the string value of the current token. If `alloc` is `NULL`, an internal buffer is returned (which the caller _should not free_). This may also cause an error if such a buffer is not available. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always_ allocate memory for the string, whether long or short. +This function gets the string value of the current token. If `alloc` is `NULL`, an internal buffer is returned, which the caller _should not free_. Fails and returns null if the A token longer than 255 characters (`MLX_STRVAL`) does not fit that buffer, so the call fails and returns `NULL`. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always_ allocate memory for the string, whether long or short. This routine works no matter what the token type, and returns a string representation of the token if not `MLX_TOK_STRING`. From ec7bb6df964cd21a7455ab3a0cc623797f8b57b0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 16:01:07 -0600 Subject: [PATCH 187/361] Add explicit void marker to functions that don't take parameters. --- centrallix-lib/include/newmalloc.h | 14 +++++++------- centrallix-lib/src/newmalloc.c | 12 ++++++------ centrallix-sysdoc/Libraries/newmalloc.md | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/centrallix-lib/include/newmalloc.h b/centrallix-lib/include/newmalloc.h index 6022e72e3..210cd9195 100644 --- a/centrallix-lib/include/newmalloc.h +++ b/centrallix-lib/include/newmalloc.h @@ -65,22 +65,22 @@ extern int blksiz[MAX_BLOCKS]; #endif extern pOverlay lists[MAX_SIZE+1]; -void nmInitialize(); +void nmInitialize(void); void nmSetErrFunction(int (*error_fn)()); -void nmClear(); -void nmCheckAll(); // checks for buffer overflows +void nmClear(void); +void nmCheckAll(void); // checks for buffer overflows void* nmMalloc(int size); void nmFree(void* ptr,int size); -void nmStats(); +void nmStats(void); void nmRegister(int size,char* name); -void nmDebug(); -void nmDeltas(); +void nmDebug(void); +void nmDeltas(void); void* nmSysMalloc(int size); void nmSysFree(void* ptr); void* nmSysRealloc(void* ptr, int newsize); char* nmSysStrdup(const char* ptr); -void nmEnableTagging(); +void nmEnableTagging(void); void nmRegisterTagID(int tag_id, char* name); void nmSetTag(void* ptr, int tag_id, void* tag); void* nmGetTag(void* ptr, int tag_id); diff --git a/centrallix-lib/src/newmalloc.c b/centrallix-lib/src/newmalloc.c index 32c02b85d..7b231fb45 100644 --- a/centrallix-lib/src/newmalloc.c +++ b/centrallix-lib/src/newmalloc.c @@ -102,7 +102,7 @@ typedef struct _RB pRegisteredBlockType blknames[MAX_SIZE+1]; void -nmInitialize() +nmInitialize(void) { int i; @@ -150,7 +150,7 @@ nmCheckItem(pMemStruct mem) #endif void -nmCheckAll() +nmCheckAll(void) { #ifdef BUFFER_OVERFLOW_CHECKING pMemStruct mem; @@ -240,7 +240,7 @@ nmSetErrFunction(int (*error_fn)()) } void -nmClear() +nmClear(void) { int i; pOverlay ov,del; @@ -425,7 +425,7 @@ nmFree(void* ptr, int size) void -nmStats() +nmStats(void) { if (!isinit) nmInitialize(); @@ -463,7 +463,7 @@ nmRegister(int size,char* name) void -nmDebug() +nmDebug(void) { int i; pRegisteredBlockType blk; @@ -497,7 +497,7 @@ nmDebug() void -nmDeltas() +nmDeltas(void) { int i, total; pRegisteredBlockType blk; diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index 75f4c32bd..3e4b7541a 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -92,7 +92,7 @@ This function frees the block of memory. ## nmStats() ```c -void nmStats(); +void nmStats(void); ``` Prints statistics about the memory manager, for debugging and optimizing. @@ -118,7 +118,7 @@ This function is usually called in a module's `Initialize()` function on each of ## nmDebug() ```c -void nmDebug(); +void nmDebug(void); ``` Prints a listing of block allocation counts, giving (by size): - The number of blocks allocated but not yet freed. @@ -129,7 +129,7 @@ Prints a listing of block allocation counts, giving (by size): ## nmDeltas() ```c -void nmDeltas(); +void nmDeltas(void); ``` Prints a listing of all blocks whose allocation count has changed, and by how much, since the last `nmDeltas()` call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. From beec7f3275b2af89cbe617cfc0ce64d46089afc6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 16:07:10 -0600 Subject: [PATCH 188/361] Rename checkNeg() to checkPos(). --- centrallix-lib/src/clusters.c | 4 +-- centrallix/expression/exp_functions.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 40 +++++++++++++-------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index d86425ce1..a71b8baf9 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -673,7 +673,7 @@ ca_lev_compare(void* str1, void* str2) if (len1 == 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ - const int edit_dist = checkNeg(ca_edit_dist((const char*)str1, (const char*)str2, len1, len2)); + const int edit_dist = checkPos(ca_edit_dist((const char*)str1, (const char*)str2, len1, len2)); if (edit_dist < 0) return NAN; /** Normalize edit distance into a similarity measure. **/ @@ -1080,7 +1080,7 @@ ca_sliding_search( pair->i = i; pair->j = j; pair->similarity = sim; - if (checkNeg(xaAddItem(pairs, (void*)pair)) < 0) goto err_free; + if (checkPos(xaAddItem(pairs, (void*)pair)) < 0) goto err_free; } } } diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 4149cec8b..93ed63c04 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4732,7 +4732,7 @@ exp_fn_levenshtein(pExpression tree) /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ - const int edit_dist = checkNeg(ca_edit_dist(str1, str2, 0lu, 0lu)); + const int edit_dist = checkPos(ca_edit_dist(str1, str2, 0lu, 0lu)); if (UNLIKELY(edit_dist < 0)) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.", tree->Name, str1, str2); diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 2d7b900ab..363ff857e 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1175,7 +1175,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source char* name = sub_inf->Name; /** Handle various struct types. **/ - const int struct_type = checkNeg(stStructType(sub_inf)); + const int struct_type = checkPos(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1220,7 +1220,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source pClusterData sub_cluster = ci_ParseClusterData(sub_inf, param_list, source_data); if (sub_cluster == NULL) goto err_free; sub_cluster->Parent = cluster_data; - if (checkNeg(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; + if (checkPos(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; break; } @@ -1427,7 +1427,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) if (UNLIKELY(name == NULL)) goto err_free; /** Handle various struct types. **/ - const int struct_type = checkNeg(stStructType(sub_inf)); + const int struct_type = checkPos(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1597,7 +1597,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) char* name = sub_inf->Name; /** Handle various struct types. **/ - const int struct_type = checkNeg(stStructType(sub_inf)); + const int struct_type = checkPos(stStructType(sub_inf)); switch (struct_type) { case ST_T_ATTRIB: @@ -1627,17 +1627,17 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (UNLIKELY(group_type == NULL)) goto err_free; if (strcmp(group_type, "cluster/parameter") == 0) { - if (checkNeg(xaAddItem(¶m_infs, sub_inf) < 0)) + if (checkPos(xaAddItem(¶m_infs, sub_inf) < 0)) goto err_free; } else if (strcmp(group_type, "cluster/cluster") == 0) { - if (checkNeg(xaAddItem(&cluster_infs, sub_inf) < 0)) + if (checkPos(xaAddItem(&cluster_infs, sub_inf) < 0)) goto err_free; } else if (strcmp(group_type, "cluster/search") == 0) { - if (checkNeg(xaAddItem(&search_infs, sub_inf) < 0)) + if (checkPos(xaAddItem(&search_infs, sub_inf) < 0)) goto err_free; } else @@ -2409,9 +2409,9 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) if (key_dup == NULL) goto end_free; char* data_dup = checkPtr(nmSysStrdup(data)); if (data_dup == NULL) goto end_free; - if (checkNeg(xaAddItem(&key_xarray, (void*)key_dup) < 0)) goto end_free; - if (checkNeg(xaAddItem(&data_xarray, (void*)data_dup) < 0)) goto end_free; - if (checkNeg(xaAddItem(&vector_xarray, (void*)vector) < 0)) goto end_free; + if (checkPos(xaAddItem(&key_xarray, (void*)key_dup) < 0)) goto end_free; + if (checkPos(xaAddItem(&data_xarray, (void*)data_dup) < 0)) goto end_free; + if (checkPos(xaAddItem(&vector_xarray, (void*)vector) < 0)) goto end_free; /** Clean up. **/ check(objClose(entry)); /* Failure ignored. */ @@ -2646,7 +2646,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) /** Iterate through each label and add the index of the specified cluster to the xArray. **/ for (unsigned long long i = 0llu; i < source_data->nDatas; i++) - if (checkNeg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; + if (checkPos(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; nmSysFree(labels); /* Free unused data. */ /** Store the indices for each cluster and free the temporary xArray. **/ @@ -2892,7 +2892,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) const pPair pair = (pPair)cluster_pairs->Items[i]; pair->i = cluster->Indexes[pair->i]; pair->j = cluster->Indexes[pair->j]; - if (checkNeg(xaAddItem(pairs, pair)) < 0) goto err_free; + if (checkPos(xaAddItem(pairs, pair)) < 0) goto err_free; } check(xaFree(cluster_pairs)); /* Failure ignored. */ } @@ -4084,11 +4084,11 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "internal_type") == 0) { check(xaInit(&(hints->EnumList), 5)); /* Failure ignored. */ - checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ - checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ - checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ - checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ - checkNeg(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("search/entry")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("search/entry")))); /* Failure ignored. */ hints->Length = 16; hints->VisualLength = 16; hints->FriendlyName = checkPtr(nmSysStrdup("Internal Type")); /* Failure ignored. */ @@ -4185,7 +4185,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) - checkNeg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -4211,7 +4211,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) - checkNeg(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; @@ -4958,7 +4958,7 @@ clusterInitialize(void) /** Setup the structure. **/ if (checkPtr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; if (check(xaInit(&drv->RootContentTypes, 1)) != 0) goto err_free; - if (checkNeg(xaAddItem(&drv->RootContentTypes, "system/cluster")) < 0) goto err_free; + if (checkPos(xaAddItem(&drv->RootContentTypes, "system/cluster")) < 0) goto err_free; drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ From 212ed3fe808ee17fca35f992ec997e13e940efa5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:02:16 -0600 Subject: [PATCH 189/361] Fix EXPECT_STR_EQL_N() improperly logging null values. --- centrallix-lib/tests/test_utils.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 2d89a3a09..1be4a51f7 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -114,8 +114,14 @@ const int success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ if (!success)\ { \ - char _tmp1[_len + 1]; STR_COPY_REPLACE_NULLS(_tmp1, _str1, _len); \ - char _tmp2[_len + 1]; STR_COPY_REPLACE_NULLS(_tmp2, _str2, _len); \ + char _tmp1[_len + 1]; \ + if (str1 == NULL) strcpy(_tmp1, "(null)"); \ + else STR_COPY_REPLACE_NULLS(_tmp1, _str1, _len); \ + \ + char _tmp2[_len + 1]; \ + if (str2 == NULL) strcpy(_tmp2, "(null)"); \ + else STR_COPY_REPLACE_NULLS(_tmp2, _str2, _len); \ + \ fprintf(stderr, \ " > Expected %s (\"%s\") to equal %s (\"%s\") where '_' is a null-terminator, at %s:%d\n", \ #str1, _tmp1, #str2, _tmp2, __FILE__, __LINE__ \ From c75c69fe6a21db5237a513dab23ba1f4c61a88b2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:09:22 -0600 Subject: [PATCH 190/361] Fix a test that used the wrong macro name. --- centrallix-lib/tests/test_util_02.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index 38e2d5f97..3ccdd8de9 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -42,8 +42,8 @@ static bool doTest(void) /** Detect if metric or CS units are intended. **/ bool cs = true; - #ifdef UTIL_USE_METRIC - if (UTIL_USE_METRIC) cs = false; + #ifdef USE_METRIC + if (USE_METRIC) cs = false; #endif /** Allocate space for the string buffer. **/ From df9b8647ca7eee5d7af6ac55364501ccd1c30607 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:21:40 -0600 Subject: [PATCH 191/361] Fix exp_fn_metaphone() clobbering it's return value variable. --- centrallix/expression/exp_functions.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 93ed63c04..c697d4f65 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4550,7 +4550,7 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress int exp_fn_metaphone(pExpression tree) { - int ret = -1; + int ret = -1, tmp; bool free_strs = true; char* primary = NULL; char* secondary = NULL; @@ -4587,10 +4587,11 @@ exp_fn_metaphone(pExpression tree) } /** Compute Double Metaphone. **/ - ret = meta_double_metaphone(str, &primary, &secondary); - if (UNLIKELY(ret != 0)) + tmp = meta_double_metaphone(str, &primary, &secondary); + if (UNLIKELY(tmp != 0)) { - mssError(1, "EXP", "Double metaphone computation failed (error code %d).", ret); + mssError(1, "EXP", "Double metaphone computation failed (error code %d).", tmp); + ret = tmp; goto end_free; } From c39de70dccad35811a1bde6f2646af617ca3b429 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:26:11 -0600 Subject: [PATCH 192/361] Fix typos in xarray.md. --- centrallix-sysdoc/Libraries/xarray.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 110ded79e..699eff9aa 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -25,7 +25,7 @@ - + @@ -61,7 +61,7 @@ ## Introduction -The xarray (xa) module is intended to manage sized growable arrays, similar to a light-weight arraylist implementation. It includes the `XArray`, which has the following fields: +The XArray (xa) module is intended to manage sized growable arrays, similar to a light-weight arraylist implementation. It includes the `XArray` struct, which has the following fields: - `nItems : int`: The number of items in the array. - `nAlloc : int`: Internal variable to store the size of the allocated memory. - `Items : void**`: The allocated array of items. @@ -89,7 +89,7 @@ Frees a `pXArray` allocated using [`xaNew`](#xanew), returning 0 if successful o ```c int xaInit(pXArray this, int init_size); ``` -This function initializes an allocated (but uninitialized) xarray. It makes room for `init_size` items initially, but this is only an optimization. A typical value for `init_size` is 16. Remember to [`xaDeInit`](#xadeinit) this xarray, do **not** [`xaFree`](#xafree) it. +This function initializes an allocated (but uninitialized) XArray. It makes room for `init_size` items initially, but this is only an optimization. A typical value for `init_size` is 16. Remember to [`xaDeInit`](#xadeinit) this XArray, do **not** [`xaFree`](#xafree) it. This function returns 0 on success, or -1 if an error occurs. @@ -98,7 +98,7 @@ This function returns 0 on success, or -1 if an error occurs. ```c int xaDeInit(pXArray this); ``` -This function de-initializes an xarray, but does not free the XArray structure itself. This is useful if the structure is a local variable allocated using [`xaInit()`](#xainit). +This function de-initializes an XArray, but does not free the XArray structure itself. This is useful if the structure is a local variable initialized using [`xaInit()`](#xainit). This function returns 0 on success, or -1 if an error occurs. @@ -107,7 +107,7 @@ For example: XArray arr; if (xaInit(&arr, 16) != 0) goto handle_error; -/** Use the xarray. **/ +/** Use the XArray. **/ if (arr.nAlloc != 0 && xaDeInit(&arr) != 0) goto handle_error; arr.nAlloc = 0; @@ -118,7 +118,7 @@ arr.nAlloc = 0; ```c int xaAddItem(pXArray this, void* item); ``` -This function adds an item to the end of the xarray. The item is assumed to be a `void*`, but this function will _not_ follow pointers stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). +This function adds an item to the end of the XArray. The item is assumed to be a `void*`, but this function will _not_ follow pointers stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). This function returns 0 on success, or -1 if an error occurs. @@ -127,7 +127,7 @@ This function returns 0 on success, or -1 if an error occurs. ```c int xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen); ``` -This function adds an item to a sorted xarray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers are stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). +This function adds an item to a sorted XArray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers are stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). ## xaAddItemSortedInt32() @@ -141,7 +141,7 @@ int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset) ```c void* xaGetItem(pXArray this, int index) ``` -This function returns an item given a specific index into the xarray, or `NULL` if the index is out of bounds. If the bounds check needs to be omitted for performance and the caller can otherwise verify that no out of bounds read is possible (e.g. because they are iterating from 0 to `xarray->nItems`), the caller should access `xarray->Items` directly. Either way, the result may need to be typecasted or stored in a variable of a specific type for it to be useable, and error checking for `NULL` values should be used. +This function returns an item given a specific index into the XArray, or `NULL` if the index is out of bounds. If the bounds check needs to be omitted for performance and the caller can otherwise verify that no out of bounds read is possible (e.g. because they are iterating from 0 to `XArray->nItems`), the caller should access `XArray->Items` directly. Either way, the result may need to be typecasted or stored in a variable of a specific type for it to be useable, and error checking for `NULL` values should be used. ## xaFindItem() @@ -179,14 +179,14 @@ This function works the same as [`xaFindItem()`](#xafinditem), however it iterat ```c int xaRemoveItem(pXArray this, int index) ``` -This function removes an item from the xarray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. +This function removes an item from the XArray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. ## xaClear() ```c int xaClear(pXArray this, int (*free_fn)(), void* free_arg); ``` -This function removes all elements from the xarray, leaving it empty. `free_fn()` is invoked on each element with a `void*` to the element to be freed as the first argument and `free_arg` as the second argument (the return value of `free_fn()` is always ignored). This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. +This function removes all elements from the XArray, leaving it empty. `free_fn()` is invoked on each element with a `void*` to the element to be freed as the first argument and `free_arg` as the second argument (the return value of `free_fn()` is always ignored). This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. ## xaClearR() @@ -200,7 +200,7 @@ This function works the same as [`xaClear()`](#xaclear), except that it is sligh ```c int xaCount(pXArray this); ``` -This function returns the number of items in the xarray, or -1 on error. It is equivalent to accessing `xarray->nItems` (although the latter expression will not return an error). +This function returns the number of items in the XArray, or -1 on error. It is equivalent to accessing `XArray->nItems` (although the latter expression will not return an error). ## xaInsertBefore() From 7896a30302fc9dbccc58c6abf45ab44eff82eddc Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:36:01 -0600 Subject: [PATCH 193/361] Fix typos and outdated info in mtsession.md. --- centrallix-sysdoc/Libraries/mtsession.md | 25 ++++-------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md index ca34b2cba..50c8d66c5 100644 --- a/centrallix-sysdoc/Libraries/mtsession.md +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -98,7 +98,7 @@ Returns the value of a session parameter of the provided name (`paramname`), or ## mssError() ```c -int mssError(int clr, char* module, char* message, ...); +void mssError(int clr, char* module, char* message, ...); ``` Formats and caches an error message for return to the user. This function returns 0 if successful, or -1 if an error occurred. @@ -111,30 +111,13 @@ Formats and caches an error message for return to the user. This function retur Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch the errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. -The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ as an expression being compiled? etc.). However, it is far easier to give too little information that too much, so it can often be best to air on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. +The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ as an expression being compiled? etc.). However, it is far easier to give too little information that too much, so it can often be best to err on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. - 📖 **Note**: The `mssError()` routines do not cause the calling function to return or exit. The function must still clean up after itself and return an appropriate value (such as `-1` or `NULL`) to indicate failure. -- ⚠️ **Warning**: Even if `-1` is returned, the error message may still be sent to the user in some scenarios. This is not guaranteed, though. - -- ⚠️ **Warning**: `%d` and `%s` are the ONLY supported format specifier for this function. **DO NOT** use any other format specifiers like `%lf`, `%u`, `%lu`, `%c` etc. **DO NOT** attempt to include `%%` for a percent symbol in your error message, as misplaced percent symbols often break this function. If you wish to use these features of printf, it is recommended to print the error message to a buffer and pass that buffer to `mssError()`, as follows: - ```c - char err_buf[256]; - snprintf(err_buf, sizeof(err_buf), - "Incorrect values detected: %u, %g (%lf), '%c'", - unsigned_int_value, double_value, char_value - ); - if (mssError(1, "EXMPL", "%s", err_buf) != 0) - { - fprintf(stderr, "ERROR! %s\n", err_buf); - } - return -1; - ``` - - ## mssErrorErrno() ```c -int mssErrorErrno(int clr, char* module, char* message, ...); +void mssErrorErrno(int clr, char* module, char* message, ...); ``` -This function works the same way as [`mssError`](#msserror), except checks the current value of `errno` and includes a description of any error stored there. This is useful if a system call or other library function is responsible for this error. +This function works the same way as [`mssError`](#msserror), except checks the current value of `errno` and postpends that error message to the end of the provided error. This is useful if a system call or other library function is responsible for this error. From 437b511ab220d4aab843b0176a45909d98cce10c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:37:55 -0600 Subject: [PATCH 194/361] Add docs for xaAddItemSortedInt32(). --- centrallix-sysdoc/Libraries/xarray.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 699eff9aa..ecdadb309 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -134,7 +134,9 @@ This function adds an item to a sorted XArray while maintaining the sorted prope ```c int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset) ``` - +This function works the same as [`xaAddItemSorted()`](#xaadditemsorted), except that the sort key is a signed 32-bit integer (an `int` on supported platforms) located `keyoffset` bytes into the structure, rather than a byte string of a given length. This function _will_ follow the pointers stored in the array, so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). Because the comparison is numeric rather than bytewise, the ordering is correct for negative values and is unaffected by the host byte order. + +This function returns the index of the last item in the array (not the index the item was inserted at), or -1 if an error occurs. ## xaGetItem() From e595d6f96ec6458bf677917080dd036901c9a534 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:39:11 -0600 Subject: [PATCH 195/361] Fix more typos. --- centrallix-sysdoc/Libraries/xarray.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index ecdadb309..561aad3b9 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -216,4 +216,4 @@ This function inserts an item before the specified index, moving all following i ```c int xaInsertAfter(pXArray this, int index, void* item) ``` -This function inserts an item after the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. +This function inserts an item after the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index of the inserted value on success, or -1 if an error occurs. From 2f3ef2ecd586bddb961a85e8d8532dcf1ddab33a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 17:58:31 -0600 Subject: [PATCH 196/361] Rename internal functions. --- centrallix-lib/src/clusters.c | 46 +- centrallix-lib/src/xhash.c | 4 +- centrallix-sysdoc/OSDriver_Authoring.md | 14 +- centrallix/expression/exp_functions.c | 56 +-- centrallix/osdrivers/objdrv_cluster.c | 322 +++++++------ centrallix/utility/double_metaphone.c | 570 ++++++++++++------------ 6 files changed, 505 insertions(+), 507 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index a71b8baf9..66713cc73 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -60,7 +60,7 @@ *** @returns The resulting hash. ***/ static unsigned int -ca_i_hash_char_pair(const unsigned char c1, const unsigned char c2) +ca_i_hashCharPair(const unsigned char c1, const unsigned char c2) { const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); @@ -74,7 +74,7 @@ ca_i_hash_char_pair(const unsigned char c1, const unsigned char c2) *** @param c1 The first character in the character pair. *** @param c2 The second character in the character pair. *** @param hash The hash for the two characters, calculated by calling the - *** ca_i_hash_char_pair() function (above). + *** ca_i_hashCharPair() function (above). **/ typedef struct { @@ -93,7 +93,7 @@ typedef struct *** 0 if p1 and p2 have identical hashes. ***/ static int -ca_i_char_pair_cmp(const void *p1, const void *p2) +ca_i_charPairCmp(const void *p1, const void *p2) { const CharPair *a = p1, *b = p2; return a->hash - b->hash; @@ -196,7 +196,7 @@ ca_build_vector(const char* str) /** Hash the character pair into an index (dimension). **/ /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ - char_pairs[i].hash = ca_i_hash_char_pair(chars[i], chars[i + 1]); + char_pairs[i].hash = ca_i_hashCharPair(chars[i], chars[i + 1]); } /** Free unused memory. **/ @@ -205,7 +205,7 @@ ca_build_vector(const char* str) /** Sort char_pairs by hash value. **/ - qsort(char_pairs, num_pairs, sizeof(CharPair), ca_i_char_pair_cmp); + qsort(char_pairs, num_pairs, sizeof(CharPair), ca_i_charPairCmp); /** Allocate space for the sparse vector. **/ @@ -286,7 +286,7 @@ ca_free_vector(pVector sparse_vector) *** @param token_value The location to save the `token_value`. ***/ static void -ca_parse_vector_token(const int token, unsigned int* dims_consumed, unsigned int* token_value) +ca_i_parseVectorToken(const int token, unsigned int* dims_consumed, unsigned int* token_value) { if (token < 0) { @@ -325,7 +325,7 @@ ca_sparse_len(const pVector vector) { /** Parse the vector token. **/ unsigned int dims_consumed = 0u, val = 0u; - ca_parse_vector_token(vector[i++], &dims_consumed, &val); + ca_i_parseVectorToken(vector[i++], &dims_consumed, &val); /** Move ahead the requested number of dimensions. **/ dim += dims_consumed; @@ -363,7 +363,7 @@ ca_print_vector(const pVector vector) *** @returns The computed magnitude. ***/ static double -ca_magnitude_sparse(const pVector vector) +ca_i_magnitudeSparse(const pVector vector) { unsigned int magnitude = 0u; @@ -371,7 +371,7 @@ ca_magnitude_sparse(const pVector vector) { /** Parse the vector token. **/ unsigned int dims_consumed = 0u, val = 0u; - ca_parse_vector_token(vector[i++], &dims_consumed, &val); + ca_i_parseVectorToken(vector[i++], &dims_consumed, &val); /** Increase magnitude. **/ magnitude += val * val; @@ -389,7 +389,7 @@ ca_magnitude_sparse(const pVector vector) *** @returns The computed magnitude. ***/ static double -ca_magnitude_dense(const pCentroid centroid) +ca_i_magnitudeDense(const pCentroid centroid) { double magnitude = 0.0; @@ -409,7 +409,7 @@ ca_magnitude_dense(const pCentroid centroid) *** 0 indicates completely different. ***/ static double -ca_sparse_similarity(const pVector v1, const pVector v2) +ca_i_sparseSimilarity(const pVector v1, const pVector v2) { /** Calculate dot product. **/ unsigned int vec1_remaining = 0u, vec2_remaining = 0u; @@ -417,8 +417,8 @@ ca_sparse_similarity(const pVector v1, const pVector v2) while (dim < CA_NUM_DIMS) { unsigned int val1 = 0u, val2 = 0u; - if (vec1_remaining == 0u) ca_parse_vector_token(v1[i1++], &vec1_remaining, &val1); - if (vec2_remaining == 0u) ca_parse_vector_token(v2[i2++], &vec2_remaining, &val2); + if (vec1_remaining == 0u) ca_i_parseVectorToken(v1[i1++], &vec1_remaining, &val1); + if (vec2_remaining == 0u) ca_i_parseVectorToken(v2[i2++], &vec2_remaining, &val2); /*** Accumulate the dot_product. If either vector is 0 here, *** the total is 0 and this statement does nothing. @@ -436,7 +436,7 @@ ca_sparse_similarity(const pVector v1, const pVector v2) if (dot_product == 0u) return 0.0; /** Return the difference score. **/ - return (double)dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_sparse(v2)); + return (double)dot_product / (ca_i_magnitudeSparse(v1) * ca_i_magnitudeSparse(v2)); } /*** Calculate the difference between two sparsely allocated vectors by @@ -448,7 +448,7 @@ ca_sparse_similarity(const pVector v1, const pVector v2) *** 1 indicates completely different and *** 0 indicates identical. ***/ -#define ca_sparse_dif(v1, v2) (1.0 - ca_sparse_similarity(v1, v2)) +#define ca_sparse_dif(v1, v2) (1.0 - ca_i_sparseSimilarity(v1, v2)) /*** Calculate the similarity between a sparsely allocated vector and a densely *** allocated centroid by taking their dot product. @@ -460,7 +460,7 @@ ca_sparse_similarity(const pVector v1, const pVector v2) *** 0 indicates completely different. ***/ static double -ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c1) +ca_i_sparseSimilarityToCentroid(const pVector v1, const pCentroid c1) { double dot_product = 0.0; @@ -468,7 +468,7 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c1) { /** Parse the vector token. **/ unsigned int dims_consumed = 0u, val = 0u; - ca_parse_vector_token(v1[i++], &dims_consumed, &val); + ca_i_parseVectorToken(v1[i++], &dims_consumed, &val); /** Increase dot product (skipped for zero-values). **/ if (val > 0u) dot_product += (double)val * c1[dim]; @@ -478,7 +478,7 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c1) } /** Return the difference score. **/ - return dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_dense(c1)); + return dot_product / (ca_i_magnitudeSparse(v1) * ca_i_magnitudeDense(c1)); } /*** Calculate the difference between a sparsely allocated vector and a densely @@ -490,7 +490,7 @@ ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c1) *** 1 indicates completely different and *** 0 indicates identical. ***/ -#define ca_sparse_dif_to_centroid(v1, c1) (1.0 - ca_sparse_similarity_to_centroid(v1, c1)) +#define ca_sparse_dif_to_centroid(v1, c1) (1.0 - ca_i_sparseSimilarityToCentroid(v1, c1)) /*** Computes Levenshtein distance between two strings. *** @@ -635,7 +635,7 @@ ca_cos_compare(void* v1, void* v2) if (!v1_empty && v2_empty) return 0.0; /** Apply rounding to avoid annoying floating point issues before returning. **/ - return round(ca_sparse_similarity(vec1, vec2) * 1000000.0) / 1000000.0; + return round(ca_i_sparseSimilarity(vec1, vec2) * 1000000.0) / 1000000.0; } /*** Compares two strings using their Levenshtein edit distance to compute a @@ -717,7 +717,7 @@ ca_eql(pVector v1, pVector v2) *** @returns The average cluster size. ***/ static double -ca_i_get_cluster_size( +ca_i_getClusterSize( pVector* vectors, const unsigned int num_vectors, unsigned int* labels, @@ -924,7 +924,7 @@ ca_kmeans( /** Is there enough improvement? **/ if (min_improvement <= -1.0) continue; /** Skip check if it will never end the loop. **/ - const double average_cluster_size = checkDouble(ca_i_get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); + const double average_cluster_size = checkDouble(ca_i_getClusterSize(vectors, num_vectors, labels, centroids, num_clusters)); if (isnan(average_cluster_size)) goto end; const double improvement = old_average_cluster_size - average_cluster_size; if (improvement < min_improvement) break; @@ -935,7 +935,7 @@ ca_kmeans( if (vector_sims != NULL) { for (unsigned int i = 0u; i < num_vectors; i++) - vector_sims[i] = ca_sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); + vector_sims[i] = ca_i_sparseSimilarityToCentroid(vectors[i], centroids[labels[i]]); } /** Success. **/ diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index aad795cd4..ccc17241d 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -337,7 +337,7 @@ xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_a *** @returns 0, success. ****/ static int -xh_i_FreeEntry(pXHashEntry entry, void* arg) +xh_i_freeEntry(pXHashEntry entry, void* arg) { /*** The passed void* actually points to a void* array with 2 elements. *** @@ -378,7 +378,7 @@ xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free /** Free each row. **/ void* args[2] = {free_fn, free_arg}; - const int ret = xhForEach(this, xh_i_FreeEntry, args); + const int ret = xhForEach(this, xh_i_freeEntry, args); /** Mark all rows as empty. **/ for (int i = 0; i < this->nRows; i++) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 74d9563ca..f399f0ead 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -1217,21 +1217,21 @@ int expSetParamFunctions(pParamObjects this, char* name, int (*type_fn)(), int ( This function sets the param accessor functions used to access params on a specific name. Some example function signatures for the `type_fn()`, `get_fn()`, and `set_fn()` are provided below: ```c -static int ci_GetParamType(void* v, char* attr_name); -static int ci_GetParamValue(void* v, char* attr_name, int datatype, pObjData val); -static int ci_SetParamValue(void* v, char* attr_name, int datatype, pObjData val); +static int cluster_i_getParamType(void* v, char* attr_name); +static int cluster_i_getParamValue(void* v, char* attr_name, int datatype, pObjData val); +static int cluster_i_setParamValue(void* v, char* attr_name, int datatype, pObjData val); ``` The functions use the following parameters: - `v : void*` is the object provided in `expAddParamToList()` (or a similar function). - `attr_name : char*` is the string name for the requested attribute. - `datatype : int` is the data type for the requested attribute. -- `val : pObjectData` is either a buffer in which to store the requested data (`ci_GetParamValue()`) or a buffer containing data that will be copied to the parameter `ci_SetParamValue()`. +- `val : pObjectData` is either a buffer in which to store the requested data (`cluster_i_getParamValue()`) or a buffer containing data that will be copied to the parameter `cluster_i_setParamValue()`. The functions return the following values: -- The `ci_GetParamType()` function returns the datatype on success (e.g. `DATA_T_INTEGER`), or -1 if an error occurs. -- The `ci_GetParamValue()` function returns 0 for success, 1 if the attribute is `NULL`, or -1 if an error occurs. -- The `ci_SetParamValue()` function returns 0 for success (even if the value was set to `NULL`), or -1 if an error occurs. +- The `cluster_i_getParamType()` function returns the datatype on success (e.g. `DATA_T_INTEGER`), or -1 if an error occurs. +- The `cluster_i_getParamValue()` function returns 0 for success, 1 if the attribute is `NULL`, or -1 if an error occurs. +- The `cluster_i_setParamValue()` function returns 0 for success (even if the value was set to `NULL`), or -1 if an error occurs. The `expSetParamFunctions()` function returns 0 if the functions were set successfully, or -1 if an error occurs. diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index c697d4f65..a8f78ddf6 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -131,7 +131,7 @@ typedef struct *** -1 if an expectation is violated (and mssError() is called). ***/ static int -exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) +exp_fn_i_verifyArg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) { /** The expectation struct cannot be NULL. **/ if (UNLIKELY(arg_expect == NULL)) @@ -355,7 +355,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e *** *** Example: *** ```c - *** if (exp_fn_i_verify_schema( + *** if (exp_fn_i_verifySchema( *** (ArgExpect[]){ *** {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_DATETIME, -1}, EXP_ARG_NOT_NULL}, *** {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, @@ -370,7 +370,7 @@ exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_e *** ``` ***/ static int -exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) +exp_fn_i_verifySchema(const ArgExpect* arg_expects, pExpression tree) { /** Verify expression tree. **/ ASSERTMAGIC(tree, MGK_EXPRESSION); @@ -420,7 +420,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) /** Verify arguments. **/ for (int i = 0; i < actual_args; i++) { - if (UNLIKELY(exp_fn_i_verify_arg(tree->Name, tree->Children.Items[i], &arg_expects[i]) != 0)) + if (UNLIKELY(exp_fn_i_verifyArg(tree->Name, tree->Children.Items[i], &arg_expects[i]) != 0)) { mssError(0, "EXP", "%s(...): Error while reading arg #%d/%d.", @@ -443,7 +443,7 @@ exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) *** 1 if the expression is NULL. ***/ static int -exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) +exp_fn_i_getNumber(pExpression numeric_expr, double* result_ptr) { /** Check for null values. **/ if (numeric_expr == NULL || numeric_expr->Flags & EXPR_F_NULL) return 1; @@ -481,7 +481,7 @@ exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) *** @param tree The affected tree. ***/ static void -exp_fn_i_free_result_string(pExpression tree) +exp_fn_i_freeResultString(pExpression tree) { /** If no string is allocated, no work is needed. **/ if (tree->Alloc == 0) return; @@ -506,10 +506,10 @@ exp_fn_i_free_result_string(pExpression tree) *** -1 if an error occurs. ***/ static int -exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) +exp_fn_i_allocResultString(pExpression tree, const size_t required_space) { /** Free the previous string (if needed) so we can store a new one. **/ - exp_fn_i_free_result_string(tree); + exp_fn_i_freeResultString(tree); /** Decide how to allocate space. **/ if (required_space <= sizeof(tree->Types.StringBuf)) @@ -1586,7 +1586,7 @@ int exp_fn_lztrim(pExpression tree) { /** Expect one nullable string parameter. **/ - if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verifySchema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, }, tree) != 0)) @@ -1617,7 +1617,7 @@ exp_fn_lztrim(pExpression tree) while (*str == '0' && (str[1] >= '0' && str[1] <= '9')) str++; /** Return the results using the tree. **/ - exp_fn_i_free_result_string(tree); + exp_fn_i_freeResultString(tree); tree->DataType = DATA_T_STRING; tree->String = str; tree->Alloc = 0; @@ -1631,7 +1631,7 @@ int exp_fn_ltrim(pExpression tree) { /** Expect one nullable string parameter. **/ - if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verifySchema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, }, tree) != 0)) @@ -1663,7 +1663,7 @@ exp_fn_ltrim(pExpression tree) while (*str == ' ') str++; /** Return the results using the tree. **/ - exp_fn_i_free_result_string(tree); + exp_fn_i_freeResultString(tree); tree->DataType = DATA_T_STRING; tree->String = str; tree->Alloc = 0; @@ -1677,7 +1677,7 @@ int exp_fn_rtrim(pExpression tree) { /** Expect one nullable string parameter. **/ - if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verifySchema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, }, tree) != 0)) @@ -1713,7 +1713,7 @@ exp_fn_rtrim(pExpression tree) } /** We need to copy to remove spaces (str is owned by a child expression). **/ - if (check(exp_fn_i_alloc_result_string(tree, n + 1)) != 0) return -1; + if (check(exp_fn_i_allocResultString(tree, n + 1)) != 0) return -1; memcpy(tree->String, str, n); tree->String[n] = '\0'; @@ -3781,14 +3781,14 @@ int exp_fn_from_base64(pExpression tree, pParamObjects objlist, pExpression i0, } static int -exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) +exp_fn_i_doMath(pExpression tree, double (*math)(), int arg_num) { /** Verify function schema: expect arg_num numeric values. **/ ArgExpect expects[arg_num + 1]; for (int i = 0; i < arg_num; i++) expects[i] = (ArgExpect){(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}; expects[arg_num] = EXP_ARG_END; - if (exp_fn_i_verify_schema(expects, tree) != 0) + if (exp_fn_i_verifySchema(expects, tree) != 0) { mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; @@ -3809,7 +3809,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) /** Maximum supported args. **/ if (arg_num > 4) { - mssError(1, "EXP", "%s(...): exp_fn_i_do_math() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", tree->Name, __FILE__, __LINE__); + mssError(1, "EXP", "%s(...): exp_fn_i_doMath() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", tree->Name, __FILE__, __LINE__); return -1; } @@ -3817,7 +3817,7 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) double n[4] = {0.0, 0.0, 0.0, 0.0}; for (int i = 0; i < arg_num; i++) { - if (check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i]))) != 0) + if (check(exp_fn_i_getNumber(tree->Children.Items[i], &(n[i]))) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); return -1; @@ -3834,26 +3834,26 @@ exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) int exp_fn_power(pExpression tree) { - return exp_fn_i_do_math(tree, pow, 2); + return exp_fn_i_doMath(tree, pow, 2); } int exp_fn_ln(pExpression tree) { - return exp_fn_i_do_math(tree, log, 1); + return exp_fn_i_doMath(tree, log, 1); } int exp_fn_log10(pExpression tree) { - return exp_fn_i_do_math(tree, log10, 1); + return exp_fn_i_doMath(tree, log10, 1); } int exp_fn_log(pExpression tree) { /** Verify function schema: A number and an optional base. **/ - if (exp_fn_i_verify_schema((ArgExpect[]){ + if (exp_fn_i_verifySchema((ArgExpect[]){ {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_OPTIONAL}, EXP_ARG_END, @@ -3865,14 +3865,14 @@ exp_fn_log(pExpression tree) /** Extract args. **/ double number, base; - if (check(exp_fn_i_get_number(checkPtr(tree->Children.Items[0]), &number)) != 0) + if (check(exp_fn_i_getNumber(checkPtr(tree->Children.Items[0]), &number)) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); return -1; } if (tree->Children.nItems > 1) { - if (check(exp_fn_i_get_number(checkPtr(tree->Children.Items[1]), &base)) != 0) + if (check(exp_fn_i_getNumber(checkPtr(tree->Children.Items[1]), &base)) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); return -1; @@ -4556,7 +4556,7 @@ exp_fn_metaphone(pExpression tree) char* secondary = NULL; /** Verify function schema. **/ - if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verifySchema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, }, tree) != 0)) @@ -4598,7 +4598,7 @@ exp_fn_metaphone(pExpression tree) /** Store the results. **/ store_data:; const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; - if (check(exp_fn_i_alloc_result_string(tree, length)) != 0) goto end_free; + if (check(exp_fn_i_allocResultString(tree, length)) != 0) goto end_free; sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); tree->DataType = DATA_T_STRING; ret = 0; @@ -4626,7 +4626,7 @@ static int exp_fn_compare(pExpression tree) { /** Verify function schema. **/ - if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verifySchema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, @@ -4707,7 +4707,7 @@ int exp_fn_levenshtein(pExpression tree) { /** Verify function schema. **/ - if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + if (UNLIKELY(exp_fn_i_verifySchema((ArgExpect[]){ {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, EXP_ARG_END, diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 363ff857e..3ac102788 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -96,7 +96,7 @@ ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[] = /** Converts a clustering algorithm to its string name. **/ char* -ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) +cluster_i_clusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) { switch (clustering_algorithm) { @@ -133,7 +133,7 @@ SimilarityMeasure ALL_SIMILARITY_MEASURES[] = *** @returns The corresponding name. ***/ char* -ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) +cluster_i_similarityMeasureToString(SimilarityMeasure similarity_measure) { switch (similarity_measure) { @@ -158,7 +158,7 @@ ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) *** if an error occurs, in which case it always calls mssError() to give *** an error message. ***/ -double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(void*, void*) +double (*cluster_i_similarityMeasureToFunction(SimilarityMeasure similarity_measure))(void*, void*) { switch (similarity_measure) { @@ -167,7 +167,7 @@ double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(v default: mssError(1, "Cluster", "Unknown similarity measure \"%s\" (%d).", - ci_SimilarityMeasureToString(similarity_measure), similarity_measure + cluster_i_similarityMeasureToString(similarity_measure), similarity_measure ); return NULL; } @@ -596,46 +596,44 @@ struct /** ================ Function Declarations ================ **/ /** ANCHOR[id=functions] **/ -/** Note: ci stands for "cluster_internal". **/ - /** Parsing Functions. **/ // LINK #parsing -static void ci_GiveHint(const char* hint); -static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values); -static void ci_UnknownAttribute(char* attr_name, int target_type); -static int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); -static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); -static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); -static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path); -static pClusterData ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data); -static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data); -static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj); +static void cluster_i_giveHint(const char* hint); +static bool cluster_i_tryHint(char* value, char** valid_values, const unsigned int n_valid_values); +static void cluster_i_unknownAttribute(char* attr_name, int target_type); +static int cluster_i_parseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); +static ClusterAlgorithm cluster_i_parseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); +static SimilarityMeasure cluster_i_parseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); +static pSourceData cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path); +static pClusterData cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data); +static pSearchData cluster_i_parseSearchData(pStructInf inf, pNodeData node_data); +static pNodeData cluster_i_parseNodeData(pStructInf inf, pObject obj); /** Freeing Functions. **/ // LINK #freeing -static void ci_FreeSourceData(pSourceData source_data); -static void ci_FreeClusterData(pClusterData cluster_data, bool recursive); -static void ci_FreeSearchData(pSearchData search_data); -static void ci_FreeNodeData(pNodeData node_data); -static void ci_ClearCaches(void); +static void cluster_i_freeSourceData(pSourceData source_data); +static void cluster_i_freeClusterData(pClusterData cluster_data, bool recursive); +static void cluster_i_freeSearchData(pSearchData search_data); +static void cluster_i_freeNodeData(pNodeData node_data); +static void cluster_i_clearCaches(void); /** Deep Size Computation Functions. **/ // LINK #sizing -static size_t ci_SizeOfSourceData(pSourceData source_data); -static size_t ci_SizeOfClusterData(pClusterData cluster_data, bool recursive); -static size_t ci_SizeOfSearchData(pSearchData search_data); +static size_t cluster_i_sizeOfSourceData(pSourceData source_data); +static size_t cluster_i_sizeOfClusterData(pClusterData cluster_data, bool recursive); +static size_t cluster_i_sizeOfSearchData(pSearchData search_data); /** Computation Functions. (Ensure data is computed.) **/ // LINK #computation -static int ci_ComputeSourceData(pSourceData source_data, pObjSession session); -static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data); -static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data); +static int cluster_i_computeSourceData(pSourceData source_data, pObjSession session); +static int cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data); +static int cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data); /** Parameter Functions. **/ // LINK #params -static int ci_GetParamType(void* inf_v, const char* attr_name); -static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); -static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); +static int cluster_i_getParamType(void* inf_v, const char* attr_name); +static int cluster_i_getParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); +static int cluster_i_setParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); /** Driver Functions. **/ // LINK #driver @@ -655,10 +653,10 @@ int clusterInfo(void* inf_v, pObjectInfo info); // LINK #method char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt); char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt); -static int ci_PrintEntry(pXHashEntry entry, void* arg); -static void ci_CacheFreeSourceData(pXHashEntry entry, void* path); -static void ci_CacheFreeCluster(pXHashEntry entry, void* path); -static void ci_CacheFreeSearch(pXHashEntry entry, void* path); +static int cluster_i_printEntry(pXHashEntry entry, void* arg); +static void cluster_i_cacheFreeSourceData(pXHashEntry entry, void* path); +static void cluster_i_cacheFreeCluster(pXHashEntry entry, void* path); +static void cluster_i_cacheFreeSearch(pXHashEntry entry, void* path); int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); /** Unimplemented DriverFunctions. **/ @@ -678,7 +676,7 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt); // LINK #functions /** Format a hint to give to the user. **/ -static void ci_GiveHint(const char* hint) +static void cluster_i_giveHint(const char* hint) { fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); @@ -697,13 +695,13 @@ static void ci_GiveHint(const char* hint) *** @returns Whether a hint was given. ***/ static bool -ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) +cluster_i_tryHint(char* value, char** valid_values, const unsigned int n_valid_values) { char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.25); if (guess == NULL) return false; /* No hint. */ /** Issue hint. **/ - ci_GiveHint(guess); + cluster_i_giveHint(guess); return true; } @@ -717,7 +715,7 @@ ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) *** attributes in this context. ***/ static void -ci_UnknownAttribute(char* attr_name, const int target_type) +cluster_i_unknownAttribute(char* attr_name, const int target_type) { /** Display the error message. **/ mssError(1, "Cluster", "Unknown attribute '%s'.", attr_name); @@ -741,8 +739,8 @@ ci_UnknownAttribute(char* attr_name, const int target_type) } /** Attempt to give hints. **/ - if (ci_TryHint(attr_name, my_attrs, n_my_attrs)); - else if (ci_TryHint(attr_name, DRIVER_ATTRIBUTE_NAMES, N_DRIVER_ATTRIBUTE_NAMES)); + if (cluster_i_tryHint(attr_name, my_attrs, n_my_attrs)); + else if (cluster_i_tryHint(attr_name, DRIVER_ATTRIBUTE_NAMES, N_DRIVER_ATTRIBUTE_NAMES)); return; } @@ -762,7 +760,7 @@ ci_UnknownAttribute(char* attr_name, const int target_type) *** describe the function parameters. ***/ static int -ci_ParseAttribute( +cluster_i_parseAttribute( pStructInf inf, char* attr_name, int datatype, @@ -844,11 +842,11 @@ ci_ParseAttribute( *** @returns The data algorithm, or ALGORITHM_NULL on failure. ***/ static ClusterAlgorithm -ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) +cluster_i_parseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) { /** Get the algorithm attribute. **/ char* algorithm; - if (UNLIKELY(ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0)) + if (UNLIKELY(cluster_i_parseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0)) { mssError(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); return ALGORITHM_NULL; @@ -868,12 +866,12 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) /** Attempt to give a hint. **/ char* all_names[N_CLUSTERING_ALGORITHMS] = {NULL}; for (unsigned int i = 1u; i < N_CLUSTERING_ALGORITHMS; i++) - all_names[i] = ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); - if (ci_TryHint(algorithm, all_names, N_CLUSTERING_ALGORITHMS)); - else if (strcasecmp(algorithm, "sliding") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); - else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); - else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); - else if (strcasecmp(algorithm, "nothing") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + all_names[i] = cluster_i_clusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); + if (cluster_i_tryHint(algorithm, all_names, N_CLUSTERING_ALGORITHMS)); + else if (strcasecmp(algorithm, "sliding") == 0) cluster_i_giveHint(cluster_i_clusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "window") == 0) cluster_i_giveHint(cluster_i_clusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "null") == 0) cluster_i_giveHint(cluster_i_clusteringAlgorithmToString(ALGORITHM_NONE)); + else if (strcasecmp(algorithm, "nothing") == 0) cluster_i_giveHint(cluster_i_clusteringAlgorithmToString(ALGORITHM_NONE)); /** Fail. **/ return ALGORITHM_NULL; @@ -892,11 +890,11 @@ ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) *** @returns The similarity measure, or SIMILARITY_NULL on failure. ***/ static SimilarityMeasure -ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) +cluster_i_parseSimilarityMeasure(pStructInf inf, pParamObjects param_list) { /** Get the similarity_measure attribute. **/ char* measure; - if (UNLIKELY(ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0)) + if (UNLIKELY(cluster_i_parseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0)) { mssError(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); return SIMILARITY_NULL; @@ -912,12 +910,12 @@ ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) /** Attempt to give a hint. **/ char* all_names[N_SIMILARITY_MEASURES] = {NULL}; for (unsigned int i = 1u; i < N_SIMILARITY_MEASURES; i++) - all_names[i] = ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); - if (ci_TryHint(measure, all_names, N_SIMILARITY_MEASURES)); - else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); - else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); - else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); - else if (strcasecmp(measure, "edit-distance") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + all_names[i] = cluster_i_similarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); + if (cluster_i_tryHint(measure, all_names, N_SIMILARITY_MEASURES)); + else if (strcasecmp(measure, "cos") == 0) cluster_i_giveHint(cluster_i_similarityMeasureToString(SIMILARITY_COSINE)); + else if (strcasecmp(measure, "lev") == 0) cluster_i_giveHint(cluster_i_similarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-dist") == 0) cluster_i_giveHint(cluster_i_similarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-distance") == 0) cluster_i_giveHint(cluster_i_similarityMeasureToString(SIMILARITY_LEVENSHTEIN)); /** Fail. **/ return SIMILARITY_NULL; @@ -939,7 +937,7 @@ ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) *** @returns A new pSourceData struct on success, or NULL on failure. ***/ static pSourceData -ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) +cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) { char* buf = NULL; pSourceData source_data = NULL; @@ -959,17 +957,17 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; /** Get source. **/ - if (UNLIKELY(ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(cluster_i_parseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; source_data->SourcePath = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->SourcePath == NULL)) goto err_free; /** Get the attribute name to use when querying keys from the source. **/ - if (UNLIKELY(ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(cluster_i_parseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; source_data->KeyAttr = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->KeyAttr == NULL)) goto err_free; /** Get the attribute name to use for querying data from the source. **/ - if (UNLIKELY(ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(cluster_i_parseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; source_data->DataAttr = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->DataAttr == NULL)) goto err_free; @@ -993,7 +991,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) /** Free data we don't need. **/ nmSysFree(source_data->CacheKey); - ci_FreeSourceData(source_data); + cluster_i_freeSourceData(source_data); /** Return the cached source data. **/ return source_maybe; @@ -1011,7 +1009,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) if (source_data != NULL) { if (source_data->CacheKey != NULL) nmSysFree(source_data->CacheKey); - ci_FreeSourceData(source_data); + cluster_i_freeSourceData(source_data); } mssError(0, "Cluster", @@ -1038,7 +1036,7 @@ ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) *** @returns A new pClusterData struct on success, or NULL on failure. ***/ static pClusterData -ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data) +cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data) { int result; pClusterData cluster_data = NULL; @@ -1068,7 +1066,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source if (check(objCurrentDate(&cluster_data->DateCreated)) != 0) goto err_free; /** Get algorithm. **/ - cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); + cluster_data->ClusterAlgorithm = cluster_i_parseClusteringAlgorithm(inf, param_list); if (UNLIKELY(cluster_data->ClusterAlgorithm == ALGORITHM_NULL)) goto err_free; /** Handle no clustering case. **/ @@ -1079,7 +1077,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source } /** Get similarity_measure. **/ - cluster_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); + cluster_data->SimilarityMeasure = cluster_i_parseSimilarityMeasure(inf, param_list); if (UNLIKELY(cluster_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; /** Handle sliding window case. **/ @@ -1090,7 +1088,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get window_size. **/ int window_size; - if (ci_ParseAttribute(inf, "window_size", DATA_T_INTEGER, POD(&window_size), param_list, true, true) != 0) + if (cluster_i_parseAttribute(inf, "window_size", DATA_T_INTEGER, POD(&window_size), param_list, true, true) != 0) goto err_free; if (window_size < 1) { @@ -1105,7 +1103,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get num_clusters. **/ int num_clusters; - if (UNLIKELY(ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0)) + if (UNLIKELY(cluster_i_parseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0)) goto err_free; if (num_clusters < 2) { @@ -1117,7 +1115,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get min_improvement. **/ double improvement; - result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); + result = cluster_i_parseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); if (UNLIKELY(result == -1)) goto err_free; else if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; else if (result == 0) @@ -1133,7 +1131,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get max_iterations. **/ int max_iterations; - result = ci_ParseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); + result = cluster_i_parseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); if (UNLIKELY(result == -1)) goto err_free; if (result == 0) { @@ -1148,7 +1146,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Get seed. **/ int seed; - result = ci_ParseAttribute(inf, "seed", DATA_T_INTEGER, POD(&seed), param_list, false, true); + result = cluster_i_parseAttribute(inf, "seed", DATA_T_INTEGER, POD(&seed), param_list, false, true); if (UNLIKELY(result == -1)) goto err_free; if (result == 0) { @@ -1194,9 +1192,9 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); - if (ci_TryHint(name, CLUSTER_ATTRS, N_INPUT_CLUSTER_ATTRS)); - else if (strcasecmp(name, "k") == 0) ci_GiveHint("num_clusters"); - else if (strcasecmp(name, "threshold") == 0) ci_GiveHint("min_improvement"); + if (cluster_i_tryHint(name, CLUSTER_ATTRS, N_INPUT_CLUSTER_ATTRS)); + else if (strcasecmp(name, "k") == 0) cluster_i_giveHint("num_clusters"); + else if (strcasecmp(name, "threshold") == 0) cluster_i_giveHint("min_improvement"); break; } @@ -1212,12 +1210,12 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", name, group_type, inf->Name ); - ci_GiveHint("cluster/cluster"); + cluster_i_giveHint("cluster/cluster"); continue; } /** Subcluster found. **/ - pClusterData sub_cluster = ci_ParseClusterData(sub_inf, param_list, source_data); + pClusterData sub_cluster = cluster_i_parseClusterData(sub_inf, param_list, source_data); if (sub_cluster == NULL) goto err_free; sub_cluster->Parent = cluster_data; if (checkPos(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; @@ -1299,7 +1297,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source ASSERTMAGIC(cluster_maybe, MGK_CL_CLUSTER_DATA); /** Free the parsed cluster that we no longer need. */ - if (LIKELY(cluster_data != NULL)) ci_FreeClusterData(cluster_data, false); + if (LIKELY(cluster_data != NULL)) cluster_i_freeClusterData(cluster_data, false); if (LIKELY(cache_key != NULL)) nmSysFree(cache_key); /** Return the cached cluster. **/ @@ -1320,12 +1318,12 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source { pClusterData cur = sub_clusters.Items[i]; if (cur == NULL) break; - ci_FreeClusterData(cur, true); + cluster_i_freeClusterData(cur, true); } check(xaDeInit(&sub_clusters)); /* Failure ignored. */ } - if (cluster_data != NULL) ci_FreeClusterData(cluster_data, false); + if (cluster_data != NULL) cluster_i_freeClusterData(cluster_data, false); mssError(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); return NULL; @@ -1345,7 +1343,7 @@ ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source *** @returns A new pSearchData struct on success, or NULL on failure. ***/ static pSearchData -ci_ParseSearchData(pStructInf inf, pNodeData node_data) +cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) { pSearchData search_data = NULL; char* key = NULL; @@ -1367,7 +1365,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Search for the source cluster. **/ char* source_cluster_name; - if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), param_list, true, true) != 0) goto err_free; + if (cluster_i_parseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), param_list, true, true) != 0) goto err_free; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { pClusterData cluster_data = node_data->ClusterDatas[i]; @@ -1392,14 +1390,14 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) char* cluster_names[node_data->nClusterDatas]; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) cluster_names[i] = node_data->ClusterDatas[i]->Name; - ci_TryHint(source_cluster_name, cluster_names, node_data->nClusterDatas); + cluster_i_tryHint(source_cluster_name, cluster_names, node_data->nClusterDatas); /** Fail. **/ goto err_free; } /** Get threshold attribute. **/ - if (UNLIKELY(ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(cluster_i_parseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0)) goto err_free; if (UNLIKELY(search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold)) { mssError(1, "Cluster", @@ -1410,7 +1408,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) } /** Get similarity measure. **/ - search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); + search_data->SimilarityMeasure = cluster_i_parseSimilarityMeasure(inf, param_list); if (UNLIKELY(search_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; /** Check for additional data to warn the user about. **/ @@ -1446,7 +1444,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); - ci_TryHint(name, SEARCH_ATTRS, N_INPUT_SEARCH_ATTRS); + cluster_i_tryHint(name, SEARCH_ATTRS, N_INPUT_SEARCH_ATTRS); break; } @@ -1494,7 +1492,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) ASSERTMAGIC(search_maybe, MGK_CL_SEARCH_DATA); /** Free the parsed search that we no longer need. **/ - if (LIKELY(search_data != NULL)) ci_FreeSearchData(search_data); + if (LIKELY(search_data != NULL)) cluster_i_freeSearchData(search_data); if (LIKELY(key != NULL)) nmSysFree(key); /** Return the cached search. **/ @@ -1510,7 +1508,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) /** Error cleanup. **/ err_free: - if (search_data != NULL) ci_FreeSearchData(search_data); + if (search_data != NULL) cluster_i_freeSearchData(search_data); mssError(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); @@ -1532,7 +1530,7 @@ ci_ParseSearchData(pStructInf inf, pNodeData node_data) *** @returns A new pNodeData struct on success, or NULL on failure. ***/ static pNodeData -ci_ParseNodeData(pStructInf inf, pObject parent) +cluster_i_parseNodeData(pStructInf inf, pObject parent) { int ret = -1; pNodeData node_data = NULL; @@ -1571,9 +1569,9 @@ ci_ParseNodeData(pStructInf inf, pObject parent) ret = expSetParamFunctions( node_data->ParamList, "parameters", - ci_GetParamType, - ci_GetParamValue, - ci_SetParamValue + cluster_i_getParamType, + cluster_i_getParamValue, + cluster_i_setParamValue ); if (UNLIKELY(ret != 0)) { @@ -1616,7 +1614,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** Give the user a warning, and attempt to give them a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in cluster driver root node \"%s\".\n", name, inf->Name); - ci_TryHint(name, ROOT_ATTRS, N_INPUT_ROOT_ATTRS); + cluster_i_tryHint(name, ROOT_ATTRS, N_INPUT_ROOT_ATTRS); break; } @@ -1647,7 +1645,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) "Warning: Unknown group type \"%s\" on group \"%s\".\n", group_type, sub_inf->Name ); - ci_TryHint(group_type, (char*[]){ + cluster_i_tryHint(group_type, (char*[]){ "cluster/parameter", "cluster/cluster", "cluster/search", @@ -1760,14 +1758,14 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (UNLIKELY(param_names == NULL)) goto err_free; for (unsigned int j = 0u; j < node_data->nParams; j++) param_names[j] = node_data->Params[j]->Name; - ci_TryHint(provided_name, param_names, node_data->nParams); + cluster_i_tryHint(provided_name, param_names, node_data->nParams); if (LIKELY(param_names != NULL)) nmSysFree(param_names); next_provided_param:; } /** Parse source data. **/ - node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); + node_data->SourceData = cluster_i_parseSourceData(inf, node_data->ParamList, path); if (UNLIKELY(node_data->SourceData == NULL)) goto err_free; /** Parse each cluster. **/ @@ -1780,7 +1778,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) memset(node_data->ClusterDatas, 0, clusters_size); for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) { - node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data->ParamList, node_data->SourceData); + node_data->ClusterDatas[i] = cluster_i_parseClusterData(cluster_infs.Items[i], node_data->ParamList, node_data->SourceData); if (node_data->ClusterDatas[i] == NULL) goto err_free; } } @@ -1798,7 +1796,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) memset(node_data->SearchDatas, 0, searches_size); for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) { - node_data->SearchDatas[i] = ci_ParseSearchData(search_infs.Items[i], node_data); + node_data->SearchDatas[i] = cluster_i_parseSearchData(search_infs.Items[i], node_data); if (node_data->SearchDatas[i] == NULL) goto err_free; } } @@ -1813,7 +1811,7 @@ ci_ParseNodeData(pStructInf inf, pObject parent) if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); /* Failure ignored. */ if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); /* Failure ignored. */ if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); /* Failure ignored. */ - if (node_data != NULL) ci_FreeNodeData(node_data); + if (node_data != NULL) cluster_i_freeNodeData(node_data); mssError(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); return NULL; @@ -1826,12 +1824,12 @@ ci_ParseNodeData(pStructInf inf, pObject parent) /** @param source_data A pSourceData struct, freed by this function. **/ static void -ci_FreeSourceData(pSourceData source_data) +cluster_i_freeSourceData(pSourceData source_data) { /** Guard segfault. **/ if (UNLIKELY(source_data == NULL)) { - fprintf(stderr, "Warning: Call to ci_FreeSourceData(NULL);\n"); + fprintf(stderr, "Warning: Call to cluster_i_freeSourceData(NULL);\n"); return; } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); @@ -1919,7 +1917,7 @@ ci_FreeSourceData(pSourceData source_data) *** @param recursive Whether to recursively free subclusters. ***/ static void -ci_FreeClusterData(pClusterData cluster_data, bool recursive) +cluster_i_freeClusterData(pClusterData cluster_data, bool recursive) { if (thExcessiveRecursion()) { @@ -1930,7 +1928,7 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) /** Guard segfault. **/ if (UNLIKELY(cluster_data == NULL)) { - fprintf(stderr, "Warning: Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + fprintf(stderr, "Warning: Call to cluster_i_freeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); return; } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); @@ -1972,7 +1970,7 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) { if (cluster_data->SubClusters[i] != NULL) { - ci_FreeClusterData(cluster_data->SubClusters[i], recursive); + cluster_i_freeClusterData(cluster_data->SubClusters[i], recursive); cluster_data->SubClusters[i] = NULL; } } @@ -1992,12 +1990,12 @@ ci_FreeClusterData(pClusterData cluster_data, bool recursive) // LINK #functions /** @param search_data A pSearchData struct, freed by this function. **/ static void -ci_FreeSearchData(pSearchData search_data) +cluster_i_freeSearchData(pSearchData search_data) { /** Guard segfault. **/ if (UNLIKELY(search_data == NULL)) { - fprintf(stderr, "Warning: Call to ci_FreeSearchData(NULL);\n"); + fprintf(stderr, "Warning: Call to cluster_i_freeSearchData(NULL);\n"); return; } ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); @@ -2032,12 +2030,12 @@ ci_FreeSearchData(pSearchData search_data) // LINK #functions /** @param node_data A pNodeData struct, freed by this function. **/ static void -ci_FreeNodeData(pNodeData node_data) +cluster_i_freeNodeData(pNodeData node_data) { /** Guard segfault. **/ if (UNLIKELY(node_data == NULL)) { - fprintf(stderr, "Warning: Call to ci_FreeNodeData(NULL);\n"); + fprintf(stderr, "Warning: Call to cluster_i_freeNodeData(NULL);\n"); return; } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -2106,14 +2104,14 @@ ci_FreeNodeData(pNodeData node_data) /** Frees all data in caches for all cluster driver instances. **/ static void -ci_ClearCaches(void) +cluster_i_clearCaches(void) { /*** Free caches in reverse of the order they are created in case *** cached data relies on its source during the freeing process. ***/ - check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, ci_CacheFreeSearch, NULL)); /* Failure ignored. */ - check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, ci_CacheFreeCluster, NULL)); /* Failure ignored. */ - check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, ci_CacheFreeSourceData, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, cluster_i_cacheFreeSearch, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, cluster_i_cacheFreeCluster, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, cluster_i_cacheFreeSourceData, NULL)); /* Failure ignored. */ return; } @@ -2133,12 +2131,12 @@ ci_ClearCaches(void) *** @returns The size in bytes of the struct and all internal allocated data. ***/ static size_t -ci_SizeOfSourceData(pSourceData source_data) +cluster_i_sizeOfSourceData(pSourceData source_data) { /** Guard segfaults. **/ if (UNLIKELY(source_data == NULL)) { - fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); + fprintf(stderr, "Warning: Call to cluster_i_sizeOfSourceData(NULL);\n"); return 0u; } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); @@ -2184,7 +2182,7 @@ ci_SizeOfSourceData(pSourceData source_data) *** @returns The size in bytes of the struct and all internal allocated data. ***/ static size_t -ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) +cluster_i_sizeOfClusterData(pClusterData cluster_data, bool recursive) { if (thExcessiveRecursion()) { @@ -2195,7 +2193,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) /** Guard segfaults. **/ if (UNLIKELY(cluster_data == NULL)) { - fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + fprintf(stderr, "Warning: Call to cluster_i_sizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); return 0u; } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); @@ -2213,7 +2211,7 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) if (recursive) { for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) - size += ci_SizeOfClusterData(cluster_data->SubClusters[i], recursive); + size += cluster_i_sizeOfClusterData(cluster_data->SubClusters[i], recursive); } size += cluster_data->nSubClusters * sizeof(pClusterData); } @@ -2234,12 +2232,12 @@ ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) *** @returns The size in bytes of the struct and all internal allocated data. ***/ static size_t -ci_SizeOfSearchData(pSearchData search_data) +cluster_i_sizeOfSearchData(pSearchData search_data) { /** Guard segfaults. **/ if (UNLIKELY(search_data == NULL)) { - fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); + fprintf(stderr, "Warning: Call to cluster_i_sizeOfSearchData(NULL);\n"); return 0u; } ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); @@ -2268,7 +2266,7 @@ ci_SizeOfSearchData(pSearchData search_data) *** -1 other value on failure. ***/ static int -ci_ComputeSourceData(pSourceData source_data, pObjSession session) +cluster_i_computeSourceData(pSourceData source_data, pObjSession session) { bool successful = false; int ret; @@ -2530,7 +2528,7 @@ ci_ComputeSourceData(pSourceData source_data, pObjSession session) *** -1 other value on failure. ***/ static int -ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) +cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) { size_t clusters_size = -1; size_t sims_size = -1; @@ -2556,7 +2554,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) pObjSession session = checkPtr(node_data->ParamList->Session); if (UNLIKELY(session == NULL)) goto err_free; ASSERTMAGIC(session, MGK_OBJSESSION); - if (UNLIKELY(ci_ComputeSourceData(source_data, session) != 0)) + if (UNLIKELY(cluster_i_computeSourceData(source_data, session) != 0)) { mssError(0, "Cluster", "ClusterData computation failed due to missing SourceData."); goto err_free; @@ -2610,7 +2608,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) { mssError(1, "Cluster", "The similarity measure \"%s\" is not implemented for 'k-means' clusters.", - ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) + cluster_i_similarityMeasureToString(cluster_data->SimilarityMeasure) ); goto err_free; } @@ -2671,7 +2669,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) if (UNLIKELY(index > __UINT32_MAX__)) { mssError(1, "Cluster", - "How did you try to cluster more than %u data points and ci_ComputeSearchData() " + "How did you try to cluster more than %u data points and cluster_i_computeSearchData() " "was the first thing to break?! Well... looks like it's time to update %s:%s to " "handle a larger amount of data.", __UINT32_MAX__, __FILE__, __LINE__ @@ -2690,7 +2688,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) default: mssError(1, "Cluster", "Clustering algorithm \"%s\" is not implemented.", - ci_ClusteringAlgorithmToString(cluster_data->ClusterAlgorithm) + cluster_i_clusteringAlgorithmToString(cluster_data->ClusterAlgorithm) ); goto err_free; } @@ -2749,7 +2747,7 @@ ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) *** -1 other value on failure. ***/ static int -ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) +cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) { pXArray pairs = NULL; @@ -2769,7 +2767,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) goto err_free; } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); - if (UNLIKELY(ci_ComputeClusterData(cluster_data, node_data) != 0)) + if (UNLIKELY(cluster_i_computeClusterData(cluster_data, node_data) != 0)) { mssError(0, "Cluster", "SearchData computation failed due to missing clusters."); goto err_free; @@ -2783,7 +2781,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) if (check(objCurrentDate(&search_data->DateComputed)) != 0) goto err_free; /** Get the comparison function based on the similarity measure. **/ - double (*similarity_function)(void *, void *) = checkPtr(ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure)); + double (*similarity_function)(void *, void *) = checkPtr(cluster_i_similarityMeasureToFunction(search_data->SimilarityMeasure)); if (UNLIKELY(similarity_function == NULL)) goto err_free; /** Execute the search using the specified algorithm. **/ @@ -2802,7 +2800,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) default: mssError(1, "Cluster", "Unknown similarity measure \"%s\".", - ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; } @@ -2820,7 +2818,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { mssError(1, "Cluster", "Failed to compute sliding search with %s similarity measure.", - ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; } @@ -2848,7 +2846,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) default: mssError(1, "Cluster", "Unknown similarity measure \"%s\".", - ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; } @@ -2881,7 +2879,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) { mssError(1, "Cluster", "Failed to compute ca_complete_search() with %s similarity measure.", - ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; } @@ -2951,7 +2949,7 @@ ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ static int -ci_GetParamType(void* inf_v, const char* attr_name) +cluster_i_getParamType(void* inf_v, const char* attr_name) { pNodeData node_data = checkPtr(inf_v); if (UNLIKELY(node_data == NULL)) return DATA_T_UNAVAILABLE; @@ -2997,7 +2995,7 @@ ci_GetParamType(void* inf_v, const char* attr_name) *** LINK ../../centrallix-lib/include/datatypes.h:72 ***/ static int -ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) +cluster_i_getParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { pNodeData node_data = checkPtr(inf_v); if (UNLIKELY(node_data == NULL)) goto err; @@ -3036,7 +3034,7 @@ ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) // LINK #functions /** Not implemented. **/ static int -ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) +cluster_i_setParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { mssError(1, "Cluster", "SetParamValue() is not implemented because clusters are immutable."); @@ -3113,7 +3111,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb ASSERTMAGIC(node_struct->Data, MGK_STRUCTINF); /** Parse node data from the node_struct. **/ - node_data = ci_ParseNodeData(node_struct->Data, parent); + node_data = cluster_i_parseNodeData(node_struct->Data, parent); if (UNLIKELY(node_data == NULL)) { mssError(0, "Cluster", "Failed to parse structure file \"%s\".", objFileName(parent)); @@ -3223,12 +3221,12 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb target_names[i] = node_data->ClusterDatas[i]->Name; for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) target_names[i + node_data->nClusterDatas] = node_data->SearchDatas[i]->Name; - ci_TryHint(target_name, target_names, n_targets); + cluster_i_tryHint(target_name, target_names, n_targets); } /** Error cleanup. **/ err_free: - if (node_data != NULL) ci_FreeNodeData(node_data); + if (node_data != NULL) cluster_i_freeNodeData(node_data); if (driver_data != NULL) nmFree(driver_data, sizeof(DriverData)); mssError(0, "Cluster", @@ -3266,7 +3264,7 @@ clusterClose(void* inf_v, pObjTrxTree* oxt) pNodeData node_data = checkPtr(driver_data->NodeData); /* Failure ignored. */ ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); if (UNLIKELY(node_data != NULL && --node_data->OpenCount == 0)) - ci_FreeNodeData(driver_data->NodeData); + cluster_i_freeNodeData(driver_data->NodeData); /** Free driver data. **/ nmFree(driver_data, sizeof(DriverData)); @@ -3413,7 +3411,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) pClusterData target = (pClusterData)checkPtr(driver_data->TargetData); if (UNLIKELY(target == NULL)) goto err_free; ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); - if (UNLIKELY(ci_ComputeClusterData(target, node_data) != 0)) + if (UNLIKELY(cluster_i_computeClusterData(target, node_data) != 0)) { mssError(0, "Cluster", "Failed to compute ClusterData for query."); goto err_free; @@ -3435,7 +3433,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) pSearchData target = (pSearchData)checkPtr(driver_data->TargetData); if (UNLIKELY(target == NULL)) goto err_free; ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - if (UNLIKELY(ci_ComputeSearchData(target, node_data) != 0)) + if (UNLIKELY(cluster_i_computeSearchData(target, node_data) != 0)) { mssError(0, "Cluster", "Failed to compute SearchData for query."); goto err_free; @@ -3876,12 +3874,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO if (strcmp(attr_name, "algorithm") == 0) { - val->String = ci_ClusteringAlgorithmToString(target->ClusterAlgorithm); + val->String = cluster_i_clusteringAlgorithmToString(target->ClusterAlgorithm); return 0; } if (strcmp(attr_name, "similarity_measure") == 0) { - val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + val->String = cluster_i_similarityMeasureToString(target->SimilarityMeasure); return 0; } if (strcmp(attr_name, "num_clusters") == 0) @@ -3924,7 +3922,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } if (strcmp(attr_name, "similarity_measure") == 0) { - val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + val->String = cluster_i_similarityMeasureToString(target->SimilarityMeasure); return 0; } if (strcmp(attr_name, "threshold") == 0) @@ -4003,7 +4001,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } unknown_attribute: - ci_UnknownAttribute(attr_name, driver_data->TargetType); + cluster_i_unknownAttribute(attr_name, driver_data->TargetType); err:; char* name; @@ -4185,7 +4183,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) - checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(cluster_i_clusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -4211,7 +4209,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Enum values. **/ check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)); /* Failure ignored. */ for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) - checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(cluster_i_similarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; @@ -4326,7 +4324,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } unknown_attribute: - ci_UnknownAttribute(attr_name, driver_data->TargetType); + cluster_i_unknownAttribute(attr_name, driver_data->TargetType); err_free: /** Error cleanup. **/ @@ -4547,7 +4545,7 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions /** Intended for use in `xhForEach()`. **/ static int -ci_PrintEntry(pXHashEntry entry, void* arg) +cluster_i_printEntry(pXHashEntry entry, void* arg) { /** Extract entry. **/ char* key = entry->Key; @@ -4574,7 +4572,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) pSourceData source_data = (pSourceData)data; /** Compute size. **/ - bytes = ci_SizeOfSourceData(source_data); + bytes = cluster_i_sizeOfSourceData(source_data); /** If less is specified, skip uncomputed source. **/ if (*less_ptr > 0llu && source_data->Vectors == NULL) goto no_print; @@ -4589,7 +4587,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) pClusterData cluster_data = (pClusterData)data; /** Compute size. **/ - bytes = ci_SizeOfClusterData(cluster_data, false); + bytes = cluster_i_sizeOfClusterData(cluster_data, false); /** If less is specified, skip uncomputed source. **/ if (*less_ptr > 0llu && cluster_data->Clusters == NULL) goto no_print; @@ -4604,7 +4602,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) pSearchData search_data = (pSearchData)data; /** Compute size. **/ - bytes = ci_SizeOfSearchData(search_data); + bytes = cluster_i_sizeOfSearchData(search_data); /** If less is specified, skip uncomputed source. **/ if (*less_ptr > 0llu && search_data->Pairs == NULL) goto no_print; @@ -4638,7 +4636,7 @@ ci_PrintEntry(pXHashEntry entry, void* arg) // LINK #functions /** Intended for use in `xhClearKeySafe()`. **/ static void -ci_CacheFreeSourceData(pXHashEntry entry, void* path) +cluster_i_cacheFreeSourceData(pXHashEntry entry, void* path) { /** Extract hash entry. **/ char* key = entry->Key; @@ -4648,7 +4646,7 @@ ci_CacheFreeSourceData(pXHashEntry entry, void* path) if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; /** Free data. **/ - ci_FreeSourceData(source_data); + cluster_i_freeSourceData(source_data); nmSysFree(key); return; @@ -4658,7 +4656,7 @@ ci_CacheFreeSourceData(pXHashEntry entry, void* path) // LINK #functions /** Intended for use in `xhClearKeySafe()`. **/ static void -ci_CacheFreeCluster(pXHashEntry entry, void* path) +cluster_i_cacheFreeCluster(pXHashEntry entry, void* path) { /** Extract hash entry. **/ char* key = entry->Key; @@ -4668,7 +4666,7 @@ ci_CacheFreeCluster(pXHashEntry entry, void* path) if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; /** Free data. **/ - ci_FreeClusterData(cluster_data, false); + cluster_i_freeClusterData(cluster_data, false); nmSysFree(key); return; @@ -4678,7 +4676,7 @@ ci_CacheFreeCluster(pXHashEntry entry, void* path) // LINK #functions /** Intended for use in `xhClearKeySafe()`. **/ static void -ci_CacheFreeSearch(pXHashEntry entry, void* path) +cluster_i_cacheFreeSearch(pXHashEntry entry, void* path) { /** Extract hash entry. **/ char* key = entry->Key; @@ -4688,7 +4686,7 @@ ci_CacheFreeSearch(pXHashEntry entry, void* path) if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; /** Free data. **/ - ci_FreeSearchData(search_data); + cluster_i_freeSearchData(search_data); nmSysFree(key); return; @@ -4751,19 +4749,19 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Entry CacheKey"); failed |= (check(xhForEach( &ClusterDriverCaches.SourceDataCache, - ci_PrintEntry, + cluster_i_printEntry, (void*[]){&i, &source_bytes, (void*)&skip_uncomputed, path} )) != 0); i++; failed |= (check(xhForEach( &ClusterDriverCaches.ClusterDataCache, - ci_PrintEntry, + cluster_i_printEntry, (void*[]){&i, &cluster_bytes, (void*)&skip_uncomputed, path} )) != 0); i++; failed |= (check(xhForEach( &ClusterDriverCaches.SearchDataCache, - ci_PrintEntry, + cluster_i_printEntry, (void*[]){&i, &search_bytes, (void*)&skip_uncomputed, path} )) != 0); if (failed) @@ -4797,7 +4795,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree /** 'drop_all'. **/ if (strcmp(param->String, "drop_all") == 0) { - ci_ClearCaches(); + cluster_i_clearCaches(); printf("Dropped cache for all cluster files.\n"); return 0; } @@ -4836,7 +4834,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree mssError(1, "Cluster", "Unknown command: \"%s\"", method_name); /** Attempt to give hint. **/ - ci_TryHint(method_name, METHOD_NAMES, METHOD_NAMES_COUNT); + cluster_i_tryHint(method_name, METHOD_NAMES, METHOD_NAMES_COUNT); err: mssError(0, "Cluster", "Failed execute command."); diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index b3bfce4ab..c8a923a32 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -124,7 +124,7 @@ typedef struct *** @returns The new MetaString, or NULL if an error occurs. ***/ pMetaString -meta_new_string(const char* init_str) +meta_i_newString(const char* init_str) { pMetaString s; char empty_string[] = ""; @@ -162,7 +162,7 @@ meta_new_string(const char* init_str) *** @param s The MetaString. ***/ void -meta_destroy_string(pMetaString s) +meta_i_destroyString(pMetaString s) { if (UNLIKELY(s == NULL)) return; @@ -182,7 +182,7 @@ meta_destroy_string(pMetaString s) *** @returns 0 if successful, or -1 if an error occurs. ***/ int -meta_increase_buffer(pMetaString s, const size_t chars_needed) +meta_i_increaseBuffer(pMetaString s, const size_t chars_needed) { s->bufsize += chars_needed + 8u; s->str = checkPtr(nmSysRealloc(s->str, s->bufsize * sizeof(char))); @@ -196,7 +196,7 @@ meta_increase_buffer(pMetaString s, const size_t chars_needed) *** @param s The MetaString being modified. ***/ void -meta_make_upper(pMetaString s) +meta_i_makeUpper(pMetaString s) { for (char* i = s->str; i[0] != '\0'; i++) *i = (char)toupper(*i); @@ -210,7 +210,7 @@ meta_make_upper(pMetaString s) *** 0 otherwise. ***/ bool -meta_is_out_of_bounds(pMetaString s, unsigned int pos) +meta_i_isOutOfBounds(pMetaString s, unsigned int pos) { return (s->length <= pos); } @@ -221,9 +221,9 @@ meta_is_out_of_bounds(pMetaString s, unsigned int pos) *** @param pos The character location to check within the MetaString. ***/ bool -meta_is_vowel(pMetaString s, unsigned int pos) +meta_i_isVowel(pMetaString s, unsigned int pos) { - if (UNLIKELY(meta_is_out_of_bounds(s, pos))) return 0; + if (UNLIKELY(meta_i_isOutOfBounds(s, pos))) return 0; const char c = *(s->str + pos); @@ -238,7 +238,7 @@ meta_is_vowel(pMetaString s, unsigned int pos) *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. ***/ bool -meta_is_slavo_germanic(pMetaString s) +meta_i_isSlavoGermanic(pMetaString s) { return (strstr(s->str, "W") != NULL) || (strstr(s->str, "K") != NULL) @@ -252,9 +252,9 @@ meta_is_slavo_germanic(pMetaString s) *** '\0' if the position is not in the MetaString. ***/ char -meta_get_char_at(pMetaString s, unsigned int pos) +meta_i_getCharAt(pMetaString s, unsigned int pos) { - return (UNLIKELY(meta_is_out_of_bounds(s, pos))) ? '\0' : ((char) *(s->str + pos)); + return (UNLIKELY(meta_i_isOutOfBounds(s, pos))) ? '\0' : ((char) *(s->str + pos)); } /*** Checks for to see if any of a list of strings appear in a the given @@ -270,13 +270,13 @@ meta_get_char_at(pMetaString s, unsigned int pos) *** in the MetaString and 0 otherwise. ***/ bool -meta_is_str_at(pMetaString s, unsigned int start, ...) +meta_i_isStrAt(pMetaString s, unsigned int start, ...) { va_list ap; bool found = false; /** Should never happen. **/ - if (UNLIKELY(meta_is_out_of_bounds(s, start))) + if (UNLIKELY(meta_i_isOutOfBounds(s, start))) return false; const char* pos = (s->str + start); @@ -306,7 +306,7 @@ meta_is_str_at(pMetaString s, unsigned int start, ...) *** @returns 0 if successful, or -1 if an error occurs. ***/ int -meta_add_str(pMetaString s, const char* new_str) +meta_i_addStr(pMetaString s, const char* new_str) { if (UNLIKELY(new_str == NULL)) return -1; @@ -314,7 +314,7 @@ meta_add_str(pMetaString s, const char* new_str) /** Increase the buffer to the required size. **/ const size_t add_length = strlen(new_str); const size_t new_length = s->length + add_length + 1; - if (UNLIKELY(new_length > s->bufsize) && check(meta_increase_buffer(s, add_length)) != 0) + if (UNLIKELY(new_length > s->bufsize) && check(meta_i_increaseBuffer(s, add_length)) != 0) return -1; /** Write the data to the buffer. **/ @@ -373,37 +373,37 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod const unsigned int last = (unsigned int)(length - 1); /** Pad original so we can index beyond end. **/ - pMetaString original = checkPtr(meta_new_string(str)); + pMetaString original = checkPtr(meta_i_newString(str)); if (UNLIKELY(original == NULL)) goto end_free; - meta_make_upper(original); - if (check(meta_add_str(original, " ")) != 0) goto end_free; + meta_i_makeUpper(original); + if (check(meta_i_addStr(original, " ")) != 0) goto end_free; /** Allocate the primary and secondary output strings. **/ - pMetaString primary = checkPtr(meta_new_string("")); - pMetaString secondary = checkPtr(meta_new_string("")); + pMetaString primary = checkPtr(meta_i_newString("")); + pMetaString secondary = checkPtr(meta_i_newString("")); if (UNLIKELY(primary == NULL || secondary == NULL)) goto end_free; /** Skip these if they are at start of a word. **/ - if (meta_is_str_at(original, 0, "GN", "KN", "PN", "WR", "PS", "")) + if (meta_i_isStrAt(original, 0, "GN", "KN", "PN", "WR", "PS", "")) current += 1; /** Initial 'X' is pronounced 'Z' e.g. 'Xavier' **/ - const char first_char = meta_get_char_at(original, 0); + const char first_char = meta_i_getCharAt(original, 0); if (first_char == 'X') { - if (check(meta_add_str(primary, "S")) != 0) goto end_free; /* 'Z' maps to 'S' */ - if (check(meta_add_str(secondary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; /* 'Z' maps to 'S' */ + if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; current += 1; } /** Precomputing this is useful. **/ - const bool is_slavo_germanic = meta_is_slavo_germanic(original); + const bool is_slavo_germanic = meta_i_isSlavoGermanic(original); /** Main loop. **/ while (current < length) { - const char cur_char = meta_get_char_at(original, current); - const char next_char = meta_get_char_at(original, current + 1); + const char cur_char = meta_i_getCharAt(original, current); + const char next_char = meta_i_getCharAt(original, current + 1); switch (cur_char) { case 'A': @@ -416,8 +416,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (current == 0) { /** All init vowels now map to 'A'. **/ - if (check(meta_add_str(primary, "A") != 0)) goto end_free; - if (check(meta_add_str(secondary, "A") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "A") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "A") != 0)) goto end_free; } current += 1; break; @@ -426,8 +426,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'B': { /** "-mb", e.g", "dumb", already skipped over... **/ - if (check(meta_add_str(primary, "P") != 0)) goto end_free; - if (check(meta_add_str(secondary, "P") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "P") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "P") != 0)) goto end_free; current += (next_char == 'B') ? 2 : 1; break; @@ -438,46 +438,46 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Various germanic. **/ if ( (current > 1) - && !meta_is_vowel(original, current - 2) - && meta_is_str_at(original, (current - 1), "ACH", "") - && meta_get_char_at(original, current + 2) != 'I' + && !meta_i_isVowel(original, current - 2) + && meta_i_isStrAt(original, (current - 1), "ACH", "") + && meta_i_getCharAt(original, current + 2) != 'I' && ( - meta_get_char_at(original, current + 2) != 'E' - || meta_is_str_at(original, (current - 2), "BACHER", "MACHER", "") + meta_i_getCharAt(original, current + 2) != 'E' + || meta_i_isStrAt(original, (current - 2), "BACHER", "MACHER", "") ) ) { - if (check(meta_add_str(primary, "K") != 0)) goto end_free; - if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; current += 2; break; } /** Special case 'caesar' **/ - if (current == 0 && meta_is_str_at(original, current, "CAESAR", "")) + if (current == 0 && meta_i_isStrAt(original, current, "CAESAR", "")) { - if (check(meta_add_str(primary, "S") != 0)) goto end_free; - if (check(meta_add_str(secondary, "S") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "S") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "S") != 0)) goto end_free; current += 2; break; } /** Italian 'chianti' **/ - if (meta_is_str_at(original, current, "CHIA", "")) + if (meta_i_isStrAt(original, current, "CHIA", "")) { - if (check(meta_add_str(primary, "K") != 0)) goto end_free; - if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; current += 2; break; } - if (meta_is_str_at(original, current, "CH", "")) + if (meta_i_isStrAt(original, current, "CH", "")) { /** Find 'michael' **/ - if (current > 0 && meta_is_str_at(original, current, "CHAE", "")) + if (current > 0 && meta_i_isStrAt(original, current, "CHAE", "")) { - if (check(meta_add_str(primary, "K") != 0)) goto end_free; - if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; current += 2; break; } @@ -485,52 +485,52 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Greek roots e.g. 'chemistry', 'chorus' **/ if ( current == 0 - && meta_is_str_at(original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") - && !meta_is_str_at(original, 0, "CHORE", "") + && meta_i_isStrAt(original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") + && !meta_i_isStrAt(original, 0, "CHORE", "") ) { - if (check(meta_add_str(primary, "K") != 0)) goto end_free; - if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; current += 2; break; } /** Germanic, greek, or otherwise 'ch' for 'kh' sound. */ if ( - meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + meta_i_isStrAt(original, 0, "SCH", "VAN ", "VON ", "") /** 'architect but not 'arch', 'orchestra', 'orchid' **/ - || meta_is_str_at(original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") - || meta_is_str_at(original, (current + 2), "T", "S", "") + || meta_i_isStrAt(original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") + || meta_i_isStrAt(original, (current + 2), "T", "S", "") || ( - (current == 0 || meta_is_str_at(original, (current - 1), "A", "O", "U", "E", "")) + (current == 0 || meta_i_isStrAt(original, (current - 1), "A", "O", "U", "E", "")) /** e.g., 'wachtler', 'wechsler', but not 'tichner' **/ - && meta_is_str_at(original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") + && meta_i_isStrAt(original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") ) ) { - if (check(meta_add_str(primary, "K") != 0)) goto end_free; - if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; } else { if (current > 0) { - if (meta_is_str_at(original, 0, "MC", "")) + if (meta_i_isStrAt(original, 0, "MC", "")) { /* e.g., "McHugh" */ - if (check(meta_add_str(primary, "K") != 0)) goto end_free; - if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; } else { - if (check(meta_add_str(primary, "X") != 0)) goto end_free; - if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "X") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; } } else { - if (check(meta_add_str(primary, "X") != 0)) goto end_free; - if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "X") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; } } current += 2; @@ -538,97 +538,97 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod } /** e.g, 'czerny' **/ - if (meta_is_str_at(original, current, "CZ", "") - && !meta_is_str_at(original, (current - 2), "WICZ", "")) + if (meta_i_isStrAt(original, current, "CZ", "") + && !meta_i_isStrAt(original, (current - 2), "WICZ", "")) { - if (check(meta_add_str(primary, "S") != 0)) goto end_free; - if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "S") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; current += 2; break; } /** e.g., 'focaccia' **/ - if (meta_is_str_at(original, (current + 1), "CIA", "")) + if (meta_i_isStrAt(original, (current + 1), "CIA", "")) { - if (check(meta_add_str(primary, "X") != 0)) goto end_free; - if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + if (check(meta_i_addStr(primary, "X") != 0)) goto end_free; + if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; current += 3; break; } /** Double 'C' rule. **/ if ( - meta_is_str_at(original, current, "CC", "") + meta_i_isStrAt(original, current, "CC", "") && !(current == 1 && first_char == 'M') /* McClellan exception. */ ) { /** 'bellocchio' but not 'bacchus' **/ if ( - meta_is_str_at(original, (current + 2), "I", "E", "H", "") - && !meta_is_str_at(original, (current + 2), "HU", "") + meta_i_isStrAt(original, (current + 2), "I", "E", "H", "") + && !meta_i_isStrAt(original, (current + 2), "HU", "") ) { /** 'accident', 'accede' 'succeed' **/ if ( - (current == 1 && meta_get_char_at(original, current - 1) == 'A') - || meta_is_str_at(original, (current - 1), "UCCEE", "UCCES", "") + (current == 1 && meta_i_getCharAt(original, current - 1) == 'A') + || meta_i_isStrAt(original, (current - 1), "UCCEE", "UCCES", "") ) { - if (check(meta_add_str(primary, "KS")) != 0) goto end_free; - if (check(meta_add_str(secondary, "KS")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "KS")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "KS")) != 0) goto end_free; /** 'bacci', 'bertucci', other italian **/ } else { - if (check(meta_add_str(primary, "X")) != 0) goto end_free; - if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; } current += 3; break; } else { /** Pierce's rule **/ - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; current += 2; break; } } - if (meta_is_str_at(original, current, "CK", "CG", "CQ", "")) + if (meta_i_isStrAt(original, current, "CK", "CG", "CQ", "")) { - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; current += 2; break; } - if (meta_is_str_at(original, current, "CI", "CE", "CY", "")) + if (meta_i_isStrAt(original, current, "CI", "CE", "CY", "")) { /* Italian vs. English */ - if (meta_is_str_at(original, current, "CIO", "CIE", "CIA", "")) + if (meta_i_isStrAt(original, current, "CIO", "CIE", "CIA", "")) { - if (check(meta_add_str(primary, "S")) != 0) goto end_free; - if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; } else { - if (check(meta_add_str(primary, "S")) != 0) goto end_free; - if (check(meta_add_str(secondary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; } current += 2; break; } /** else **/ - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; /** Name sent in 'mac caffrey', 'mac gregor **/ - if (meta_is_str_at(original, (current + 1), " C", " Q", " G", "")) + if (meta_i_isStrAt(original, (current + 1), " C", " Q", " G", "")) current += 3; - else if (meta_is_str_at(original, (current + 1), "C", "K", "Q", "") - && !meta_is_str_at(original, (current + 1), "CE", "CI", "")) + else if (meta_i_isStrAt(original, (current + 1), "C", "K", "Q", "") + && !meta_i_isStrAt(original, (current + 1), "CE", "CI", "")) current += 2; else current += 1; @@ -637,37 +637,37 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'D': { - if (meta_is_str_at(original, current, "DG", "")) + if (meta_i_isStrAt(original, current, "DG", "")) { - if (meta_is_str_at(original, (current + 2), "I", "E", "Y", "")) + if (meta_i_isStrAt(original, (current + 2), "I", "E", "Y", "")) { /** e.g. 'edge' **/ - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; current += 3; break; } else { /** e.g. 'edgar' **/ - if (check(meta_add_str(primary, "TK")) != 0) goto end_free; - if (check(meta_add_str(secondary, "TK")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "TK")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "TK")) != 0) goto end_free; current += 2; break; } } - if (meta_is_str_at(original, current, "DT", "DD", "")) + if (meta_i_isStrAt(original, current, "DT", "DD", "")) { - if (check(meta_add_str(primary, "T")) != 0) goto end_free; - if (check(meta_add_str(secondary, "T")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "T")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; current += 2; break; } /** else **/ - if (check(meta_add_str(primary, "T")) != 0) goto end_free; - if (check(meta_add_str(secondary, "T")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "T")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; current += 1; break; } @@ -675,8 +675,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'F': { current += (next_char == 'F') ? 2 : 1; - if (check(meta_add_str(primary, "F")) != 0) goto end_free; - if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; break; } @@ -685,10 +685,10 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (next_char == 'H') { /** 'Vghee' */ - if (current > 0 && !meta_is_vowel(original, (current - 1))) + if (current > 0 && !meta_i_isVowel(original, (current - 1))) { - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; current += 2; break; } @@ -698,15 +698,15 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** 'ghislane', 'ghiradelli' **/ if (current == 0) { - if (meta_get_char_at(original, (current + 2)) == 'I') + if (meta_i_getCharAt(original, (current + 2)) == 'I') { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; } else { - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; } current += 2; break; @@ -715,11 +715,11 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if ( /** Parker's rule (with some further refinements) - e.g., 'hugh' **/ - (current > 1 && meta_is_str_at(original, (current - 2), "B", "H", "D", "")) + (current > 1 && meta_i_isStrAt(original, (current - 2), "B", "H", "D", "")) /** e.g., 'bough' **/ - || (current > 2 && meta_is_str_at(original, (current - 3), "B", "H", "D", "")) + || (current > 2 && meta_i_isStrAt(original, (current - 3), "B", "H", "D", "")) /** e.g., 'broughton' **/ - || (current > 3 && meta_is_str_at(original, (current - 4), "B", "H", "")) + || (current > 3 && meta_i_isStrAt(original, (current - 4), "B", "H", "")) ) { current += 2; @@ -730,17 +730,17 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough' **/ if ( current > 2 - && meta_get_char_at(original, (current - 1)) == 'U' - && meta_is_str_at(original, (current - 3), "C", "G", "L", "R", "T", "") + && meta_i_getCharAt(original, (current - 1)) == 'U' + && meta_i_isStrAt(original, (current - 3), "C", "G", "L", "R", "T", "") ) { - if (check(meta_add_str(primary, "F")) != 0) goto end_free; - if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; } - else if (current > 0 && meta_get_char_at(original, (current - 1)) != 'I') + else if (current > 0 && meta_i_getCharAt(original, (current - 1)) != 'I') { - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; } current += 2; @@ -750,26 +750,26 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (next_char == 'N') { - if (current == 1 && !is_slavo_germanic && meta_is_vowel(original, 0)) + if (current == 1 && !is_slavo_germanic && meta_i_isVowel(original, 0)) { - if (check(meta_add_str(primary, "KN")) != 0) goto end_free; - if (check(meta_add_str(secondary, "N")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "KN")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "N")) != 0) goto end_free; } else /** not e.g. 'cagney' **/ if ( next_char != 'Y' && !is_slavo_germanic - && !meta_is_str_at(original, (current + 2), "EY", "") + && !meta_i_isStrAt(original, (current + 2), "EY", "") ) { - if (check(meta_add_str(primary, "N")) != 0) goto end_free; - if (check(meta_add_str(secondary, "KN")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "N")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "KN")) != 0) goto end_free; } else { - if (check(meta_add_str(primary, "KN")) != 0) goto end_free; - if (check(meta_add_str(secondary, "KN")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "KN")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "KN")) != 0) goto end_free; } current += 2; break; @@ -778,11 +778,11 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** 'tagliaro' **/ if ( !is_slavo_germanic - && meta_is_str_at(original, (current + 1), "LI", "") + && meta_i_isStrAt(original, (current + 1), "LI", "") ) { - if (check(meta_add_str(primary, "KL")) != 0) goto end_free; - if (check(meta_add_str(secondary, "L")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "KL")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "L")) != 0) goto end_free; current += 2; break; } @@ -792,7 +792,7 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod current == 0 && ( next_char == 'Y' - || meta_is_str_at( + || meta_i_isStrAt( original, (current + 1), "ES", "EP", "EB", "EL", "EY", "IB", "IL", "IN", "IE", "EI", "ER", "" @@ -800,51 +800,51 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ) ) { - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; current += 2; break; } /** -ger-, -gy- **/ if ( - (next_char == 'Y' || meta_is_str_at(original, (current + 1), "ER", "")) + (next_char == 'Y' || meta_i_isStrAt(original, (current + 1), "ER", "")) /** Exceptions. **/ - && !meta_is_str_at(original, 0, "DANGER", "RANGER", "MANGER", "") - && !meta_is_str_at(original, (current - 1), "E", "I", "RGY", "OGY", "") + && !meta_i_isStrAt(original, 0, "DANGER", "RANGER", "MANGER", "") + && !meta_i_isStrAt(original, (current - 1), "E", "I", "RGY", "OGY", "") ) { - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; current += 2; break; } /** Italian e.g, 'biaggi' **/ if ( - meta_is_str_at(original, (current + 1), "E", "I", "Y", "") - || meta_is_str_at(original, (current - 1), "AGGI", "OGGI", "") + meta_i_isStrAt(original, (current + 1), "E", "I", "Y", "") + || meta_i_isStrAt(original, (current - 1), "AGGI", "OGGI", "") ) { /** Obvious germanic. **/ - if (meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") - || meta_is_str_at(original, (current + 1), "ET", "")) + if (meta_i_isStrAt(original, 0, "SCH", "VAN ", "VON ", "") + || meta_i_isStrAt(original, (current + 1), "ET", "")) { - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; } else { /** Always soft, if french ending. **/ - if (meta_is_str_at(original, (current + 1), "IER ", "")) + if (meta_i_isStrAt(original, (current + 1), "IER ", "")) { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; } else { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; } } current += 2; @@ -852,8 +852,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod } current += (next_char == 'G') ? 2 : 1; - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; break; } @@ -861,12 +861,12 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { /** Only keep if first & before vowel or between 2 vowels. **/ if ( - (current == 0 || meta_is_vowel(original, (current - 1))) - && meta_is_vowel(original, current + 1) + (current == 0 || meta_i_isVowel(original, (current - 1))) + && meta_i_isVowel(original, current + 1) ) { - if (check(meta_add_str(primary, "H")) != 0) goto end_free; - if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "H")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; current += 2; } else /* also takes care of 'HH' */ @@ -877,23 +877,23 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'J': { /** Obvious spanish, 'jose', 'san jacinto' **/ - const bool has_jose_next = meta_is_str_at(original, current, "JOSE", ""); - const bool starts_with_san = meta_is_str_at(original, 0, "SAN ", ""); + const bool has_jose_next = meta_i_isStrAt(original, current, "JOSE", ""); + const bool starts_with_san = meta_i_isStrAt(original, 0, "SAN ", ""); if (has_jose_next || starts_with_san) { if ( starts_with_san /** I don't know what this condition means. **/ - || (current == 0 && meta_get_char_at(original, current + 4) == ' ') + || (current == 0 && meta_i_getCharAt(original, current + 4) == ' ') ) { - if (check(meta_add_str(primary, "H")) != 0) goto end_free; - if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "H")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; } else { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; } current += 1; break; @@ -901,8 +901,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if (current == 0 && !has_jose_next) { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; /* Yankelovich/Jankelowicz */ - if (check(meta_add_str(secondary, "A")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; /* Yankelovich/Jankelowicz */ + if (check(meta_i_addStr(secondary, "A")) != 0) goto end_free; } else { @@ -910,28 +910,28 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if ( !is_slavo_germanic && (next_char == 'A' || next_char == 'O') - && meta_is_vowel(original, (current - 1)) + && meta_i_isVowel(original, (current - 1)) ) { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; } else { if (current == last) { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "")) != 0) goto end_free; } else { if ( - !meta_is_str_at(original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") - && !meta_is_str_at(original, (current - 1), "S", "K", "L", "") + !meta_i_isStrAt(original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") + && !meta_i_isStrAt(original, (current - 1), "S", "K", "L", "") ) { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; } } } @@ -944,8 +944,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'K': { current += (next_char == 'K') ? 2 : 1; - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; break; } @@ -957,19 +957,19 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod if ( ( current == length - 3 - && meta_is_str_at(original, (current - 1), "ILLO", "ILLA", "ALLE", "") + && meta_i_isStrAt(original, (current - 1), "ILLO", "ILLA", "ALLE", "") ) || ( - meta_is_str_at(original, (current - 1), "ALLE", "") + meta_i_isStrAt(original, (current - 1), "ALLE", "") && ( - meta_is_str_at(original, (last - 1), "AS", "OS", "") - || meta_is_str_at(original, last, "A", "O", "") + meta_i_isStrAt(original, (last - 1), "AS", "OS", "") + || meta_i_isStrAt(original, last, "A", "O", "") ) ) ) { - if (check(meta_add_str(primary, "L")) != 0) goto end_free; - if (check(meta_add_str(secondary, "")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "L")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "")) != 0) goto end_free; current += 2; break; } @@ -977,8 +977,8 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod } else current += 1; - if (check(meta_add_str(primary, "L")) != 0) goto end_free; - if (check(meta_add_str(secondary, "L")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "L")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "L")) != 0) goto end_free; break; } @@ -986,22 +986,22 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { current += ( ( - meta_is_str_at(original, (current - 1), "UMB", "") - && (current + 1 == last || meta_is_str_at(original, (current + 2), "ER", "")) + meta_i_isStrAt(original, (current - 1), "UMB", "") + && (current + 1 == last || meta_i_isStrAt(original, (current + 2), "ER", "")) ) /** 'dumb','thumb' **/ || next_char == 'M' ) ? 2 : 1; - if (check(meta_add_str(primary, "M")) != 0) goto end_free; - if (check(meta_add_str(secondary, "M")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "M")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "M")) != 0) goto end_free; break; } case 'N': { current += (next_char == 'N') ? 2 : 1; - if (check(meta_add_str(primary, "N")) != 0) goto end_free; - if (check(meta_add_str(secondary, "N")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "N")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "N")) != 0) goto end_free; break; } @@ -1009,24 +1009,24 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { if (next_char == 'H') { - if (check(meta_add_str(primary, "F")) != 0) goto end_free; - if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; current += 2; break; } /** Also account for "campbell", "raspberry" **/ - current += (meta_is_str_at(original, (current + 1), "P", "B", "")) ? 2 : 1; - if (check(meta_add_str(primary, "P")) != 0) goto end_free; - if (check(meta_add_str(secondary, "P")) != 0) goto end_free; + current += (meta_i_isStrAt(original, (current + 1), "P", "B", "")) ? 2 : 1; + if (check(meta_i_addStr(primary, "P")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "P")) != 0) goto end_free; break; } case 'Q': { current += (next_char == 'Q') ? 2 : 1; - if (check(meta_add_str(primary, "K")) != 0) goto end_free; - if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; break; } @@ -1036,12 +1036,12 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod const bool no_primary = ( !is_slavo_germanic && current == last - && meta_is_str_at(original, (current - 2), "IE", "") - && !meta_is_str_at(original, (current - 4), "ME", "MA", "") + && meta_i_isStrAt(original, (current - 2), "IE", "") + && !meta_i_isStrAt(original, (current - 4), "ME", "MA", "") ); - if (check(meta_add_str(primary, (no_primary) ? "" : "R")) != 0) goto end_free; - if (check(meta_add_str(secondary, "R")) != 0) goto end_free; + if (check(meta_i_addStr(primary, (no_primary) ? "" : "R")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "R")) != 0) goto end_free; current += (next_char == 'R') ? 2 : 1; break; } @@ -1049,69 +1049,69 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'S': { /** Special cases 'island', 'isle', 'carlisle', 'carlysle' **/ - if (meta_is_str_at(original, (current - 1), "ISL", "YSL", "")) + if (meta_i_isStrAt(original, (current - 1), "ISL", "YSL", "")) { current += 1; break; } /** Special case 'sugar-' **/ - if (current == 0 && meta_is_str_at(original, current, "SUGAR", "")) + if (current == 0 && meta_i_isStrAt(original, current, "SUGAR", "")) { - if (check(meta_add_str(primary, "X")) != 0) goto end_free; - if (check(meta_add_str(secondary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; current += 1; break; } - if (meta_is_str_at(original, current, "SH", "")) + if (meta_i_isStrAt(original, current, "SH", "")) { - const bool germanic = meta_is_str_at(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); + const bool germanic = meta_i_isStrAt(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); const char* sound = (germanic) ? "S" : "X"; - if (check(meta_add_str(primary, sound)) != 0) goto end_free; - if (check(meta_add_str(secondary, sound)) != 0) goto end_free; + if (check(meta_i_addStr(primary, sound)) != 0) goto end_free; + if (check(meta_i_addStr(secondary, sound)) != 0) goto end_free; current += 2; break; } /** Italian & Armenian. **/ - if (meta_is_str_at(original, current, "SIO", "SIA", "SIAN", "")) + if (meta_i_isStrAt(original, current, "SIO", "SIA", "SIAN", "")) { - if (check(meta_add_str(primary, "S")) != 0) goto end_free; - if (check(meta_add_str(secondary, (is_slavo_germanic) ? "S" : "X")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, (is_slavo_germanic) ? "S" : "X")) != 0) goto end_free; current += 3; break; } /** german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' **/ /** also, -sz- in slavic language although in hungarian it is pronounced 's' **/ - if (current == 0 && meta_is_str_at(original, (current + 1), "M", "N", "L", "W", "")) + if (current == 0 && meta_i_isStrAt(original, (current + 1), "M", "N", "L", "W", "")) { - if (check(meta_add_str(primary, "S")) != 0) goto end_free; - if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; current += 1; break; } - if (meta_is_str_at(original, (current + 1), "Z", "")) + if (meta_i_isStrAt(original, (current + 1), "Z", "")) { - if (check(meta_add_str(primary, "S")) != 0) goto end_free; - if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; current += 2; break; } - if (meta_is_str_at(original, current, "SC", "")) + if (meta_i_isStrAt(original, current, "SC", "")) { /** Schlesinger's rule. **/ - if (meta_get_char_at(original, current + 2) == 'H') + if (meta_i_getCharAt(original, current + 2) == 'H') { /** Dutch origin, e.g. 'school', 'schooner' **/ - if (meta_is_str_at(original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) + if (meta_i_isStrAt(original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) { /** 'schermerhorn', 'schenker' **/ - const bool x_sound = meta_is_str_at(original, (current + 3), "ER", "EN", ""); - if (check(meta_add_str(primary, (x_sound) ? "X" : "SK")) != 0) goto end_free; - if (check(meta_add_str(secondary, "SK")) != 0) goto end_free; + const bool x_sound = meta_i_isStrAt(original, (current + 3), "ER", "EN", ""); + if (check(meta_i_addStr(primary, (x_sound) ? "X" : "SK")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "SK")) != 0) goto end_free; current += 3; break; } @@ -1119,66 +1119,66 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod { const bool s_sound = ( current == 0 - && !meta_is_vowel(original, 3) - && meta_get_char_at(original, 3) != 'W' + && !meta_i_isVowel(original, 3) + && meta_i_getCharAt(original, 3) != 'W' ); - if (check(meta_add_str(primary, "X")) != 0) goto end_free; - if (check(meta_add_str(secondary, (s_sound) ? "S" : "X")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, (s_sound) ? "S" : "X")) != 0) goto end_free; current += 3; break; } } /** Default case. **/ - const char* sound = (meta_is_str_at(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; - if (check(meta_add_str(primary, sound)) != 0) goto end_free; - if (check(meta_add_str(secondary, sound)) != 0) goto end_free; + const char* sound = (meta_i_isStrAt(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; + if (check(meta_i_addStr(primary, sound)) != 0) goto end_free; + if (check(meta_i_addStr(secondary, sound)) != 0) goto end_free; current += 3; break; } /** French e.g. 'resnais', 'artois' **/ - const bool no_primary = (current == last && meta_is_str_at(original, (current - 2), "AI", "OI", "")); - if (check(meta_add_str(primary, (no_primary) ? "" : "S")) != 0) goto end_free; - if (check(meta_add_str(secondary, "S")) != 0) goto end_free; - current += (meta_is_str_at(original, (current + 1), "S", "Z", "")) ? 2 : 1; + const bool no_primary = (current == last && meta_i_isStrAt(original, (current - 2), "AI", "OI", "")); + if (check(meta_i_addStr(primary, (no_primary) ? "" : "S")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; + current += (meta_i_isStrAt(original, (current + 1), "S", "Z", "")) ? 2 : 1; break; } case 'T': { - if (meta_is_str_at(original, current, "TIA", "TCH", "TION", "")) + if (meta_i_isStrAt(original, current, "TIA", "TCH", "TION", "")) { - if (check(meta_add_str(primary, "X")) != 0) goto end_free; - if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; current += 3; break; } - if (meta_is_str_at(original, current, "TH", "TTH", "")) + if (meta_i_isStrAt(original, current, "TH", "TTH", "")) { /** Special case 'thomas', 'thames' or germanic. **/ char* primary_char = ( - meta_is_str_at(original, (current + 2), "OM", "AM", "") - || meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + meta_i_isStrAt(original, (current + 2), "OM", "AM", "") + || meta_i_isStrAt(original, 0, "SCH", "VAN ", "VON ", "") ) ? "T" : "0"; /* Zero, not O. */ - if (check(meta_add_str(primary, primary_char)) != 0) goto end_free; - if (check(meta_add_str(secondary, "T")) != 0) goto end_free; + if (check(meta_i_addStr(primary, primary_char)) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; current += 2; break; } - if (check(meta_add_str(primary, "T")) != 0) goto end_free; - if (check(meta_add_str(secondary, "T")) != 0) goto end_free; - current += (meta_is_str_at(original, (current + 1), "T", "D", "")) ? 2 : 1; + if (check(meta_i_addStr(primary, "T")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; + current += (meta_i_isStrAt(original, (current + 1), "T", "D", "")) ? 2 : 1; break; } case 'V': { - if (check(meta_add_str(primary, "F")) != 0) goto end_free; - if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; current += (next_char == 'V') ? 2 : 1; break; } @@ -1186,39 +1186,39 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod case 'W': { /** Can also be in middle of word. **/ - if (meta_is_str_at(original, current, "WR", "")) + if (meta_i_isStrAt(original, current, "WR", "")) { - if (check(meta_add_str(primary, "R")) != 0) goto end_free; - if (check(meta_add_str(secondary, "R")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "R")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "R")) != 0) goto end_free; current += 2; break; } - const bool next_is_vowel = meta_is_vowel(original, current + 1); - if (current == 0 && (next_is_vowel || meta_is_str_at(original, current, "WH", ""))) + const bool next_is_vowel = meta_i_isVowel(original, current + 1); + if (current == 0 && (next_is_vowel || meta_i_isStrAt(original, current, "WH", ""))) { /** Wasserman should match Vasserman. **/ - if (check(meta_add_str(primary, "A")) != 0) goto end_free; - if (check(meta_add_str(secondary, (next_is_vowel) ? "F" : "A")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "A")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, (next_is_vowel) ? "F" : "A")) != 0) goto end_free; } /** Arnow should match Arnoff. **/ - if ((current == last && meta_is_vowel(original, current - 1)) - || meta_is_str_at(original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") - || meta_is_str_at(original, 0, "SCH", "") + if ((current == last && meta_i_isVowel(original, current - 1)) + || meta_i_isStrAt(original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") + || meta_i_isStrAt(original, 0, "SCH", "") ) { - if (check(meta_add_str(primary, "")) != 0) goto end_free; - if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; current += 1; break; } /** Polish e.g. 'filipowicz' **/ - if (meta_is_str_at(original, current, "WICZ", "WITZ", "")) + if (meta_i_isStrAt(original, current, "WICZ", "WITZ", "")) { - if (check(meta_add_str(primary, "TS")) != 0) goto end_free; - if (check(meta_add_str(secondary, "FX")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "TS")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "FX")) != 0) goto end_free; current += 4; break; } @@ -1234,17 +1234,17 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod const bool silent = ( current == last && ( - meta_is_str_at(original, (current - 2), "AU", "OU", "") - || meta_is_str_at(original, (current - 3), "IAU", "EAU", "") + meta_i_isStrAt(original, (current - 2), "AU", "OU", "") + || meta_i_isStrAt(original, (current - 3), "IAU", "EAU", "") ) ); if (!silent) { - if (check(meta_add_str(primary, "KS")) != 0) goto end_free; - if (check(meta_add_str(secondary, "KS")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "KS")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "KS")) != 0) goto end_free; } - current += (meta_is_str_at(original, (current + 1), "C", "X", "")) ? 2 : 1; + current += (meta_i_isStrAt(original, (current + 1), "C", "X", "")) ? 2 : 1; break; } @@ -1253,18 +1253,18 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod /** Chinese pinyin e.g. 'zhao' **/ if (next_char == 'H') { - if (check(meta_add_str(primary, "J")) != 0) goto end_free; - if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; current += 2; break; } const bool has_t_sound = ( - meta_is_str_at(original, (current + 1), "ZO", "ZI", "ZA", "") - || (is_slavo_germanic && current > 0 && meta_get_char_at(original, (current - 1)) != 'T') + meta_i_isStrAt(original, (current + 1), "ZO", "ZI", "ZA", "") + || (is_slavo_germanic && current > 0 && meta_i_getCharAt(original, (current - 1)) != 'T') ); - if (check(meta_add_str(primary, "S")) != 0) goto end_free; - if (check(meta_add_str(secondary, (has_t_sound) ? "TS" : "S")) != 0) goto end_free; + if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; + if (check(meta_i_addStr(secondary, (has_t_sound) ? "TS" : "S")) != 0) goto end_free; current += (next_char == 'Z') ? 2 : 1; break; } @@ -1283,9 +1283,9 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod end_free: if (UNLIKELY(ret != 0)) fprintf(stderr, "Error: meta_double_metaphone() failed (error code %d).\n", ret); - meta_destroy_string(original); - meta_destroy_string(primary); - meta_destroy_string(secondary); + meta_i_destroyString(original); + meta_i_destroyString(primary); + meta_i_destroyString(secondary); return ret; } From b5f329fd9426af09d946094b8d8cd78b4cd25522 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 18:02:48 -0600 Subject: [PATCH 197/361] Rename incorrectly named functions to follow the style. --- centrallix-lib/include/clusters.h | 34 +++++------ centrallix-lib/src/clusters.c | 58 +++++++++--------- centrallix-lib/tests/test_clusters_00.c | 76 +++++++++++------------ centrallix-lib/tests/test_clusters_01.c | 10 ++-- centrallix-lib/tests/test_clusters_02.c | 10 ++-- centrallix-lib/tests/test_clusters_03.c | 14 ++--- centrallix-lib/tests/test_clusters_04.c | 54 ++++++++--------- centrallix-lib/tests/test_clusters_05.c | 80 ++++++++++++------------- centrallix-lib/tests/test_clusters_06.c | 22 +++---- centrallix-lib/tests/test_utils.h | 6 +- centrallix-sysdoc/string_similarity.md | 10 ++-- centrallix/expression/exp_functions.c | 18 +++--- centrallix/include/double_metaphone.h | 4 +- centrallix/osdrivers/objdrv_cluster.c | 34 +++++------ centrallix/utility/double_metaphone.c | 8 +-- 15 files changed, 219 insertions(+), 219 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 6d366d9f6..b83862965 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -93,16 +93,16 @@ typedef struct /** Edit distance function. **/ -int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); +int caEditDist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); /** Vector functions. **/ -pVector ca_build_vector(const char* str); -unsigned int ca_sparse_len(const pVector vector); -void ca_print_vector(const pVector vector); -void ca_free_vector(pVector sparse_vector); +pVector caBuildVector(const char* str); +unsigned int caSparseLen(const pVector vector); +void caPrintVector(const pVector vector); +void caFreeVector(pVector sparse_vector); /** k-means function. **/ -int ca_kmeans( +int caKmeans( pVector* vectors, const unsigned int num_vectors, const unsigned int num_clusters, @@ -113,16 +113,16 @@ int ca_kmeans( bool auto_seed); /** Vector helper macros. **/ -#define ca_is_empty(vector) (vector[0] == -CA_NUM_DIMS) -/*** Note: Given that CA_NUM_DIMS == 251, ca_build_vector("") will give the - *** vector we check for in the ca_has_no_pairs() macro, [-172, 11, -78], +#define caIsEmpty(vector) (vector[0] == -CA_NUM_DIMS) +/*** Note: Given that CA_NUM_DIMS == 251, caBuildVector("") will give the + *** vector we check for in the caHasNoPairs() macro, [-172, 11, -78], *** which has a single pair of boundary characters. *** If CA_NUM_DIMS is modified, this macro will need to be updated, hence the *** compiler directive causing it to be undefined in this case, likely leading *** to a lot of compiler or linker issues to remind the developer about this. ***/ #if CA_NUM_DIMS == 251 -#define ca_has_no_pairs(vector) \ +#define caHasNoPairs(vector) \ ({ \ __typeof__ (vector) _v = (vector); \ _v[0] == -172 && _v[1] == 11 && _v[2] == -78; \ @@ -130,25 +130,25 @@ int ca_kmeans( #endif /** Comparison functions (see ca_search()). **/ -double ca_cos_compare(void* v1, void* v2); -double ca_lev_compare(void* str1, void* str2); -bool ca_eql(pVector v1, pVector v2); +double caCosCompare(void* v1, void* v2); +double caLevCompare(void* str1, void* str2); +bool caEql(pVector v1, pVector v2); /** Similarity search functions. **/ -void* ca_most_similar( +void* caMostSimilar( void* target, void** data, const unsigned int num_data, double (*similarity)(void*, void*), const double threshold); -pXArray ca_sliding_search( +pXArray caSlidingSearch( void** data, const unsigned int num_data, const unsigned int window_size, double (*similarity)(void*, void*), const double threshold, pXArray maybe_pairs); -pXArray ca_complete_search( +pXArray caCompleteSearch( void** data, const unsigned int num_data, double (*similarity)(void*, void*), @@ -156,6 +156,6 @@ pXArray ca_complete_search( pXArray maybe_pairs); /** Module management functions. **/ -void ca_init(void); +void caInit(void); #endif /* End of .h file. */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 66713cc73..09f92bdd1 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -147,7 +147,7 @@ ca_i_charPairCmp(const void *p1, const void *p2) *** @returns The sparse vector built using the hashed character pairs. ***/ pVector -ca_build_vector(const char* str) +caBuildVector(const char* str) { unsigned char* chars = NULL; CharPair* char_pairs = NULL; @@ -268,7 +268,7 @@ ca_build_vector(const char* str) *** @param sparse_vector The sparse vector being freed. ***/ void -ca_free_vector(pVector sparse_vector) +caFreeVector(pVector sparse_vector) { nmSysFree(sparse_vector); @@ -311,7 +311,7 @@ ca_i_parseVectorToken(const int token, unsigned int* dims_consumed, unsigned int *** @returns The computed length. ***/ unsigned int -ca_sparse_len(const pVector vector) +caSparseLen(const pVector vector) { unsigned int i = 0u; @@ -340,7 +340,7 @@ ca_sparse_len(const pVector vector) *** @param vector The vector to print. ***/ void -ca_print_vector(const pVector vector) +caPrintVector(const pVector vector) { if (vector == NULL) { @@ -348,7 +348,7 @@ ca_print_vector(const pVector vector) return; } - const unsigned int len = ca_sparse_len(vector); + const unsigned int len = caSparseLen(vector); printf("Vector: [%d", vector[0]); for (unsigned int i = 1u; i < len; i++) printf(", %d", vector[i]); @@ -448,7 +448,7 @@ ca_i_sparseSimilarity(const pVector v1, const pVector v2) *** 1 indicates completely different and *** 0 indicates identical. ***/ -#define ca_sparse_dif(v1, v2) (1.0 - ca_i_sparseSimilarity(v1, v2)) +#define ca_i_sparseDif(v1, v2) (1.0 - ca_i_sparseSimilarity(v1, v2)) /*** Calculate the similarity between a sparsely allocated vector and a densely *** allocated centroid by taking their dot product. @@ -490,7 +490,7 @@ ca_i_sparseSimilarityToCentroid(const pVector v1, const pCentroid c1) *** 1 indicates completely different and *** 0 indicates identical. ***/ -#define ca_sparse_dif_to_centroid(v1, c1) (1.0 - ca_i_sparseSimilarityToCentroid(v1, c1)) +#define ca_i_sparseDifToCentroid(v1, c1) (1.0 - ca_i_sparseSimilarityToCentroid(v1, c1)) /*** Computes Levenshtein distance between two strings. *** @@ -503,7 +503,7 @@ ca_i_sparseSimilarityToCentroid(const pVector v1, const pCentroid c1) *** @returns The edit distance between the two strings, or a negative value on error. ***/ int -ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) +caEditDist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) { int result = -1; unsigned int** lev_matrix = NULL; @@ -581,7 +581,7 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const if (unsigned_result > INT_MAX) { fprintf(stderr, - "Warning: Integer overflow detected in ca_edit_dist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", + "Warning: Integer overflow detected in caEditDist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", str1, str2, str1_length, str2_length, unsigned_result, INT_MAX ); } @@ -620,7 +620,7 @@ ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const *** @returns The cosine similarity between the two strings. ***/ double -ca_cos_compare(void* v1, void* v2) +caCosCompare(void* v1, void* v2) { /** Input validation checks. **/ if (v1 == NULL || v2 == NULL) return 0.0; @@ -628,8 +628,8 @@ ca_cos_compare(void* v1, void* v2) /** Input validation checks. **/ const pVector vec1 = v1, vec2 = v2; - const bool v1_empty = (vec1 == NULL || ca_is_empty(vec1) || ca_has_no_pairs(vec1)); - const bool v2_empty = (vec2 == NULL || ca_is_empty(vec2) || ca_has_no_pairs(vec2)); + const bool v1_empty = (vec1 == NULL || caIsEmpty(vec1) || caHasNoPairs(vec1)); + const bool v2_empty = (vec2 == NULL || caIsEmpty(vec2) || caHasNoPairs(vec2)); if (v1_empty && v2_empty) return 1.0; if (v1_empty && !v2_empty) return 0.0; if (!v1_empty && v2_empty) return 0.0; @@ -643,7 +643,7 @@ ca_cos_compare(void* v1, void* v2) *** If both strings are empty, this function returns `1.0` (identical). If *** either OR BOTH strings are NULL, this function returns `0.0`. *** - *** @attention - Note: Unlike `ca_cos_compare()`, punctuation, whitespace, + *** @attention - Note: Unlike `caCosCompare()`, punctuation, whitespace, *** etc. are NOT ignored. In fact, this functions supports strings that *** contain ANY valid ASCII characters other than the NULL-terminator, *** which is used to terminate the string. @@ -659,7 +659,7 @@ ca_cos_compare(void* v1, void* v2) *** or NAN on failure. ***/ double -ca_lev_compare(void* str1, void* str2) +caLevCompare(void* str1, void* str2) { /** Input validation checks. **/ if (str1 == NULL || str2 == NULL) return 0.0; @@ -673,7 +673,7 @@ ca_lev_compare(void* str1, void* str2) if (len1 == 0lu && len2 != 0lu) return 0.0; /** Compute levenshtein edit distance. **/ - const int edit_dist = checkPos(ca_edit_dist((const char*)str1, (const char*)str2, len1, len2)); + const int edit_dist = checkPos(caEditDist((const char*)str1, (const char*)str2, len1, len2)); if (edit_dist < 0) return NAN; /** Normalize edit distance into a similarity measure. **/ @@ -693,14 +693,14 @@ ca_lev_compare(void* str1, void* str2) *** false if any element is different. ***/ bool -ca_eql(pVector v1, pVector v2) +caEql(pVector v1, pVector v2) { /** Edge cases. **/ if (v1 == v2) return true; if (v1 == NULL || v2 == NULL) return false; - const unsigned int len = ca_sparse_len(v1); - if (len != ca_sparse_len(v2)) return false; + const unsigned int len = caSparseLen(v1); + if (len != caSparseLen(v2)) return false; for (unsigned int i = 0u; i < len; i++) if (v1[i] != v2[i]) return false; @@ -731,7 +731,7 @@ ca_i_getClusterSize( /** Allocate space to store clusters as averages are computed. **/ /*** We use nmMalloc() here because this function is usually called *** repeatedly with the same number of clusters at the end of the - *** loop in ca_kmeans(). Also, ca_kmeans() may be called multiple + *** loop in caKmeans(). Also, caKmeans() may be called multiple *** times with the same k value, increasing this benefit. ***/ cluster_sums = checkPtr(nmMalloc(num_clusters * sizeof(double))); @@ -747,7 +747,7 @@ ca_i_getClusterSize( for (unsigned int i = 0u; i < num_vectors; i++) { const unsigned int label = labels[i]; - cluster_sums[label] += ca_sparse_dif_to_centroid(vectors[i], centroids[label]); + cluster_sums[label] += ca_i_sparseDifToCentroid(vectors[i], centroids[label]); cluster_counts[label]++; } @@ -806,7 +806,7 @@ ca_i_getClusterSize( *** @returns 0 if successful, or -1 if an error occurs. ***/ int -ca_kmeans( +caKmeans( pVector* vectors, const unsigned int num_vectors, const unsigned int num_clusters, @@ -882,7 +882,7 @@ ca_kmeans( /** Find nearest centroid. **/ for (unsigned int j = 0u; j < num_clusters; j++) { - const double dist = ca_sparse_dif_to_centroid(vector, centroids[j]); + const double dist = ca_i_sparseDifToCentroid(vector, centroids[j]); if (dist < min_dist) { min_dist = dist; @@ -981,7 +981,7 @@ ca_kmeans( *** array, or NULL if the most similar data did not meet the threshold. ***/ void* -ca_most_similar( +caMostSimilar( void* target, void** data, const unsigned int num_data, @@ -1031,7 +1031,7 @@ ca_most_similar( *** will be that xArray, to allow for chaining. ***/ pXArray -ca_sliding_search( +caSlidingSearch( void** data, const unsigned int num_data, const unsigned int window_size, @@ -1113,19 +1113,19 @@ ca_sliding_search( *** will be that xArray, to allow for chaining. ***/ pXArray -ca_complete_search( +caCompleteSearch( void** data, const unsigned int num_data, double (*similarity)(void*, void*), const double threshold, pXArray maybe_pairs) { - return ca_sliding_search(data, num_data, num_data, similarity, threshold, maybe_pairs); + return caSlidingSearch(data, num_data, num_data, similarity, threshold, maybe_pairs); } /** Initialize the module. **/ void -ca_init(void) +caInit(void) { nmRegister(CENTROID_SIZE, "Centroid"); nmRegister(sizeof(Pair), "Pair"); @@ -1134,5 +1134,5 @@ ca_init(void) } /** Scope cleanup. **/ -#undef ca_sparse_dif -#undef ca_sparse_dif_to_centroid +#undef ca_i_sparseDif +#undef ca_i_sparseDifToCentroid diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c index 141ee7b7f..7b58b439b 100644 --- a/centrallix-lib/tests/test_clusters_00.c +++ b/centrallix-lib/tests/test_clusters_00.c @@ -11,7 +11,7 @@ /* Module: test_clusters_00.c */ /* Author: Israel Fuller */ /* Creation: November 25th, 2025 */ -/* Description: Test the ca_edit_dist() function from clusters.h. */ +/* Description: Test the caEditDist() function from clusters.h. */ /************************************************************************/ #include @@ -31,22 +31,22 @@ static bool doTest(void) bool success = true; /** Kitten tests. **/ - success &= EXPECT_EQL(ca_edit_dist("kitten", "kitten", 0, 0), 0, "%d"); /* 0 edits. */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "skitten", 0, 0), 1, "%d"); /* 1 insert. */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "itten", 0, 0), 1, "%d"); /* 1 delete. */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "mitten", 0, 0), 1, "%d"); /* 1 replace. */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "smitten", 0, 0), 2, "%d"); /* 1 insert and 1 replace. */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "iktten", 0, 0), 1, "%d"); /* 1 transpose. */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "kittens", 0, 0), 1, "%d"); /* 1 insert (end). */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "kitte", 0, 0), 1, "%d"); /* 1 delete (end). */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "kittem", 0, 0), 1, "%d"); /* 1 replace (end). */ - success &= EXPECT_EQL(ca_edit_dist("kitten", "kittne", 0, 0), 1, "%d"); /* 1 transpose (end). */ + success &= EXPECT_EQL(caEditDist("kitten", "kitten", 0, 0), 0, "%d"); /* 0 edits. */ + success &= EXPECT_EQL(caEditDist("kitten", "skitten", 0, 0), 1, "%d"); /* 1 insert. */ + success &= EXPECT_EQL(caEditDist("kitten", "itten", 0, 0), 1, "%d"); /* 1 delete. */ + success &= EXPECT_EQL(caEditDist("kitten", "mitten", 0, 0), 1, "%d"); /* 1 replace. */ + success &= EXPECT_EQL(caEditDist("kitten", "smitten", 0, 0), 2, "%d"); /* 1 insert and 1 replace. */ + success &= EXPECT_EQL(caEditDist("kitten", "iktten", 0, 0), 1, "%d"); /* 1 transpose. */ + success &= EXPECT_EQL(caEditDist("kitten", "kittens", 0, 0), 1, "%d"); /* 1 insert (end). */ + success &= EXPECT_EQL(caEditDist("kitten", "kitte", 0, 0), 1, "%d"); /* 1 delete (end). */ + success &= EXPECT_EQL(caEditDist("kitten", "kittem", 0, 0), 1, "%d"); /* 1 replace (end). */ + success &= EXPECT_EQL(caEditDist("kitten", "kittne", 0, 0), 1, "%d"); /* 1 transpose (end). */ /** Alternate words. **/ - success &= EXPECT_EQL(ca_edit_dist("lawn", "flown", 0, 0), 2, "%d"); /* 1 insert and one replace. */ - success &= EXPECT_EQL(ca_edit_dist("hello", "hello!", 0, 0), 1, "%d"); /* 1 insert (end). */ - success &= EXPECT_EQL(ca_edit_dist("zert", "zerf", 0, 0), 1, "%d"); /* 1 replace (end). */ - success &= EXPECT_EQL(ca_edit_dist("llearr", "lear", 0, 0), 2, "%d"); /* 2 deletes (start & end). */ + success &= EXPECT_EQL(caEditDist("lawn", "flown", 0, 0), 2, "%d"); /* 1 insert and one replace. */ + success &= EXPECT_EQL(caEditDist("hello", "hello!", 0, 0), 1, "%d"); /* 1 insert (end). */ + success &= EXPECT_EQL(caEditDist("zert", "zerf", 0, 0), 1, "%d"); /* 1 replace (end). */ + success &= EXPECT_EQL(caEditDist("llearr", "lear", 0, 0), 2, "%d"); /* 2 deletes (start & end). */ /** Long strings for testing edge cases. **/ char* str1 = "This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string..."; @@ -55,41 +55,41 @@ static bool doTest(void) char* str3 = "This is quite a lengthy string. I do not expect the function to compute any longer string since this one is a full 254 characters. That is plenty, even if someone adds many contact details to their record!! Thus, this test should cover most cases we see."; /** Test edge cases. **/ - success &= EXPECT_EQL(ca_edit_dist("", "", 0, 0), 0, "%d"); /* Identical, empty string: 0 edits. */ - success &= EXPECT_EQL(ca_edit_dist(str1, str1, 0, 0), 0, "%d"); /* Identical, very long strings. */ - success &= EXPECT_EQL(ca_edit_dist(str1, str2, 0, 0), 0, "%d"); /* Identical, very long strings (different pointers). */ - success &= EXPECT_EQL(ca_edit_dist(str2, str3, 0, 0), 133, "%d"); /* 133 edits. */ + success &= EXPECT_EQL(caEditDist("", "", 0, 0), 0, "%d"); /* Identical, empty string: 0 edits. */ + success &= EXPECT_EQL(caEditDist(str1, str1, 0, 0), 0, "%d"); /* Identical, very long strings. */ + success &= EXPECT_EQL(caEditDist(str1, str2, 0, 0), 0, "%d"); /* Identical, very long strings (different pointers). */ + success &= EXPECT_EQL(caEditDist(str2, str3, 0, 0), 133, "%d"); /* 133 edits. */ /** Empty string comparsions. **/ - success &= EXPECT_EQL(ca_edit_dist(str1, "", 0, 0), (int)strlen(str1), "%d"); - success &= EXPECT_EQL(ca_edit_dist(str2, "", 0, 0), (int)strlen(str2), "%d"); - success &= EXPECT_EQL(ca_edit_dist(str3, "", 0, 0), (int)strlen(str3), "%d"); + success &= EXPECT_EQL(caEditDist(str1, "", 0, 0), (int)strlen(str1), "%d"); + success &= EXPECT_EQL(caEditDist(str2, "", 0, 0), (int)strlen(str2), "%d"); + success &= EXPECT_EQL(caEditDist(str3, "", 0, 0), (int)strlen(str3), "%d"); /** Specifying lengths with overflows. **/ - success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 13, 13), 0, "%d"); - success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 13, 0), 9, "%d"); - success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 0, 13), 6, "%d"); - success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 0, 0), 3, "%d"); - success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 19, 0), 3, "%d"); - success &= EXPECT_EQL(ca_edit_dist("A string with edits", "A string without edits", 0, 19), 6, "%d"); + success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 13, 13), 0, "%d"); + success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 13, 0), 9, "%d"); + success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 0, 13), 6, "%d"); + success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 0, 0), 3, "%d"); + success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 19, 0), 3, "%d"); + success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 0, 19), 6, "%d"); /** Test for errors in identical pointer optimizations with specified lengths. **/ char identical_string[] = "Identical String!"; - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 0), 0, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 17), 0, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 17, 0), 0, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 16), 1, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 8), 9, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 5, 6), 1, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 16, 13), 3, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 0, 1), 16, "%d"); - success &= EXPECT_EQL(ca_edit_dist(identical_string, identical_string, 1, 0), 16, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 0), 0, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 17), 0, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 17, 0), 0, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 16), 1, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 8), 9, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 5, 6), 1, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 16, 13), 3, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 1), 16, "%d"); + success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 1, 0), 16, "%d"); return success; } long long test(char** tname) { - *tname = "cluster-00 ca_edit_dist()"; + *tname = "cluster-00 caEditDist()"; return loopTest(doTest) * 36; } diff --git a/centrallix-lib/tests/test_clusters_01.c b/centrallix-lib/tests/test_clusters_01.c index dbf5ef585..37890547a 100644 --- a/centrallix-lib/tests/test_clusters_01.c +++ b/centrallix-lib/tests/test_clusters_01.c @@ -11,7 +11,7 @@ /* Module: test_clusters_01.c */ /* Author: Israel Fuller */ /* Creation: November 25th, 2025 */ -/* Description: Test the ca_edit_dist() function from clusters.h. */ +/* Description: Test the caEditDist() function from clusters.h. */ /************************************************************************/ #include @@ -37,15 +37,15 @@ static bool doTest(void) memset(AAA, 'A', num_edits); memset(BBB, 'B', num_edits); AAA[num_edits] = BBB[num_edits] = '\0'; - success &= EXPECT_EQL(ca_edit_dist(AAA, "", 0, 0), num_edits, "%d"); - success &= EXPECT_EQL(ca_edit_dist("", BBB, 0, 0), num_edits, "%d"); - success &= EXPECT_EQL(ca_edit_dist(AAA, BBB, 0, 0), num_edits, "%d"); + success &= EXPECT_EQL(caEditDist(AAA, "", 0, 0), num_edits, "%d"); + success &= EXPECT_EQL(caEditDist("", BBB, 0, 0), num_edits, "%d"); + success &= EXPECT_EQL(caEditDist(AAA, BBB, 0, 0), num_edits, "%d"); return success; } long long test(char** tname) { - *tname = "cluster-01 ca_edit_dist(): Stress test"; + *tname = "cluster-01 caEditDist(): Stress test"; return loopTest(doTest) * 3; } diff --git a/centrallix-lib/tests/test_clusters_02.c b/centrallix-lib/tests/test_clusters_02.c index 1ec6bc2b8..eec411550 100644 --- a/centrallix-lib/tests/test_clusters_02.c +++ b/centrallix-lib/tests/test_clusters_02.c @@ -11,7 +11,7 @@ /* Module: test_clusters_02.c */ /* Author: Israel Fuller */ /* Creation: November 25th, 2025 */ -/* Description: Test the ca_build_vector() function from clusters.h. */ +/* Description: Test the caBuildVector() function from clusters.h. */ /************************************************************************/ #include @@ -36,10 +36,10 @@ static bool doTest(void) unsigned int index = 0u; pVector free_list[max_index]; #define STORE(v) (free_list[index++] = (v)) - #define vec(s) STORE(ca_build_vector(s)) + #define vec(s) STORE(caBuildVector(s)) /** Edge case: Null string. **/ - success &= EXPECT_EQL(ca_build_vector(NULL), NULL, "%p"); + success &= EXPECT_EQL(caBuildVector(NULL), NULL, "%p"); /** Edge case: Empty string. **/ success &= EXPECT_VEC_EQL(vec(""), ((int[]){-172, 11, -78})); @@ -68,7 +68,7 @@ static bool doTest(void) { pVector cur_vector = free_list[--index]; if (cur_vector == NULL) continue; - else ca_free_vector(cur_vector); + else caFreeVector(cur_vector); } return success; @@ -76,7 +76,7 @@ static bool doTest(void) long long test(char** tname) { - *tname = "cluster-02 ca_build_vector()"; + *tname = "cluster-02 caBuildVector()"; return loopTest(doTest) * 9; } diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c index 2eef4d240..f93bc5f59 100644 --- a/centrallix-lib/tests/test_clusters_03.c +++ b/centrallix-lib/tests/test_clusters_03.c @@ -11,7 +11,7 @@ /* Module: test_clusters_03.c */ /* Author: Israel Fuller */ /* Creation: November 26th, 2025 */ -/* Description: Test the ca_cos_compare() function from clusters.h. */ +/* Description: Test the caCosCompare() function from clusters.h. */ /************************************************************************/ #include @@ -37,11 +37,11 @@ static bool doTest(void) pVector free_list[max_index]; #define STORE(v) (free_list[index++] = (v)) - /** ca_cos_compare() shortcut macro. **/ + /** caCosCompare() shortcut macro. **/ #define cos_cmp(str1, str2) ({ \ - pVector _v1 = STORE(ca_build_vector(str1)); \ - pVector _v2 = STORE(ca_build_vector(str2)); \ - ca_cos_compare(_v1, _v2); \ + pVector _v1 = STORE(caBuildVector(str1)); \ + pVector _v2 = STORE(caBuildVector(str2)); \ + caCosCompare(_v1, _v2); \ }) /** Basic tests of cosine similarity. **/ @@ -78,7 +78,7 @@ static bool doTest(void) { pVector cur_vector = free_list[--index]; if (cur_vector == NULL) continue; - else ca_free_vector(cur_vector); + else caFreeVector(cur_vector); } return success; @@ -86,6 +86,6 @@ static bool doTest(void) long long test(char** tname) { - *tname = "cluster-03 ca_cos_compare()"; + *tname = "cluster-03 caCosCompare()"; return loopTest(doTest) * 11; } diff --git a/centrallix-lib/tests/test_clusters_04.c b/centrallix-lib/tests/test_clusters_04.c index 110d512e5..c7d9f53a2 100644 --- a/centrallix-lib/tests/test_clusters_04.c +++ b/centrallix-lib/tests/test_clusters_04.c @@ -11,7 +11,7 @@ /* Module: test_clusters_04.c */ /* Author: Israel Fuller */ /* Creation: November 26th, 2025 */ -/* Description: Test the ca_lev_compare() function from clusters.h. */ +/* Description: Test the caLevCompare() function from clusters.h. */ /************************************************************************/ #include @@ -32,39 +32,39 @@ static bool doTest(void) bool success = true; /** Basic tests of Levenshtein edit distance similarity. **/ - success &= EXPECT_RANGE(ca_lev_compare("hello", "hello"), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hello", "hello!"), 0.8, 1.0, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hello", "asdfkh"), 0.0, 0.1, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hello", "aaaaaaaaaaaaaaaaa"), 0.0, 0.1, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hello", "nope"), 0.0, 0.2, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hello", "noepo"), 0.15, 0.25, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("below", "hello!"), 0.4, 0.6, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "smitten"), 0.65, 0.85, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hello", "bobbobbobbob"), 0.0, 0.1, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hello", ""), 0.0, 0.05, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("", ""), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("blooooop", "blob"), 0.3, 0.5, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("", "!"), 0.0, 0.01, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("h", "h"), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("hi", "hi"), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", "hello"), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", "hello!"), 0.8, 1.0, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", "asdfkh"), 0.0, 0.1, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", "aaaaaaaaaaaaaaaaa"), 0.0, 0.1, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", "nope"), 0.0, 0.2, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", "noepo"), 0.15, 0.25, "%g"); + success &= EXPECT_RANGE(caLevCompare("below", "hello!"), 0.4, 0.6, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "smitten"), 0.65, 0.85, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", "bobbobbobbob"), 0.0, 0.1, "%g"); + success &= EXPECT_RANGE(caLevCompare("hello", ""), 0.0, 0.05, "%g"); + success &= EXPECT_RANGE(caLevCompare("", ""), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(caLevCompare("blooooop", "blob"), 0.3, 0.5, "%g"); + success &= EXPECT_RANGE(caLevCompare("", "!"), 0.0, 0.01, "%g"); + success &= EXPECT_RANGE(caLevCompare("h", "h"), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(caLevCompare("hi", "hi"), 0.99, 1.0, "%g"); /** Kitten tests with specific edit operations. **/ - success &= EXPECT_RANGE(ca_lev_compare("kitten", "kitten"), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "skitten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "itten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "mitten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "smitten"), 0.7, 0.8, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "iktten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "kittens"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "kitte"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "kittem"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(ca_lev_compare("kitten", "kittne"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "kitten"), 0.99, 1.0, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "skitten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "itten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "mitten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "smitten"), 0.7, 0.8, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "iktten"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "kittens"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "kitte"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "kittem"), 0.8, 0.9, "%g"); + success &= EXPECT_RANGE(caLevCompare("kitten", "kittne"), 0.8, 0.9, "%g"); return success; } long long test(char** tname) { - *tname = "cluster-04 ca_lev_compare()"; + *tname = "cluster-04 caLevCompare()"; return loopTest(doTest) * 25; } diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index 84af473e3..2855b66cf 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -11,7 +11,7 @@ /* Module: test_clusters_05.c */ /* Author: Israel Fuller */ /* Creation: November 26th, 2025 */ -/* Description: Test the ca_most_similar() function from clusters.h. */ +/* Description: Test the caMostSimilar() function from clusters.h. */ /************************************************************************/ #include @@ -66,68 +66,68 @@ static bool doTest(void) bool success = true; /** Check error cases. **/ - success &= EXPECT_STR_EQL(ca_most_similar(NULL, (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", NULL, 2, ca_lev_compare, 0.0), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 0, ca_lev_compare, 0.0), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, NULL, 0.0), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 1.1), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, -0.1), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, INFINITY), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, -INFINITY), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, NAN), NULL); + success &= EXPECT_STR_EQL(caMostSimilar(NULL, (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", NULL, 2, caLevCompare, 0.0), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 0, caLevCompare, 0.0), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, NULL, 0.0), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 1.1), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, -0.1), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, INFINITY), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, -INFINITY), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, NAN), NULL); /** Simple test cases. **/ - success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), "str1"); - success &= EXPECT_STR_EQL(ca_most_similar("str", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), "str1"); - success &= EXPECT_STR_EQL(ca_most_similar("kitten", (void*[]){"str_abc", "str1"}, 2, ca_lev_compare, 0.0), "str1"); - success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, ca_lev_compare, 0.0), "str2"); + success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); + success &= EXPECT_STR_EQL(caMostSimilar("kitten", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); + success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, caLevCompare, 0.0), "str2"); /** Many, identically similar options. */ - success &= EXPECT_STR_EQL(ca_most_similar("kitten", + success &= EXPECT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "itten", "mitten", "iktten", "kittens", "kitte", "kittem", "kittne"}, 8, - ca_lev_compare, 0.0), "skitten"); + caLevCompare, 0.0), "skitten"); /** Pointer-perfect handling. **/ char* target = "string"; - success &= EXPECT_EQL((char*)ca_most_similar(target, (void*[]){"str", target}, 2, ca_lev_compare, 0.0), target, "%s"); + success &= EXPECT_EQL((char*)caMostSimilar(target, (void*[]){"str", target}, 2, caLevCompare, 0.0), target, "%s"); /** List overflow. **/ - success &= EXPECT_STR_EQL(ca_most_similar("target", (void*[]){"str1", "targets", "target", "walmart"}, 2, ca_lev_compare, 0.0), "targets"); + success &= EXPECT_STR_EQL(caMostSimilar("target", (void*[]){"str1", "targets", "target", "walmart"}, 2, caLevCompare, 0.0), "targets"); /** Threshold exceeded. **/ - success &= EXPECT_STR_EQL(ca_most_similar("blob", (void*[]){"blooooop", "targets", "string"}, 3, ca_lev_compare, 0.0), "blooooop"); - success &= EXPECT_STR_EQL(ca_most_similar("blob", (void*[]){"blooooop", "targets", "string"}, 3, ca_lev_compare, 0.5), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("hello", (void*[]){"bane", "noepo", "stars"}, 3, ca_lev_compare, 0.0), "noepo"); - success &= EXPECT_STR_EQL(ca_most_similar("hello", (void*[]){"bane", "noepo", "stars"}, 3, ca_lev_compare, 0.25), NULL); - success &= EXPECT_STR_EQL(ca_most_similar("kitten", (void*[]){"skitten", "fit"}, 2, ca_lev_compare, 0.0), "skitten"); - success &= EXPECT_STR_EQL(ca_most_similar("kitten", (void*[]){"skitten", "fit"}, 2, ca_lev_compare, 0.9), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.0), "blooooop"); + success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.5), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("hello", (void*[]){"bane", "noepo", "stars"}, 3, caLevCompare, 0.0), "noepo"); + success &= EXPECT_STR_EQL(caMostSimilar("hello", (void*[]){"bane", "noepo", "stars"}, 3, caLevCompare, 0.25), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "fit"}, 2, caLevCompare, 0.0), "skitten"); + success &= EXPECT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "fit"}, 2, caLevCompare, 0.9), NULL); /** Make an array to STORE() pointers to vectors so we can free them. **/ const unsigned int max_index = 32u; unsigned int index = 0u; pVector free_list[max_index]; #define STORE(v) (free_list[index++] = (v)) - #define vec(s) STORE(ca_build_vector(s)) + #define vec(s) STORE(caBuildVector(s)) /** Alternative similarity function. **/ pVector hello = vec("hello"), fellow = vec("fellow"), felon = vec("felon"); pVector held = vec("held"), zephora = vec("zephora"), hexza = vec("hexza"); pVector hello_there = vec("hello there"), hello_world = vec("hello world"); pVector hellow_there = vec("hellow there"); - success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){fellow, felon, hello, held}, 4, ca_cos_compare, 0.0), hello); - success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){zephora, hello_world, hexza}, 3, ca_cos_compare, 0.0), hello_world); - success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){zephora, hello_world}, 1, ca_cos_compare, 0.0), zephora); - success &= EXPECT_VEC_EQL(ca_most_similar(hello, (void*[]){zephora}, 1, ca_cos_compare, 0.0), zephora); - success &= EXPECT_VEC_EQL(ca_most_similar(hello_there, (void*[]){hello_world, zephora, hellow_there, hexza}, 4, ca_cos_compare, 0.0), hellow_there); - success &= EXPECT_VEC_EQL(ca_most_similar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, ca_cos_compare, 0.0), hello_world); - success &= EXPECT_VEC_EQL(ca_most_similar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, ca_cos_compare, 0.8), NULL); + success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){fellow, felon, hello, held}, 4, caCosCompare, 0.0), hello); + success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora, hello_world, hexza}, 3, caCosCompare, 0.0), hello_world); + success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora, hello_world}, 1, caCosCompare, 0.0), zephora); + success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora}, 1, caCosCompare, 0.0), zephora); + success &= EXPECT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there, hexza}, 4, caCosCompare, 0.0), hellow_there); + success &= EXPECT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, caCosCompare, 0.0), hello_world); + success &= EXPECT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, caCosCompare, 0.8), NULL); /** Special characters (ignored by the similarity function). **/ pVector yip = vec("Yippee!!!"); pVector str1 = vec("@*#((%^!&@*-+!"), str2 = vec(">>->y i!&P^^_pe$/\n?e"), str3 = vec("yip"); success &= EXPECT_VEC_EQL(yip, str2); - success &= EXPECT_VEC_EQL(ca_most_similar(yip, (void*[]){str1, str2, str3}, 3, ca_cos_compare, 0.0), str2); - success &= EXPECT_VEC_EQL(ca_most_similar(yip, (void*[]){str1, str2, str3}, 3, ca_cos_compare, 1.0), str2); + success &= EXPECT_VEC_EQL(caMostSimilar(yip, (void*[]){str1, str2, str3}, 3, caCosCompare, 0.0), str2); + success &= EXPECT_VEC_EQL(caMostSimilar(yip, (void*[]){str1, str2, str3}, 3, caCosCompare, 1.0), str2); /** Clean up scope. **/ #undef STORE @@ -145,7 +145,7 @@ static bool doTest(void) { pVector cur_vector = free_list[--index]; if (cur_vector == NULL) continue; - else ca_free_vector(cur_vector); + else caFreeVector(cur_vector); } /** Set up the mock similarity function. **/ @@ -159,8 +159,8 @@ static bool doTest(void) if (check(xhAdd(&sim_table, "str1|str", (void*)&str1_str)) != 0) return false; if (check(xhAdd(&sim_table, "str1|str2", (void*)&str1_str2)) != 0) return false; if (check(xhAdd(&sim_table, "str1|eight", (void*)&str1_eight)) != 0) return false; - success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.0), "eight"); - success &= EXPECT_STR_EQL(ca_most_similar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.9), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.0), "eight"); + success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.9), NULL); if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; /** Nans are skipped. **/ @@ -168,8 +168,8 @@ static bool doTest(void) if (check(xhAdd(&sim_table, "val|nan", (void*)&val_nan)) != 0) return false; if (check(xhAdd(&sim_table, "val|vals", (void*)&val_vals)) != 0) return false; if (check(xhAdd(&sim_table, "val|val", (void*)&val_val)) != 0) return false; - success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.0), "nan"); - success &= EXPECT_STR_EQL(ca_most_similar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.9), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.0), "nan"); + success &= EXPECT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.9), NULL); if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; /** Clean up. **/ @@ -180,6 +180,6 @@ static bool doTest(void) long long test(char** tname) { - *tname = "cluster-05 ca_most_similar()"; + *tname = "cluster-05 caMostSimilar()"; return loopTest(doTest) * 35; } diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index c4bcb20df..bba789efb 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -70,20 +70,20 @@ static bool doTest(void) }; /** Check error cases. **/ - success &= EXPECT_EQL(ca_complete_search(NULL, 6, ca_lev_compare, 0.8, NULL), NULL, "%p"); - success &= EXPECT_EQL(ca_complete_search(data, 0, ca_lev_compare, 0.8, NULL), NULL, "%p"); - success &= EXPECT_EQL(ca_complete_search(data, 6, NULL, 0.8, NULL), NULL, "%p"); - success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, 1.1, NULL), NULL, "%p"); - success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, -0.1, NULL), NULL, "%p"); - success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, INFINITY, NULL), NULL, "%p"); - success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, -INFINITY, NULL), NULL, "%p"); - success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, NAN, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(NULL, 6, caLevCompare, 0.8, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 0, caLevCompare, 0.8, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 6, NULL, 0.8, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, 1.1, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, -0.1, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, INFINITY, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, -INFINITY, NULL), NULL, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, NAN, NULL), NULL, "%p"); /** Test complete search. **/ { XArray xPairs; if (check(xaInit(&xPairs, 4)) != 0) return false; - success &= EXPECT_EQL(ca_complete_search(data, 6, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); + success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) { @@ -107,7 +107,7 @@ static bool doTest(void) { XArray xPairs; if (check(xaInit(&xPairs, 4)) != 0) return false; - success &= EXPECT_EQL(ca_sliding_search(data, 6, 5, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); + success &= EXPECT_EQL(caSlidingSearch(data, 6, 5, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) { @@ -131,7 +131,7 @@ static bool doTest(void) { XArray xPairs; if (check(xaInit(&xPairs, 4)) != 0) return false; - success &= EXPECT_EQL(ca_sliding_search(data, 6, 2, ca_lev_compare, 0.8, &xPairs), &xPairs, "%p"); + success &= EXPECT_EQL(caSlidingSearch(data, 6, 2, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) { diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 1be4a51f7..e93d506c6 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -140,12 +140,12 @@ ({ \ pVector _v1 = (v1); \ pVector _v2 = (v2); \ - int success = ca_eql(_v1, _v2); \ + int success = caEql(_v1, _v2); \ if (!success) \ { \ printf(" > Expected %s (1.) to equal %s (2.) at %s:%d, but got:\n", #v1, #v2, __FILE__, __LINE__); \ - printf(" > 1. (%p) ", _v1); ca_print_vector(_v1); printf("\n"); \ - printf(" > 2. (%p) ", _v2); ca_print_vector(_v2); printf("\n"); \ + printf(" > 1. (%p) ", _v1); caPrintVector(_v1); printf("\n"); \ + printf(" > 2. (%p) ", _v2); caPrintVector(_v2); printf("\n"); \ fflush(stdout); \ } \ success; \ diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 805b281e5..7717a2a8c 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -96,16 +96,16 @@ As noted above, the vast majority of elements in a vector generated by a typical **Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. -**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. +**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `caSparseLen()` function can be used to do this. Also, the `caBuildVector()` and `caFreeVector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. ### Computing Similarity -Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their corresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. +Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their corresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `caCosCompare()` function. ## Levenshtein Similarity The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using Levenshtein edit distance. -The Levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. +The Levenshtein similarity of two strings can be compared using the `caLevCompare()` function. ## Clustering @@ -120,7 +120,7 @@ When clustering data using the [k-means](https://en.wikipedia.org/wiki/K-means_c The implementation used in `clusters.c` also allows the programmer to specify a maximum number of iterations (called `max_iter` in the code) to prevent this process from running forever. Additionally, successive iterations can give diminishing results or even produce clusters that are slightly worse. To improve performance, the programmer can also specify a minimum improvement threshold (called `min_improvement`). Clusters must become more similar by at least this amount each iteration, otherwise the algorithm ends, even if the maximum number of iterations has not yet been reached. -The `ca_kmeans()` function can be invoked using [the cosine comparison string vectors](#string-vectors) (see above) to cluster them into similar clusters. +The `caKmeans()` function can be invoked using [the cosine comparison string vectors](#string-vectors) (see above) to cluster them into similar clusters. ### K-means++ Clustering **Not yet implemented** @@ -136,7 +136,7 @@ This method is also very similar to k-means, except that [k-medoids](https://en. ### Sliding Clusters A far more basic method of "clustering" is to simply sort all data alphabetically, then, instead of comparing each string to all other strings, it can be compared to only the next `n` strings. Of course, differences near the start of a string (for example, "fox" vs. "box") will cause those strings to sort far away from each other, leading them to be completely missed. -Sorting using a similarity measure, such as `ca_cos_compare()` or `ca_lev_compare()` would resolve this issue. However, these comparison functions do not meet the transitivity requirement for sorting, which is that `(A < B) & (B < C) -> (A < C)`. For example, "car" is similar to "boxcar", which is also similar to "box". However, "car" and "box" are not similar at all. +Sorting using a similarity measure, such as `caCosCompare()` or `caLevCompare()` would resolve this issue. However, these comparison functions do not meet the transitivity requirement for sorting, which is that `(A < B) & (B < C) -> (A < C)`. For example, "car" is similar to "boxcar", which is also similar to "box". However, "car" and "box" are not similar at all. Additionally, sorting by the cosine vectors (similarly to how we cluster by them when using k-means) was proposed, but further investigation showed that this was also not possible. diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index a8f78ddf6..51d44afd6 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4587,7 +4587,7 @@ exp_fn_metaphone(pExpression tree) } /** Compute Double Metaphone. **/ - tmp = meta_double_metaphone(str, &primary, &secondary); + tmp = metaDoubleMetaphone(str, &primary, &secondary); if (UNLIKELY(tmp != 0)) { mssError(1, "EXP", "Double metaphone computation failed (error code %d).", tmp); @@ -4656,8 +4656,8 @@ exp_fn_compare(pExpression tree) int ret; /** Build vectors. **/ - const pVector v1 = checkPtr(ca_build_vector(str1)); - const pVector v2 = checkPtr(ca_build_vector(str2)); + const pVector v1 = checkPtr(caBuildVector(str1)); + const pVector v2 = checkPtr(caBuildVector(str2)); if (UNLIKELY(v1 == NULL || v2 == NULL)) { mssError(1, "EXP", @@ -4669,20 +4669,20 @@ exp_fn_compare(pExpression tree) else { /** Compute the similarity. **/ - tree->Types.Double = ca_cos_compare(v1, v2); + tree->Types.Double = caCosCompare(v1, v2); tree->DataType = DATA_T_DOUBLE; ret = 0; } /** Clean up. **/ - if (LIKELY(v1 != NULL)) ca_free_vector(v1); - if (LIKELY(v2 != NULL)) ca_free_vector(v2); + if (LIKELY(v1 != NULL)) caFreeVector(v1); + if (LIKELY(v2 != NULL)) caFreeVector(v2); if (UNLIKELY(ret == -1)) goto err; else return 0; } else { /* lev_compare() */ - const double lev_sim = checkDouble(ca_lev_compare(str1, str2)); + const double lev_sim = checkDouble(caLevCompare(str1, str2)); if (UNLIKELY(isnan(lev_sim))) { mssError(1, "EXP", @@ -4733,7 +4733,7 @@ exp_fn_levenshtein(pExpression tree) /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ - const int edit_dist = checkPos(ca_edit_dist(str1, str2, 0lu, 0lu)); + const int edit_dist = checkPos(caEditDist(str1, str2, 0lu, 0lu)); if (UNLIKELY(edit_dist < 0)) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.", tree->Name, str1, str2); @@ -4871,7 +4871,7 @@ int exp_fn_argon2id(pExpression tree, pParamObjects objlist, pExpression passwor int exp_internal_DefineFunctions() { /** Initialize clustering library. **/ - ca_init(); + caInit(); /** Function list for EXPR_N_FUNCTION nodes **/ xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h index 5f07de7f6..e27d2d52d 100644 --- a/centrallix/include/double_metaphone.h +++ b/centrallix/include/double_metaphone.h @@ -37,7 +37,7 @@ /* b) the "Artistic License" (5). */ /* */ /* Citations: */ -/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ +/* 1: https://github.com/gitpan/Text-metaDoubleMetaphone */ /* 2: https://dev.perl.org/licenses/gpl1.html */ /* 3: http://www.fsf.org */ /* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ @@ -78,6 +78,6 @@ /* standards so that it could be included here. */ /************************************************************************/ -int meta_double_metaphone(const char* str, char** primary_code, char** secondary_code); +int metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code); #endif /* End of .h file. */ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 3ac102788..6fbc8fefa 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -162,8 +162,8 @@ double (*cluster_i_similarityMeasureToFunction(SimilarityMeasure similarity_meas { switch (similarity_measure) { - case SIMILARITY_COSINE: return ca_cos_compare; - case SIMILARITY_LEVENSHTEIN: return ca_lev_compare; + case SIMILARITY_COSINE: return caCosCompare; + case SIMILARITY_LEVENSHTEIN: return caLevCompare; default: mssError(1, "Cluster", "Unknown similarity measure \"%s\" (%d).", @@ -697,7 +697,7 @@ static void cluster_i_giveHint(const char* hint) static bool cluster_i_tryHint(char* value, char** valid_values, const unsigned int n_valid_values) { - char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.25); + char* guess = caMostSimilar(value, (void**)valid_values, n_valid_values, caLevCompare, 0.25); if (guess == NULL) return false; /* No hint. */ /** Issue hint. **/ @@ -1894,7 +1894,7 @@ cluster_i_freeSourceData(pSourceData source_data) { if (source_data->Vectors[i] != NULL) { - ca_free_vector(source_data->Vectors[i]); + caFreeVector(source_data->Vectors[i]); source_data->Vectors[i] = NULL; } } @@ -2161,7 +2161,7 @@ cluster_i_sizeOfSourceData(pSourceData source_data) if (source_data->Vectors != NULL) { for (unsigned int i = 0u; i < source_data->nDatas; i++) - size += ca_sparse_len(source_data->Vectors[i]) * sizeof(int); + size += caSparseLen(source_data->Vectors[i]) * sizeof(int); size += source_data->nDatas * sizeof(pVector); } size += sizeof(SourceData); @@ -2352,21 +2352,21 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (strlen(data) == 0) continue; /** Convert the string to a vector. **/ - pVector vector = ca_build_vector(data); + pVector vector = caBuildVector(data); if (UNLIKELY(vector == NULL)) { mssError(1, "Cluster", "Failed to build vectors for string \"%s\".", data); goto end_free; } - if (UNLIKELY(ca_is_empty(vector))) + if (UNLIKELY(caIsEmpty(vector))) { mssError(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); goto end_free; } - if (ca_has_no_pairs(vector)) + if (caHasNoPairs(vector)) { /** Skip pVector with only a single pair of boundary characters. **/ - ca_free_vector(vector); + caFreeVector(vector); continue; } @@ -2502,7 +2502,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) for (unsigned int i = 0u; i < vector_xarray.nItems; i++) { pVector vec = vector_xarray.Items[i]; - if (vec != NULL) ca_free_vector(vec); + if (vec != NULL) caFreeVector(vec); } check(xaDeInit(&vector_xarray)); /* Failure ignored. */ } @@ -2613,17 +2613,17 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) goto err_free; } - /** Allocate labels. Note: ca_kmeans() initializes labels for us. **/ + /** Allocate labels. Note: caKmeans() initializes labels for us. **/ const size_t labels_size = source_data->nDatas * sizeof(unsigned int); unsigned int* labels = checkPtr(nmSysMalloc(labels_size)); if (UNLIKELY(labels == NULL)) goto err_free; - /** Handle seed for ca_kmeans(). **/ + /** Handle seed for caKmeans(). **/ const bool auto_seed = (cluster_data->Seed == CI_NO_SEED); if (!auto_seed) srand(cluster_data->Seed); - /** Run ca_kmeans(). **/ - const bool successful = (check(ca_kmeans( + /** Run caKmeans(). **/ + const bool successful = (check(caKmeans( source_data->Vectors, source_data->nDatas, cluster_data->nClusters, @@ -2806,7 +2806,7 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) } /** Execute sliding search. **/ - pairs = checkPtr(ca_sliding_search( + pairs = checkPtr(caSlidingSearch( data, source_data->nDatas, cluster_data->WindowSize, @@ -2867,7 +2867,7 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) } /** Execute complete search. **/ - const pXArray cluster_pairs = checkPtr(ca_complete_search( + const pXArray cluster_pairs = checkPtr(caCompleteSearch( filtered_data, cluster->Size, similarity_function, @@ -2878,7 +2878,7 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) if (UNLIKELY(cluster_pairs == NULL)) { mssError(1, "Cluster", - "Failed to compute ca_complete_search() with %s similarity measure.", + "Failed to compute caCompleteSearch() with %s similarity measure.", cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) ); goto err_free; diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index c8a923a32..d0cf4e8dc 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -34,7 +34,7 @@ /* b) the "Artistic License" (5). */ /* */ /* Citations: */ -/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ +/* 1: https://github.com/gitpan/Text-metaDoubleMetaphone */ /* 2: https://dev.perl.org/licenses/gpl1.html */ /* 3: http://www.fsf.org */ /* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ @@ -330,7 +330,7 @@ meta_i_addStr(pMetaString s, const char* new_str) *** ```c *** char* primary_code; *** char* secondary_code; - *** meta_double_metaphone(input, &primary_code, &secondary_code); + *** metaDoubleMetaphone(input, &primary_code, &secondary_code); *** ``` *** *** @param str The string to compute. @@ -341,7 +341,7 @@ meta_i_addStr(pMetaString s, const char* new_str) *** @returns 0 if successful, or -1 if an error occurs. ***/ int -meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) +metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) { int ret = -1; @@ -1282,7 +1282,7 @@ meta_double_metaphone(const char* str, char** primary_code, char** secondary_cod ret = 0; end_free: - if (UNLIKELY(ret != 0)) fprintf(stderr, "Error: meta_double_metaphone() failed (error code %d).\n", ret); + if (UNLIKELY(ret != 0)) fprintf(stderr, "Error: metaDoubleMetaphone() failed (error code %d).\n", ret); meta_i_destroyString(original); meta_i_destroyString(primary); meta_i_destroyString(secondary); From cea1cfcbb169d9b67514689be86872d13d135c89 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 18:08:32 -0600 Subject: [PATCH 198/361] Drop a bad test case. --- centrallix-lib/tests/test_clusters_05.c | 1 - 1 file changed, 1 deletion(-) diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index 2855b66cf..63c8b23a3 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -68,7 +68,6 @@ static bool doTest(void) /** Check error cases. **/ success &= EXPECT_STR_EQL(caMostSimilar(NULL, (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), NULL); success &= EXPECT_STR_EQL(caMostSimilar("str", NULL, 2, caLevCompare, 0.0), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 0, caLevCompare, 0.0), NULL); success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, NULL, 0.0), NULL); success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 1.1), NULL); success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, -0.1), NULL); From 46686abafc7c201d3f880c3c3e43b28807f0b4ab Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 18:08:42 -0600 Subject: [PATCH 199/361] Fix memory leak in tests. --- centrallix-lib/tests/test_clusters_00.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c index 7b58b439b..1e5918fa0 100644 --- a/centrallix-lib/tests/test_clusters_00.c +++ b/centrallix-lib/tests/test_clusters_00.c @@ -84,6 +84,9 @@ static bool doTest(void) success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 16, 13), 3, "%d"); success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 1), 16, "%d"); success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 1, 0), 16, "%d"); + + /** Clean up. **/ + free(str2); return success; } From 276252d983cc35732be680b53b66d106e72d6d9b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 11 Sep 2026 18:10:57 -0600 Subject: [PATCH 200/361] Add tests for null-terminated list detection in caMostSimilar(). --- centrallix-lib/tests/test_clusters_05.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index 63c8b23a3..3886eba68 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -93,6 +93,12 @@ static bool doTest(void) /** List overflow. **/ success &= EXPECT_STR_EQL(caMostSimilar("target", (void*[]){"str1", "targets", "target", "walmart"}, 2, caLevCompare, 0.0), "targets"); + /** Length detection on null-terminated lists. **/ + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1", NULL}, 0, caLevCompare, 0.0), "str1"); + success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){NULL}, 0, caLevCompare, 0.0), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string", NULL}, 0, caLevCompare, 0.5), NULL); + success &= EXPECT_STR_EQL(caMostSimilar("target", (void*[]){"str1", "targets", "target", NULL}, 2, caLevCompare, 0.0), "targets"); + /** Threshold exceeded. **/ success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.0), "blooooop"); success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.5), NULL); @@ -180,5 +186,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "cluster-05 caMostSimilar()"; - return loopTest(doTest) * 35; + return loopTest(doTest) * 39; } From aa2fc76ab528fc6f5fa43133b9d2b1442da1712b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 14 Sep 2026 14:34:09 -0600 Subject: [PATCH 201/361] Improve the clarity of a warning. --- centrallix-sysdoc/Libraries/xarray.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 561aad3b9..47857d700 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -68,7 +68,7 @@ The XArray (xa) module is intended to manage sized growable arrays, similar to a - 📖 **Note**: Some code occasionally sets `nAlloc` to 0 after an XArray struct has been deinitialized to indicate that the relevant data is no longer allocated. Other than this, it is only used internally by the library. -- ⚠️ **Warning**: Do not mix calls to [`xaNew()`](#xanew)/[`xaFree()`](#xafree) with calls to [`xaInit()`](#xainit)/[`xaDeInit()`](#xadeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. +- ⚠️ **Warning**: Do not mix calls to [`xaNew()`](#xanew)/[`xaFree()`](#xafree) with calls to [`xaInit()`](#xainit)/[`xaDeInit()`](#xadeinit). Freeing a struct allocated with init or deinitting a struct allocated with new is undefined behavior and typically causes bad frees, crashes, and memory leaks. ## xaNew() From 2860ff84ba071a148faa31c2b694c722646444c3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 13:58:40 -0600 Subject: [PATCH 202/361] Trim comment. --- centrallix-lib/src/xhash.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index ccc17241d..07dd627d3 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -344,10 +344,6 @@ xh_i_freeEntry(pXHashEntry entry, void* arg) *** The first element is a function pointer to the free function, which *** we invoke using the provided entry and the free_arg, specified as the *** second element of the array. - *** - *** Interestingly, you can write this code in one line like this: - *** ((void (*)(pXHashEntry, void*))((void**)arg)[0])(entry, ((void**)arg)[1]); - *** But I value code readability, so fortunately, I can't be THAT clever... ***/ void** args = (void**)arg; void (*free_fn)(pXHashEntry, void*) = args[0]; From b6bbdd092d624a1774893862f642f7109eb71da0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 14:21:44 -0600 Subject: [PATCH 203/361] Simplify xhash.c code that was confusing to read. Refactor xhClearKeySafe() to use a simpler design with a loop. Remove xh_i_freeEntry(). --- centrallix-lib/src/xhash.c | 63 +++++++++++--------------------------- 1 file changed, 18 insertions(+), 45 deletions(-) diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index 07dd627d3..5ce67fe83 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -325,44 +325,14 @@ xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_a return 0; } -/*** A helper function for `xhClearKeySafe()`. Deallocates a hash table entry - *** after calling the appropriate free function with the provided free arg. - *** - *** @param entry A pointer to the hash table entry to be freed (passing NULL - *** causes undefined behavior). - *** @param arg A pointer to a void* array with 2 elements: The first element - *** is a function pointer to the free function, which we invoke using the - *** provided entry and the free_arg, specified as the second element of - *** this array. - *** @returns 0, success. - ****/ -static int -xh_i_freeEntry(pXHashEntry entry, void* arg) - { - /*** The passed void* actually points to a void* array with 2 elements. - *** - *** The first element is a function pointer to the free function, which - *** we invoke using the provided entry and the free_arg, specified as the - *** second element of the array. - ***/ - void** args = (void**)arg; - void (*free_fn)(pXHashEntry, void*) = args[0]; - free_fn(entry, args[1]); - - /** Free the entry. **/ - nmFree(entry, sizeof(XHashEntry)); - - return 0; - } - -/*** Clears all contents from a hash table. The free function is passed each - *** hash entry struct, allowing it to free both the value and key, if needed. +/*** Clears all contents from a hash table using a free function. *** *** @param this The affected hash table (passing NULL causes undefined *** behavior). - *** @param free_fn A pointer to a free function which will be called with a - *** pointer to each `XHashEntry` before they are deallocated. It is also - *** passed a `void*`, which will be `free_arg` (the third argument). + *** @param free_fn A pointer to a free function, which is passed each hash + *** entry struct and is responsible for freeing the key, the value, and + *** any data the they own (but not the passed entry itself). The function + *** is also passed a `void*` argument, which is `free_arg`. *** @param free_arg The void pointer value passed to the free function. *** @returns 0 if successful, or *** -1 if `free_fn()` is `NULL`. @@ -373,16 +343,19 @@ xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free if (free_fn == NULL) return -1; /** Free each row. **/ - void* args[2] = {free_fn, free_arg}; - const int ret = xhForEach(this, xh_i_freeEntry, args); - - /** Mark all rows as empty. **/ - for (int i = 0; i < this->nRows; i++) - this->Rows.Items[i] = NULL; + for (int row = 0; row < this->nRows; row++) + { + pXHashEntry entry = (pXHashEntry)(this->Rows.Items[row]); + while (entry != NULL) + { + pXHashEntry next = entry->Next; + free_fn(entry, free_arg); + nmFree(entry, sizeof(XHashEntry)); + entry = next; + } + this->Rows.Items[row] = NULL; + } this->nItems = 0; - /*** We are successful only if the free function didn't fail (and it should - *** not be able to fail). - ***/ - return ret; + return 0; } From 991a581fe28de432900723ec06d4433462871140 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 14:21:49 -0600 Subject: [PATCH 204/361] Remove confusing unused functionality from the cacheFree functions in the cluster driver. --- centrallix/osdrivers/objdrv_cluster.c | 45 +++++++-------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 6fbc8fefa..6d8433181 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4636,18 +4636,11 @@ cluster_i_printEntry(pXHashEntry entry, void* arg) // LINK #functions /** Intended for use in `xhClearKeySafe()`. **/ static void -cluster_i_cacheFreeSourceData(pXHashEntry entry, void* path) +cluster_i_cacheFreeSourceData(pXHashEntry entry, void* unused) { - /** Extract hash entry. **/ - char* key = entry->Key; - pSourceData source_data = (pSourceData)entry->Data; - - /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; - - /** Free data. **/ - cluster_i_freeSourceData(source_data); - nmSysFree(key); + /** Free the data, the key, and the entry itself. **/ + cluster_i_freeSourceData((pSourceData)entry->Data); + nmSysFree(entry->Key); return; } @@ -4656,18 +4649,11 @@ cluster_i_cacheFreeSourceData(pXHashEntry entry, void* path) // LINK #functions /** Intended for use in `xhClearKeySafe()`. **/ static void -cluster_i_cacheFreeCluster(pXHashEntry entry, void* path) +cluster_i_cacheFreeCluster(pXHashEntry entry, void* unused) { - /** Extract hash entry. **/ - char* key = entry->Key; - pClusterData cluster_data = (pClusterData)entry->Data; - - /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; - - /** Free data. **/ - cluster_i_freeClusterData(cluster_data, false); - nmSysFree(key); + /** Free the data, the key, and the entry itself. **/ + cluster_i_freeClusterData((pClusterData)entry->Data, false); + nmSysFree(entry->Key); return; } @@ -4676,18 +4662,11 @@ cluster_i_cacheFreeCluster(pXHashEntry entry, void* path) // LINK #functions /** Intended for use in `xhClearKeySafe()`. **/ static void -cluster_i_cacheFreeSearch(pXHashEntry entry, void* path) +cluster_i_cacheFreeSearch(pXHashEntry entry, void* unused) { - /** Extract hash entry. **/ - char* key = entry->Key; - pSearchData search_data = (pSearchData)entry->Data; - - /** If a path is provided, check that it matches the start of the key. **/ - if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; - - /** Free data. **/ - cluster_i_freeSearchData(search_data); - nmSysFree(key); + /** Free the data, the key, and the entry itself. **/ + cluster_i_freeSearchData((pSearchData)entry->Data); + nmSysFree(entry->Key); return; } From 96b69ffbe9a76be91f9795462b960a959c5c6e99 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 14:29:50 -0600 Subject: [PATCH 205/361] Refactor xhForEach() using variable arguments. --- centrallix-lib/include/xhash.h | 4 +++- centrallix-lib/src/xhash.c | 24 ++++++++++++++++-------- centrallix-sysdoc/Libraries/xhash.md | 4 ++-- centrallix/osdrivers/objdrv_cluster.c | 19 +++++++++---------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/centrallix-lib/include/xhash.h b/centrallix-lib/include/xhash.h index 53cc8d892..0cfbafd0b 100644 --- a/centrallix-lib/include/xhash.h +++ b/centrallix-lib/include/xhash.h @@ -20,6 +20,8 @@ /************************************************************************/ +#include + #ifdef CXLIB_INTERNAL #include "xarray.h" #else @@ -54,7 +56,7 @@ int xhAdd(pXHashTable this, char* key, char* data); int xhRemove(pXHashTable this, char* key); char* xhLookup(pXHashTable this, char* key); int xhClear(pXHashTable this, int (*free_fn)(), void* free_arg); -int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); +int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, va_list), ...); int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg); #endif /* _XHASH_H */ diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index 5ce67fe83..2fdeb7246 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "xhash.h" #include "xarray.h" @@ -295,18 +296,19 @@ xhClear(pXHashTable this, int (*free_fn)(), void* free_arg) *** @param this The affected hash table (passing NULL causes undefined *** behavior). *** @param callback_fn A callback function to be called on each hash table - *** entry. It takes 2 parameters: the current hash table entry and a void* - *** argument specified using each_arg. If any invocation of the callback - *** function returns a value other than 0, xhForEach() will immediately - *** fail, returning that value as the error code. - *** @param each_arg An additional argument which will be passed to each - *** invocation of the callback function. + *** entry. It takes 2 parameters: the current hash table entry and a + *** va_list of the additional arguments passed to this function. If any + *** invocation of the callback function returns a value other than 0, + *** xhForEach() will immediately fail, returning that value as the error + *** code. + *** @param ... Additional arguments, which are passed to each invocation of + *** the callback function as a va_list. *** @returns 0 if the function executes successfully. *** 1 if the callback function is NULL. *** n (where n != 0) if the callback function returns n. ***/ int -xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg) +xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, va_list), ...) { if (callback_fn == NULL) return 1; @@ -316,7 +318,13 @@ xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_a while (entry != NULL) { pXHashEntry next = entry->Next; - const int ret = callback_fn(entry, each_arg); + + /** Start the arguments fresh so each call gets a fresh args value. **/ + va_list args; + va_start(args, callback_fn); + const int ret = callback_fn(entry, args); + va_end(args); + if (ret != 0) return ret; entry = next; } diff --git a/centrallix-sysdoc/Libraries/xhash.md b/centrallix-sysdoc/Libraries/xhash.md index 65ab27b8b..a06907686 100644 --- a/centrallix-sysdoc/Libraries/xhash.md +++ b/centrallix-sysdoc/Libraries/xhash.md @@ -110,9 +110,9 @@ Clears all items from a hash table. If a `free_fn()` is provided, it will be in ## xhForEach() ```c -int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); +int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, va_list), ...); ``` -This function executes an operation on each entry of the hash table entry. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `void*` argument specified using `each_arg`. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. +This function executes an operation on each entry of the hash table entry. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `va_list` of any additional arguments passed to `xhForEach()`. Each invocation receives the full list, so the callback may consume the arguments freely. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. This function returns 0 if the function executes successfully, 1 if the callback function is `NULL`, or n (where n != 0) if the callback function returns n. It does not return any error code other than 1 or any error codes returned by `callback_fn()`. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 6d8433181..8a2a2b6aa 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -653,7 +653,7 @@ int clusterInfo(void* inf_v, pObjectInfo info); // LINK #method char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt); char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt); -static int cluster_i_printEntry(pXHashEntry entry, void* arg); +static int cluster_i_printEntry(pXHashEntry entry, va_list args); static void cluster_i_cacheFreeSourceData(pXHashEntry entry, void* path); static void cluster_i_cacheFreeCluster(pXHashEntry entry, void* path); static void cluster_i_cacheFreeSearch(pXHashEntry entry, void* path); @@ -4545,18 +4545,17 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions /** Intended for use in `xhForEach()`. **/ static int -cluster_i_printEntry(pXHashEntry entry, void* arg) +cluster_i_printEntry(pXHashEntry entry, va_list args) { /** Extract entry. **/ char* key = entry->Key; void* data = entry->Data; /** Extract args. **/ - void** args = (void**)arg; - unsigned int* type_id_ptr = (unsigned int*)args[0]; - unsigned int* total_bytes_ptr = (unsigned int*)args[1]; - unsigned long long* less_ptr = (unsigned long long*)args[2]; - char* path = (char*)args[3]; + unsigned int* type_id_ptr = va_arg(args, unsigned int*); + unsigned int* total_bytes_ptr = va_arg(args, unsigned int*); + unsigned long long* less_ptr = va_arg(args, unsigned long long*); + char* path = va_arg(args, char*); /** If a path is provided, check that it matches the start of the key. **/ if (path != NULL && strncmp(key, path, strlen(path)) != 0) return 0; @@ -4729,19 +4728,19 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree failed |= (check(xhForEach( &ClusterDriverCaches.SourceDataCache, cluster_i_printEntry, - (void*[]){&i, &source_bytes, (void*)&skip_uncomputed, path} + &i, &source_bytes, &skip_uncomputed, path )) != 0); i++; failed |= (check(xhForEach( &ClusterDriverCaches.ClusterDataCache, cluster_i_printEntry, - (void*[]){&i, &cluster_bytes, (void*)&skip_uncomputed, path} + &i, &cluster_bytes, &skip_uncomputed, path )) != 0); i++; failed |= (check(xhForEach( &ClusterDriverCaches.SearchDataCache, cluster_i_printEntry, - (void*[]){&i, &search_bytes, (void*)&skip_uncomputed, path} + &i, &search_bytes, &skip_uncomputed, path )) != 0); if (failed) { From dcb605bbde3caceab29b9e13016fd911cd0daa21 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 14:39:40 -0600 Subject: [PATCH 206/361] Fix tests requiring an old, buggy behavior. --- centrallix/tests/test_objdrv_cluster_00.cmp | 50 ++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/centrallix/tests/test_objdrv_cluster_00.cmp b/centrallix/tests/test_objdrv_cluster_00.cmp index 171c2a910..d6e852346 100644 --- a/centrallix/tests/test_objdrv_cluster_00.cmp +++ b/centrallix/tests/test_objdrv_cluster_00.cmp @@ -11,16 +11,16 @@ Attribute [threshold]: double 0.9 Attribute [similarity_measure]: string "cosine" Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' -Attribute [items]: stringve "johnny" +Attribute [items]: stringve ["johnny"] Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' -Attribute [items]: stringve "MatTheW" +Attribute [items]: stringve ["MatTheW"] Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' -Attribute [items]: stringve "john","johns" +Attribute [items]: stringve ["john","johns"] Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' -Attribute [items]: stringve "mathew","mathews","math" +Attribute [items]: stringve ["mathew","mathews","math"] Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' Attribute [key1]: string "3: mathew" @@ -28,26 +28,26 @@ Attribute [key2]: string "5: mathews" Attribute [sim]: double 0.947714 Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' -Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" -Attribute [k2_items]: stringve "john","johnny","johns" -Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" -Attribute [k3_items]: stringve "johnny" -Attribute [k3_items]: stringve "mathew","MatTheW","mathews","math" -Attribute [k3_items]: stringve "john","johns" -Attribute [k4_items]: stringve "johnny" -Attribute [k4_items]: stringve "MatTheW" -Attribute [k4_items]: stringve "john","johns" -Attribute [k4_items]: stringve "mathew","mathews","math" -Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" -Attribute [k2_items]: stringve "john","johnny","johns" -Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" -Attribute [k3_items]: stringve "johnny" -Attribute [k3_items]: stringve "mathew","MatTheW","mathews","math" -Attribute [k3_items]: stringve "john","johns" -Attribute [k4_items]: stringve "johnny" -Attribute [k4_items]: stringve "MatTheW" -Attribute [k4_items]: stringve "john","johns" -Attribute [k4_items]: stringve "mathew","mathews","math" +Attribute [k1_items]: stringve ["john","johnny","johns","mathew","MatTheW","mathews","math"] +Attribute [k2_items]: stringve ["john","johnny","johns"] +Attribute [k2_items]: stringve ["mathew","MatTheW","mathews","math"] +Attribute [k3_items]: stringve ["johnny"] +Attribute [k3_items]: stringve ["mathew","MatTheW","mathews","math"] +Attribute [k3_items]: stringve ["john","johns"] +Attribute [k4_items]: stringve ["johnny"] +Attribute [k4_items]: stringve ["MatTheW"] +Attribute [k4_items]: stringve ["john","johns"] +Attribute [k4_items]: stringve ["mathew","mathews","math"] +Attribute [k1_items]: stringve ["john","johnny","johns","mathew","MatTheW","mathews","math"] +Attribute [k2_items]: stringve ["john","johnny","johns"] +Attribute [k2_items]: stringve ["mathew","MatTheW","mathews","math"] +Attribute [k3_items]: stringve ["johnny"] +Attribute [k3_items]: stringve ["mathew","MatTheW","mathews","math"] +Attribute [k3_items]: stringve ["john","johns"] +Attribute [k4_items]: stringve ["johnny"] +Attribute [k4_items]: stringve ["MatTheW"] +Attribute [k4_items]: stringve ["john","johns"] +Attribute [k4_items]: stringve ["mathew","mathews","math"] Attribute [k1_key1]: string "0: john" Attribute [k1_key2]: string "2: johns" Attribute [k1_sim]: double 0.757051 @@ -132,7 +132,7 @@ Attribute [slide_sim]: double 0.8 Attribute [slide_key1]: string "3: mathew" Attribute [slide_key2]: string "5: mathews" Attribute [slide_sim]: double 0.857143 -Attribute [items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [items]: stringve ["john","johnny","johns","mathew","MatTheW","mathews","math"] Attribute [date_created]: datetime '01 Jan 1970 00:00' Attribute [date_computed]: datetime '01 Jan 1970 00:00' Attribute [key1]: string "0: john" From f65b919f04beddbf6f99ecf2493f4d11f853185b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 15:02:57 -0600 Subject: [PATCH 207/361] Fix mistakes in xstring.md. --- centrallix-sysdoc/Libraries/xstring.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 76531b2cb..71c1e2ca8 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -80,7 +80,7 @@ The xstring (xs) module is used for managing growable strings. It is based on a ```c pXString xsNew() ``` -This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](newmalloc.md#nmmalloc) because the XString struct is always a consistent size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. +This function allocates a new XString structure to contain a new, empty string, initializing it with [`xsInit()`](#xsinit) before returning. It uses [`nmMalloc()`](newmalloc.md#nmmalloc) because the XString struct is always a consistent size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. ## xsFree() @@ -177,6 +177,7 @@ This function prints additional data onto the end of the string. It is similar - `%d`: Add a number (`int`). - `%X`: Add something? - `%%`: Add a `'%'` character. + Attempting to use other features of printf (such as `%lf`, `%c`, `%u`, etc.) will cause unexpected results. This function returns 0 if successful, or -1 if an error occurs. @@ -235,7 +236,7 @@ This function works the same as [`xsFind()`](#xsfind) except that it searches fr ```c int xsSubst(pXString this, int offset, int len, char* rep, int replen) ``` -This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsreplace), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified handle null characters. Both `len` and `replen` can be left blank to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. +This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsreplace), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified in order to handle null characters. Pass -1 for `len` or `replen` to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. ## xsReplace() From c1712876dd1b98ef7c85596dbb9269cab025dd9d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 15:03:15 -0600 Subject: [PATCH 208/361] Added a description for %X and %llX. --- centrallix-sysdoc/Libraries/xstring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 71c1e2ca8..8888a4b14 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -175,7 +175,7 @@ int xsConcatPrintf(pXString this, char* fmt, ...); This function prints additional data onto the end of the string. It is similar to printf, however, only the following features are supported: - `%s`: Add a string (`char*`). - `%d`: Add a number (`int`). -- `%X`: Add something? +- `%X`: Add a number in uppercase hexadecimal (`int`, or `long long` with `%llX`). - `%%`: Add a `'%'` character. Attempting to use other features of printf (such as `%lf`, `%c`, `%u`, etc.) will cause unexpected results. From 3079d00e013d032ef482d03b8b8feb23bbd3b807 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 15:12:50 -0600 Subject: [PATCH 209/361] Trim example. --- centrallix-sysdoc/Libraries/xstring.md | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 8888a4b14..d9ab637cc 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -121,36 +121,26 @@ This function concatenates the `text` string onto the end of the XString's value For example, **DO NOT**: ```c - XString xs; - if (xsInit(&xs) != 0) goto handle_error; + XString xs; xsInit(&xs); - if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + xsConcatenate(&xs, "This is the first sentence. ", -1); char* ptr = xsStringEnd(&xs); /* Stores string pointer! */ - if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + xsConcatenate(&xs, "This is the second sentence.", -1); /** Print will probably read invalid memory. **/ printf("A pointer to the second sentence is '%s'\n", ptr); - - ... - - if (xsDeInit(&xs) != 0) goto handle_error; ``` - Instead, use indexes and pointer arithmetic like this: + Instead, use indexes and pointer arithmetic, like this: ```c - XString xs; - if (xsInit(&xs) != 0) goto handle_error; + XString xs; xsInit(&xs); - if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + xsConcatenate(&xs, "This is the first sentence. ", -1); int offset = xsStringEnd(&xs) - xs->String; /* Stores index offset. */ - if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + xsConcatenate(&xs, "This is the second sentence.", -1); /** Print will probably work fine. **/ printf("A pointer to the second sentence is '%s'\n", xs->String + offset); - - ... - - if (xsDeInit(&xs) != 0) goto handle_error; ``` From d38f4366ae30ef285f57fe17158c08ee9c10caf1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 15:19:59 -0600 Subject: [PATCH 210/361] Add detail to docs. Resolve a todo. --- centrallix-sysdoc/Libraries/xstring.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index d9ab637cc..2523ccf2f 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -148,14 +148,14 @@ This function concatenates the `text` string onto the end of the XString's value ```c int xsCopy(pXString this, char* text, int len); ``` -This function copies the string `text` into the XString, overwriting any previous contents. This function returns 0 if successful, or -1 if an error occurs. +This function copies the string `text` into the XString, overwriting any previous contents. The `len` param here is the same as in [`xsConcatenate()`](#xsconcatenate). This function returns 0 if successful, or -1 if an error occurs. ## xsStringEnd() ```c char* xsStringEnd(pXString this); ``` -This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some string may contain nulls, using `strlen()` may produce an incorrect result. +This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some string may contain nulls, using `strlen()` may produce an incorrect result. It is equivalent to `this->String + xsLength()`, but it is shorter to type and keeps the internal workings of the xstring abstracted away from the caller. ## xsConcatPrintf() @@ -275,8 +275,6 @@ xsLength(pXString this); ``` This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. - - ## xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf() ```c @@ -284,4 +282,4 @@ int xsQPrintf_va(pXString this, char* fmt, va_list va); int xsQPrintf(pXString this, char* fmt, ...); int xsConcatQPrintf(pXString this, char* fmt, ...); ``` -These functions use the `QPrintf` to add data to an xstring. They return 0 on success, or some other value on failure. +These functions are similar to [`xsPrintf()`](#xsprintf) and [`xsConcatPrintf()`](#xsconcatprintf). However, they use [`QPrintf`](../QPrintf.md) to process the provided format instead of an internal `printf()`-like parser before adding the results to the xstring. They return 0 on success, or some other value on failure. From b98a4a25683796d17b35d680511e49d0abccba57 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 18:13:15 -0600 Subject: [PATCH 211/361] Update the test driver to handle the new snprintBytes() and snprintCommasLlu() return format. --- centrallix-lib/tests/t_driver.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 7949b0231..5878b90ea 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -108,7 +108,8 @@ start(void* v) { char buf[32]; unsigned long long rounded = (unsigned long long)(ops_per_second / factor + 0.5) * factor; - printf("%-62.62s PASS %s\n", tname, snprintCommasLlu(buf, sizeof(buf), rounded)); + snprintCommasLlu(buf, sizeof(buf), rounded); + printf("%-62.62s PASS %s\n", tname, buf); } else printf("%-62.62s PASS %.*f\n", tname, precision, ops_per_second); From c40b5a8b0710b82856b2608869bac92d965669c5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 18:18:17 -0600 Subject: [PATCH 212/361] Update the tests to handle and test the new snprintBytes() and snprintCommasLlu() return format. --- centrallix-lib/tests/test_util_02.c | 43 ++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index 3ccdd8de9..9125a3ef3 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -27,14 +27,29 @@ /** Tested module. **/ #include "util.h" -/** Assumes success is in scope. **/ +/*** Call an snprint-style function and check both what it wrote and what it + *** returned. + *** `expect` is the untruncated result so that we can expect the return to be + *** the correct length while requring the function to write exactly as much as + *** `buf_size` allows. Nothing is written when `buf_size` is zero. + ***/ +#define TEST_SNPRINT(fn, buf, buf_size, value, expect) \ + ({ \ + char* _expect = (expect); \ + const size_t _buf_size = (size_t)(buf_size); \ + const int _len = fn((buf), _buf_size, (value)); \ + char _trunc[strlen(_expect) + 1]; \ + strcpy(_trunc, _expect); \ + if (_buf_size > 0 && (size_t)_len >= _buf_size) _trunc[_buf_size - 1] = '\0'; \ + EXPECT_EQL(_len, (int)strlen(_expect), "%d") && \ + (_buf_size == 0 || EXPECT_STR_EQL((buf), _trunc)); \ + }) + #define TEST_SNPRINT_COMMAS_LLU(buf, buf_size, value, expect) \ - EXPECT_STR_EQL(snprintCommasLlu(buf, buf_size, value), expect) && \ - EXPECT_EQL(snprintCommasLlu(buf, buf_size, value), &buf[0], "%p") - + TEST_SNPRINT(snprintCommasLlu, buf, buf_size, value, expect) + #define TEST_SNPRINT_BYTES(buf, buf_size, value, expect) \ - EXPECT_STR_EQL(snprintBytes(buf, buf_size, value), expect) && \ - EXPECT_EQL(snprintBytes(buf, buf_size, value), &buf[0], "%p") + TEST_SNPRINT(snprintBytes, buf, buf_size, value, expect) static bool doTest(void) { @@ -70,6 +85,13 @@ static bool doTest(void) success &= TEST_SNPRINT_BYTES(buf, buf_size, INT_MAX, (cs) ? "2 GiB" : "2.15 GB"); success &= TEST_SNPRINT_BYTES(buf, buf_size, UINT_MAX, (cs) ? "4 GiB" : "4.29 GB"); + /** Truncation: the full length is returned, but only part is written. **/ + success &= TEST_SNPRINT_BYTES(buf, 0, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); + success &= TEST_SNPRINT_BYTES(buf, 1, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); + success &= TEST_SNPRINT_BYTES(buf, 4, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); + success &= TEST_SNPRINT_BYTES(buf, 5, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); + success &= TEST_SNPRINT_BYTES(buf, 6, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); + /** Test snprintCommasLlu(). Note: 10^16 would fail due to the double precision limit. **/ success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, 0, "0"); success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, pow(10, 1) - 1, "9"); @@ -114,6 +136,13 @@ static bool doTest(void) success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, UINT_MAX, "4,294,967,295"); success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, LLONG_MAX, "9,223,372,036,854,775,807"); success &= TEST_SNPRINT_COMMAS_LLU(buf, buf_size, ULLONG_MAX, "18,446,744,073,709,551,615"); + + /** Truncation: the full length is returned, but only part is written. **/ + success &= TEST_SNPRINT_COMMAS_LLU(buf, 0, pow(10, 3), "1,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, 1, pow(10, 3), "1,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, 3, pow(10, 3), "1,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, 5, pow(10, 3), "1,000"); + success &= TEST_SNPRINT_COMMAS_LLU(buf, 6, pow(10, 3), "1,000"); return success; } @@ -121,5 +150,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "util-02 Printing"; - return loopTest(doTest) * (17 + 43); + return loopTest(doTest) * (22 + 48); } From 71e19db308f342f5261f5027e2d3129cdc28eae7 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 15 Sep 2026 18:23:33 -0600 Subject: [PATCH 213/361] Fix mistakes from my attempt to handle an earlier merge conflict. --- centrallix-lib/tests/test_strtcpy_13.c | 47 +++++++++++++------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/centrallix-lib/tests/test_strtcpy_13.c b/centrallix-lib/tests/test_strtcpy_13.c index 2ec526ea0..7cd96062c 100644 --- a/centrallix-lib/tests/test_strtcpy_13.c +++ b/centrallix-lib/tests/test_strtcpy_13.c @@ -63,12 +63,15 @@ static size_t bad_positions[] = static bool can_fail = false; /*** This test verifies that strtcatf() refuses bad input safely, covering the - *** two paths ordinary appends never reach. When a conversion fails, the + *** three paths ordinary appends never reach. When a conversion fails, the *** append must be abandoned and the string already in dst left intact, even - *** though vsnprintf() may have written part of its output first. A *pos at - *** or past the end of dst, including one large enough to overflow the guard's - *** own arithmetic, must append nothing. Both return 0, leave *pos alone, and - *** write nothing outside the caller's dstlen. + *** though vsnprintf() may have written part of its output first; that reports + *** -1, as does a NULL dst, pos or fmt. A *pos at or past the end of dst, + *** including one large enough to overflow the guard's own arithmetic, is a + *** full buffer rather than an error and so reports 0. All of them leave *pos + *** alone and write nothing outside the caller's dstlen. The -1 cannot be + *** mistaken for a truncated append, which appends its null terminator over at + *** least one character and so returns -2 or less. ***/ static bool doTest(void) @@ -92,10 +95,9 @@ doTest(void) unconvertible, unconvertible); /** A failed conversion is an error, not a full buffer. **/ - assert(rval == -1); + assert(rval == -1); - /**The text already in dst survives, and *pos with it. **/ - + /** The text already in dst survives, and *pos with it. **/ assert(pos == strlen(prefixes[f])); assert(!strcmp(dst, prefixes[f])); @@ -117,10 +119,9 @@ doTest(void) rval = strtcatf(dst, AREA, &pos, "%s", "XYZ"); /** A full buffer is not an error, so it reports 0, not -1. **/ - assert(rval == 0); + assert(rval == 0); - /**Nothing appended, and *pos left exactly as it was. **/ - + /** Nothing appended, and *pos left exactly as it was. **/ assert(pos == bad_positions[c]); assert(!strcmp(dst, "abc")); @@ -129,17 +130,17 @@ doTest(void) assert(raw[n] == (n < GUARD || n > GUARD + 3 ? 0xAA : "abc"[n-GUARD])); } - /** A NULL dst, pos or fmt is an error, checked before anything else. **/ - memset(raw, 0xAA, RAW); - memcpy(dst, "abc", 4); - pos = 3; - assert(strtcatf(NULL, AREA, &pos, "%s", "XYZ") == -1); - assert(strtcatf(dst, AREA, NULL, "%s", "XYZ") == -1); - assert(strtcatf(dst, AREA, &pos, NULL) == -1); - assert(pos == 3); - assert(!strcmp(dst, "abc")); - for(n=0;n GUARD + 3 ? 0xAA : "abc"[n-GUARD])); + /** A NULL dst, pos or fmt is an error, checked before anything else. **/ + memset(raw, 0xAA, RAW); + memcpy(dst, "abc", 4); + pos = 3; + assert(strtcatf(NULL, AREA, &pos, "%s", "XYZ") == -1); + assert(strtcatf(dst, AREA, NULL, "%s", "XYZ") == -1); + assert(strtcatf(dst, AREA, &pos, NULL) == -1); + assert(pos == 3); + assert(!strcmp(dst, "abc")); + for(n=0;n GUARD + 3 ? 0xAA : "abc"[n-GUARD])); return true; } @@ -157,7 +158,7 @@ test(char** tname) can_fail = (snprintf(probe, sizeof(probe), failing_fmts[0], unconvertible) < 0); if (!can_fail) printf("(vsnprintf() converts %%ls here, skipping those cases) "); - ncases = NBAD + (can_fail ? NFMTS * NPFX : 0); + ncases = NBAD + 3 + (can_fail ? NFMTS * NPFX : 0); return loopTest(doTest) * ncases; } From d8804a2042c3c0b5923385b3f6d20508ec2c6d8f Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 09:36:32 -0600 Subject: [PATCH 214/361] Improve test readability. --- centrallix-lib/tests/test_strtcpy_13.c | 33 ++++++++++++-------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/centrallix-lib/tests/test_strtcpy_13.c b/centrallix-lib/tests/test_strtcpy_13.c index 7cd96062c..cd5deb231 100644 --- a/centrallix-lib/tests/test_strtcpy_13.c +++ b/centrallix-lib/tests/test_strtcpy_13.c @@ -18,7 +18,7 @@ /*** A wide character with no representation in the C locale, so that any *** attempt to convert it makes vsnprintf() fail with EILSEQ. ***/ -static wchar_t unconvertible[] = { (wchar_t)0x4E2D, (wchar_t)0 }; +static wchar_t inconvertible[] = { (wchar_t)(0x4E2D), (wchar_t)(0) }; /*** Formats that fail partway through, some only after emitting text. Held *** in a volatile pointer so the compiler cannot fold the probe call below. @@ -51,7 +51,7 @@ static size_t bad_positions[] = AREA, AREA + 1, AREA + 12, - (size_t)-1, + (size_t)(-1), }; /** Sizes of the case tables run per call to doTest(). **/ @@ -76,57 +76,54 @@ static bool can_fail = false; static bool doTest(void) { - int c, f, rval; unsigned char raw[RAW]; char* dst = (char*)raw + GUARD; size_t pos; - size_t n; /** A failed conversion appends nothing and keeps dst intact. **/ - for(c=0;can_fail && c GUARD + 3 ? 0xAA : "abc"[n-GUARD])); } @@ -138,8 +135,8 @@ doTest(void) assert(strtcatf(dst, AREA, NULL, "%s", "XYZ") == -1); assert(strtcatf(dst, AREA, &pos, NULL) == -1); assert(pos == 3); - assert(!strcmp(dst, "abc")); - for(n=0;n GUARD + 3 ? 0xAA : "abc"[n-GUARD])); return true; @@ -155,7 +152,7 @@ test(char** tname) /** Only run the conversion cases where the platform really fails. **/ setlocale(LC_ALL, "C"); - can_fail = (snprintf(probe, sizeof(probe), failing_fmts[0], unconvertible) < 0); + can_fail = (snprintf(probe, sizeof(probe), failing_fmts[0], inconvertible) < 0); if (!can_fail) printf("(vsnprintf() converts %%ls here, skipping those cases) "); ncases = NBAD + 3 + (can_fail ? NFMTS * NPFX : 0); From 5d18d7acd06ceddece34ec8bc8e5e90b11b46e6b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 10:02:38 -0600 Subject: [PATCH 215/361] Fix EXPECT_STR_EQL_N() writing "(null)" into a buffer that might be too small to hold it. --- centrallix-lib/tests/test_utils.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 1be4a51f7..46989c042 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -22,6 +22,7 @@ #include #include "timer.h" +#include "range.h" /*** Define lockup times. Valgrind instruments every memory access, so tests *** may need longer to finish when running under valgrind. @@ -114,11 +115,12 @@ const int success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ if (!success)\ { \ - char _tmp1[_len + 1]; \ + const size_t buf_len = max(7, _len + 1); \ + char _tmp1[buf_len]; \ if (str1 == NULL) strcpy(_tmp1, "(null)"); \ else STR_COPY_REPLACE_NULLS(_tmp1, _str1, _len); \ \ - char _tmp2[_len + 1]; \ + char _tmp2[buf_len]; \ if (str2 == NULL) strcpy(_tmp2, "(null)"); \ else STR_COPY_REPLACE_NULLS(_tmp2, _str2, _len); \ \ From 8d370c3e73c873c3b6e9f49e79178432a1802cee Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 12:28:55 -0600 Subject: [PATCH 216/361] Fix mistakes and make improvements in objdrv_cluster.c. --- centrallix/osdrivers/objdrv_cluster.c | 40 ++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 8a2a2b6aa..10ff20253 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1417,7 +1417,7 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) pStructInf sub_inf = checkPtr(inf->SubInf[i]); if (UNLIKELY(sub_inf == NULL)) { - mssError(1, "Cluster", "Failed to get %uth subinf.", i); + mssError(1, "Cluster", "Failed to get subinf #%u/%u.", i + 1, inf->nSubInf); goto err_free; } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); @@ -1835,7 +1835,7 @@ cluster_i_freeSourceData(pSourceData source_data) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Free top level attributes, if they exist. **/ - /** Note: The key field is handled by the caching system, so we shouldn't free it here. **/ + /** Note: The CacheKey field is handled by the caching system, so we shouldn't free it here. **/ if (LIKELY(source_data->Name != NULL)) { nmSysFree(source_data->Name); @@ -2322,7 +2322,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(data_datatype == -1)) { mssError(0, "Cluster", - "Failed to get type for data of entry #%u.", + "Failed to get type for data of entry #%d.", vector_xarray.nItems ); goto end_free; @@ -2330,7 +2330,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(data_datatype != DATA_T_STRING)) { mssError(1, "Cluster", - "Type for data of entry #%u was %s instead of String:\n", + "Type for data of entry #%d was %s instead of String:\n", vector_xarray.nItems, objTypeToStr(data_datatype) ); goto end_free; @@ -2342,7 +2342,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", - "Failed to get attribute value for data entry #%u (error code: %d).", + "Failed to get attribute value for data entry #%d (error code: %d).", vector_xarray.nItems, ret ); goto end_free; @@ -2376,7 +2376,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(key_datatype == -1)) { mssError(0, "Cluster", - "Failed to get type of key on entry #%u.", + "Failed to get type of key on entry #%d.", vector_xarray.nItems ); goto end_free; @@ -2384,7 +2384,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(key_datatype != DATA_T_STRING)) { mssError(1, "Cluster", - "Type of key on entry #%u was %s instead of String:", + "Type of key on entry #%d was %s instead of String:", vector_xarray.nItems, objTypeToStr(key_datatype) ); goto end_free; @@ -2396,7 +2396,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", - "Failed to value for key on entry #%u (error code: %d).", + "Failed to value for key on entry #%d (error code: %d).", vector_xarray.nItems, ret ); goto end_free; @@ -2642,7 +2642,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) for (unsigned int i = 0u; i < cluster_data->nClusters; i++) if (check(xaInit(&indexes_in_cluster[i], 8)) != 0) goto err_free; - /** Iterate through each label and add the index of the specified cluster to the xArray. **/ + /** Iterate through each label and add the index of the data to the specified cluster. **/ for (unsigned long long i = 0llu; i < source_data->nDatas; i++) if (checkPos(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; nmSysFree(labels); /* Free unused data. */ @@ -2669,7 +2669,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) if (UNLIKELY(index > __UINT32_MAX__)) { mssError(1, "Cluster", - "How did you try to cluster more than %u data points and cluster_i_computeSearchData() " + "How did you try to cluster more than %u data points and cluster_i_computeClusterData() " "was the first thing to break?! Well... looks like it's time to update %s:%s to " "handle a larger amount of data.", __UINT32_MAX__, __FILE__, __LINE__ @@ -3379,7 +3379,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { unsigned int index = query_data->RowIndex++; - /** Iterate over clusters. **/ + /** Fetch a cluster at the current index. **/ const unsigned int n_cluster_datas = node_data->nClusterDatas; if (index < n_cluster_datas) { @@ -3390,7 +3390,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) } else index -= n_cluster_datas; - /** Iterate over searches. **/ + /** Fetch a search at the current index. **/ const unsigned int n_search_datas = node_data->nSearchDatas; if (index < n_search_datas) { @@ -3401,7 +3401,6 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) } else index -= n_search_datas; - /** Iteration complete. **/ goto done_free; } @@ -3417,7 +3416,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) goto err_free; } - /** Stop iteration if the requested data does not exist. **/ + /** Stop fetching if the requested data does not exist. **/ if (UNLIKELY(query_data->RowIndex >= target->nClusters)) goto done_free; /** Set the data being fetched. **/ @@ -3439,7 +3438,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) goto err_free; } - /** Stop iteration if the requested data does not exist. **/ + /** Stop fetching if the requested data does not exist. **/ if (UNLIKELY(query_data->RowIndex >= target->nPairs)) goto done_free; /** Set the data being fetched. **/ @@ -3459,7 +3458,9 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) goto err_free; } - /** Add a link to the NodeData so that it isn't freed while we're using it. **/ + /*** Add a link to node_data, which is referenced by the result_data + *** struct we are about to return. + ***/ node_data->OpenCount++; /** Success. **/ @@ -3623,6 +3624,7 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) *** found. If a value other than 0 is returned, the buffer is not updated. *** @param oxt The transaction tree (for the incomplete transaction system). *** @returns 0 if successful, + *** 1 if the attribute's value is NULL, *** -1 if an error occurs. *** *** LINK ../../centrallix-lib/include/datatypes.h:72 @@ -3717,7 +3719,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: val->String = "Clustering driver: Cluster."; break; case TARGET_CLUSTER_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; case TARGET_SEARCH: val->String = "Clustering driver: Search."; break; - case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Search Entry."; break; default: mssError(1, "Cluster", "Unknown target type %u.", target_type); @@ -4021,8 +4023,8 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO *** *** Note: Failures from nmSysStrdup() and several others are ignored because *** the worst case scenario is that the attributes are set to null, which - *** will cause them to be ignored. I consider that to be better than than - *** throwing an error that could unnecessarily disrupt normal usage. + *** will cause them to be ignored. This prevents throwing an error that + *** could unnecessarily disrupt normal usage. *** *** @param inf_v The driver instance to be read. *** @param attr_name The name of the requested attribute. From f17c3114154ff702e27da525688b9d489f488a9c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 13:56:26 -0600 Subject: [PATCH 217/361] Improve code quality in the "Unknown provided parameter" code. --- centrallix/osdrivers/objdrv_cluster.c | 36 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 10ff20253..e0a3a9e03 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1740,7 +1740,9 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) check(xaDeInit(¶m_infs)); /* Failure ignored. */ param_infs.nAlloc = 0; - /** Iterate over provided parameters and warn the user if they specified a parameter that does not exist. **/ + /*** Iterate over provided parameters to warn the user if they + *** specified a parameter that does not exist. + ***/ for (unsigned int i = 0u; i < num_provided_params; i++) { pStruct provided_param = checkPtr(provided_params[i]); @@ -1748,20 +1750,30 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) char* provided_name = provided_param->Name; /** Look to see if this provided param actually exists for this driver instance. **/ + bool param_exists = false; for (unsigned int j = 0u; j < node_data->nParams; j++) if (strcmp(provided_name, node_data->Params[j]->Name) == 0) - goto next_provided_param; - - /** This param doesn't exist, warn the user and attempt to give them a hint. **/ - fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, objFileName(parent)); - char** param_names = checkPtr(nmSysMalloc(node_data->nParams * sizeof(char*))); - if (UNLIKELY(param_names == NULL)) goto err_free; - for (unsigned int j = 0u; j < node_data->nParams; j++) - param_names[j] = node_data->Params[j]->Name; - cluster_i_tryHint(provided_name, param_names, node_data->nParams); - if (LIKELY(param_names != NULL)) nmSysFree(param_names); + { + param_exists = true; + break; + } - next_provided_param:; + /** If this param doesn't exist, warn the user and attempt to give a hint. **/ + if (!param_exists) + { + fprintf(stderr, + "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", + provided_name, objFileName(parent) + ); + + /** Attempt hint. **/ + char** param_names = checkPtr(nmSysMalloc(node_data->nParams * sizeof(char*))); + if (UNLIKELY(param_names == NULL)) goto err_free; + for (unsigned int j = 0u; j < node_data->nParams; j++) + param_names[j] = node_data->Params[j]->Name; + cluster_i_tryHint(provided_name, param_names, node_data->nParams); + nmSysFree(param_names); + } } /** Parse source data. **/ From 6c9942c240916cda90bb2cb2b43bfa17111d1854 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 14:18:07 -0600 Subject: [PATCH 218/361] Remove redundant expect.h calls on check.h macros. --- centrallix/osdrivers/objdrv_cluster.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index e0a3a9e03..fbd164457 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2289,7 +2289,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) XArray vector_xarray = {0}; /** Guard segfaults. **/ - if (UNLIKELY(checkPtr(source_data) == NULL)) goto end_free; + if (checkPtr(source_data) == NULL) goto end_free; ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** If the vectors are already computed, we're done. **/ @@ -2546,7 +2546,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) size_t sims_size = -1; /** Guard segfaults. **/ - if (UNLIKELY(checkPtr(cluster_data) == NULL || checkPtr(node_data) == NULL)) goto err_free; + if (checkPtr(cluster_data) == NULL || checkPtr(node_data) == NULL) goto err_free; ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -2764,7 +2764,7 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) pXArray pairs = NULL; /** Guard segfaults. **/ - if (UNLIKELY(checkPtr(search_data) == NULL || checkPtr(node_data) == NULL)) goto err_free; + if (checkPtr(search_data) == NULL || checkPtr(node_data) == NULL) goto err_free; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); @@ -3501,7 +3501,7 @@ clusterQueryClose(void* qy_v, pObjTrxTree* oxt) { /** Cast the query data. **/ pQueryData query_data = qy_v; - if (UNLIKELY(checkPtr(qy_v) == NULL)) return -1; + if (checkPtr(qy_v) == NULL) return -1; ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); /** Free the query data. **/ From 5e6460b73c0306470d93edce0344fbdc8a504147 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 14:18:23 -0600 Subject: [PATCH 219/361] Remove an inaccurate expect.h call. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index fbd164457..33bd32712 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2293,7 +2293,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** If the vectors are already computed, we're done. **/ - if (LIKELY(source_data->Vectors != NULL)) + if (source_data->Vectors != NULL) { successful = true; goto end_free; From 1ad768698362e4355c651cbc07575b366b583292 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 14:37:40 -0600 Subject: [PATCH 220/361] Improve cluster_i_printEntry() value passing. --- centrallix/osdrivers/objdrv_cluster.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 33bd32712..aa423c008 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4566,7 +4566,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) void* data = entry->Data; /** Extract args. **/ - unsigned int* type_id_ptr = va_arg(args, unsigned int*); + unsigned int data_type = va_arg(args, unsigned int); unsigned int* total_bytes_ptr = va_arg(args, unsigned int*); unsigned long long* less_ptr = va_arg(args, unsigned long long*); char* path = va_arg(args, char*); @@ -4578,7 +4578,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) char* type; char* name; size_t bytes; - switch (*type_id_ptr) + switch (data_type) { case 1u: { @@ -4626,7 +4626,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) break; } default: - mssError(0, "Cluster", "Unknown type_id %u.", *type_id_ptr); + mssError(0, "Cluster", "Unknown type_id %u.", data_type); return -1; } @@ -4742,19 +4742,19 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree failed |= (check(xhForEach( &ClusterDriverCaches.SourceDataCache, cluster_i_printEntry, - &i, &source_bytes, &skip_uncomputed, path + i, &source_bytes, &skip_uncomputed, path )) != 0); i++; failed |= (check(xhForEach( &ClusterDriverCaches.ClusterDataCache, cluster_i_printEntry, - &i, &cluster_bytes, &skip_uncomputed, path + i, &cluster_bytes, &skip_uncomputed, path )) != 0); i++; failed |= (check(xhForEach( &ClusterDriverCaches.SearchDataCache, cluster_i_printEntry, - &i, &search_bytes, &skip_uncomputed, path + i, &search_bytes, &skip_uncomputed, path )) != 0); if (failed) { From c5c2daebe77bb6ae5856bf38a25489cf544f5771 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 14:44:21 -0600 Subject: [PATCH 221/361] Add a new enum to handle when an unknown data struct is passed. --- centrallix/osdrivers/objdrv_cluster.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index aa423c008..20a2622c3 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -113,6 +113,7 @@ cluster_i_clusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) return NULL; /** Unreachable. **/ } + /** Enum type representing a similarity measurement algorithm. **/ typedef unsigned char SimilarityMeasure; #define SIMILARITY_NULL ((SimilarityMeasure)0u) @@ -173,6 +174,26 @@ double (*cluster_i_similarityMeasureToFunction(SimilarityMeasure similarity_meas } } + +/*** Enum representing the type of an unknown data struct. + *** + *** `0u` is reserved for a possible NULL value in the future. However, NULL + *** values for CIDataType are not currently allowed. + ***/ +typedef unsigned char CIDataType; +#define CI_SOURCE_DATA ((CIDataType)1u) +#define CI_CLUSTER_DATA ((CIDataType)2u) +#define CI_SEARCH_DATA ((CIDataType)3u) + +CIDataType ALL_CI_DATA_TYPES[] = + { + CI_SOURCE_DATA, + CI_CLUSTER_DATA, + CI_SEARCH_DATA, + }; +#define N_CI_DATA_TYPES ((unsigned int)(sizeof(ALL_CI_DATA_TYPES) / sizeof(ALL_CI_DATA_TYPES[0]))) + + /*** Enum representing the type of data targeted by the driver, set based on *** the path given when the driver is used to open a cluster file. *** @@ -196,6 +217,7 @@ TargetType ALL_TARGET_TYPES[] = }; #define N_TARGET_TYPES ((unsigned int)(sizeof(ALL_TARGET_TYPES) / sizeof(ALL_TARGET_TYPES[0]))) + /*** Attribute name lists by TargetType. *** *** Promises that input attributes are listed first, before computed attributes. From 0511a3a9ae876f5f12affe43f1e09c1980e6885e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 15:25:41 -0600 Subject: [PATCH 222/361] Replace magic values in objdrv_cluster.c with constants. --- centrallix/osdrivers/objdrv_cluster.c | 70 ++++++++++++++++----------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 20a2622c3..924824cf9 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -65,9 +65,22 @@ /** Defaults for unspecified optional attributes. **/ #define CI_DEFAULT_MIN_IMPROVEMENT 0.0001 -#define CI_DEFAULT_MAX_ITERATIONS 64u +#define CI_DEFAULT_MAX_ITERATIONS 64 #define CI_NO_SEED 0u +/** Stringify a constant value. **/ +#define CI_STRINGIFY_CONSTANT_(value) #value +#define CI_STRINGIFY_CONSTANT(value) CI_STRINGIFY_CONSTANT_(value) + +/** Initial sizes. **/ +#define CI_INITIAL_SOURCE_DATAS 64 +#define CI_INITIAL_INFS 8 +#define CI_INITIAL_SUBCLUSTERS 4 +#define CI_INITIAL_POINTS_PER_CLUSTER 8 +#define CI_CACHE_HASHTABLE_ROWS 251 + +/** Developer config values. **/ +#define CI_HINT_SIMILARITY_THRESHOLD 0.25 /** ================ Enum Declarations ================ **/ /** ANCHOR[id=enums] **/ @@ -719,7 +732,10 @@ static void cluster_i_giveHint(const char* hint) static bool cluster_i_tryHint(char* value, char** valid_values, const unsigned int n_valid_values) { - char* guess = caMostSimilar(value, (void**)valid_values, n_valid_values, caLevCompare, 0.25); + char* guess = caMostSimilar( + value, (void**)valid_values, n_valid_values, + caLevCompare, CI_HINT_SIMILARITY_THRESHOLD + ); if (guess == NULL) return false; /* No hint. */ /** Issue hint. **/ @@ -1182,7 +1198,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData else cluster_data->Seed = CI_NO_SEED; /** Search for sub-clusters. **/ - if (check(xaInit(&sub_clusters, 4u)) != 0) goto err_free; + if (check(xaInit(&sub_clusters, CI_INITIAL_SUBCLUSTERS)) != 0) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = checkPtr(inf->SubInf[i]); @@ -1602,9 +1618,9 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) } /** Detect relevant groups. **/ - if (check(xaInit(¶m_infs, 8)) != 0) goto err_free; - if (check(xaInit(&cluster_infs, 8)) != 0) goto err_free; - if (check(xaInit(&search_infs, 8)) != 0) goto err_free; + if (check(xaInit(¶m_infs, CI_INITIAL_INFS)) != 0) goto err_free; + if (check(xaInit(&cluster_infs, CI_INITIAL_INFS)) != 0) goto err_free; + if (check(xaInit(&search_infs, CI_INITIAL_INFS)) != 0) goto err_free; for (unsigned int i = 0u; i < inf->nSubInf; i++) { pStructInf sub_inf = checkPtr(inf->SubInf[i]); @@ -2341,9 +2357,9 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) } /** Initialize an xarray to store the retrieved data. **/ - if (check(xaInit(&key_xarray, 64)) != 0) goto end_free; - if (check(xaInit(&data_xarray, 64)) != 0) goto end_free; - if (check(xaInit(&vector_xarray, 64)) != 0) goto end_free; + if (check(xaInit(&key_xarray, CI_INITIAL_SOURCE_DATAS)) != 0) goto end_free; + if (check(xaInit(&data_xarray, CI_INITIAL_SOURCE_DATAS)) != 0) goto end_free; + if (check(xaInit(&vector_xarray, CI_INITIAL_SOURCE_DATAS)) != 0) goto end_free; /** Fetch data and build vectors. **/ pObject entry; @@ -2674,7 +2690,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate temporary xArrays for tracking the indices stored in each cluster. **/ XArray indexes_in_cluster[cluster_data->nClusters]; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - if (check(xaInit(&indexes_in_cluster[i], 8)) != 0) goto err_free; + if (check(xaInit(&indexes_in_cluster[i], CI_INITIAL_POINTS_PER_CLUSTER)) != 0) goto err_free; /** Iterate through each label and add the index of the data to the specified cluster. **/ for (unsigned long long i = 0llu; i < source_data->nDatas; i++) @@ -4191,7 +4207,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "min_improvement") == 0) { /** Min and max values. **/ - hints->DefaultExpr = expCompileExpression("0.0001", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->DefaultExpr = expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MIN_IMPROVEMENT), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -4204,7 +4220,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "max_iterations") == 0) { /** Min and max values. **/ - hints->DefaultExpr = expCompileExpression("64", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->DefaultExpr = expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MAX_ITERATIONS), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -4588,7 +4604,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) void* data = entry->Data; /** Extract args. **/ - unsigned int data_type = va_arg(args, unsigned int); + CIDataType data_type = (CIDataType)va_arg(args, int); unsigned int* total_bytes_ptr = va_arg(args, unsigned int*); unsigned long long* less_ptr = va_arg(args, unsigned long long*); char* path = va_arg(args, char*); @@ -4602,7 +4618,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) size_t bytes; switch (data_type) { - case 1u: + case CI_SOURCE_DATA: { pSourceData source_data = (pSourceData)data; @@ -4617,7 +4633,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) name = source_data->Name; break; } - case 2u: + case CI_CLUSTER_DATA: { pClusterData cluster_data = (pClusterData)data; @@ -4632,7 +4648,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) name = cluster_data->Name; break; } - case 3u: + case CI_SEARCH_DATA: { pSearchData search_data = (pSearchData)data; @@ -4653,7 +4669,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) } /** Print the cache entry data. **/ - char buf[12]; + char buf[SNPRINT_BYTES_BUF_SIZE]; snprintBytes(buf, sizeof(buf), bytes); printf("%-8s %-16s %-12s \"%s\"\n", type, name, buf, key); goto increment_total; @@ -4755,7 +4771,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree { /** Print cache info table. **/ int ret = 0; - unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; + unsigned int source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; bool failed = false; printf("\nShowing cache for "); if (path != NULL) printf("\"%s\":\n", path); @@ -4764,19 +4780,17 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree failed |= (check(xhForEach( &ClusterDriverCaches.SourceDataCache, cluster_i_printEntry, - i, &source_bytes, &skip_uncomputed, path + (int)CI_SOURCE_DATA, &source_bytes, &skip_uncomputed, path )) != 0); - i++; failed |= (check(xhForEach( &ClusterDriverCaches.ClusterDataCache, cluster_i_printEntry, - i, &cluster_bytes, &skip_uncomputed, path + (int)CI_CLUSTER_DATA, &cluster_bytes, &skip_uncomputed, path )) != 0); - i++; failed |= (check(xhForEach( &ClusterDriverCaches.SearchDataCache, cluster_i_printEntry, - i, &search_bytes, &skip_uncomputed, path + (int)CI_SEARCH_DATA, &search_bytes, &skip_uncomputed, path )) != 0); if (failed) { @@ -4792,7 +4806,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (total_caches <= skip_uncomputed) printf("All caches skipped, nothing to show...\n"); /** Print stats. **/ - char buf[16]; + char buf[SNPRINT_BYTES_BUF_SIZE]; printf("\nCache Stats:\n"); printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprintBytes(buf, sizeof(buf), source_bytes)); @@ -4824,7 +4838,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (strcmp(method_name, "stat") == 0) { - char buf[12]; + char buf[SNPRINT_COMMAS_LLU_BUF_SIZE]; printf("Cluster Driver Statistics:\n"); printf(" Stat Name %12s\n", "Value"); printf(" OpenCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); @@ -4963,9 +4977,9 @@ clusterInitialize(void) /** Initialize caches. **/ // memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); - if (check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0)) != 0) goto err_free; - if (check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0)) != 0) goto err_free; - if (check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0)) != 0) goto err_free; + if (check(xhInit(&ClusterDriverCaches.SourceDataCache, CI_CACHE_HASHTABLE_ROWS, 0)) != 0) goto err_free; + if (check(xhInit(&ClusterDriverCaches.ClusterDataCache, CI_CACHE_HASHTABLE_ROWS, 0)) != 0) goto err_free; + if (check(xhInit(&ClusterDriverCaches.SearchDataCache, CI_CACHE_HASHTABLE_ROWS, 0)) != 0) goto err_free; /** Setup the structure. **/ if (checkPtr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; From de2d40222fcb8ee54f5e8c6513365ce26e8e95b8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 15:26:03 -0600 Subject: [PATCH 223/361] Improve code readability. --- centrallix/osdrivers/objdrv_cluster.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 924824cf9..190bc1488 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -777,8 +777,8 @@ cluster_i_unknownAttribute(char* attr_name, const int target_type) } /** Attempt to give hints. **/ - if (cluster_i_tryHint(attr_name, my_attrs, n_my_attrs)); - else if (cluster_i_tryHint(attr_name, DRIVER_ATTRIBUTE_NAMES, N_DRIVER_ATTRIBUTE_NAMES)); + if (cluster_i_tryHint(attr_name, my_attrs, n_my_attrs)) {} + else if (cluster_i_tryHint(attr_name, DRIVER_ATTRIBUTE_NAMES, N_DRIVER_ATTRIBUTE_NAMES)) {} return; } @@ -4093,7 +4093,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (UNLIKELY(driver_data == NULL)) goto err_free; ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const TargetType target_type = driver_data->TargetType; - + /** Malloc presentation hints struct. **/ hints = checkPtr(nmMalloc(sizeof(ObjPresentationHints))); if (UNLIKELY(hints == NULL)) goto err_free; From 2a35dcf154a24dfa8c96ff1d3edd49c3364272fe Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 15:26:51 -0600 Subject: [PATCH 224/361] Fix mistakes and stale info. --- centrallix/osdrivers/objdrv_cluster.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 190bc1488..3a2ddd856 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1439,7 +1439,7 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) if (UNLIKELY(search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold)) { mssError(1, "Cluster", - "Invalid value for [threshold : 0.0 < x < 1.0 | \"none\"]: %g", + "Invalid value for [threshold : 0.0 < x < 1.0]: %g", search_data->Threshold ); goto err_free; @@ -4803,7 +4803,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree + (unsigned int)ClusterDriverCaches.SourceDataCache.nItems + (unsigned int)ClusterDriverCaches.ClusterDataCache.nItems + (unsigned int)ClusterDriverCaches.SearchDataCache.nItems; - if (total_caches <= skip_uncomputed) printf("All caches skipped, nothing to show...\n"); + if (total_caches < skip_uncomputed) printf("All caches skipped, nothing to show...\n"); /** Print stats. **/ char buf[SNPRINT_BYTES_BUF_SIZE]; @@ -4815,7 +4815,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprintBytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); /** Print skip stats (if anything was skipped.) **/ - if (skip_uncomputed > 0llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); + if (skip_uncomputed > 1llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); return ret; } From 0690dfb3830c3299a5818e0ce3ff0489e8c325d9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 15:45:51 -0600 Subject: [PATCH 225/361] Fix printing formats. --- centrallix/osdrivers/objdrv_cluster.c | 44 ++++++++++++++++++--------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 3a2ddd856..d4908684a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1751,7 +1751,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) mssError(0, "Cluster", "Failed to set param value from struct info.\n" " > Param #%u: %s\n" - " > Provided Param #%u: %n\n" + " > Provided Param #%u: %s\n" " > Error code: %d", i, param->Name, j, provided_param->Name, @@ -4809,10 +4809,14 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree char buf[SNPRINT_BYTES_BUF_SIZE]; printf("\nCache Stats:\n"); printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); - printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprintBytes(buf, sizeof(buf), source_bytes)); - printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprintBytes(buf, sizeof(buf), cluster_bytes)); - printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprintBytes(buf, sizeof(buf), search_bytes)); - printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprintBytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); + snprintBytes(buf, sizeof(buf), source_bytes); + printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, buf); + snprintBytes(buf, sizeof(buf), cluster_bytes); + printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, buf); + snprintBytes(buf, sizeof(buf), search_bytes); + printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, buf); + snprintBytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes); + printf("%-8s %-4d %-12s\n\n", "Total", total_caches, buf); /** Print skip stats (if anything was skipped.) **/ if (skip_uncomputed > 1llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); @@ -4841,16 +4845,26 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree char buf[SNPRINT_COMMAS_LLU_BUF_SIZE]; printf("Cluster Driver Statistics:\n"); printf(" Stat Name %12s\n", "Value"); - printf(" OpenCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); - printf(" OpenQueryCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); - printf(" FetchCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); - printf(" CloseCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); - printf(" GetTypeCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); - printf(" GetValCalls %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); - printf(" GetValCalls_name %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); - printf(" GetValCalls_key1 %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); - printf(" GetValCalls_key2 %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); - printf(" GetValCalls_sim %12s\n", snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.OpenCalls); + printf(" OpenCalls %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls); + printf(" OpenQueryCalls %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.FetchCalls); + printf(" FetchCalls %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.CloseCalls); + printf(" CloseCalls %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls); + printf(" GetTypeCalls %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls); + printf(" GetValCalls %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name); + printf(" GetValCalls_name %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1); + printf(" GetValCalls_key1 %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2); + printf(" GetValCalls_key2 %12s\n", buf); + snprintCommasLlu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim); + printf(" GetValCalls_sim %12s\n", buf); printf("\n"); nmStats(); From 5a1973726d8cb645ad59ba56067eba3b6a9210d9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 15:58:04 -0600 Subject: [PATCH 226/361] Fix memory clean up when kmeans clustering fails. --- centrallix/osdrivers/objdrv_cluster.c | 51 ++++++++++++++++++--------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index d4908684a..c0644b280 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2653,6 +2653,10 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) case ALGORITHM_KMEANS: { + unsigned int* labels = NULL; + XArray indexes_in_cluster[cluster_data->nClusters]; + memset(indexes_in_cluster, 0, sizeof(indexes_in_cluster)); + /** Check for unimplemented similarity measures. **/ if (UNLIKELY(cluster_data->SimilarityMeasure != SIMILARITY_COSINE)) { @@ -2660,13 +2664,13 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) "The similarity measure \"%s\" is not implemented for 'k-means' clusters.", cluster_i_similarityMeasureToString(cluster_data->SimilarityMeasure) ); - goto err_free; + goto err_cleanup; } /** Allocate labels. Note: caKmeans() initializes labels for us. **/ const size_t labels_size = source_data->nDatas * sizeof(unsigned int); - unsigned int* labels = checkPtr(nmSysMalloc(labels_size)); - if (UNLIKELY(labels == NULL)) goto err_free; + labels = checkPtr(nmSysMalloc(labels_size)); + if (UNLIKELY(labels == NULL)) goto err_cleanup; /** Handle seed for caKmeans(). **/ const bool auto_seed = (cluster_data->Seed == CI_NO_SEED); @@ -2683,19 +2687,25 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) cluster_data->Sims, auto_seed )) == 0); - if (UNLIKELY(!successful)) goto err_free; + if (UNLIKELY(!successful)) goto err_cleanup; /** Convert the labels into clusters. **/ /** Allocate temporary xArrays for tracking the indices stored in each cluster. **/ - XArray indexes_in_cluster[cluster_data->nClusters]; for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - if (check(xaInit(&indexes_in_cluster[i], CI_INITIAL_POINTS_PER_CLUSTER)) != 0) goto err_free; + if (check(xaInit(&indexes_in_cluster[i], CI_INITIAL_POINTS_PER_CLUSTER)) != 0) + { + memset(&indexes_in_cluster[i], 0, sizeof(XArray)); + goto err_cleanup; + } /** Iterate through each label and add the index of the data to the specified cluster. **/ for (unsigned long long i = 0llu; i < source_data->nDatas; i++) - if (checkPos(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; - nmSysFree(labels); /* Free unused data. */ + if (checkPos(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_cleanup; + + /** Free unused data. **/ + nmSysFree(labels); + labels = NULL; /** Store the indices for each cluster and free the temporary xArray. **/ for (unsigned int i = 0u; i < cluster_data->nClusters; i++) @@ -2706,13 +2716,9 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) /** Store the data in the cluster. **/ cluster->Size = indexes_in_this_cluster->nItems; - if (cluster->Size == 0) - { - cluster->Indexes = NULL; - continue; - } + if (cluster->Size == 0) goto cluster_cleanup; /* Not a failure, but the array still needs to be freed. */ cluster->Indexes = checkPtr(nmSysMalloc(cluster->Size * sizeof(unsigned int*))); - if (UNLIKELY(cluster->Indexes == NULL)) goto err_free; + if (UNLIKELY(cluster->Indexes == NULL)) goto err_cleanup; for (unsigned int i = 0u; i < indexes_in_this_cluster->nItems; i++) { const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[i]; @@ -2720,19 +2726,32 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) { mssError(1, "Cluster", "How did you try to cluster more than %u data points and cluster_i_computeClusterData() " - "was the first thing to break?! Well... looks like it's time to update %s:%s to " + "was the first thing to break?! Well... looks like it's time to update %s:%d to " "handle a larger amount of data.", __UINT32_MAX__, __FILE__, __LINE__ ); - goto err_free; + goto err_cleanup; } cluster->Indexes[i] = (unsigned int)index; } + + cluster_cleanup: check(xaDeInit(indexes_in_this_cluster)); /* Failure ignored. */ + indexes_in_this_cluster->Items = NULL; } /** k-means done. **/ break; + + err_cleanup: + /** Error clean up. **/ + if (labels != NULL) nmSysFree(labels); + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + if (indexes_in_cluster[i].Items == NULL) continue; + check(xaDeInit(&indexes_in_cluster[i])); /* Failure ignored. */ + } + goto err_free; } default: From f26a9bb9250c786be4e50d7f384a7838f9be4406 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 16:18:53 -0600 Subject: [PATCH 227/361] Fix memory clean up during source data fetching. --- centrallix/osdrivers/objdrv_cluster.c | 75 ++++++++++++++++----------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index c0644b280..ef625f9ed 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2366,6 +2366,10 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) while ((entry = objQueryFetch(query, O_RDONLY)) != NULL) { ASSERTMAGIC(entry, MGK_OBJECT); + pVector vector = NULL; + char* key_dup = NULL; + char* data_dup = NULL; + bool entry_ok = false; /** Data value: Type checking. **/ const int data_datatype = objGetAttrType(entry, source_data->DataAttr); @@ -2375,7 +2379,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) "Failed to get type for data of entry #%d.", vector_xarray.nItems ); - goto end_free; + goto entry_free; } if (UNLIKELY(data_datatype != DATA_T_STRING)) { @@ -2383,7 +2387,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) "Type for data of entry #%d was %s instead of String:\n", vector_xarray.nItems, objTypeToStr(data_datatype) ); - goto end_free; + goto entry_free; } /** Data value: Get value from database. **/ @@ -2395,29 +2399,33 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) "Failed to get attribute value for data entry #%d (error code: %d).", vector_xarray.nItems, ret ); - goto end_free; + goto entry_free; } /** Skip empty strings. **/ - if (strlen(data) == 0) continue; + if (strlen(data) == 0) + { + entry_ok = true; + goto entry_free; + } /** Convert the string to a vector. **/ - pVector vector = caBuildVector(data); + vector = caBuildVector(data); if (UNLIKELY(vector == NULL)) { mssError(1, "Cluster", "Failed to build vectors for string \"%s\".", data); - goto end_free; + goto entry_free; } if (UNLIKELY(caIsEmpty(vector))) { mssError(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); - goto end_free; + goto entry_free; } if (caHasNoPairs(vector)) { /** Skip pVector with only a single pair of boundary characters. **/ - caFreeVector(vector); - continue; + entry_ok = true; + goto entry_free; } @@ -2429,7 +2437,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) "Failed to get type of key on entry #%d.", vector_xarray.nItems ); - goto end_free; + goto entry_free; } if (UNLIKELY(key_datatype != DATA_T_STRING)) { @@ -2437,7 +2445,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) "Type of key on entry #%d was %s instead of String:", vector_xarray.nItems, objTypeToStr(key_datatype) ); - goto end_free; + goto entry_free; } /** key value: Get value from database. **/ @@ -2449,20 +2457,31 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) "Failed to value for key on entry #%d (error code: %d).", vector_xarray.nItems, ret ); - goto end_free; + goto entry_free; } /** Store values. **/ - char* key_dup = checkPtr(nmSysStrdup(key)); - if (key_dup == NULL) goto end_free; - char* data_dup = checkPtr(nmSysStrdup(data)); - if (data_dup == NULL) goto end_free; - if (checkPos(xaAddItem(&key_xarray, (void*)key_dup) < 0)) goto end_free; - if (checkPos(xaAddItem(&data_xarray, (void*)data_dup) < 0)) goto end_free; - if (checkPos(xaAddItem(&vector_xarray, (void*)vector) < 0)) goto end_free; + key_dup = checkPtr(nmSysStrdup(key)); + if (key_dup == NULL) goto entry_free; + data_dup = checkPtr(nmSysStrdup(data)); + if (data_dup == NULL) goto entry_free; - /** Clean up. **/ + /** Hand each value to its xarray, which owns it from then on. **/ + if (checkPos(xaAddItem(&key_xarray, (void*)key_dup)) < 0) goto entry_free; + key_dup = NULL; + if (checkPos(xaAddItem(&data_xarray, (void*)data_dup)) < 0) goto entry_free; + data_dup = NULL; + if (checkPos(xaAddItem(&vector_xarray, (void*)vector)) < 0) goto entry_free; + vector = NULL; + entry_ok = true; + + entry_free: + /** Clean up owned memory, then fail if an error occurred. **/ + if (vector != NULL) caFreeVector(vector); + if (key_dup != NULL) nmSysFree(key_dup); + if (data_dup != NULL) nmSysFree(data_dup); check(objClose(entry)); /* Failure ignored. */ + if (UNLIKELY(!entry_ok)) goto end_free; } source_data->nDatas = vector_xarray.nItems; @@ -2472,21 +2491,19 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) goto end_free; } - /** Trim and store keys. **/ + /** Trim and store the keys, data strings, and vectors. **/ source_data->Keys = (char**)checkPtr(xaToArray(&key_xarray)); if (UNLIKELY(source_data->Keys == NULL)) goto end_free; - check(xaDeInit(&key_xarray)); /* Failure ignored. */ - key_xarray.nAlloc = 0; - - /** Trim and store data strings. **/ source_data->Strings = (char**)checkPtr(xaToArray(&data_xarray)); if (UNLIKELY(source_data->Strings == NULL)) goto end_free; - check(xaDeInit(&data_xarray)); /* Failure ignored. */ - data_xarray.nAlloc = 0; - - /** Trim and store vectors. **/ source_data->Vectors = (int**)checkPtr(xaToArray(&vector_xarray)); if (UNLIKELY(source_data->Vectors == NULL)) goto end_free; + + /** The stored arrays own the values now, so only free the xarrays. **/ + check(xaDeInit(&key_xarray)); /* Failure ignored. */ + key_xarray.nAlloc = 0; + check(xaDeInit(&data_xarray)); /* Failure ignored. */ + data_xarray.nAlloc = 0; check(xaDeInit(&vector_xarray)); /* Failure ignored. */ vector_xarray.nAlloc = 0; From 6477d9332c8612f024854233daf29f8be3efe1e4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 16:46:05 -0600 Subject: [PATCH 228/361] Fix a possible failure caused by using nmSysMalloc(0). --- centrallix/osdrivers/objdrv_cluster.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ef625f9ed..0574e6d0d 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2986,7 +2986,11 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) search_data->nPairs = pairs->nItems; if (pairs->nItems == 0) { - search_data->Pairs = checkPtr(nmSysMalloc(0)); + /*** We need to set a valid pointer to indicate that the pairs + *** were computed, but nmSysMalloc(0) may return NULL, so we + *** allocate a 1 byte memory section to be a marker. + ***/ + search_data->Pairs = checkPtr(nmSysMalloc(1)); if (search_data->Pairs == NULL) goto err_free; } else From d54c5f5e27539711deeb5384131c67ff098e8d91 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 16:52:07 -0600 Subject: [PATCH 229/361] Fix missing deallocation. --- centrallix/osdrivers/objdrv_cluster.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 0574e6d0d..f1fa3c1de 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2992,6 +2992,8 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) ***/ search_data->Pairs = checkPtr(nmSysMalloc(1)); if (search_data->Pairs == NULL) goto err_free; + check(xaFree(pairs)); /* Failure ignored. */ + pairs = NULL; } else { From ef4291674b0b2217a8e2c27c3afda85f5032287a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 17:47:54 -0600 Subject: [PATCH 230/361] Fix comments claiming to know that the provided data source was a database. --- centrallix/osdrivers/objdrv_cluster.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index f1fa3c1de..3e91eccd1 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -321,8 +321,9 @@ char* METHOD_NAMES[] = *** the driver represented by SourcePath. *** *** @skip --> Fetched/Computed Data. - *** @param Keys The keys for each data string received from the database, used - *** when the results are queried, or NULL if the data has not been fetched. + *** @param Keys The keys for each data string received from the data source, + *** used when the results are queried, or NULL if the data has not been + *** fetched. *** @param Strings The data strings to be clustered and searched, or NULL if *** they have not been fetched from the source. *** @param Vectors The cosine comparison vectors from the fetched data, or @@ -2390,7 +2391,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) goto entry_free; } - /** Data value: Get value from database. **/ + /** Data value: Get value from provided data source. **/ char* data; ret = objGetAttrValue(entry, source_data->DataAttr, DATA_T_STRING, POD(&data)); if (UNLIKELY(ret != 0)) @@ -2448,7 +2449,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) goto entry_free; } - /** key value: Get value from database. **/ + /** key value: Get value from provided data source. **/ char* key; ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); if (UNLIKELY(ret != 0)) From 3453f0d6da19f22804c77cc6219f5119a048979c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 17:48:09 -0600 Subject: [PATCH 231/361] Add null checks for data from objGetAttrValue(). --- centrallix/osdrivers/objdrv_cluster.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 3e91eccd1..0022c8817 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2402,6 +2402,15 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) ); goto entry_free; } + + if (UNLIKELY(data == NULL)) + { + mssError(1, "Cluster", + "Got null string value from successful call to objGetAttrValue() for data entry #%d.", + vector_xarray.nItems + ); + goto entry_free; + } /** Skip empty strings. **/ if (strlen(data) == 0) @@ -2461,6 +2470,15 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) goto entry_free; } + if (UNLIKELY(key == NULL)) + { + mssError(1, "Cluster", + "Got null string value from successful call to objGetAttrValue() for key on entry #%d.", + vector_xarray.nItems + ); + goto entry_free; + } + /** Store values. **/ key_dup = checkPtr(nmSysStrdup(key)); if (key_dup == NULL) goto entry_free; From 33c47864f9ae057f13705debea2a68611fd4b4d9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 16 Sep 2026 17:48:32 -0600 Subject: [PATCH 232/361] Fix misleading wording in driver authoring docs. --- centrallix-sysdoc/OSDriver_Authoring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index f399f0ead..fcc750757 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -679,7 +679,7 @@ The value pointer points to a union struct which can hold one of several types o \*_See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more info about this datatype._ -In this way, `int`s and `double`s can be returned by value while other types are returned by reference. Items returned by reference must be guaranteed to be valid until either the object is closed, or another `GetAttrValue()` or `SetAttrValue()` call is made on the same driver (which ever happens first). +In this way, `int`s and `double`s can be returned by value while other types are returned by reference. Items returned by reference must be guaranteed to be valid until either the object is closed, or another `GetAttrValue()` or `SetAttrValue()` call is made to any driver (which ever happens first). This function should return 0 on success, 1 if the value is `NULL` or undefined / unset, or -1 on a non-existent attribute or other error. From dde2afda359c47760036e003b7c4ff1e6dee8ee2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 10:10:21 -0600 Subject: [PATCH 233/361] Improve styling and readability. --- centrallix-lib/src/clusters.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 09f92bdd1..55149873f 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -816,12 +816,12 @@ caKmeans( double* vector_sims, bool auto_seed) { + bool successful = false; pCentroid* centroids = NULL; pCentroid* new_centroids = NULL; - - /** Setup variables. **/ - bool successful = false; - unsigned int cluster_counts[num_clusters]; + unsigned int cluster_counts[num_clusters]; + + /** Initialize labels. **/ memset(labels, 0u, num_vectors * sizeof(unsigned int)); /** Allocate space to store centroids and new_centroids. **/ From d5ce6a8d8768ff13d6f44e6e37e3fa6fb2ad41a0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 10:10:53 -0600 Subject: [PATCH 234/361] Improve the error message when kmeans() fails. --- centrallix/osdrivers/objdrv_cluster.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 0022c8817..2adcfd0a3 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2713,7 +2713,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) if (!auto_seed) srand(cluster_data->Seed); /** Run caKmeans(). **/ - const bool successful = (check(caKmeans( + const int kmeans_result = caKmeans( source_data->Vectors, source_data->nDatas, cluster_data->nClusters, @@ -2722,8 +2722,21 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) labels, cluster_data->Sims, auto_seed - )) == 0); - if (UNLIKELY(!successful)) goto err_cleanup; + ); + if (UNLIKELY(kmeans_result != 0)) + { + mssError(1, "Cluster", + "kmeans(pVector[], %u, %u, %u, %lf, labels[], similarities[], %s) failed (error code: %d).", + source_data->nDatas, + cluster_data->nClusters, + cluster_data->MaxIterations, + cluster_data->MinImprovement, + cluster_data->Sims, + (auto_seed) ? "true" : "false", + kmeans_result + ); + goto err_cleanup; + } /** Convert the labels into clusters. **/ From 16031f282d8049f206bb65036beb30f69d054d62 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 11:24:56 -0600 Subject: [PATCH 235/361] Hoist logic to simplify code. --- centrallix/osdrivers/objdrv_cluster.c | 42 +++++++++------------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 2adcfd0a3..5d5b948ba 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2902,6 +2902,20 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) double (*similarity_function)(void *, void *) = checkPtr(cluster_i_similarityMeasureToFunction(search_data->SimilarityMeasure)); if (UNLIKELY(similarity_function == NULL)) goto err_free; + /** Get a pointer to the data that will be used for the search. **/ + void** data = NULL; + switch (search_data->SimilarityMeasure) + { + case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; + case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; + default: + mssError(1, "Cluster", + "Unknown similarity measure \"%s\".", + cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; + } + /** Execute the search using the specified algorithm. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) { @@ -2909,20 +2923,6 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) *** was computed during the clustering phase. ***/ - /** Get a pointer to the data that will be used for the search. **/ - void** data = NULL; - switch (search_data->SimilarityMeasure) - { - case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; - case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; - default: - mssError(1, "Cluster", - "Unknown similarity measure \"%s\".", - cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) - ); - goto err_free; - } - /** Execute sliding search. **/ pairs = checkPtr(caSlidingSearch( data, @@ -2955,20 +2955,6 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) pCluster cluster = &cluster_data->Clusters[i]; ASSERTMAGIC(cluster, MGK_CL_CLUSTER); - /** Get a pointer to the data of the type needed for the search. **/ - void** data = NULL; - switch (search_data->SimilarityMeasure) - { - case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; - case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; - default: - mssError(1, "Cluster", - "Unknown similarity measure \"%s\".", - cluster_i_similarityMeasureToString(search_data->SimilarityMeasure) - ); - goto err_free; - } - /** Filter the data to only include values in the current cluster. **/ void** filtered_data = data; bool free_filtered_data = false; From a36e503fcd4af656ae28628ab3371b1c829dd86d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 11:38:22 -0600 Subject: [PATCH 236/361] Add info for the sys_type parameter of clusterOpen(). --- centrallix/osdrivers/objdrv_cluster.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 5d5b948ba..17c4762ed 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3162,7 +3162,9 @@ cluster_i_setParamValue(void* inf_v, char* attr_name, int datatype, pObjData val *** @param parent The parent of the object to be opened, including useful *** information such as the pathname, session, etc. *** @param mask Driver permission mask (unused). - *** @param sys_type ? (unused) + *** @param sys_type The content type registered by this driver that caused it + *** to be picked to open this content. This driver only registers itself + *** for "system/cluster" currently. *** @param usr_type The object system file type being opened. Should always *** be "system/cluster" because this driver is only registered for that *** type of file. From ea82a100b22cd5c4c9fa1322fc3293eb0529a56d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 11:48:27 -0600 Subject: [PATCH 237/361] Reduce code complexity caused by gotos in clusterGetAttrValue(). --- centrallix/osdrivers/objdrv_cluster.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 17c4762ed..866327c6d 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3769,7 +3769,8 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Type check. **/ const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); - if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE)) goto unknown_attribute; + if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE)) + cluster_i_unknownAttribute(attr_name, driver_data->TargetType); if (UNLIKELY(datatype != expected_datatype)) { mssError(1, "Cluster", @@ -4114,7 +4115,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO goto err; } - unknown_attribute: + /** No checks matched the requested attribute. **/ cluster_i_unknownAttribute(attr_name, driver_data->TargetType); err:; From eeaa3f1a5f87669edb24c3e7dd596a6b49854b17 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 11:53:46 -0600 Subject: [PATCH 238/361] Reduce goto complexity in clusterGetAttrValue(). --- centrallix/osdrivers/objdrv_cluster.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 866327c6d..ac4ca726c 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3641,9 +3641,6 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) return DATA_T_UNAVAILABLE; } - /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ - if (attr_name[0] == 'k' || attr_name[0] == 's') goto handle_targets; - /** Types for general attributes. **/ if (LIKELY(strcmp(attr_name, "name") == 0) || strcmp(attr_name, "annotation") == 0 @@ -3666,7 +3663,6 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) } /** Types for specific data targets. **/ - handle_targets: switch (target_type) { case TARGET_NODE: @@ -3762,11 +3758,6 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO goto err; } - /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ - if ((attr_name[0] == 'k' && datatype == DATA_T_STRING) /* key1, key2 : string */ - || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ - ) goto handle_targets; - /** Type check. **/ const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE)) @@ -3954,7 +3945,6 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } /** Handle attributes for specific data targets. **/ - handle_targets: switch (target_type) { case TARGET_NODE: From 6cfbffa563d1b77da5cac18518be8788d5b33c8f Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 11:57:18 -0600 Subject: [PATCH 239/361] Modify cluster_i_getParamType() to return -1 if the parameter doesn't exist instead of DATA_T_UNAVAILABLE. --- centrallix/osdrivers/objdrv_cluster.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ac4ca726c..38b4dc879 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3062,7 +3062,7 @@ static int cluster_i_getParamType(void* inf_v, const char* attr_name) { pNodeData node_data = checkPtr(inf_v); - if (UNLIKELY(node_data == NULL)) return DATA_T_UNAVAILABLE; + if (UNLIKELY(node_data == NULL)) return -1; ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Find the parameter. **/ @@ -3073,11 +3073,11 @@ cluster_i_getParamType(void* inf_v, const char* attr_name) if (strcmp(param->Name, attr_name) != 0) continue; /** Parameter found. **/ - return (param->Value == NULL) ? DATA_T_UNAVAILABLE : param->Value->DataType; + return (param->Value == NULL) ? -1 : param->Value->DataType; } /** Parameter not found. **/ - return DATA_T_UNAVAILABLE; + return -1; } @@ -3760,7 +3760,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Type check. **/ const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); - if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE)) + if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE || expected_datatype < 0)) cluster_i_unknownAttribute(attr_name, driver_data->TargetType); if (UNLIKELY(datatype != expected_datatype)) { From 63a66f8a917693c9d0dde13a7982bde78368f2a1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 12:28:46 -0600 Subject: [PATCH 240/361] Fix ignored failures that would cause immediate faults. --- centrallix/osdrivers/objdrv_cluster.c | 38 ++++++++++++++++++--------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 38b4dc879..8d74522c3 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4186,12 +4186,14 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } if (strcmp(attr_name, "internal_type") == 0) { - check(xaInit(&(hints->EnumList), 5)); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("search/entry")))); /* Failure ignored. */ + if (check(xaInit(&(hints->EnumList), 5)) == 0) + { + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ + checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("search/entry")))); /* Failure ignored. */ + } hints->Length = 16; hints->VisualLength = 16; hints->FriendlyName = checkPtr(nmSysStrdup("Internal Type")); /* Failure ignored. */ @@ -4286,9 +4288,15 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "algorithm") == 0) { /** Enum values. **/ - check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)); /* Failure ignored. */ - for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) - checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(cluster_i_clusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i])))); /* Failure ignored. */ + if (check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)) == 0) + { + for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) + { + char* cluster_string = checkPtr(nmSysStrdup(cluster_i_clusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]))); + if (cluster_string == NULL) continue; /* Skip this. */ + checkPos(xaAddItem(&(hints->EnumList), cluster_string)); /* Failure ignored. */ + } + } /** Min and max values. **/ hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); @@ -4312,9 +4320,15 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "similarity_measure") == 0) { /** Enum values. **/ - check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)); /* Failure ignored. */ - for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) - checkPos(xaAddItem(&(hints->EnumList), nmSysStrdup(cluster_i_similarityMeasureToString(ALL_SIMILARITY_MEASURES[i])))); /* Failure ignored. */ + if (check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)) == 0) + { + for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) + { + char* similarity_string = checkPtr(nmSysStrdup(cluster_i_similarityMeasureToString(ALL_SIMILARITY_MEASURES[i]))); + if (similarity_string == NULL) continue; /* Skip this. */ + checkPos(xaAddItem(&(hints->EnumList), similarity_string)); /* Failure ignored. */ + } + } /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; From 7562d86d544751a42e4f96cb99f9819770c126f6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 12:34:48 -0600 Subject: [PATCH 241/361] Add checkPtr() warnings expCompileExpression() fails when calculating PresentationHints. --- centrallix/osdrivers/objdrv_cluster.c | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 8d74522c3..6307a8d0f 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4250,8 +4250,8 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "num_clusters") == 0) { /** Min and max values. **/ - hints->MinValue = expCompileExpression("2", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = checkPtr(expCompileExpression("2", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = checkPtr(expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ hints->Length = 8; @@ -4262,9 +4262,9 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "min_improvement") == 0) { /** Min and max values. **/ - hints->DefaultExpr = expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MIN_IMPROVEMENT), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->DefaultExpr = checkPtr(expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MIN_IMPROVEMENT), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ hints->Length = 16; @@ -4275,9 +4275,9 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "max_iterations") == 0) { /** Min and max values. **/ - hints->DefaultExpr = expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MAX_ITERATIONS), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->DefaultExpr = checkPtr(expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MAX_ITERATIONS), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = checkPtr(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = checkPtr(expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ hints->Length = 8; @@ -4299,10 +4299,10 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } /** Min and max values. **/ - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = checkPtr(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ char buf[8]; snprintf(buf, sizeof(buf), "%u", N_CLUSTERING_ALGORITHMS - 1u); - hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = checkPtr(expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; @@ -4335,10 +4335,10 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; /** Min and max values. **/ - hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = checkPtr(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ char buf[8]; snprintf(buf, sizeof(buf), "%u", N_SIMILARITY_MEASURES - 1u); - hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = checkPtr(expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ hints->Length = 32; @@ -4360,8 +4360,8 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "threshold") == 0) { /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ hints->Length = 16; @@ -4389,8 +4389,8 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "sim") == 0) { /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ hints->Length = 16; @@ -4425,8 +4425,8 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "sim") == 0) { /** Min and max values. **/ - hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); - hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ hints->Length = 16; From 5a797bc92dadeb95ce6b9812fcd9f15448b14aaf Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 12:41:16 -0600 Subject: [PATCH 242/361] Fix incorrect presentation hint. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 6307a8d0f..ea3640101 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4250,7 +4250,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "num_clusters") == 0) { /** Min and max values. **/ - hints->MinValue = checkPtr(expCompileExpression("2", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = checkPtr(expCompileExpression("1", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ hints->MaxValue = checkPtr(expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ /** Other hints. **/ From 0c8db7fbaf885c1a9f54c220ef550f7c1b3b1db4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 12:42:28 -0600 Subject: [PATCH 243/361] Reduce code complexity caused by gotos. --- centrallix/osdrivers/objdrv_cluster.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ea3640101..013e3864c 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4216,7 +4216,11 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) hints->Format = checkPtr(nmSysStrdup("datetime")); /* Failure ignored. */ goto end; } - else goto unknown_attribute; + else + { + cluster_i_unknownAttribute(attr_name, driver_data->TargetType); + goto err_free; + } } /** Search by target type. **/ @@ -4442,7 +4446,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) goto err_free; } - unknown_attribute: + /** No checks matched the requested attribute. **/ cluster_i_unknownAttribute(attr_name, driver_data->TargetType); err_free: From 8628a96d51ca0f638ab40d8e1af2b15fd7a4519e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 12:55:49 -0600 Subject: [PATCH 244/361] Trim unused code. --- centrallix/osdrivers/objdrv_cluster.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 013e3864c..ff1250e96 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4377,11 +4377,6 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) case TARGET_CLUSTER_ENTRY: { - /** Unused. **/ - // pClusterData target = checkPtr(driver_data->TargetData); - // if (UNLIKELY(target == NULL)) goto err_free; - // ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); - if (strcmp(attr_name, "items") == 0) { /** Other hints. **/ @@ -4407,11 +4402,6 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) case TARGET_SEARCH_ENTRY: { - /** Unused. **/ - // pSearchData target = checkPtr(driver_data->TargetData); - // if (UNLIKELY(target == NULL)) goto err_free; - // ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - if (strcmp(attr_name, "key1") == 0) { hints->Length = 255; From 6b901d46888bebee2df6d65106815d2672279e06 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 12:58:12 -0600 Subject: [PATCH 245/361] Fix typo. --- centrallix/osdrivers/objdrv_cluster.c | 1 - 1 file changed, 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ff1250e96..da4e6cc85 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2731,7 +2731,6 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) cluster_data->nClusters, cluster_data->MaxIterations, cluster_data->MinImprovement, - cluster_data->Sims, (auto_seed) ? "true" : "false", kmeans_result ); From cb21976d5a31252560059f9596db1993e88717d3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:10:45 -0600 Subject: [PATCH 246/361] Improve style of to string functions. --- centrallix/osdrivers/objdrv_cluster.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index da4e6cc85..60e15654e 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -120,7 +120,7 @@ cluster_i_clusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) case ALGORITHM_KMEANS_PLUS_PLUS: return "k-means++"; case ALGORITHM_KMEDOIDS: return "k-medoids"; case ALGORITHM_DB_SCAN: return "db-scan"; - default: return "Unknown algorithm"; + default: return "unknown algorithm"; } return NULL; /** Unreachable. **/ @@ -143,7 +143,7 @@ SimilarityMeasure ALL_SIMILARITY_MEASURES[] = /*** Converts a similarity measure to its string name. *** - *** @param similarity_measure The similarity measure to convert. + *** @param similarity_measure The similarity measure value to convert. *** @returns The corresponding name. ***/ char* @@ -154,7 +154,7 @@ cluster_i_similarityMeasureToString(SimilarityMeasure similarity_measure) case SIMILARITY_NULL: return "NULL similarity measure"; case SIMILARITY_COSINE: return "cosine"; case SIMILARITY_LEVENSHTEIN: return "levenshtein"; - default: return "Unknown similarity measure"; + default: return "unknown similarity measure"; } return NULL; /** Unreachable. **/ @@ -164,7 +164,7 @@ cluster_i_similarityMeasureToString(SimilarityMeasure similarity_measure) *** corresponding comparison function. This function can be called directly *** or passed to functions from `clusters.c`. *** - *** @param similarity_measure The similarity measure to be converted. + *** @param similarity_measure The similarity measure value to be converted. *** @returns A similarity computation function. This function takes two void *** pointers, representing the data to be compared, and returns a double *** representing how similar the data is from 0.0 (no similarity) to 1.0 From ede3d9c8c1f1ea52449fd041d5d87069d6d4a95d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:10:54 -0600 Subject: [PATCH 247/361] Add cluster_i_targetTypeToString(). --- centrallix/osdrivers/objdrv_cluster.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 60e15654e..612d8d290 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -230,6 +230,27 @@ TargetType ALL_TARGET_TYPES[] = }; #define N_TARGET_TYPES ((unsigned int)(sizeof(ALL_TARGET_TYPES) / sizeof(ALL_TARGET_TYPES[0]))) +/*** Converts a target type to its string name. + *** + *** @param target_type The target type value to convert. + *** @returns The corresponding name. + ***/ +char* +cluster_i_targetTypeToString(TargetType target_type) + { + switch (target_type) + { + case TARGET_NODE: return "node"; + case TARGET_CLUSTER: return "cluster"; + case TARGET_SEARCH: return "search"; + case TARGET_CLUSTER_ENTRY: return "cluster entry"; + case TARGET_SEARCH_ENTRY: return "search entry"; + default: return "unknown target type"; + } + + return NULL; /** Unreachable. **/ + } + /*** Attribute name lists by TargetType. *** From 35ff5b07f5d1a967d5d88852e8b6b710c9701df4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:11:33 -0600 Subject: [PATCH 248/361] Add an error message. --- centrallix/osdrivers/objdrv_cluster.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 612d8d290..888f967a5 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3422,6 +3422,12 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) { pQueryData query_data = NULL; + /*** When an error occurs in this function, it's hard to detect if the + *** error stack should be cleared so we clear it preemptively while + *** we know no error is occurring. + ***/ + mssClearError(); + /** Get driver data. **/ pDriverData driver_data = checkPtr(inf_v); if (driver_data == NULL) goto err_free; @@ -3432,7 +3438,11 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) && driver_data->TargetType != TARGET_NODE) { /** Queries are not supported for this target type. **/ - goto err; + mssError(1, "Cluster", + "The cluster driver object targeting a %s does not support queries.", + cluster_i_targetTypeToString(driver_data->TargetType) + ); + goto err_free; } /** Update statistics. **/ From 270eccc2f47d2757f935321b2f8352c329f8cc30 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:21:21 -0600 Subject: [PATCH 249/361] Add a comment to explain why the "unnecessary scope block" is necessary. --- centrallix/osdrivers/objdrv_cluster.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 888f967a5..1939779b9 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3345,8 +3345,9 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** We were unable to find the requested cluster or search. **/ mssError(1, "Cluster", "\"%s\" is not the name of a declared cluster or search.", target_name); - /** Attempt to give a hint. **/ + /** A separate scope is needed because target_names is a dynamically sized array. **/ { + /** Attempt to give a hint. **/ const unsigned int n_targets = node_data->nClusterDatas + node_data->nSearchDatas; char* target_names[n_targets]; for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) From c2bfde2f4b50064f30c5c2d95985f57a4210f52e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:25:42 -0600 Subject: [PATCH 250/361] Fix crash on clusterOpen(NULL, ...). --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 1939779b9..3ebdef0a7 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3206,7 +3206,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb if (UNLIKELY(parent == NULL)) { mssError(0, "Cluster", "Warning: Call to clusterOpen(NULL, ...);\n"); - goto err_free; + return NULL; /* The function failure assumes a valid parent, skip it. */ } ASSERTMAGIC(parent, MGK_OBJECT); From 1ce204a56862f50474df1963b40a716d2b0c4594 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:29:48 -0600 Subject: [PATCH 251/361] Reduce code complexity caused by gotos. --- centrallix/osdrivers/objdrv_cluster.c | 32 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 3ebdef0a7..d4293b0e8 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3266,7 +3266,9 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Target found: Root **/ driver_data->TargetType = TARGET_NODE; driver_data->TargetData = (void*)driver_data->NodeData->SourceData; - goto success; + + /** Success. **/ + return driver_data; } /** Search clusters. **/ @@ -3284,7 +3286,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Sub-clusters are not fully implemented yet, skip the cluster logic below. **/ parent->SubCnt++; driver_data->TargetData = (void*)cluster_data; - goto success; /* Done! */ + return driver_data; /* Success. */ /** Check for sub-clusters in the path. **/ while (true) @@ -3300,6 +3302,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb } /** Need to go deeper: Search for the requested sub-cluster. **/ + bool found = false; for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) { pClusterData sub_cluster = cluster_data->SubClusters[i]; @@ -3307,16 +3310,20 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Target found: Sub-cluster_data **/ cluster_data = sub_cluster; - goto continue_descent; + found = true; + break; } - - /** Path names sub-cluster that does not exist. **/ - mssError(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); - goto err_free; - continue_descent:; + /** Error if path names sub-cluster that does not exist. **/ + if (!found) + { + mssError(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); + goto err_free; + } } - goto success; + + /** Success. **/ + return driver_data; } /** Search searches. **/ @@ -3339,7 +3346,9 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb mssError(1, "Cluster", "Unknown path part %s.", extra_data); goto err_free; } - return (void*)driver_data; /* Success. */ + + /** Success. **/ + return driver_data; } /** We were unable to find the requested cluster or search. **/ @@ -3368,9 +3377,6 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb ); return NULL; - - success: - return driver_data; } From b19fa4fd118a74371ccb5dc8a030d39f151de169 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:39:48 -0600 Subject: [PATCH 252/361] Improve spacing. --- centrallix/osdrivers/objdrv_cluster.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index d4293b0e8..ed1cd610a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -913,12 +913,12 @@ cluster_i_parseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) } /** Parse known clustering algorithms. **/ - if (strcasecmp(algorithm, "none") == 0) return ALGORITHM_NONE; + if (strcasecmp(algorithm, "none") == 0) return ALGORITHM_NONE; if (strcasecmp(algorithm, "sliding-window") == 0) return ALGORITHM_SLIDING_WINDOW; - if (strcasecmp(algorithm, "k-means") == 0) return ALGORITHM_KMEANS; - if (strcasecmp(algorithm, "k-means++") == 0) return ALGORITHM_KMEANS_PLUS_PLUS; - if (strcasecmp(algorithm, "k-medoids") == 0) return ALGORITHM_KMEDOIDS; - if (strcasecmp(algorithm, "db-scan") == 0) return ALGORITHM_DB_SCAN; + if (strcasecmp(algorithm, "k-means") == 0) return ALGORITHM_KMEANS; + if (strcasecmp(algorithm, "k-means++") == 0) return ALGORITHM_KMEANS_PLUS_PLUS; + if (strcasecmp(algorithm, "k-medoids") == 0) return ALGORITHM_KMEDOIDS; + if (strcasecmp(algorithm, "db-scan") == 0) return ALGORITHM_DB_SCAN; /** Unknown value for clustering algorithm. **/ mssError(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); From 683ffe017df32b5396a344bd9b6d41827787be79 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 17 Sep 2026 13:45:16 -0600 Subject: [PATCH 253/361] Clean up. --- centrallix/osdrivers/objdrv_cluster.c | 1 - 1 file changed, 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index ed1cd610a..6a5012515 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3471,7 +3471,6 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) if (query_data != NULL) nmFree(query_data, sizeof(ClusterQuery)); mssError(0, "Cluster", "Failed to open query."); - err: return NULL; } From baa01315079ba53078de501a656c254443cf6204 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 10:36:57 -0600 Subject: [PATCH 254/361] Add binding support to stGetAttrValueOSML(). Add a domain parameter to stGetAttrValueOSML(), used to specify a binding domain. Update existing calls outside the cluster driver to pass 0 for the domain parameter (replicating the old functionality). --- centrallix/include/stparse.h | 3 +-- centrallix/osdrivers/objdrv_http.c | 2 +- centrallix/osdrivers/objdrv_query.c | 3 +-- centrallix/osdrivers/objdrv_report_v3.c | 5 ++--- centrallix/utility/stparse.c | 8 +++++--- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/centrallix/include/stparse.h b/centrallix/include/stparse.h index a941b6a37..d59db8d13 100644 --- a/centrallix/include/stparse.h +++ b/centrallix/include/stparse.h @@ -124,7 +124,7 @@ int stPrintInf(pStructInf this); int stRemoveInf(pStructInf inf); int stGetAttrValue(pStructInf this, int type, pObjData value, int nval); int stGetObjAttrValue(pStructInf this, char* attrname, int type, pObjData value); -int stGetAttrValueOSML(pStructInf this, int type, pObjData value, int nval, pObjSession sess, pParamObjects objlist); +int stGetAttrValueOSML(pStructInf this, int type, pObjData value, int nval, pObjSession sess, pParamObjects objlist, int domain); int stGetAttrType(pStructInf this, int nval); int stStructType(pStructInf this); int stSetAttrValue(pStructInf this, int type, pObjData value, int nval); @@ -135,4 +135,3 @@ int stAttrIsNull(pStructInf this); pStructInf stFind(pStructInf this, char* name); #endif /* _STPARSE_H */ - diff --git a/centrallix/osdrivers/objdrv_http.c b/centrallix/osdrivers/objdrv_http.c index cf5f42b20..744a24613 100644 --- a/centrallix/osdrivers/objdrv_http.c +++ b/centrallix/osdrivers/objdrv_http.c @@ -2057,7 +2057,7 @@ http_internal_GetConfig(pHttpData inf, char* configname, pObjData pod, int datat } return -1; } - else if (stGetAttrValueOSML(stLookup(inf->Node->Data, configname), datatype, pod, index, inf->Obj->Session, NULL) < 0) + else if (stGetAttrValueOSML(stLookup(inf->Node->Data, configname), datatype, pod, index, inf->Obj->Session, NULL, 0) < 0) { /** Failed to get value **/ return -1; diff --git a/centrallix/osdrivers/objdrv_query.c b/centrallix/osdrivers/objdrv_query.c index 4595329ed..92bcb5ae6 100644 --- a/centrallix/osdrivers/objdrv_query.c +++ b/centrallix/osdrivers/objdrv_query.c @@ -392,7 +392,7 @@ qyOpen(pObject obj, int mask, pContentType systype, char* usrtype, pObjTrxTree* } /** Get SQL string **/ - if (stGetAttrValueOSML(stLookup(inf->Node->Data,"sql"), DATA_T_STRING, POD(&sql), 0, obj->Session, inf->ObjList) != 0) + if (stGetAttrValueOSML(stLookup(inf->Node->Data,"sql"), DATA_T_STRING, POD(&sql), 0, obj->Session, inf->ObjList, 0) != 0) { mssError(1,"QY","'sql' property must be supplied for query objects"); goto error; @@ -957,4 +957,3 @@ qyInitialize() return 0; } - diff --git a/centrallix/osdrivers/objdrv_report_v3.c b/centrallix/osdrivers/objdrv_report_v3.c index 21785e6af..de469e23b 100755 --- a/centrallix/osdrivers/objdrv_report_v3.c +++ b/centrallix/osdrivers/objdrv_report_v3.c @@ -4851,7 +4851,7 @@ rpt_internal_DoImage(pRptData inf, pStructInf image, pRptSession rs, pRptSource } /** Load the image **/ - if (stGetAttrValueOSML(stLookup(image,"source"), DATA_T_STRING, POD(&imgsrc), 0, inf->Obj->Session, NULL) != 0) + if (stGetAttrValueOSML(stLookup(image,"source"), DATA_T_STRING, POD(&imgsrc), 0, inf->Obj->Session, NULL, 0) != 0) { mssError(1,"RPT","report/image object must have a valid 'source' attribute"); return -1; @@ -4908,7 +4908,7 @@ rpt_internal_DoSvg(pRptData inf, pStructInf image, pRptSession rs, pRptSource th } /** Load the image **/ - if (stGetAttrValueOSML(stLookup(image,"source"), DATA_T_STRING, POD(&svgsrc), 0, inf->Obj->Session, NULL) != 0) + if (stGetAttrValueOSML(stLookup(image,"source"), DATA_T_STRING, POD(&svgsrc), 0, inf->Obj->Session, NULL, 0) != 0) { mssError(1,"RPT","report/svg object must have a valid 'source' attribute"); return -1; @@ -6755,4 +6755,3 @@ rptInitialize() return 0; } - diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index cd6d5b2bc..14cb924c2 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -557,7 +557,7 @@ stGetExpression(pStructInf this, int nval) int stGetAttrValue(pStructInf this, int type, pObjData pod, int nval) { - return stGetAttrValueOSML(this, type, pod, nval, NULL, NULL); + return stGetAttrValueOSML(this, type, pod, nval, NULL, NULL, 0); } @@ -578,7 +578,7 @@ stGetObjAttrValue(pStructInf this, char* attrname, int type, pObjData value) return 1; } - return stGetAttrValueOSML(attr_inf, type, value, 0, NULL, NULL); + return stGetAttrValueOSML(attr_inf, type, value, 0, NULL, NULL, 0); } @@ -586,7 +586,7 @@ stGetObjAttrValue(pStructInf this, char* attrname, int type, pObjData value) *** in the context of an OSML session. ***/ int -stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSession sess, pParamObjects objlist) +stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSession sess, pParamObjects objlist, int domain) { pExpression find_exp; pParamObjects my_objlist = objlist; @@ -615,6 +615,8 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio my_objlist = expCreateParamList(); my_objlist->Session = sess; } + if (domain != 0) + expBindExpression(find_exp, my_objlist, domain); expEvalTree(find_exp, my_objlist); if (!objlist) expFreeParamList(my_objlist); From adbdeb2dea5c68f95c4de1ec23cbc5e9c57af493 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 10:38:39 -0600 Subject: [PATCH 255/361] Update the cluster driver to call stGetAttrValueOSML() with a domain instead of coding its own solution. Update calls to cluster_i_parseAttribute() to use stGetAttrValueOSML(). Delete cluster_i_parseAttribute(). --- centrallix/osdrivers/objdrv_cluster.c | 208 +++++++++++++------------- 1 file changed, 108 insertions(+), 100 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 6a5012515..04a19ceb5 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -658,7 +658,6 @@ struct static void cluster_i_giveHint(const char* hint); static bool cluster_i_tryHint(char* value, char** valid_values, const unsigned int n_valid_values); static void cluster_i_unknownAttribute(char* attr_name, int target_type); -static int cluster_i_parseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); static ClusterAlgorithm cluster_i_parseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); static SimilarityMeasure cluster_i_parseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); static pSourceData cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path); @@ -806,91 +805,6 @@ cluster_i_unknownAttribute(char* attr_name, const int target_type) } -// LINK #functions -/*** @returns 0 if a value is found, - *** -1 if an error occurs, or - *** 1 if attribute is null, calling mssError() for required attributes. - *** - *** @attention - Promises that a failure invokes mssError() at least once. - *** - *** TODO: Greg - Review carefully. I think this code is the reason that - *** runserver() is sometimes not required for dynamic attributes in the - *** cluster driver, which I'm pretty sure is incorrect. - *** TODO: Israel - After Greg's review, update this doc comment to properly - *** describe the function parameters. - ***/ -static int -cluster_i_parseAttribute( - pStructInf inf, - char* attr_name, - int datatype, - pObjData data, - pParamObjects param_list, - bool required, - bool print_type_error) - { - int ret; - - /** Get attribute inf. **/ - pStructInf attr_info = stLookup(inf, attr_name); - if (UNLIKELY(attr_info == NULL)) - { - if (required) mssError(1, "Cluster", "'%s' must be specified for clustering.", attr_name); - return 1; - } - ASSERTMAGIC(attr_info, MGK_STRUCTINF); - - /** Allocate expression. **/ - pExpression exp = (pExpression)checkPtr(stGetExpression(attr_info, 0)); - if (UNLIKELY(exp == NULL)) goto err; - - /** Bind parameters. **/ - /** TODO: Greg - What does this return? How do I know if it fails? **/ - expBindExpression(exp, param_list, EXPR_F_RUNSERVER); - - /** Evaluate expression. **/ - ret = expEvalTree(exp, param_list); - if (UNLIKELY(ret != 0)) - { - mssError(0, "Cluster", "Expression evaluation failed (error code %d).", ret); - goto err; - } - - /** Check for data type mismatch. **/ - if (UNLIKELY(datatype != exp->DataType)) - { - mssError(1, "Cluster", - "Expected ['%s' : %s], but got type %s.", - attr_name, objTypeToStr(datatype), objTypeToStr(exp->DataType) - ); - goto err; - } - - /** Get the data out of the expression. **/ - ret = expExpressionToPod(exp, datatype, data); - if (UNLIKELY(ret != 0)) - { - mssError(1, "Cluster", - "Failed to get ['%s' : %s] using expression \"%s\" (error code %d).", - attr_name, objTypeToStr(datatype), exp->Name, ret - ); - goto err; - } - - /** Success. **/ - return 0; - - err: - mssError(0, "Cluster", - "Failed to parse attribute \"%s\" from group \"%s\"", - attr_name, inf->Name - ); - - /** Return error. **/ - return -1; - } - - // LINK #functions /*** Parses a ClusteringAlgorithm from the algorithm attribute in the pStructInf. *** @@ -906,7 +820,15 @@ cluster_i_parseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) { /** Get the algorithm attribute. **/ char* algorithm; - if (UNLIKELY(cluster_i_parseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0)) + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "algorithm"), + DATA_T_STRING, + POD(&algorithm), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) { mssError(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); return ALGORITHM_NULL; @@ -954,7 +876,15 @@ cluster_i_parseSimilarityMeasure(pStructInf inf, pParamObjects param_list) { /** Get the similarity_measure attribute. **/ char* measure; - if (UNLIKELY(cluster_i_parseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0)) + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "similarity_measure"), + DATA_T_STRING, + POD(&measure), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) { mssError(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); return SIMILARITY_NULL; @@ -1017,17 +947,41 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; /** Get source. **/ - if (UNLIKELY(cluster_i_parseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "source"), + DATA_T_STRING, + POD(&buf), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) goto err_free; source_data->SourcePath = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->SourcePath == NULL)) goto err_free; /** Get the attribute name to use when querying keys from the source. **/ - if (UNLIKELY(cluster_i_parseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "key_attr"), + DATA_T_STRING, + POD(&buf), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) goto err_free; source_data->KeyAttr = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->KeyAttr == NULL)) goto err_free; /** Get the attribute name to use for querying data from the source. **/ - if (UNLIKELY(cluster_i_parseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "data_attr"), + DATA_T_STRING, + POD(&buf), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) goto err_free; source_data->DataAttr = (char*)checkPtr(nmSysStrdup(buf)); if (UNLIKELY(source_data->DataAttr == NULL)) goto err_free; @@ -1148,8 +1102,15 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get window_size. **/ int window_size; - if (cluster_i_parseAttribute(inf, "window_size", DATA_T_INTEGER, POD(&window_size), param_list, true, true) != 0) - goto err_free; + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "window_size"), + DATA_T_INTEGER, + POD(&window_size), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) goto err_free; if (window_size < 1) { mssError(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); @@ -1163,8 +1124,15 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get num_clusters. **/ int num_clusters; - if (UNLIKELY(cluster_i_parseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0)) - goto err_free; + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "num_clusters"), + DATA_T_INTEGER, + POD(&num_clusters), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) goto err_free; if (num_clusters < 2) { mssError(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); @@ -1175,7 +1143,15 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get min_improvement. **/ double improvement; - result = cluster_i_parseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); + result = stGetAttrValueOSML( + stLookup(inf, "min_improvement"), + DATA_T_DOUBLE, + POD(&improvement), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ); if (UNLIKELY(result == -1)) goto err_free; else if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; else if (result == 0) @@ -1191,7 +1167,15 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get max_iterations. **/ int max_iterations; - result = cluster_i_parseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); + result = stGetAttrValueOSML( + stLookup(inf, "max_iterations"), + DATA_T_INTEGER, + POD(&max_iterations), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ); if (UNLIKELY(result == -1)) goto err_free; if (result == 0) { @@ -1206,7 +1190,15 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get seed. **/ int seed; - result = cluster_i_parseAttribute(inf, "seed", DATA_T_INTEGER, POD(&seed), param_list, false, true); + result = stGetAttrValueOSML( + stLookup(inf, "seed"), + DATA_T_INTEGER, + POD(&seed), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ); if (UNLIKELY(result == -1)) goto err_free; if (result == 0) { @@ -1425,7 +1417,15 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) /** Search for the source cluster. **/ char* source_cluster_name; - if (cluster_i_parseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), param_list, true, true) != 0) goto err_free; + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "source"), + DATA_T_STRING, + POD(&source_cluster_name), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) goto err_free; for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { pClusterData cluster_data = node_data->ClusterDatas[i]; @@ -1457,7 +1457,15 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) } /** Get threshold attribute. **/ - if (UNLIKELY(cluster_i_parseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(stGetAttrValueOSML( + stLookup(inf, "threshold"), + DATA_T_DOUBLE, + POD(&search_data->Threshold), + 0, + param_list->Session, + param_list, + EXPR_F_RUNSERVER + ) != 0)) goto err_free; if (UNLIKELY(search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold)) { mssError(1, "Cluster", From 71e2b445b0b95d6b6a6efe87490f1cf99f8e9d50 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 10:59:31 -0600 Subject: [PATCH 256/361] Fix a type in a function signature. --- centrallix/osdrivers/objdrv_cluster.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 04a19ceb5..7288046ca 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -657,7 +657,7 @@ struct // LINK #parsing static void cluster_i_giveHint(const char* hint); static bool cluster_i_tryHint(char* value, char** valid_values, const unsigned int n_valid_values); -static void cluster_i_unknownAttribute(char* attr_name, int target_type); +static void cluster_i_unknownAttribute(char* attr_name, TargetType target_type); static ClusterAlgorithm cluster_i_parseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); static SimilarityMeasure cluster_i_parseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); static pSourceData cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path); @@ -774,7 +774,7 @@ cluster_i_tryHint(char* value, char** valid_values, const unsigned int n_valid_v *** attributes in this context. ***/ static void -cluster_i_unknownAttribute(char* attr_name, const int target_type) +cluster_i_unknownAttribute(char* attr_name, const TargetType target_type) { /** Display the error message. **/ mssError(1, "Cluster", "Unknown attribute '%s'.", attr_name); From 4e7f3246fc13d0df33ea0fce64978a5c16e64c61 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 15:50:47 -0600 Subject: [PATCH 257/361] Replace check.h uses in tests with EXPECT() calls. - Unwrap check(), whose enclosing EXPECT_EQL() already asserts the result. - Replace checkPtr() with EXPECT_NOT_NULL(), folding it into the null checks that already guarded the allocations. --- centrallix-lib/tests/test_newmalloc_00.c | 15 ++++++----- centrallix-lib/tests/test_newmalloc_01.c | 15 ++++++----- centrallix-lib/tests/test_newmalloc_02.c | 1 - centrallix-lib/tests/test_xarray_00.c | 32 +++++++++++------------ centrallix-lib/tests/test_xhash_00.c | 25 +++++++++--------- centrallix-lib/tests/test_xhash_01.c | 11 ++++---- centrallix-lib/tests/test_xhash_02.c | 13 +++++----- centrallix-lib/tests/test_xhash_03.c | 33 ++++++++++++------------ centrallix-lib/tests/test_xhash_04.c | 9 +++---- centrallix-lib/tests/test_xhash_05.c | 3 +-- 10 files changed, 77 insertions(+), 80 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 33039bac5..4223cd402 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -22,7 +22,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" #include "range.h" /** Tested module. **/ @@ -55,7 +54,8 @@ static int mockErrorFn(char* error_msg) while (len > err_buf_size - err_buf_i) { err_buf_size *= 2; - err_buf = checkPtr(realloc(err_buf, err_buf_size)); + err_buf = realloc(err_buf, err_buf_size); + EXPECT_NOT_NULL(err_buf); } err_buf_i += snprintf( @@ -86,7 +86,8 @@ static bool doTest(void) srand(seed_counter++); /** Initialize the mock error function. **/ - err_buf = checkPtr(malloc(err_buf_size = 256)); + err_buf = malloc(err_buf_size = 256); + EXPECT_NOT_NULL(err_buf); err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); @@ -101,12 +102,14 @@ static bool doTest(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ - void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); - void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + void** data = malloc(TEST_LIMIT * sizeof(void*)); + void** test = malloc(TEST_LIMIT * sizeof(void*)); + success &= EXPECT_NOT_NULL(data); + success &= EXPECT_NOT_NULL(test); for (size_t i = 1lu; i < TEST_LIMIT; i++) { success &= EXPECT_NOT_NULL(test[i] = nmSysMalloc(i)); - data[i] = randomInit(checkPtr(malloc(i)), i); + success &= EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i)); memcpy(test[i], data[i], i); /* Write test data into test memory. */ } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 2dd707178..0cd11c11d 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -22,7 +22,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" /** Tested module. **/ #include "newmalloc.h" @@ -54,7 +53,8 @@ static int mockErrorFn(char* error_msg) while (len > err_buf_size - err_buf_i) { err_buf_size *= 2; - err_buf = checkPtr(realloc(err_buf, err_buf_size)); + err_buf = realloc(err_buf, err_buf_size); + EXPECT_NOT_NULL(err_buf); } err_buf_i += snprintf( @@ -85,7 +85,8 @@ static bool doTest(void) srand(seed_counter++); /** Initialize the mock error function. **/ - err_buf = checkPtr(malloc(err_buf_size = 256)); + err_buf = malloc(err_buf_size = 256); + success &= EXPECT_NOT_NULL(err_buf); err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); @@ -100,12 +101,14 @@ static bool doTest(void) success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ - void** data = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); - void** test = checkPtr(malloc(TEST_LIMIT * sizeof(void*))); + void** data = malloc(TEST_LIMIT * sizeof(void*)); + void** test = malloc(TEST_LIMIT * sizeof(void*)); + success &= EXPECT_NOT_NULL(data); + success &= EXPECT_NOT_NULL(test); for (size_t i = 1lu; i < TEST_LIMIT; i++) { success &= EXPECT_NOT_NULL(test[i] = nmMalloc(i)); - data[i] = randomInit(checkPtr(malloc(i)), i); + success &= EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i)); memcpy(test[i], data[i], i); } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) diff --git a/centrallix-lib/tests/test_newmalloc_02.c b/centrallix-lib/tests/test_newmalloc_02.c index 08fcd6345..d1ef1af41 100644 --- a/centrallix-lib/tests/test_newmalloc_02.c +++ b/centrallix-lib/tests/test_newmalloc_02.c @@ -21,7 +21,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" /** Tested module. **/ #include "newmalloc.h" diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 3cb5ac24e..7faf28d22 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 2005 LightSys Technology Services, Inc. */ +/* Copyright (C) 2025-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -25,7 +25,6 @@ /** Test dependencies. **/ #include "test_utils.h" #include "newmalloc.h" -#include "check.h" /** Tested module. **/ #include "xarray.h" @@ -67,24 +66,23 @@ static bool doTest(void) const int value_not = possible_value_not; /** Create a new xarray. **/ - pXArray xa = checkPtr(xaNew(2)); - success &= EXPECT_EQL(xa != NULL, true, "%d"); - if (xa == NULL) return false; + pXArray xa = xaNew(2); + if (!EXPECT_NOT_NULL(xa)) return false; /** A new xarray should have 0 items. **/ success &= EXPECT_EQL(xaCount(xa), 0, "%d"); /** Test adding an item. **/ - int* v1 = checkPtr(nmMalloc(sizeof(int))); - if (v1 == NULL) return false; + int* v1 = nmMalloc(sizeof(int)); + if (!EXPECT_NOT_NULL(v1)) return false; *v1 = value1; success &= EXPECT_EQL(xaAddItem(xa, v1), 0, "%d"); success &= EXPECT_EQL(xaCount(xa), 1, "%d"); success &= EXPECT_EQL(*(int*)xaGetItem(xa, 0), value1, "%d"); /** Test adding another item. **/ - int* v2 = checkPtr(nmMalloc(sizeof(int))); - if (v2 == NULL) return false; + int* v2 = nmMalloc(sizeof(int)); + if (!EXPECT_NOT_NULL(v2)) return false; *v2 = value2; success &= EXPECT_EQL(xaAddItem(xa, v2), 1, "%d"); success &= EXPECT_EQL(xaCount(xa), 2, "%d"); @@ -97,8 +95,8 @@ static bool doTest(void) success &= EXPECT_EQL(xaFindItemR(xa, v1), 0, "%d"); /** Test finding items that don't exist. **/ - int* v_not = checkPtr(nmMalloc(sizeof(int))); - if (v_not == NULL) return false; + int* v_not = nmMalloc(sizeof(int)); + if (!EXPECT_NOT_NULL(v_not)) return false; *v_not = value_not; success &= EXPECT_EQL(xaFindItem(xa, NULL), -1, "%d"); success &= EXPECT_EQL(xaFindItemR(xa, NULL), -1, "%d"); @@ -107,24 +105,24 @@ static bool doTest(void) nmFree(v_not, sizeof(int)); v_not = NULL; /** Insert before index 1. **/ - int* v3 = checkPtr(nmMalloc(sizeof(int))); - if (v3 == NULL) return false; + int* v3 = nmMalloc(sizeof(int)); + if (!EXPECT_NOT_NULL(v3)) return false; *v3 = value3; success &= EXPECT_EQL(xaInsertBefore(xa, 1, v3), 1, "%d"); success &= EXPECT_EQL(*(int*)xaGetItem(xa, 1), value3, "%d"); success &= EXPECT_EQL(xaCount(xa), 3, "%d"); /** Insert after index 2. **/ - int* v4 = checkPtr(nmMalloc(sizeof(int))); - if (v4 == NULL) return false; + int* v4 = nmMalloc(sizeof(int)); + if (!EXPECT_NOT_NULL(v4)) return false; *v4 = value4; success &= EXPECT_EQL(xaInsertAfter(xa, 2, v4), 3, "%d"); success &= EXPECT_EQL(*(int*)xaGetItem(xa, 3), value4, "%d"); success &= EXPECT_EQL(xaCount(xa), 4, "%d"); /** xaSetItem() beyond current end should create NULL gaps. **/ - int* vset = checkPtr(nmMalloc(sizeof(int))); - if (vset == NULL) return false; + int* vset = nmMalloc(sizeof(int)); + if (!EXPECT_NOT_NULL(vset)) return false; *vset = value5; success &= EXPECT_EQL(xaSetItem(xa, 5, vset), 5, "%d"); success &= EXPECT_EQL(xaCount(xa), 6, "%d"); diff --git a/centrallix-lib/tests/test_xhash_00.c b/centrallix-lib/tests/test_xhash_00.c index 651d75fab..a435b0840 100644 --- a/centrallix-lib/tests/test_xhash_00.c +++ b/centrallix-lib/tests/test_xhash_00.c @@ -27,7 +27,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" /** Tested module. **/ #include "xhash.h" @@ -44,7 +43,7 @@ static bool doTest(void) XHashTable hash; /** A key length of zero selects NUL-terminated string keys. **/ - success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, 0)), 0, "%d"); + success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); success &= EXPECT_EQL(hash.nRows, HASH_ROWS, "%d"); success &= EXPECT_EQL(hash.KeyLen, 0, "%d"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); @@ -63,7 +62,7 @@ static bool doTest(void) /** Add each pair. The table stores the pointers it is given. **/ for (int i = 0; i < KEY_COUNT; i++) { - success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], data[i])), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); } @@ -91,15 +90,15 @@ static bool doTest(void) success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); /** The empty string is a usable key. **/ - success &= EXPECT_EQL(check(xhAdd(&hash, "", data[0])), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, "", data[0]), 0, "%d"); success &= EXPECT_EQL(xhLookup(&hash, ""), data[0], "%p"); - success &= EXPECT_EQL(check(xhRemove(&hash, "")), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&hash, ""), 0, "%d"); success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); /** Remove each key. A removed key cannot be found or removed again. **/ for (int i = 0; i < KEY_COUNT; i++) { - success &= EXPECT_EQL(check(xhRemove(&hash, keys[i])), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); success &= EXPECT_EQL(xhLookup(&hash, keys[i]), NULL, "%p"); success &= EXPECT_EQL(xhRemove(&hash, keys[i]), -1, "%d"); success &= EXPECT_EQL(hash.nItems, KEY_COUNT - i - 1, "%d"); @@ -107,20 +106,20 @@ static bool doTest(void) /** Two tables are independent, even when given the same keys. **/ XHashTable other; - success &= EXPECT_EQL(check(xhInit(&other, HASH_ROWS, 0)), 0, "%d"); - success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], data[0])), 0, "%d"); - success &= EXPECT_EQL(check(xhAdd(&other, keys[0], data[1])), 0, "%d"); + success &= EXPECT_EQL(xhInit(&other, HASH_ROWS, 0), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[0], data[0]), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&other, keys[0], data[1]), 0, "%d"); success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); success &= EXPECT_EQL(xhLookup(&other, keys[0]), data[1], "%p"); - success &= EXPECT_EQL(check(xhRemove(&other, keys[0])), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&other, keys[0]), 0, "%d"); success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); success &= EXPECT_EQL(hash.nItems, 1, "%d"); success &= EXPECT_EQL(other.nItems, 0, "%d"); - success &= EXPECT_EQL(check(xhDeInit(&other)), 0, "%d"); + success &= EXPECT_EQL(xhDeInit(&other), 0, "%d"); /** Clean up. **/ - success &= EXPECT_EQL(check(xhRemove(&hash, keys[0])), 0, "%d"); - success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&hash, keys[0]), 0, "%d"); + success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_01.c b/centrallix-lib/tests/test_xhash_01.c index 882ee320a..9fd51d010 100644 --- a/centrallix-lib/tests/test_xhash_01.c +++ b/centrallix-lib/tests/test_xhash_01.c @@ -22,7 +22,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" /** Tested module. **/ #include "xhash.h" @@ -55,14 +54,14 @@ static bool doTest(void) XHashTable hash; /** A nonzero key length selects fixed length binary keys. **/ - success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, KEY_LEN)), 0, "%d"); + success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, KEY_LEN), 0, "%d"); success &= EXPECT_EQL(hash.KeyLen, KEY_LEN, "%d"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); /** Add each pair. **/ for (int i = 0; i < KEY_COUNT; i++) { - success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], data[i])), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); } @@ -86,7 +85,7 @@ static bool doTest(void) success &= EXPECT_EQL(xhRemove(&hash, other_key), -1, "%d"); /** Removing by an equal copy of a key removes the original entry. **/ - success &= EXPECT_EQL(check(xhRemove(&hash, long_key)), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&hash, long_key), 0, "%d"); success &= EXPECT_EQL(xhLookup(&hash, keys[0]), NULL, "%p"); success &= EXPECT_EQL(hash.nItems, KEY_COUNT - 1, "%d"); @@ -95,9 +94,9 @@ static bool doTest(void) success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); /** Clean up. **/ - success &= EXPECT_EQL(check(xhClear(&hash, NULL, NULL)), 0, "%d"); + success &= EXPECT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); - success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_02.c b/centrallix-lib/tests/test_xhash_02.c index 827ae4f28..9c3a28338 100644 --- a/centrallix-lib/tests/test_xhash_02.c +++ b/centrallix-lib/tests/test_xhash_02.c @@ -22,7 +22,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" /** Tested module. **/ #include "xhash.h" @@ -75,13 +74,13 @@ static bool doTest(void) int remaining_count = 0; /** One row means every key collides. **/ - success &= EXPECT_EQL(check(xhInit(&hash, 1, 0)), 0, "%d"); + success &= EXPECT_EQL(xhInit(&hash, 1, 0), 0, "%d"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); /** Build the chain. **/ for (int i = 0; i < CHAIN_COUNT; i++) { - success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], data[i])), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); } success &= checkChain(&hash, keys, CHAIN_COUNT); @@ -105,7 +104,7 @@ static bool doTest(void) { if (isRemoved(i)) { - success &= EXPECT_EQL(check(xhRemove(&hash, keys[i])), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); removed_count++; } else @@ -123,17 +122,17 @@ static bool doTest(void) success &= checkChain(&hash, remaining, remaining_count); /** A removed key can be added back, landing at the end of the chain. **/ - success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], data[0])), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[0], data[0]), 0, "%d"); success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT + 1, "%d"); remaining[remaining_count++] = keys[0]; success &= checkChain(&hash, remaining, remaining_count); /** Clean up. **/ - success &= EXPECT_EQL(check(xhClear(&hash, NULL, NULL)), 0, "%d"); + success &= EXPECT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); success &= EXPECT_EQL(hash.Rows.Items[0], NULL, "%p"); - success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_03.c b/centrallix-lib/tests/test_xhash_03.c index 3b2604630..33e11aecf 100644 --- a/centrallix-lib/tests/test_xhash_03.c +++ b/centrallix-lib/tests/test_xhash_03.c @@ -24,7 +24,6 @@ /** Test dependencies. **/ #include "test_utils.h" #include "newmalloc.h" -#include "check.h" /** Tested module. **/ #include "xhash.h" @@ -55,15 +54,15 @@ static bool doTest(void) /** Use a distinct seed each pass so the data varies. **/ srand(seed_counter++); - success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, 0)), 0, "%d"); + success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); /** Fill the table with data that xhClear() has to free. **/ for (int i = 0; i < ITEM_COUNT; i++) { - int* value = checkPtr(nmMalloc(DATA_SIZE)); - if (value == NULL) return false; + int* value = nmMalloc(DATA_SIZE); + if (!EXPECT_NOT_NULL(value)) return false; *value = rand(); - success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], (char*)value)), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[i], (char*)value), 0, "%d"); char* found = xhLookup(&hash, keys[i]); success &= EXPECT_EQL(found, (char*)value, "%p"); if (found != NULL) success &= EXPECT_EQL(*(int*)found, *value, "%d"); @@ -71,20 +70,20 @@ static bool doTest(void) success &= EXPECT_EQL(hash.nItems, ITEM_COUNT, "%d"); /** An entry may carry NULL data; only its key marks its presence. **/ - success &= EXPECT_EQL(check(xhAdd(&hash, "null-data", NULL)), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, "null-data", NULL), 0, "%d"); success &= EXPECT_EQL(hash.nItems, ITEM_COUNT + 1, "%d"); success &= EXPECT_EQL(xhLookup(&hash, "null-data"), NULL, "%p"); /** Such an entry is still found by a removal, and by a duplicate add. **/ success &= EXPECT_EQL(xhAdd(&hash, "null-data", NULL), -1, "%d"); - success &= EXPECT_EQL(check(xhRemove(&hash, "null-data")), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&hash, "null-data"), 0, "%d"); success &= EXPECT_EQL(hash.nItems, ITEM_COUNT, "%d"); /*** Clear the table. Every entry is handed to the free function, *** including the one whose data is NULL. ***/ - success &= EXPECT_EQL(check(xhAdd(&hash, "null-data", NULL)), 0, "%d"); - success &= EXPECT_EQL(check(xhClear(&hash, test_free, &freed)), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, "null-data", NULL), 0, "%d"); + success &= EXPECT_EQL(xhClear(&hash, test_free, &freed), 0, "%d"); success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); @@ -97,28 +96,28 @@ static bool doTest(void) success &= EXPECT_EQL(used_rows, 0, "%d"); /** Clearing an already cleared table frees nothing. **/ - success &= EXPECT_EQL(check(xhClear(&hash, test_free, &freed)), 0, "%d"); + success &= EXPECT_EQL(xhClear(&hash, test_free, &freed), 0, "%d"); success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); /** The table is usable again after being cleared. **/ - success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], "reused")), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[0], "reused"), 0, "%d"); success &= EXPECT_STR_EQL(xhLookup(&hash, keys[0]), "reused"); success &= EXPECT_EQL(hash.nItems, 1, "%d"); /** A NULL free function leaves the data alone. **/ - success &= EXPECT_EQL(check(xhClear(&hash, NULL, NULL)), 0, "%d"); + success &= EXPECT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); /** A free function may be given a NULL argument. **/ - int* value = checkPtr(nmMalloc(DATA_SIZE)); - if (value == NULL) return false; - success &= EXPECT_EQL(check(xhAdd(&hash, keys[0], (char*)value)), 0, "%d"); - success &= EXPECT_EQL(check(xhClear(&hash, test_free, NULL)), 0, "%d"); + int* value = nmMalloc(DATA_SIZE); + if (!EXPECT_NOT_NULL(value)) return false; + success &= EXPECT_EQL(xhAdd(&hash, keys[0], (char*)value), 0, "%d"); + success &= EXPECT_EQL(xhClear(&hash, test_free, NULL), 0, "%d"); success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); /** Clean up. **/ - success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_04.c b/centrallix-lib/tests/test_xhash_04.c index a71946708..92942065e 100644 --- a/centrallix-lib/tests/test_xhash_04.c +++ b/centrallix-lib/tests/test_xhash_04.c @@ -20,7 +20,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" /** Tested module. **/ #include "xhash.h" @@ -36,7 +35,7 @@ static bool doTest(void) char keys[KEY_COUNT][KEY_SIZE]; int used_rows = 0; - success &= EXPECT_EQL(check(xhInit(&hash, HASH_ROWS, 0)), 0, "%d"); + success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); /*** Each key doubles as its own data, giving every entry a unique *** pointer to look up. The keys share the stack of an mtask thread, @@ -45,7 +44,7 @@ static bool doTest(void) for (int i = 0; i < KEY_COUNT; i++) { snprintf(keys[i], sizeof(keys[i]), "spread-key-%d", i); - success &= EXPECT_EQL(check(xhAdd(&hash, keys[i], keys[i])), 0, "%d"); + success &= EXPECT_EQL(xhAdd(&hash, keys[i], keys[i]), 0, "%d"); } success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); @@ -55,14 +54,14 @@ static bool doTest(void) /** Removing every key empties every row. **/ for (int i = 0; i < KEY_COUNT; i++) - success &= EXPECT_EQL(check(xhRemove(&hash, keys[i])), 0, "%d"); + success &= EXPECT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); for (int i = 0; i < HASH_ROWS; i++) if (hash.Rows.Items[i] != NULL) used_rows++; success &= EXPECT_EQL(used_rows, 0, "%d"); success &= EXPECT_EQL(hash.nItems, 0, "%d"); /** Clean up. **/ - success &= EXPECT_EQL(check(xhDeInit(&hash)), 0, "%d"); + success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_05.c b/centrallix-lib/tests/test_xhash_05.c index ab8e7b34b..19ea83412 100644 --- a/centrallix-lib/tests/test_xhash_05.c +++ b/centrallix-lib/tests/test_xhash_05.c @@ -20,7 +20,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" /** Tested module. **/ #include "xhash.h" @@ -55,7 +54,7 @@ static bool doTest(void) { int hash = xh_internal_ComputeHash(key, KEY_LEN, rows[r]); success &= EXPECT_RANGE(hash, 0, rows[r] - 1, "%d"); - success &= EXPECT_EQL(check(xhInitialize()), 0, "%d"); + success &= EXPECT_EQL(xhInitialize(), 0, "%d"); success &= EXPECT_EQL(xh_internal_ComputeHash(key, KEY_LEN, rows[r]), hash, "%d"); /** An empty key hashes to the first row of any table. **/ From 7d130726bad2a42b66a661cf7e7ad0a43a6aabfb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 15:50:53 -0600 Subject: [PATCH 258/361] Modify tests to fail immediately when they are about to crash. Return from the test when a failed allocation or a failed pipe() would be dereferenced or used by the statements that follow it, rather than carrying the failure in `success` until the end of the test. --- centrallix-lib/tests/test_newmalloc_00.c | 24 ++++++++++++------------ centrallix-lib/tests/test_newmalloc_01.c | 18 +++++++++--------- centrallix-lib/tests/test_newmalloc_02.c | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 4223cd402..1d1f7ae81 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -55,7 +55,7 @@ static int mockErrorFn(char* error_msg) { err_buf_size *= 2; err_buf = realloc(err_buf, err_buf_size); - EXPECT_NOT_NULL(err_buf); + if (!EXPECT_NOT_NULL(err_buf)) return -1; } err_buf_i += snprintf( @@ -87,16 +87,16 @@ static bool doTest(void) /** Initialize the mock error function. **/ err_buf = malloc(err_buf_size = 256); - EXPECT_NOT_NULL(err_buf); + if (!EXPECT_NOT_NULL(err_buf)) return false; err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); /** Basic string data. **/ char* str1; - success &= EXPECT_NOT_NULL(str1 = nmSysMalloc(16)); + if (!EXPECT_NOT_NULL(str1 = nmSysMalloc(16))) return false; snprintf(str1, 16, "ThisIsSomeData!"); char* str2; - success &= EXPECT_NOT_NULL(str2 = nmSysMalloc(32)); + if (!EXPECT_NOT_NULL(str2 = nmSysMalloc(32))) return false; snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); @@ -104,12 +104,12 @@ static bool doTest(void) /** Random data, varying sizes. **/ void** data = malloc(TEST_LIMIT * sizeof(void*)); void** test = malloc(TEST_LIMIT * sizeof(void*)); - success &= EXPECT_NOT_NULL(data); - success &= EXPECT_NOT_NULL(test); + if (!EXPECT_NOT_NULL(data)) return false; + if (!EXPECT_NOT_NULL(test)) return false; for (size_t i = 1lu; i < TEST_LIMIT; i++) { - success &= EXPECT_NOT_NULL(test[i] = nmSysMalloc(i)); - success &= EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i)); + if (!EXPECT_NOT_NULL(test[i] = nmSysMalloc(i))) return false; + if (!EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; memcpy(test[i], data[i], i); /* Write test data into test memory. */ } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) @@ -121,7 +121,7 @@ static bool doTest(void) /** Reallocate all variably sized memory to a different size. **/ for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) - success &= EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i)); + if (!EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i))) return false; for (size_t i = 1lu; i < TEST_LIMIT; i++) success &= EXPECT_EQL(memcmp(data[i], test[i], min(i, TEST_LIMIT - i)), 0, "%d"); @@ -132,8 +132,8 @@ static bool doTest(void) /** Testing strdup. **/ char* str_dup1; char* str_dup2; - success &= EXPECT_NOT_NULL(str_dup1 = nmSysStrdup(str1)); - success &= EXPECT_NOT_NULL(str_dup2 = nmSysStrdup(str2)); + if (!EXPECT_NOT_NULL(str_dup1 = nmSysStrdup(str1))) return false; + if (!EXPECT_NOT_NULL(str_dup2 = nmSysStrdup(str2))) return false; success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeData!"); success &= EXPECT_STR_EQL(str_dup2, "ThisDataIsDifferentStringData.\n"); str_dup1[12] = '\0'; @@ -169,7 +169,7 @@ static bool doTest(void) /** Large singular allocation. **/ void* large_buf; - success &= EXPECT_NOT_NULL(large_buf = nmSysMalloc(LARGE_BUF_SIZE)); + if (!EXPECT_NOT_NULL(large_buf = nmSysMalloc(LARGE_BUF_SIZE))) return false; for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 0cd11c11d..58cd2786f 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -54,7 +54,7 @@ static int mockErrorFn(char* error_msg) { err_buf_size *= 2; err_buf = realloc(err_buf, err_buf_size); - EXPECT_NOT_NULL(err_buf); + if (!EXPECT_NOT_NULL(err_buf)) return -1; } err_buf_i += snprintf( @@ -86,16 +86,16 @@ static bool doTest(void) /** Initialize the mock error function. **/ err_buf = malloc(err_buf_size = 256); - success &= EXPECT_NOT_NULL(err_buf); + if (!EXPECT_NOT_NULL(err_buf)) return false; err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); /** Basic string data. **/ char* str1; - success &= EXPECT_NOT_NULL(str1 = nmMalloc(16)); + if (!EXPECT_NOT_NULL(str1 = nmMalloc(16))) return false; snprintf(str1, 16, "ThisIsSomeData!"); char* str2; - success &= EXPECT_NOT_NULL(str2 = nmMalloc(32)); + if (!EXPECT_NOT_NULL(str2 = nmMalloc(32))) return false; snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); @@ -103,12 +103,12 @@ static bool doTest(void) /** Random data, varying sizes. **/ void** data = malloc(TEST_LIMIT * sizeof(void*)); void** test = malloc(TEST_LIMIT * sizeof(void*)); - success &= EXPECT_NOT_NULL(data); - success &= EXPECT_NOT_NULL(test); + if (!EXPECT_NOT_NULL(data)) return false; + if (!EXPECT_NOT_NULL(test)) return false; for (size_t i = 1lu; i < TEST_LIMIT; i++) { - success &= EXPECT_NOT_NULL(test[i] = nmMalloc(i)); - success &= EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i)); + if (!EXPECT_NOT_NULL(test[i] = nmMalloc(i))) return false; + if (!EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; memcpy(test[i], data[i], i); } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) @@ -120,7 +120,7 @@ static bool doTest(void) /** Large singular allocation. **/ void* large_buf; - success &= EXPECT_NOT_NULL(large_buf = nmMalloc(LARGE_BUF_SIZE)); + if (!EXPECT_NOT_NULL(large_buf = nmMalloc(LARGE_BUF_SIZE))) return false; for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; diff --git a/centrallix-lib/tests/test_newmalloc_02.c b/centrallix-lib/tests/test_newmalloc_02.c index d1ef1af41..dc0221792 100644 --- a/centrallix-lib/tests/test_newmalloc_02.c +++ b/centrallix-lib/tests/test_newmalloc_02.c @@ -47,7 +47,7 @@ static bool doTest(void) ***/ char stats_buf[2048]; int stats_pipe[2]; - success &= EXPECT_EQL(pipe(stats_pipe), 0, "%d"); + if (!EXPECT_EQL(pipe(stats_pipe), 0, "%d")) return false; fflush(stdout); int saved_stdout = dup(STDOUT_FILENO); dup2(stats_pipe[1], STDOUT_FILENO); From 47b16f9196e9cf28b6e014e7f9d543fe0d2230d8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 15:54:02 -0600 Subject: [PATCH 259/361] Rename EXPECT macros to ASSERT. --- centrallix-lib/tests/test_newmalloc_00.c | 68 ++++++++++---------- centrallix-lib/tests/test_newmalloc_01.c | 40 ++++++------ centrallix-lib/tests/test_newmalloc_02.c | 14 ++--- centrallix-lib/tests/test_range_00.c | 60 +++++++++--------- centrallix-lib/tests/test_range_01.c | 44 ++++++------- centrallix-lib/tests/test_timer_00.c | 24 +++---- centrallix-lib/tests/test_util_02.c | 4 +- centrallix-lib/tests/test_utils.h | 28 ++++----- centrallix-lib/tests/test_xarray_00.c | 76 +++++++++++----------- centrallix-lib/tests/test_xhash_00.c | 80 ++++++++++++------------ centrallix-lib/tests/test_xhash_01.c | 36 +++++------ centrallix-lib/tests/test_xhash_02.c | 48 +++++++------- centrallix-lib/tests/test_xhash_03.c | 62 +++++++++--------- centrallix-lib/tests/test_xhash_04.c | 16 ++--- centrallix-lib/tests/test_xhash_05.c | 16 ++--- 15 files changed, 308 insertions(+), 308 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 1d1f7ae81..98f2ad8eb 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -55,7 +55,7 @@ static int mockErrorFn(char* error_msg) { err_buf_size *= 2; err_buf = realloc(err_buf, err_buf_size); - if (!EXPECT_NOT_NULL(err_buf)) return -1; + if (!ASSERT_NOT_NULL(err_buf)) return -1; } err_buf_i += snprintf( @@ -87,64 +87,64 @@ static bool doTest(void) /** Initialize the mock error function. **/ err_buf = malloc(err_buf_size = 256); - if (!EXPECT_NOT_NULL(err_buf)) return false; + if (!ASSERT_NOT_NULL(err_buf)) return false; err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); /** Basic string data. **/ char* str1; - if (!EXPECT_NOT_NULL(str1 = nmSysMalloc(16))) return false; + if (!ASSERT_NOT_NULL(str1 = nmSysMalloc(16))) return false; snprintf(str1, 16, "ThisIsSomeData!"); char* str2; - if (!EXPECT_NOT_NULL(str2 = nmSysMalloc(32))) return false; + if (!ASSERT_NOT_NULL(str2 = nmSysMalloc(32))) return false; snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ void** data = malloc(TEST_LIMIT * sizeof(void*)); void** test = malloc(TEST_LIMIT * sizeof(void*)); - if (!EXPECT_NOT_NULL(data)) return false; - if (!EXPECT_NOT_NULL(test)) return false; + if (!ASSERT_NOT_NULL(data)) return false; + if (!ASSERT_NOT_NULL(test)) return false; for (size_t i = 1lu; i < TEST_LIMIT; i++) { - if (!EXPECT_NOT_NULL(test[i] = nmSysMalloc(i))) return false; - if (!EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; + if (!ASSERT_NOT_NULL(test[i] = nmSysMalloc(i))) return false; + if (!ASSERT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; memcpy(test[i], data[i], i); /* Write test data into test memory. */ } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) - success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); + success &= ASSERT_EQL(memcmp(data[i], test[i], i), 0, "%d"); /** Basic string data is unharmed. **/ - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Reallocate all variably sized memory to a different size. **/ for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) - if (!EXPECT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i))) return false; + if (!ASSERT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i))) return false; for (size_t i = 1lu; i < TEST_LIMIT; i++) - success &= EXPECT_EQL(memcmp(data[i], test[i], min(i, TEST_LIMIT - i)), 0, "%d"); + success &= ASSERT_EQL(memcmp(data[i], test[i], min(i, TEST_LIMIT - i)), 0, "%d"); /** Basic string data is unharmed. **/ - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Testing strdup. **/ char* str_dup1; char* str_dup2; - if (!EXPECT_NOT_NULL(str_dup1 = nmSysStrdup(str1))) return false; - if (!EXPECT_NOT_NULL(str_dup2 = nmSysStrdup(str2))) return false; - success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str_dup2, "ThisDataIsDifferentStringData.\n"); + if (!ASSERT_NOT_NULL(str_dup1 = nmSysStrdup(str1))) return false; + if (!ASSERT_NOT_NULL(str_dup2 = nmSysStrdup(str2))) return false; + success &= ASSERT_STR_EQL(str_dup1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str_dup2, "ThisDataIsDifferentStringData.\n"); str_dup1[12] = '\0'; str_dup2[2] = 'a'; str_dup2[3] = 't'; - success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeDa"); - success &= EXPECT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str_dup1, "ThisIsSomeDa"); + success &= ASSERT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); /** Basic string data is unharmed. **/ - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ for (size_t i = 1lu; i < TEST_LIMIT; i++) @@ -156,31 +156,31 @@ static bool doTest(void) free(test); /** Basic string data is unharmed. **/ - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free data. **/ nmSysFree(str1); nmSysFree(str2); /** Dup string data is unharmed. **/ - success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeDa"); - success &= EXPECT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str_dup1, "ThisIsSomeDa"); + success &= ASSERT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); /** Large singular allocation. **/ void* large_buf; - if (!EXPECT_NOT_NULL(large_buf = nmSysMalloc(LARGE_BUF_SIZE))) return false; + if (!ASSERT_NOT_NULL(large_buf = nmSysMalloc(LARGE_BUF_SIZE))) return false; for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; size_t mismatches = 0lu; for (size_t i = 0lu; i < LARGE_BUF_SIZE; i++) if (*((unsigned char*)large_buf + i) != (unsigned char)(i % 255lu)) mismatches++; - success &= EXPECT_EQL(mismatches, 0lu, "%zu"); + success &= ASSERT_EQL(mismatches, 0lu, "%zu"); /** Dup string data is unharmed. **/ - success &= EXPECT_STR_EQL(str_dup1, "ThisIsSomeDa"); - success &= EXPECT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str_dup1, "ThisIsSomeDa"); + success &= ASSERT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); /** Free dups. **/ nmSysFree(str_dup1); @@ -190,7 +190,7 @@ static bool doTest(void) nmSysFree(large_buf); /** Expect no captured errors. **/ - success &= EXPECT_STR_EQL(err_buf, ""); + success &= ASSERT_STR_EQL(err_buf, ""); /** Clean up. **/ free(err_buf); diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 58cd2786f..29377f2eb 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -54,7 +54,7 @@ static int mockErrorFn(char* error_msg) { err_buf_size *= 2; err_buf = realloc(err_buf, err_buf_size); - if (!EXPECT_NOT_NULL(err_buf)) return -1; + if (!ASSERT_NOT_NULL(err_buf)) return -1; } err_buf_i += snprintf( @@ -86,52 +86,52 @@ static bool doTest(void) /** Initialize the mock error function. **/ err_buf = malloc(err_buf_size = 256); - if (!EXPECT_NOT_NULL(err_buf)) return false; + if (!ASSERT_NOT_NULL(err_buf)) return false; err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); /** Basic string data. **/ char* str1; - if (!EXPECT_NOT_NULL(str1 = nmMalloc(16))) return false; + if (!ASSERT_NOT_NULL(str1 = nmMalloc(16))) return false; snprintf(str1, 16, "ThisIsSomeData!"); char* str2; - if (!EXPECT_NOT_NULL(str2 = nmMalloc(32))) return false; + if (!ASSERT_NOT_NULL(str2 = nmMalloc(32))) return false; snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ void** data = malloc(TEST_LIMIT * sizeof(void*)); void** test = malloc(TEST_LIMIT * sizeof(void*)); - if (!EXPECT_NOT_NULL(data)) return false; - if (!EXPECT_NOT_NULL(test)) return false; + if (!ASSERT_NOT_NULL(data)) return false; + if (!ASSERT_NOT_NULL(test)) return false; for (size_t i = 1lu; i < TEST_LIMIT; i++) { - if (!EXPECT_NOT_NULL(test[i] = nmMalloc(i))) return false; - if (!EXPECT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; + if (!ASSERT_NOT_NULL(test[i] = nmMalloc(i))) return false; + if (!ASSERT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; memcpy(test[i], data[i], i); } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) - success &= EXPECT_EQL(memcmp(data[i], test[i], i), 0, "%d"); + success &= ASSERT_EQL(memcmp(data[i], test[i], i), 0, "%d"); /** Basic string data is unharmed. **/ - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Large singular allocation. **/ void* large_buf; - if (!EXPECT_NOT_NULL(large_buf = nmMalloc(LARGE_BUF_SIZE))) return false; + if (!ASSERT_NOT_NULL(large_buf = nmMalloc(LARGE_BUF_SIZE))) return false; for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; size_t mismatches = 0lu; for (size_t i = 0lu; i < LARGE_BUF_SIZE; i++) if (*((unsigned char*)large_buf + i) != (unsigned char)(i % 255lu)) mismatches++; - success &= EXPECT_EQL(mismatches, 0lu, "%zu"); + success &= ASSERT_EQL(mismatches, 0lu, "%zu"); /** Dup string data is unharmed. **/ - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ for (size_t i = 1lu; i < TEST_LIMIT; i++) @@ -143,8 +143,8 @@ static bool doTest(void) free(test); /** Basic string data is unharmed. **/ - success &= EXPECT_STR_EQL(str1, "ThisIsSomeData!"); - success &= EXPECT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); + success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); + success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free data. **/ nmFree(str1, 16); @@ -157,7 +157,7 @@ static bool doTest(void) nmClear(); /** Expect no captured errors. **/ - success &= EXPECT_STR_EQL(err_buf, ""); + success &= ASSERT_STR_EQL(err_buf, ""); /** Clean up. **/ free(err_buf); diff --git a/centrallix-lib/tests/test_newmalloc_02.c b/centrallix-lib/tests/test_newmalloc_02.c index dc0221792..7fa604fb0 100644 --- a/centrallix-lib/tests/test_newmalloc_02.c +++ b/centrallix-lib/tests/test_newmalloc_02.c @@ -35,7 +35,7 @@ static bool doTest(void) /** Give the allocator some activity to report on. **/ void* blocks[BLOCK_COUNT]; for (size_t i = 0lu; i < BLOCK_COUNT; i++) - success &= EXPECT_NOT_NULL(blocks[i] = nmMalloc(BLOCK_SIZE)); + success &= ASSERT_NOT_NULL(blocks[i] = nmMalloc(BLOCK_SIZE)); for (size_t i = 0lu; i < BLOCK_COUNT; i++) nmFree(blocks[i], BLOCK_SIZE); @@ -47,7 +47,7 @@ static bool doTest(void) ***/ char stats_buf[2048]; int stats_pipe[2]; - if (!EXPECT_EQL(pipe(stats_pipe), 0, "%d")) return false; + if (!ASSERT_EQL(pipe(stats_pipe), 0, "%d")) return false; fflush(stdout); int saved_stdout = dup(STDOUT_FILENO); dup2(stats_pipe[1], STDOUT_FILENO); @@ -64,11 +64,11 @@ static bool doTest(void) *** tracked when the library is built with NMMALLOC_PROFILING, so *** their values aren't checked here. ***/ - success &= EXPECT_RANGE(strlen(stats_buf), (size_t)32, sizeof(stats_buf) - 1lu, "%zu"); - success &= EXPECT_NOT_NULL(strstr(stats_buf, "NewMalloc subsystem statistics:")); - success &= EXPECT_NOT_NULL(strstr(stats_buf, "nmMalloc:")); - success &= EXPECT_NOT_NULL(strstr(stats_buf, "nmFree:")); - success &= EXPECT_NOT_NULL(strstr(stats_buf, "bigblks:")); + success &= ASSERT_RANGE(strlen(stats_buf), (size_t)32, sizeof(stats_buf) - 1lu, "%zu"); + success &= ASSERT_NOT_NULL(strstr(stats_buf, "NewMalloc subsystem statistics:")); + success &= ASSERT_NOT_NULL(strstr(stats_buf, "nmMalloc:")); + success &= ASSERT_NOT_NULL(strstr(stats_buf, "nmFree:")); + success &= ASSERT_NOT_NULL(strstr(stats_buf, "bigblks:")); /** Clear cache. **/ nmClear(); diff --git a/centrallix-lib/tests/test_range_00.c b/centrallix-lib/tests/test_range_00.c index c7ec952c6..2a16d2917 100644 --- a/centrallix-lib/tests/test_range_00.c +++ b/centrallix-lib/tests/test_range_00.c @@ -32,42 +32,42 @@ static bool doTest(void) bool success = true; /** Min with doubles. **/ - success &= EXPECT_EQL(min(0.0, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(min(1.0, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(min(-1.0, 0.0), -1.0, "%g"); - success &= EXPECT_EQL(min(-1.1, 0.1), -1.1, "%g"); - success &= EXPECT_EQL(min(0.0001, 0.00011), 0.0001, "%g"); - success &= EXPECT_EQL(min(DBL_MAX, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(min(DBL_MIN, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(min(-DBL_MIN, 0.0), -DBL_MIN, "%g"); - success &= EXPECT_EQL(min(-DBL_MAX, 0.0), -DBL_MAX, "%g"); - success &= EXPECT_EQL(min(pow(10, DBL_DIG), pow(10, FLT_DIG)), pow(10, FLT_DIG), "%g"); + success &= ASSERT_EQL(min(0.0, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(min(1.0, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(min(-1.0, 0.0), -1.0, "%g"); + success &= ASSERT_EQL(min(-1.1, 0.1), -1.1, "%g"); + success &= ASSERT_EQL(min(0.0001, 0.00011), 0.0001, "%g"); + success &= ASSERT_EQL(min(DBL_MAX, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(min(DBL_MIN, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(min(-DBL_MIN, 0.0), -DBL_MIN, "%g"); + success &= ASSERT_EQL(min(-DBL_MAX, 0.0), -DBL_MAX, "%g"); + success &= ASSERT_EQL(min(pow(10, DBL_DIG), pow(10, FLT_DIG)), pow(10, FLT_DIG), "%g"); /** Max with doubles. **/ - success &= EXPECT_EQL(max(0.0, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(max(1.0, 0.0), 1.0, "%g"); - success &= EXPECT_EQL(max(-1.0, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(max(-1.1, 0.1), 0.1, "%g"); - success &= EXPECT_EQL(max(0.0001, 0.00011), 0.00011, "%g"); - success &= EXPECT_EQL(max(DBL_MAX, 0.0), DBL_MAX, "%g"); - success &= EXPECT_EQL(max(DBL_MIN, 0.0), DBL_MIN, "%g"); - success &= EXPECT_EQL(max(-DBL_MIN, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(max(-DBL_MAX, 0.0), 0.0, "%g"); - success &= EXPECT_EQL(max(pow(10, DBL_DIG), pow(10, FLT_DIG)), pow(10, DBL_DIG), "%g"); + success &= ASSERT_EQL(max(0.0, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(max(1.0, 0.0), 1.0, "%g"); + success &= ASSERT_EQL(max(-1.0, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(max(-1.1, 0.1), 0.1, "%g"); + success &= ASSERT_EQL(max(0.0001, 0.00011), 0.00011, "%g"); + success &= ASSERT_EQL(max(DBL_MAX, 0.0), DBL_MAX, "%g"); + success &= ASSERT_EQL(max(DBL_MIN, 0.0), DBL_MIN, "%g"); + success &= ASSERT_EQL(max(-DBL_MIN, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(max(-DBL_MAX, 0.0), 0.0, "%g"); + success &= ASSERT_EQL(max(pow(10, DBL_DIG), pow(10, FLT_DIG)), pow(10, DBL_DIG), "%g"); /** Min with ints. **/ - success &= EXPECT_EQL(min(0, 0), 0, "%d"); - success &= EXPECT_EQL(min(1, 0), 0, "%d"); - success &= EXPECT_EQL(min(-1, 0), -1, "%d"); - success &= EXPECT_EQL(min(INT_MAX, 0), 0, "%d"); - success &= EXPECT_EQL(min(INT_MIN, 0), INT_MIN, "%d"); + success &= ASSERT_EQL(min(0, 0), 0, "%d"); + success &= ASSERT_EQL(min(1, 0), 0, "%d"); + success &= ASSERT_EQL(min(-1, 0), -1, "%d"); + success &= ASSERT_EQL(min(INT_MAX, 0), 0, "%d"); + success &= ASSERT_EQL(min(INT_MIN, 0), INT_MIN, "%d"); /** Max with ints. **/ - success &= EXPECT_EQL(max(0, 0), 0, "%d"); - success &= EXPECT_EQL(max(1, 0), 1, "%d"); - success &= EXPECT_EQL(max(-1, 0), 0, "%d"); - success &= EXPECT_EQL(max(INT_MAX, 0), INT_MAX, "%d"); - success &= EXPECT_EQL(max(INT_MIN, 0), 0, "%d"); + success &= ASSERT_EQL(max(0, 0), 0, "%d"); + success &= ASSERT_EQL(max(1, 0), 1, "%d"); + success &= ASSERT_EQL(max(-1, 0), 0, "%d"); + success &= ASSERT_EQL(max(INT_MAX, 0), INT_MAX, "%d"); + success &= ASSERT_EQL(max(INT_MIN, 0), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_range_01.c b/centrallix-lib/tests/test_range_01.c index 6479e966d..1b027d363 100644 --- a/centrallix-lib/tests/test_range_01.c +++ b/centrallix-lib/tests/test_range_01.c @@ -31,36 +31,36 @@ static bool doTest(void) bool success = true; /** Rounding to 0 decimals does not change whole numbers. **/ - success &= EXPECT_EQL(roundTo(0.0, 0), 0.0, "%g"); - success &= EXPECT_EQL(roundTo(123, 0), 123.0, "%g"); - success &= EXPECT_EQL(roundTo(-123, 0), -123.0, "%g"); + success &= ASSERT_EQL(roundTo(0.0, 0), 0.0, "%g"); + success &= ASSERT_EQL(roundTo(123, 0), 123.0, "%g"); + success &= ASSERT_EQL(roundTo(-123, 0), -123.0, "%g"); /** Rounding to 0 decimals rounds decimals. **/ - success &= EXPECT_EQL(roundTo(1.23, 0), 1.0, "%g"); - success &= EXPECT_EQL(roundTo(24.43, 0), 24.0, "%g"); - success &= EXPECT_EQL(roundTo(1234567890.499, 0), 1234567890.0, "%g"); - success &= EXPECT_EQL(roundTo(-1.82, 0), -2.0, "%g"); - success &= EXPECT_EQL(roundTo(-987654321.499, 0), -987654321.0, "%g"); + success &= ASSERT_EQL(roundTo(1.23, 0), 1.0, "%g"); + success &= ASSERT_EQL(roundTo(24.43, 0), 24.0, "%g"); + success &= ASSERT_EQL(roundTo(1234567890.499, 0), 1234567890.0, "%g"); + success &= ASSERT_EQL(roundTo(-1.82, 0), -2.0, "%g"); + success &= ASSERT_EQL(roundTo(-987654321.499, 0), -987654321.0, "%g"); /** Test rounding to various numbers of decimals. **/ - success &= EXPECT_EQL(roundTo(1.23, 1), 1.2, "%g"); - success &= EXPECT_EQL(roundTo(1.23, 2), 1.23, "%g"); - success &= EXPECT_EQL(roundTo(1.23, 3), 1.23, "%g"); - success &= EXPECT_EQL(roundTo(1.23, 8), 1.23, "%g"); - success &= EXPECT_EQL(roundTo(5824113.8, 8), 5824113.8, "%g"); + success &= ASSERT_EQL(roundTo(1.23, 1), 1.2, "%g"); + success &= ASSERT_EQL(roundTo(1.23, 2), 1.23, "%g"); + success &= ASSERT_EQL(roundTo(1.23, 3), 1.23, "%g"); + success &= ASSERT_EQL(roundTo(1.23, 8), 1.23, "%g"); + success &= ASSERT_EQL(roundTo(5824113.8, 8), 5824113.8, "%g"); /** Test rounding to negative numbers of decimals. **/ - success &= EXPECT_EQL(roundTo(1.23, -1), 0.0, "%g"); - success &= EXPECT_EQL(roundTo(123.4, -1), 120.0, "%g"); - success &= EXPECT_EQL(roundTo(586241.7, -4), 590000.0, "%g"); + success &= ASSERT_EQL(roundTo(1.23, -1), 0.0, "%g"); + success &= ASSERT_EQL(roundTo(123.4, -1), 120.0, "%g"); + success &= ASSERT_EQL(roundTo(586241.7, -4), 590000.0, "%g"); /** Rounding infinity. */ - success &= EXPECT_EQL(roundTo( INFINITY, 0), INFINITY, "%g"); - success &= EXPECT_EQL(roundTo(-INFINITY, 0), -INFINITY, "%g"); - success &= EXPECT_EQL(roundTo( INFINITY, 16), INFINITY, "%g"); - success &= EXPECT_EQL(roundTo(-INFINITY, 16), -INFINITY, "%g"); - success &= EXPECT_EQL(roundTo( INFINITY, -16), INFINITY, "%g"); - success &= EXPECT_EQL(roundTo(-INFINITY, -16), -INFINITY, "%g"); + success &= ASSERT_EQL(roundTo( INFINITY, 0), INFINITY, "%g"); + success &= ASSERT_EQL(roundTo(-INFINITY, 0), -INFINITY, "%g"); + success &= ASSERT_EQL(roundTo( INFINITY, 16), INFINITY, "%g"); + success &= ASSERT_EQL(roundTo(-INFINITY, 16), -INFINITY, "%g"); + success &= ASSERT_EQL(roundTo( INFINITY, -16), INFINITY, "%g"); + success &= ASSERT_EQL(roundTo(-INFINITY, -16), -INFINITY, "%g"); return success; } diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c index ae5951761..96fb48121 100644 --- a/centrallix-lib/tests/test_timer_00.c +++ b/centrallix-lib/tests/test_timer_00.c @@ -72,12 +72,12 @@ long long test(char** tname) double t2_val = timerGet(timer2); /** Check for incorrect values. **/ - if (!EXPECT_RANGE(t1_inter, ELAPSED_MIN(1), ELAPSED_MAX(1), "%g")) goto fail; - if (!EXPECT_RANGE(t2_inter, ELAPSED_MIN(1), ELAPSED_MAX(1), "%g")) goto fail; - if (!EXPECT_RANGE(fabs(t1_inter - t2_inter), 0.0, SKEW, "%g")) goto fail; - if (!EXPECT_RANGE(t1_val, ELAPSED_MIN(2), ELAPSED_MAX(2), "%g")) goto fail; - if (!EXPECT_RANGE(t2_val, ELAPSED_MIN(2), ELAPSED_MAX(2), "%g")) goto fail; - if (!EXPECT_RANGE(fabs(t1_val - t2_val), 0.0, SKEW, "%g")) goto fail; + if (!ASSERT_RANGE(t1_inter, ELAPSED_MIN(1), ELAPSED_MAX(1), "%g")) goto fail; + if (!ASSERT_RANGE(t2_inter, ELAPSED_MIN(1), ELAPSED_MAX(1), "%g")) goto fail; + if (!ASSERT_RANGE(fabs(t1_inter - t2_inter), 0.0, SKEW, "%g")) goto fail; + if (!ASSERT_RANGE(t1_val, ELAPSED_MIN(2), ELAPSED_MAX(2), "%g")) goto fail; + if (!ASSERT_RANGE(t2_val, ELAPSED_MIN(2), ELAPSED_MAX(2), "%g")) goto fail; + if (!ASSERT_RANGE(fabs(t1_val - t2_val), 0.0, SKEW, "%g")) goto fail; /** Test that timer can resume properly. **/ timerStart(timer1); @@ -93,12 +93,12 @@ long long test(char** tname) double t2_val2 = timerGet(timer2); /** Check for incorrect values. **/ - if (!EXPECT_RANGE(t1_inter2, ELAPSED_MIN(3), ELAPSED_MAX(3), "%g")) goto fail; - if (!EXPECT_RANGE(t2_inter2, ELAPSED_MIN(3), ELAPSED_MAX(3), "%g")) goto fail; - if (!EXPECT_RANGE(fabs(t1_inter2 - t2_inter2), 0.0, SKEW, "%g")) goto fail; - if (!EXPECT_RANGE(t1_val2, ELAPSED_MIN(4), ELAPSED_MAX(4), "%g")) goto fail; - if (!EXPECT_RANGE(t2_val2, ELAPSED_MIN(4), ELAPSED_MAX(4), "%g")) goto fail; - if (!EXPECT_RANGE(fabs(t1_val2 - t2_val2), 0.0, SKEW, "%g")) goto fail; + if (!ASSERT_RANGE(t1_inter2, ELAPSED_MIN(3), ELAPSED_MAX(3), "%g")) goto fail; + if (!ASSERT_RANGE(t2_inter2, ELAPSED_MIN(3), ELAPSED_MAX(3), "%g")) goto fail; + if (!ASSERT_RANGE(fabs(t1_inter2 - t2_inter2), 0.0, SKEW, "%g")) goto fail; + if (!ASSERT_RANGE(t1_val2, ELAPSED_MIN(4), ELAPSED_MAX(4), "%g")) goto fail; + if (!ASSERT_RANGE(t2_val2, ELAPSED_MIN(4), ELAPSED_MAX(4), "%g")) goto fail; + if (!ASSERT_RANGE(fabs(t1_val2 - t2_val2), 0.0, SKEW, "%g")) goto fail; /** Clean up. **/ timerDeInit(timer1); diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index 9125a3ef3..8f616491d 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -41,8 +41,8 @@ char _trunc[strlen(_expect) + 1]; \ strcpy(_trunc, _expect); \ if (_buf_size > 0 && (size_t)_len >= _buf_size) _trunc[_buf_size - 1] = '\0'; \ - EXPECT_EQL(_len, (int)strlen(_expect), "%d") && \ - (_buf_size == 0 || EXPECT_STR_EQL((buf), _trunc)); \ + ASSERT_EQL(_len, (int)strlen(_expect), "%d") && \ + (_buf_size == 0 || ASSERT_STR_EQL((buf), _trunc)); \ }) #define TEST_SNPRINT_COMMAS_LLU(buf, buf_size, value, expect) \ diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 46989c042..1d646a97b 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -44,13 +44,13 @@ ***/ #define MIN_TEST_SECONDS 0.1 -/*** Expect a value to be true. +/*** Assert a value to be true. *** *** @param v1 The value. *** @param v2 The second value. *** @returns true if successful, false otherwise. ***/ -#define EXPECT_TRUE(v) \ +#define ASSERT_TRUE(v) \ ({ \ const int success = !!(v); \ if (!success) fprintf(stderr, \ @@ -60,7 +60,7 @@ success; \ }) -/*** Expect two values to be equal. +/*** Assert two values to be equal. *** *** @param v1 The first value. *** @param v2 The second value. @@ -69,7 +69,7 @@ *** sting literal or a macro that expands to one. *** @returns true if successful, false otherwise. ***/ -#define EXPECT_EQL(v1, v2, sp) \ +#define ASSERT_EQL(v1, v2, sp) \ ({ \ __typeof__ (v1) _v1 = (v1); \ __typeof__ (v2) _v2 = (v2); \ @@ -81,13 +81,13 @@ success; \ }) -/*** Expect two strings to be equal. +/*** Assert two strings to be equal. *** *** @param str1 The first string. *** @param str2 The second string. *** @returns true if successful, false otherwise. ***/ -#define EXPECT_STR_EQL(str1, str2) \ +#define ASSERT_STR_EQL(str1, str2) \ ({ \ char* _str1 = (str1); \ char* _str2 = (str2); \ @@ -99,7 +99,7 @@ success; \ }) -/*** Expect two strings to be equal, including null characters. +/*** Assert two strings to be equal, including null characters. *** Warning: Can read off the end of strings. *** *** @param str1 The first string. @@ -107,7 +107,7 @@ *** @param len The length of strings to check (aka. N). *** @returns true if successful, false otherwise. ***/ -#define EXPECT_STR_EQL_N(str1, str2, len) \ +#define ASSERT_STR_EQL_N(str1, str2, len) \ ({ \ const char* _str1 = (str1); \ const char* _str2 = (str2); \ @@ -132,13 +132,13 @@ success; \ }) -/*** Expect two cosine vectors from `cluster.c` to be equal. +/*** Assert two cosine vectors from `cluster.c` to be equal. *** *** @param v1 The first vector. *** @param v2 The second vector. *** @returns true if successful, false otherwise. ***/ -#define EXPECT_VEC_EQL(v1, v2) \ +#define ASSERT_VEC_EQL(v1, v2) \ ({ \ pVector _v1 = (v1); \ pVector _v2 = (v2); \ @@ -153,7 +153,7 @@ success; \ }) -/*** Expect a value to fall within a range. +/*** Assert a value to fall within a range. *** *** @param v The value. *** @param min_v The minimum acceptable value. @@ -163,7 +163,7 @@ *** sting literal or a macro that expands to one. *** @returns true if successful, false otherwise. ***/ -#define EXPECT_RANGE(v, min_v, max_v, sp) \ +#define ASSERT_RANGE(v, min_v, max_v, sp) \ ({ \ __typeof__ (v) _v = (v); \ __typeof__ (min_v) _min = (min_v); \ @@ -176,13 +176,13 @@ success; \ }) -/*** Syntactic sugar to expect a pointer to be non null in a clearer, more +/*** Syntactic sugar to assert a pointer to be non null in a clearer, more *** concise way. *** *** @param ptr The pointer. *** @returns true if successful, false otherwise. ***/ -#define EXPECT_NOT_NULL(ptr) \ +#define ASSERT_NOT_NULL(ptr) \ ({ \ __typeof__ (ptr) _ptr = (ptr); \ int success = (_ptr != NULL); \ diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 7faf28d22..1becd94b0 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -67,71 +67,71 @@ static bool doTest(void) /** Create a new xarray. **/ pXArray xa = xaNew(2); - if (!EXPECT_NOT_NULL(xa)) return false; + if (!ASSERT_NOT_NULL(xa)) return false; /** A new xarray should have 0 items. **/ - success &= EXPECT_EQL(xaCount(xa), 0, "%d"); + success &= ASSERT_EQL(xaCount(xa), 0, "%d"); /** Test adding an item. **/ int* v1 = nmMalloc(sizeof(int)); - if (!EXPECT_NOT_NULL(v1)) return false; + if (!ASSERT_NOT_NULL(v1)) return false; *v1 = value1; - success &= EXPECT_EQL(xaAddItem(xa, v1), 0, "%d"); - success &= EXPECT_EQL(xaCount(xa), 1, "%d"); - success &= EXPECT_EQL(*(int*)xaGetItem(xa, 0), value1, "%d"); + success &= ASSERT_EQL(xaAddItem(xa, v1), 0, "%d"); + success &= ASSERT_EQL(xaCount(xa), 1, "%d"); + success &= ASSERT_EQL(*(int*)xaGetItem(xa, 0), value1, "%d"); /** Test adding another item. **/ int* v2 = nmMalloc(sizeof(int)); - if (!EXPECT_NOT_NULL(v2)) return false; + if (!ASSERT_NOT_NULL(v2)) return false; *v2 = value2; - success &= EXPECT_EQL(xaAddItem(xa, v2), 1, "%d"); - success &= EXPECT_EQL(xaCount(xa), 2, "%d"); - success &= EXPECT_EQL(*(int*)xaGetItem(xa, 1), value2, "%d"); + success &= ASSERT_EQL(xaAddItem(xa, v2), 1, "%d"); + success &= ASSERT_EQL(xaCount(xa), 2, "%d"); + success &= ASSERT_EQL(*(int*)xaGetItem(xa, 1), value2, "%d"); /** Test finding items. **/ - success &= EXPECT_EQL(xaFindItem(xa, v2), 1, "%d"); - success &= EXPECT_EQL(xaFindItem(xa, v1), 0, "%d"); - success &= EXPECT_EQL(xaFindItemR(xa, v2), 1, "%d"); - success &= EXPECT_EQL(xaFindItemR(xa, v1), 0, "%d"); + success &= ASSERT_EQL(xaFindItem(xa, v2), 1, "%d"); + success &= ASSERT_EQL(xaFindItem(xa, v1), 0, "%d"); + success &= ASSERT_EQL(xaFindItemR(xa, v2), 1, "%d"); + success &= ASSERT_EQL(xaFindItemR(xa, v1), 0, "%d"); /** Test finding items that don't exist. **/ int* v_not = nmMalloc(sizeof(int)); - if (!EXPECT_NOT_NULL(v_not)) return false; + if (!ASSERT_NOT_NULL(v_not)) return false; *v_not = value_not; - success &= EXPECT_EQL(xaFindItem(xa, NULL), -1, "%d"); - success &= EXPECT_EQL(xaFindItemR(xa, NULL), -1, "%d"); - success &= EXPECT_EQL(xaFindItem(xa, &v_not), -1, "%d"); - success &= EXPECT_EQL(xaFindItemR(xa, &v_not), -1, "%d"); + success &= ASSERT_EQL(xaFindItem(xa, NULL), -1, "%d"); + success &= ASSERT_EQL(xaFindItemR(xa, NULL), -1, "%d"); + success &= ASSERT_EQL(xaFindItem(xa, &v_not), -1, "%d"); + success &= ASSERT_EQL(xaFindItemR(xa, &v_not), -1, "%d"); nmFree(v_not, sizeof(int)); v_not = NULL; /** Insert before index 1. **/ int* v3 = nmMalloc(sizeof(int)); - if (!EXPECT_NOT_NULL(v3)) return false; + if (!ASSERT_NOT_NULL(v3)) return false; *v3 = value3; - success &= EXPECT_EQL(xaInsertBefore(xa, 1, v3), 1, "%d"); - success &= EXPECT_EQL(*(int*)xaGetItem(xa, 1), value3, "%d"); - success &= EXPECT_EQL(xaCount(xa), 3, "%d"); + success &= ASSERT_EQL(xaInsertBefore(xa, 1, v3), 1, "%d"); + success &= ASSERT_EQL(*(int*)xaGetItem(xa, 1), value3, "%d"); + success &= ASSERT_EQL(xaCount(xa), 3, "%d"); /** Insert after index 2. **/ int* v4 = nmMalloc(sizeof(int)); - if (!EXPECT_NOT_NULL(v4)) return false; + if (!ASSERT_NOT_NULL(v4)) return false; *v4 = value4; - success &= EXPECT_EQL(xaInsertAfter(xa, 2, v4), 3, "%d"); - success &= EXPECT_EQL(*(int*)xaGetItem(xa, 3), value4, "%d"); - success &= EXPECT_EQL(xaCount(xa), 4, "%d"); + success &= ASSERT_EQL(xaInsertAfter(xa, 2, v4), 3, "%d"); + success &= ASSERT_EQL(*(int*)xaGetItem(xa, 3), value4, "%d"); + success &= ASSERT_EQL(xaCount(xa), 4, "%d"); /** xaSetItem() beyond current end should create NULL gaps. **/ int* vset = nmMalloc(sizeof(int)); - if (!EXPECT_NOT_NULL(vset)) return false; + if (!ASSERT_NOT_NULL(vset)) return false; *vset = value5; - success &= EXPECT_EQL(xaSetItem(xa, 5, vset), 5, "%d"); - success &= EXPECT_EQL(xaCount(xa), 6, "%d"); - success &= EXPECT_EQL(*(int*)xaGetItem(xa, 5), value5, "%d"); - success &= EXPECT_EQL(xaGetItem(xa, 4), NULL, "%p"); /* Check null gap. */ + success &= ASSERT_EQL(xaSetItem(xa, 5, vset), 5, "%d"); + success &= ASSERT_EQL(xaCount(xa), 6, "%d"); + success &= ASSERT_EQL(*(int*)xaGetItem(xa, 5), value5, "%d"); + success &= ASSERT_EQL(xaGetItem(xa, 4), NULL, "%p"); /* Check null gap. */ /** Remove an item and ensure that it is gone. **/ - success &= EXPECT_EQL(xaRemoveItem(xa, 2), 0, "%d"); /* Remove original index 2. */ - success &= EXPECT_EQL(xaCount(xa), 5, "%d"); + success &= ASSERT_EQL(xaRemoveItem(xa, 2), 0, "%d"); /* Remove original index 2. */ + success &= ASSERT_EQL(xaCount(xa), 5, "%d"); nmFree(v2, sizeof(int)); v2 = NULL; /* v2 is removed so it isn't freed with xaClear(). */ /** Count non-NULL items prior to clearing. **/ @@ -142,12 +142,12 @@ static bool doTest(void) /** Clear and verify free was called for each non-NULL item. **/ unsigned int freed_count = 0; - success &= EXPECT_EQL(xaClear(xa, test_free, &freed_count), 0, "%d"); - success &= EXPECT_EQL(freed_count, nonnull, "%d"); - success &= EXPECT_EQL(xaCount(xa), 0, "%d"); + success &= ASSERT_EQL(xaClear(xa, test_free, &freed_count), 0, "%d"); + success &= ASSERT_EQL(freed_count, nonnull, "%d"); + success &= ASSERT_EQL(xaCount(xa), 0, "%d"); /** Clean up. **/ - success &= EXPECT_EQL(xaFree(xa), 0, "%d"); + success &= ASSERT_EQL(xaFree(xa), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_00.c b/centrallix-lib/tests/test_xhash_00.c index a435b0840..4218b8e3f 100644 --- a/centrallix-lib/tests/test_xhash_00.c +++ b/centrallix-lib/tests/test_xhash_00.c @@ -43,83 +43,83 @@ static bool doTest(void) XHashTable hash; /** A key length of zero selects NUL-terminated string keys. **/ - success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); - success &= EXPECT_EQL(hash.nRows, HASH_ROWS, "%d"); - success &= EXPECT_EQL(hash.KeyLen, 0, "%d"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); + success &= ASSERT_EQL(hash.nRows, HASH_ROWS, "%d"); + success &= ASSERT_EQL(hash.KeyLen, 0, "%d"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); /** Every row starts out empty. **/ - success &= EXPECT_EQL(xaCount(&hash.Rows), HASH_ROWS, "%d"); + success &= ASSERT_EQL(xaCount(&hash.Rows), HASH_ROWS, "%d"); int used_rows = 0; for (int i = 0; i < HASH_ROWS; i++) if (hash.Rows.Items[i] != NULL) used_rows++; - success &= EXPECT_EQL(used_rows, 0, "%d"); + success &= ASSERT_EQL(used_rows, 0, "%d"); /** An empty table has nothing to find or remove. **/ - success &= EXPECT_EQL(xhLookup(&hash, keys[0]), NULL, "%p"); - success &= EXPECT_EQL(xhRemove(&hash, keys[0]), -1, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[0]), NULL, "%p"); + success &= ASSERT_EQL(xhRemove(&hash, keys[0]), -1, "%d"); /** Add each pair. The table stores the pointers it is given. **/ for (int i = 0; i < KEY_COUNT; i++) { - success &= EXPECT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); - success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + success &= ASSERT_EQL(hash.nItems, i + 1, "%d"); } /** Every entry survives the additions that follow it. **/ for (int i = 0; i < KEY_COUNT; i++) - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); /** Keys are matched by content rather than by pointer. **/ char key_copy[16]; strcpy(key_copy, keys[0]); - success &= EXPECT_EQL(xhLookup(&hash, key_copy), data[0], "%p"); + success &= ASSERT_EQL(xhLookup(&hash, key_copy), data[0], "%p"); /** Neither a prefix nor an extension of a key matches it. **/ - success &= EXPECT_EQL(xhLookup(&hash, "alph"), NULL, "%p"); - success &= EXPECT_EQL(xhLookup(&hash, "alphabet"), NULL, "%p"); + success &= ASSERT_EQL(xhLookup(&hash, "alph"), NULL, "%p"); + success &= ASSERT_EQL(xhLookup(&hash, "alphabet"), NULL, "%p"); /** A duplicate key is rejected and leaves the existing entry alone. **/ - success &= EXPECT_EQL(xhAdd(&hash, key_copy, "duplicate"), -1, "%d"); - success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + success &= ASSERT_EQL(xhAdd(&hash, key_copy, "duplicate"), -1, "%d"); + success &= ASSERT_EQL(hash.nItems, KEY_COUNT, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); /** Removing a key that was never added does not disturb the table. **/ - success &= EXPECT_EQL(xhRemove(&hash, "alphabet"), -1, "%d"); - success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + success &= ASSERT_EQL(xhRemove(&hash, "alphabet"), -1, "%d"); + success &= ASSERT_EQL(hash.nItems, KEY_COUNT, "%d"); /** The empty string is a usable key. **/ - success &= EXPECT_EQL(xhAdd(&hash, "", data[0]), 0, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, ""), data[0], "%p"); - success &= EXPECT_EQL(xhRemove(&hash, ""), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, "", data[0]), 0, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, ""), data[0], "%p"); + success &= ASSERT_EQL(xhRemove(&hash, ""), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, KEY_COUNT, "%d"); /** Remove each key. A removed key cannot be found or removed again. **/ for (int i = 0; i < KEY_COUNT; i++) { - success &= EXPECT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), NULL, "%p"); - success &= EXPECT_EQL(xhRemove(&hash, keys[i]), -1, "%d"); - success &= EXPECT_EQL(hash.nItems, KEY_COUNT - i - 1, "%d"); + success &= ASSERT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), NULL, "%p"); + success &= ASSERT_EQL(xhRemove(&hash, keys[i]), -1, "%d"); + success &= ASSERT_EQL(hash.nItems, KEY_COUNT - i - 1, "%d"); } /** Two tables are independent, even when given the same keys. **/ XHashTable other; - success &= EXPECT_EQL(xhInit(&other, HASH_ROWS, 0), 0, "%d"); - success &= EXPECT_EQL(xhAdd(&hash, keys[0], data[0]), 0, "%d"); - success &= EXPECT_EQL(xhAdd(&other, keys[0], data[1]), 0, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); - success &= EXPECT_EQL(xhLookup(&other, keys[0]), data[1], "%p"); - success &= EXPECT_EQL(xhRemove(&other, keys[0]), 0, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); - success &= EXPECT_EQL(hash.nItems, 1, "%d"); - success &= EXPECT_EQL(other.nItems, 0, "%d"); - success &= EXPECT_EQL(xhDeInit(&other), 0, "%d"); + success &= ASSERT_EQL(xhInit(&other, HASH_ROWS, 0), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[0], data[0]), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&other, keys[0], data[1]), 0, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + success &= ASSERT_EQL(xhLookup(&other, keys[0]), data[1], "%p"); + success &= ASSERT_EQL(xhRemove(&other, keys[0]), 0, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + success &= ASSERT_EQL(hash.nItems, 1, "%d"); + success &= ASSERT_EQL(other.nItems, 0, "%d"); + success &= ASSERT_EQL(xhDeInit(&other), 0, "%d"); /** Clean up. **/ - success &= EXPECT_EQL(xhRemove(&hash, keys[0]), 0, "%d"); - success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); + success &= ASSERT_EQL(xhRemove(&hash, keys[0]), 0, "%d"); + success &= ASSERT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_01.c b/centrallix-lib/tests/test_xhash_01.c index 9fd51d010..14e961c85 100644 --- a/centrallix-lib/tests/test_xhash_01.c +++ b/centrallix-lib/tests/test_xhash_01.c @@ -54,49 +54,49 @@ static bool doTest(void) XHashTable hash; /** A nonzero key length selects fixed length binary keys. **/ - success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, KEY_LEN), 0, "%d"); - success &= EXPECT_EQL(hash.KeyLen, KEY_LEN, "%d"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(xhInit(&hash, HASH_ROWS, KEY_LEN), 0, "%d"); + success &= ASSERT_EQL(hash.KeyLen, KEY_LEN, "%d"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); /** Add each pair. **/ for (int i = 0; i < KEY_COUNT; i++) { - success &= EXPECT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, i + 1, "%d"); } /** Keys that differ only past a NUL byte are separate entries. **/ for (int i = 0; i < KEY_COUNT; i++) - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); /** Only the first KEY_LEN bytes of a key are examined. **/ char long_key[KEY_LEN * 2]; memcpy(long_key, keys[0], KEY_LEN); memset(long_key + KEY_LEN, 'x', KEY_LEN); - success &= EXPECT_EQL(xhLookup(&hash, long_key), data[0], "%p"); - success &= EXPECT_EQL(xhAdd(&hash, long_key, "duplicate"), -1, "%d"); - success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, long_key), data[0], "%p"); + success &= ASSERT_EQL(xhAdd(&hash, long_key, "duplicate"), -1, "%d"); + success &= ASSERT_EQL(hash.nItems, KEY_COUNT, "%d"); /** A key differing in its last examined byte is a different key. **/ char other_key[KEY_LEN]; memcpy(other_key, keys[0], KEY_LEN); other_key[KEY_LEN - 1] = 'Z'; - success &= EXPECT_EQL(xhLookup(&hash, other_key), NULL, "%p"); - success &= EXPECT_EQL(xhRemove(&hash, other_key), -1, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, other_key), NULL, "%p"); + success &= ASSERT_EQL(xhRemove(&hash, other_key), -1, "%d"); /** Removing by an equal copy of a key removes the original entry. **/ - success &= EXPECT_EQL(xhRemove(&hash, long_key), 0, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, keys[0]), NULL, "%p"); - success &= EXPECT_EQL(hash.nItems, KEY_COUNT - 1, "%d"); + success &= ASSERT_EQL(xhRemove(&hash, long_key), 0, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[0]), NULL, "%p"); + success &= ASSERT_EQL(hash.nItems, KEY_COUNT - 1, "%d"); /** The other entries are untouched by that removal. **/ for (int i = 1; i < KEY_COUNT; i++) - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); /** Clean up. **/ - success &= EXPECT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); - success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); + success &= ASSERT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_02.c b/centrallix-lib/tests/test_xhash_02.c index 9c3a28338..cd24a65e2 100644 --- a/centrallix-lib/tests/test_xhash_02.c +++ b/centrallix-lib/tests/test_xhash_02.c @@ -57,11 +57,11 @@ static bool checkChain(pXHashTable hash, char** expect_keys, int expect_count) for (int i = 0; i < expect_count; i++) { - if (!EXPECT_NOT_NULL(entry)) return false; - success &= EXPECT_EQL(entry->Key, expect_keys[i], "%p"); + if (!ASSERT_NOT_NULL(entry)) return false; + success &= ASSERT_EQL(entry->Key, expect_keys[i], "%p"); entry = entry->Next; } - success &= EXPECT_EQL(entry, NULL, "%p"); + success &= ASSERT_EQL(entry, NULL, "%p"); return success; } @@ -74,29 +74,29 @@ static bool doTest(void) int remaining_count = 0; /** One row means every key collides. **/ - success &= EXPECT_EQL(xhInit(&hash, 1, 0), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(xhInit(&hash, 1, 0), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); /** Build the chain. **/ for (int i = 0; i < CHAIN_COUNT; i++) { - success &= EXPECT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, i + 1, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, i + 1, "%d"); } success &= checkChain(&hash, keys, CHAIN_COUNT); /** Every key in the chain is reachable. **/ for (int i = 0; i < CHAIN_COUNT; i++) - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), data[i], "%p"); /** A key absent from a populated chain is neither found nor removed. **/ - success &= EXPECT_EQL(xhLookup(&hash, "absent"), NULL, "%p"); - success &= EXPECT_EQL(xhRemove(&hash, "absent"), -1, "%d"); - success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, "absent"), NULL, "%p"); + success &= ASSERT_EQL(xhRemove(&hash, "absent"), -1, "%d"); + success &= ASSERT_EQL(hash.nItems, CHAIN_COUNT, "%d"); /** A duplicate is caught no matter how deep in the chain it sits. **/ - success &= EXPECT_EQL(xhAdd(&hash, keys[CHAIN_COUNT / 2], "duplicate"), -1, "%d"); - success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[CHAIN_COUNT / 2], "duplicate"), -1, "%d"); + success &= ASSERT_EQL(hash.nItems, CHAIN_COUNT, "%d"); /** Unlink the head, the middle, and the tail of the chain. **/ int removed_count = 0; @@ -104,35 +104,35 @@ static bool doTest(void) { if (isRemoved(i)) { - success &= EXPECT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); + success &= ASSERT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); removed_count++; } else remaining[remaining_count++] = keys[i]; } - success &= EXPECT_EQL(removed_count, REMOVED_COUNT, "%d"); + success &= ASSERT_EQL(removed_count, REMOVED_COUNT, "%d"); /*** The chain kept the other entries, in order, and lost the removed *** ones. ***/ for (int i = 0; i < CHAIN_COUNT; i++) - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), isRemoved(i) ? NULL : data[i], "%p"); - success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT, "%d"); + success &= ASSERT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT, "%d"); success &= checkChain(&hash, remaining, remaining_count); /** A removed key can be added back, landing at the end of the chain. **/ - success &= EXPECT_EQL(xhAdd(&hash, keys[0], data[0]), 0, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); - success &= EXPECT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT + 1, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[0], data[0]), 0, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); + success &= ASSERT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT + 1, "%d"); remaining[remaining_count++] = keys[0]; success &= checkChain(&hash, remaining, remaining_count); /** Clean up. **/ - success &= EXPECT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); - success &= EXPECT_EQL(hash.Rows.Items[0], NULL, "%p"); - success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); + success &= ASSERT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(hash.Rows.Items[0], NULL, "%p"); + success &= ASSERT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_03.c b/centrallix-lib/tests/test_xhash_03.c index 33e11aecf..eaa6f607c 100644 --- a/centrallix-lib/tests/test_xhash_03.c +++ b/centrallix-lib/tests/test_xhash_03.c @@ -54,70 +54,70 @@ static bool doTest(void) /** Use a distinct seed each pass so the data varies. **/ srand(seed_counter++); - success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); + success &= ASSERT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); /** Fill the table with data that xhClear() has to free. **/ for (int i = 0; i < ITEM_COUNT; i++) { int* value = nmMalloc(DATA_SIZE); - if (!EXPECT_NOT_NULL(value)) return false; + if (!ASSERT_NOT_NULL(value)) return false; *value = rand(); - success &= EXPECT_EQL(xhAdd(&hash, keys[i], (char*)value), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[i], (char*)value), 0, "%d"); char* found = xhLookup(&hash, keys[i]); - success &= EXPECT_EQL(found, (char*)value, "%p"); - if (found != NULL) success &= EXPECT_EQL(*(int*)found, *value, "%d"); + success &= ASSERT_EQL(found, (char*)value, "%p"); + if (found != NULL) success &= ASSERT_EQL(*(int*)found, *value, "%d"); } - success &= EXPECT_EQL(hash.nItems, ITEM_COUNT, "%d"); + success &= ASSERT_EQL(hash.nItems, ITEM_COUNT, "%d"); /** An entry may carry NULL data; only its key marks its presence. **/ - success &= EXPECT_EQL(xhAdd(&hash, "null-data", NULL), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, ITEM_COUNT + 1, "%d"); - success &= EXPECT_EQL(xhLookup(&hash, "null-data"), NULL, "%p"); + success &= ASSERT_EQL(xhAdd(&hash, "null-data", NULL), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, ITEM_COUNT + 1, "%d"); + success &= ASSERT_EQL(xhLookup(&hash, "null-data"), NULL, "%p"); /** Such an entry is still found by a removal, and by a duplicate add. **/ - success &= EXPECT_EQL(xhAdd(&hash, "null-data", NULL), -1, "%d"); - success &= EXPECT_EQL(xhRemove(&hash, "null-data"), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, ITEM_COUNT, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, "null-data", NULL), -1, "%d"); + success &= ASSERT_EQL(xhRemove(&hash, "null-data"), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, ITEM_COUNT, "%d"); /*** Clear the table. Every entry is handed to the free function, *** including the one whose data is NULL. ***/ - success &= EXPECT_EQL(xhAdd(&hash, "null-data", NULL), 0, "%d"); - success &= EXPECT_EQL(xhClear(&hash, test_free, &freed), 0, "%d"); - success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, "null-data", NULL), 0, "%d"); + success &= ASSERT_EQL(xhClear(&hash, test_free, &freed), 0, "%d"); + success &= ASSERT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); /** The cleared table holds nothing and every row is empty. **/ for (int i = 0; i < ITEM_COUNT; i++) - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), NULL, "%p"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), NULL, "%p"); int used_rows = 0; for (int i = 0; i < HASH_ROWS; i++) if (hash.Rows.Items[i] != NULL) used_rows++; - success &= EXPECT_EQL(used_rows, 0, "%d"); + success &= ASSERT_EQL(used_rows, 0, "%d"); /** Clearing an already cleared table frees nothing. **/ - success &= EXPECT_EQL(xhClear(&hash, test_free, &freed), 0, "%d"); - success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); + success &= ASSERT_EQL(xhClear(&hash, test_free, &freed), 0, "%d"); + success &= ASSERT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); /** The table is usable again after being cleared. **/ - success &= EXPECT_EQL(xhAdd(&hash, keys[0], "reused"), 0, "%d"); - success &= EXPECT_STR_EQL(xhLookup(&hash, keys[0]), "reused"); - success &= EXPECT_EQL(hash.nItems, 1, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[0], "reused"), 0, "%d"); + success &= ASSERT_STR_EQL(xhLookup(&hash, keys[0]), "reused"); + success &= ASSERT_EQL(hash.nItems, 1, "%d"); /** A NULL free function leaves the data alone. **/ - success &= EXPECT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); /** A free function may be given a NULL argument. **/ int* value = nmMalloc(DATA_SIZE); - if (!EXPECT_NOT_NULL(value)) return false; - success &= EXPECT_EQL(xhAdd(&hash, keys[0], (char*)value), 0, "%d"); - success &= EXPECT_EQL(xhClear(&hash, test_free, NULL), 0, "%d"); - success &= EXPECT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); + if (!ASSERT_NOT_NULL(value)) return false; + success &= ASSERT_EQL(xhAdd(&hash, keys[0], (char*)value), 0, "%d"); + success &= ASSERT_EQL(xhClear(&hash, test_free, NULL), 0, "%d"); + success &= ASSERT_EQL(freed, (unsigned int)ITEM_COUNT + 1, "%u"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); /** Clean up. **/ - success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); + success &= ASSERT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_04.c b/centrallix-lib/tests/test_xhash_04.c index 92942065e..e7f40b835 100644 --- a/centrallix-lib/tests/test_xhash_04.c +++ b/centrallix-lib/tests/test_xhash_04.c @@ -35,7 +35,7 @@ static bool doTest(void) char keys[KEY_COUNT][KEY_SIZE]; int used_rows = 0; - success &= EXPECT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); + success &= ASSERT_EQL(xhInit(&hash, HASH_ROWS, 0), 0, "%d"); /*** Each key doubles as its own data, giving every entry a unique *** pointer to look up. The keys share the stack of an mtask thread, @@ -44,24 +44,24 @@ static bool doTest(void) for (int i = 0; i < KEY_COUNT; i++) { snprintf(keys[i], sizeof(keys[i]), "spread-key-%d", i); - success &= EXPECT_EQL(xhAdd(&hash, keys[i], keys[i]), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&hash, keys[i], keys[i]), 0, "%d"); } - success &= EXPECT_EQL(hash.nItems, KEY_COUNT, "%d"); + success &= ASSERT_EQL(hash.nItems, KEY_COUNT, "%d"); /** Nothing was lost or confused with another key. **/ for (int i = 0; i < KEY_COUNT; i++) - success &= EXPECT_EQL(xhLookup(&hash, keys[i]), (char*)keys[i], "%p"); + success &= ASSERT_EQL(xhLookup(&hash, keys[i]), (char*)keys[i], "%p"); /** Removing every key empties every row. **/ for (int i = 0; i < KEY_COUNT; i++) - success &= EXPECT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); + success &= ASSERT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); for (int i = 0; i < HASH_ROWS; i++) if (hash.Rows.Items[i] != NULL) used_rows++; - success &= EXPECT_EQL(used_rows, 0, "%d"); - success &= EXPECT_EQL(hash.nItems, 0, "%d"); + success &= ASSERT_EQL(used_rows, 0, "%d"); + success &= ASSERT_EQL(hash.nItems, 0, "%d"); /** Clean up. **/ - success &= EXPECT_EQL(xhDeInit(&hash), 0, "%d"); + success &= ASSERT_EQL(xhDeInit(&hash), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_xhash_05.c b/centrallix-lib/tests/test_xhash_05.c index 19ea83412..775f3d7ce 100644 --- a/centrallix-lib/tests/test_xhash_05.c +++ b/centrallix-lib/tests/test_xhash_05.c @@ -53,28 +53,28 @@ static bool doTest(void) for (int r = 0; r < ROW_COUNT; r++) { int hash = xh_internal_ComputeHash(key, KEY_LEN, rows[r]); - success &= EXPECT_RANGE(hash, 0, rows[r] - 1, "%d"); - success &= EXPECT_EQL(xhInitialize(), 0, "%d"); - success &= EXPECT_EQL(xh_internal_ComputeHash(key, KEY_LEN, rows[r]), hash, "%d"); + success &= ASSERT_RANGE(hash, 0, rows[r] - 1, "%d"); + success &= ASSERT_EQL(xhInitialize(), 0, "%d"); + success &= ASSERT_EQL(xh_internal_ComputeHash(key, KEY_LEN, rows[r]), hash, "%d"); /** An empty key hashes to the first row of any table. **/ - success &= EXPECT_EQL(xh_internal_ComputeHash(key, 0, rows[r]), 0, "%d"); + success &= ASSERT_EQL(xh_internal_ComputeHash(key, 0, rows[r]), 0, "%d"); } /*** The hash is in memory only, so it may be changed, but only on *** purpose: update this value when the hash or the key above changes. ***/ - success &= EXPECT_EQL(xh_internal_ComputeHash(key, KEY_LEN, WIDE_ROWS), 609235, "%d"); + success &= ASSERT_EQL(xh_internal_ComputeHash(key, KEY_LEN, WIDE_ROWS), 609235, "%d"); /** Where a byte sits matters, not just which bytes are present. **/ - success &= EXPECT_EQL(xh_internal_ComputeHash("ab", 2, WIDE_ROWS) + success &= ASSERT_EQL(xh_internal_ComputeHash("ab", 2, WIDE_ROWS) != xh_internal_ComputeHash("ba", 2, WIDE_ROWS), true, "%d"); /** Only the first keylen bytes of the key are hashed. **/ char altered[KEY_LEN]; memcpy(altered, key, KEY_LEN); altered[KEY_LEN - 1] ^= 0x5A; - success &= EXPECT_EQL( + success &= ASSERT_EQL( xh_internal_ComputeHash(altered, KEY_LEN - 1, WIDE_ROWS), xh_internal_ComputeHash(key, KEY_LEN - 1, WIDE_ROWS), "%d"); @@ -84,7 +84,7 @@ static bool doTest(void) { memcpy(altered, key, KEY_LEN); altered[i] ^= 0x5A; - bool differs = EXPECT_EQL( + bool differs = ASSERT_EQL( xh_internal_ComputeHash(altered, KEY_LEN, WIDE_ROWS) != base, true, "%d"); if (!differs) fprintf(stderr, " > Byte %d of the key did not affect the hash.\n", i); success &= differs; From 5695672b5dc64f6dc4d7db5facf16511019c8fc5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 16:25:55 -0600 Subject: [PATCH 260/361] Increase min test time because some tests blocked enough to not meet the required number of CPU cycles in time. --- centrallix-lib/tests/test_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 1d646a97b..ac5955d38 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -42,7 +42,7 @@ *** time than this might not use enough cpu cycles, confusing the test suite *** performance tracking. ***/ -#define MIN_TEST_SECONDS 0.1 +#define MIN_TEST_SECONDS 0.2 /*** Assert a value to be true. *** From 31a477f5b56a2cf8f9844a7cce298cb41be934cb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 17:20:47 -0600 Subject: [PATCH 261/361] Update error handling in stparse.c. --- centrallix/utility/stparse.c | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index 14cb924c2..3e0b90160 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -8,6 +8,7 @@ #include "cxlib/exception.h" #include "stparse_ne.h" #include "stparse.h" +#include "cxlib/check.h" #include "cxlib/mtsession.h" #include "cxlib/xstring.h" #include "cxlib/newmalloc.h" @@ -590,15 +591,19 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio { pExpression find_exp; pParamObjects my_objlist = objlist; + int rval = -1; - /** Do some error-cascade checking. **/ - if (!this) return -1; - + /** Handle edge cases. **/ + if (this == NULL) + { + mssError(0, "ST", "stGetAttrValueOSML(NULL, ...) failed: Cannot get attribute value from null struct inf."); + return -1; /* Skip end error handler because it uses this. */ + } ASSERTMAGIC(this, MGK_STRUCTINF); /** Get the correct expression **/ - find_exp = stGetExpression(this, nval); - if (!find_exp) return -1; + find_exp = checkPtr(stGetExpression(this, nval)); + if (find_exp == NULL) goto end; /** expression code? **/ if (type == DATA_T_CODE && (find_exp->Flags & (EXPR_F_RUNCLIENT | EXPR_F_RUNSERVER))) @@ -612,7 +617,7 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio { if (!objlist) { - my_objlist = expCreateParamList(); + my_objlist = checkPtr(expCreateParamList()); my_objlist->Session = sess; } if (domain != 0) @@ -624,9 +629,30 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio /** Correct type requested? **/ if (find_exp->Flags & EXPR_F_NULL) return 1; - if (type != DATA_T_ANY && type != find_exp->DataType) return -1; + + /** Check for data type mismatch. **/ + if (type != DATA_T_ANY && type != find_exp->DataType) + { + mssError(1, "ST", + "Expected ['%s' : %s], but got type %s.", + this->Name, objTypeToStr(type), objTypeToStr(find_exp->DataType) + ); + goto end; + } + + /** Get the expression value. **/ + rval = expExpressionToPod(find_exp, find_exp->DataType, pod); + + end: + if (UNLIKELY(rval < 0)) + { + mssError(1, "ST", + "Failed to parse attribute \"%s\" from group \"%s\"", + this->Name, this->Parent->Name + ); + } - return expExpressionToPod(find_exp, find_exp->DataType, pod); + return rval; } @@ -1787,5 +1813,3 @@ stPrint_ne(pStruct inf) { return stPrint_ne_r(inf, 0); } - - From 56af74a96ceae0fef198dbe3e8e0b12fd05b005c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 17:27:14 -0600 Subject: [PATCH 262/361] Fix stGetAttrValueOSML() domain and coverage mask handling. --- centrallix/utility/stparse.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index 3e0b90160..d65356d38 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -612,8 +612,11 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio return 0; } - /** If external ref, do eval **/ - if ((find_exp->ObjCoverageMask & (EXPR_MASK_EXTREF | EXPR_MASK_INDETERMINATE)) && !(find_exp->Flags & EXPR_F_RUNCLIENT)) + /*** If binding to a domain or external ref, do eval. If a domain is + *** specified, we ignore the coverage mask because expBindExpression() + *** overwrites it with the resolved mask. + ***/ + if ((domain != 0 || (find_exp->ObjCoverageMask & (EXPR_MASK_EXTREF | EXPR_MASK_INDETERMINATE))) && !(find_exp->Flags & EXPR_F_RUNCLIENT)) { if (!objlist) { From 316994716a6c1ac9a20363e1f299af7197ae28eb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 17:42:11 -0600 Subject: [PATCH 263/361] Implement stGetObjAttrValueOSML(). Add stGetObjAttrValueOSML(). Simplify stGetObjAttrValue() by calling stGetObjAttrValueOSML(). --- centrallix/include/stparse.h | 1 + centrallix/utility/stparse.c | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/centrallix/include/stparse.h b/centrallix/include/stparse.h index d59db8d13..3bf02588a 100644 --- a/centrallix/include/stparse.h +++ b/centrallix/include/stparse.h @@ -124,6 +124,7 @@ int stPrintInf(pStructInf this); int stRemoveInf(pStructInf inf); int stGetAttrValue(pStructInf this, int type, pObjData value, int nval); int stGetObjAttrValue(pStructInf this, char* attrname, int type, pObjData value); +int stGetObjAttrValueOSML(pStructInf this, char* attrname, int type, pObjData pod, int nval, pObjSession sess, pParamObjects objlist, int domain); int stGetAttrValueOSML(pStructInf this, int type, pObjData value, int nval, pObjSession sess, pParamObjects objlist, int domain); int stGetAttrType(pStructInf this, int nval); int stStructType(pStructInf this); diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index d65356d38..a9ca072ce 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -569,19 +569,27 @@ stGetAttrValue(pStructInf this, int type, pObjData pod, int nval) int stGetObjAttrValue(pStructInf this, char* attrname, int type, pObjData value) { - pStructInf attr_inf; + return stGetObjAttrValueOSML(this, attrname, type, value, 0, NULL, NULL, 0); + } - /** Find the attribute **/ - attr_inf = stLookup(this, attrname); - if (!attr_inf) +/*** stGetAttrValueOSML - return the value of an expression, evaluated + *** in the context of an OSML session. + ***/ +int +stGetObjAttrValueOSML(pStructInf this, char* attrname, int type, pObjData pod, int nval, pObjSession sess, pParamObjects objlist, int domain) + { + if (this == NULL) { - /** NULL value - attribute does not exist **/ - return 1; + mssError(0, "ST", "stGetObjAttrValueOSML(NULL, ...) failed: Cannot get attribute value from null struct inf."); + return -1; } - return stGetAttrValueOSML(attr_inf, type, value, 0, NULL, NULL, 0); - } + /** Look up the requested inf. */ + pStructInf target_inf = stLookup(this, attrname); + if (target_inf == NULL) return 1; /* Null value for nonexistent attribute. */ + return stGetAttrValueOSML(target_inf, type, pod, nval, sess, objlist, domain); + } /*** stGetAttrValueOSML - return the value of an expression, evaluated *** in the context of an OSML session. From 2df11c67d6a421387f0dd055f0c24c6cbbafe044 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 17:44:27 -0600 Subject: [PATCH 264/361] Update the cluster driver to use stGetObjAttrValueOSML(). --- centrallix/osdrivers/objdrv_cluster.c | 60 ++++++++++++++++----------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 7288046ca..55c0fb2f4 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -820,8 +820,9 @@ cluster_i_parseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) { /** Get the algorithm attribute. **/ char* algorithm; - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "algorithm"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "algorithm", DATA_T_STRING, POD(&algorithm), 0, @@ -876,8 +877,9 @@ cluster_i_parseSimilarityMeasure(pStructInf inf, pParamObjects param_list) { /** Get the similarity_measure attribute. **/ char* measure; - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "similarity_measure"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "similarity_measure", DATA_T_STRING, POD(&measure), 0, @@ -947,8 +949,9 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; /** Get source. **/ - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "source"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "source", DATA_T_STRING, POD(&buf), 0, @@ -960,8 +963,9 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) if (UNLIKELY(source_data->SourcePath == NULL)) goto err_free; /** Get the attribute name to use when querying keys from the source. **/ - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "key_attr"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "key_attr", DATA_T_STRING, POD(&buf), 0, @@ -973,8 +977,9 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) if (UNLIKELY(source_data->KeyAttr == NULL)) goto err_free; /** Get the attribute name to use for querying data from the source. **/ - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "data_attr"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "data_attr", DATA_T_STRING, POD(&buf), 0, @@ -1102,8 +1107,9 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get window_size. **/ int window_size; - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "window_size"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "window_size", DATA_T_INTEGER, POD(&window_size), 0, @@ -1124,8 +1130,9 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get num_clusters. **/ int num_clusters; - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "num_clusters"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "num_clusters", DATA_T_INTEGER, POD(&num_clusters), 0, @@ -1143,8 +1150,9 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get min_improvement. **/ double improvement; - result = stGetAttrValueOSML( - stLookup(inf, "min_improvement"), + result = stGetObjAttrValueOSML( + inf, + "min_improvement", DATA_T_DOUBLE, POD(&improvement), 0, @@ -1167,8 +1175,9 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get max_iterations. **/ int max_iterations; - result = stGetAttrValueOSML( - stLookup(inf, "max_iterations"), + result = stGetObjAttrValueOSML( + inf, + "max_iterations", DATA_T_INTEGER, POD(&max_iterations), 0, @@ -1190,8 +1199,9 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get seed. **/ int seed; - result = stGetAttrValueOSML( - stLookup(inf, "seed"), + result = stGetObjAttrValueOSML( + inf, + "seed", DATA_T_INTEGER, POD(&seed), 0, @@ -1417,8 +1427,9 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) /** Search for the source cluster. **/ char* source_cluster_name; - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "source"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "source", DATA_T_STRING, POD(&source_cluster_name), 0, @@ -1457,8 +1468,9 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) } /** Get threshold attribute. **/ - if (UNLIKELY(stGetAttrValueOSML( - stLookup(inf, "threshold"), + if (UNLIKELY(stGetObjAttrValueOSML( + inf, + "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), 0, From 433f992767b0bd43ad5e25631ce02bff2628c2ae Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 18:01:03 -0600 Subject: [PATCH 265/361] Use consistent default attribute handling style. --- centrallix/osdrivers/objdrv_cluster.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 55c0fb2f4..c8fbef71c 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1160,8 +1160,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData param_list, EXPR_F_RUNSERVER ); - if (UNLIKELY(result == -1)) goto err_free; - else if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; + if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; else if (result == 0) { if (-1.0 <= improvement && improvement <= 1.0) @@ -1172,6 +1171,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData goto err_free; } } + else goto err_free; /** Get max_iterations. **/ int max_iterations; @@ -1185,8 +1185,8 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData param_list, EXPR_F_RUNSERVER ); - if (UNLIKELY(result == -1)) goto err_free; - if (result == 0) + if (result == 1) cluster_data->MaxIterations = CI_DEFAULT_MAX_ITERATIONS; + else if (result == 0) { if (max_iterations < 1) { @@ -1195,7 +1195,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData } cluster_data->MaxIterations = (unsigned int)max_iterations; } - else cluster_data->MaxIterations = CI_DEFAULT_MAX_ITERATIONS; + else goto err_free; /** Get seed. **/ int seed; @@ -1209,8 +1209,8 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData param_list, EXPR_F_RUNSERVER ); - if (UNLIKELY(result == -1)) goto err_free; - if (result == 0) + if (result == 1) cluster_data->Seed = CI_NO_SEED; + else if (result == 0) { if (UNLIKELY(seed < 1)) { @@ -1219,7 +1219,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData } cluster_data->Seed = (unsigned int)seed; } - else cluster_data->Seed = CI_NO_SEED; + else goto err_free; /** Search for sub-clusters. **/ if (check(xaInit(&sub_clusters, CI_INITIAL_SUBCLUSTERS)) != 0) goto err_free; From 5879266592efc21e934c67c25cdd85e91f12582e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 18 Sep 2026 18:01:35 -0600 Subject: [PATCH 266/361] Drop noisy error message. --- centrallix/utility/stparse.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index a9ca072ce..e71760f88 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -602,11 +602,7 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio int rval = -1; /** Handle edge cases. **/ - if (this == NULL) - { - mssError(0, "ST", "stGetAttrValueOSML(NULL, ...) failed: Cannot get attribute value from null struct inf."); - return -1; /* Skip end error handler because it uses this. */ - } + if (this == NULL) return -1; /* Skip end error handler which uses this. */ ASSERTMAGIC(this, MGK_STRUCTINF); /** Get the correct expression **/ From 6309582fbc4896a7144ef3ee4c45697fe21032bb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 10:12:40 -0600 Subject: [PATCH 267/361] Rename EXPECT macros to ASSERT. --- centrallix-lib/tests/test_clusters_00.c | 72 +++++++++++------------ centrallix-lib/tests/test_clusters_01.c | 6 +- centrallix-lib/tests/test_clusters_02.c | 16 ++--- centrallix-lib/tests/test_clusters_03.c | 22 +++---- centrallix-lib/tests/test_clusters_04.c | 50 ++++++++-------- centrallix-lib/tests/test_clusters_05.c | 78 ++++++++++++------------- centrallix-lib/tests/test_clusters_06.c | 54 ++++++++--------- 7 files changed, 149 insertions(+), 149 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c index 1e5918fa0..d28052c99 100644 --- a/centrallix-lib/tests/test_clusters_00.c +++ b/centrallix-lib/tests/test_clusters_00.c @@ -31,22 +31,22 @@ static bool doTest(void) bool success = true; /** Kitten tests. **/ - success &= EXPECT_EQL(caEditDist("kitten", "kitten", 0, 0), 0, "%d"); /* 0 edits. */ - success &= EXPECT_EQL(caEditDist("kitten", "skitten", 0, 0), 1, "%d"); /* 1 insert. */ - success &= EXPECT_EQL(caEditDist("kitten", "itten", 0, 0), 1, "%d"); /* 1 delete. */ - success &= EXPECT_EQL(caEditDist("kitten", "mitten", 0, 0), 1, "%d"); /* 1 replace. */ - success &= EXPECT_EQL(caEditDist("kitten", "smitten", 0, 0), 2, "%d"); /* 1 insert and 1 replace. */ - success &= EXPECT_EQL(caEditDist("kitten", "iktten", 0, 0), 1, "%d"); /* 1 transpose. */ - success &= EXPECT_EQL(caEditDist("kitten", "kittens", 0, 0), 1, "%d"); /* 1 insert (end). */ - success &= EXPECT_EQL(caEditDist("kitten", "kitte", 0, 0), 1, "%d"); /* 1 delete (end). */ - success &= EXPECT_EQL(caEditDist("kitten", "kittem", 0, 0), 1, "%d"); /* 1 replace (end). */ - success &= EXPECT_EQL(caEditDist("kitten", "kittne", 0, 0), 1, "%d"); /* 1 transpose (end). */ + success &= ASSERT_EQL(caEditDist("kitten", "kitten", 0, 0), 0, "%d"); /* 0 edits. */ + success &= ASSERT_EQL(caEditDist("kitten", "skitten", 0, 0), 1, "%d"); /* 1 insert. */ + success &= ASSERT_EQL(caEditDist("kitten", "itten", 0, 0), 1, "%d"); /* 1 delete. */ + success &= ASSERT_EQL(caEditDist("kitten", "mitten", 0, 0), 1, "%d"); /* 1 replace. */ + success &= ASSERT_EQL(caEditDist("kitten", "smitten", 0, 0), 2, "%d"); /* 1 insert and 1 replace. */ + success &= ASSERT_EQL(caEditDist("kitten", "iktten", 0, 0), 1, "%d"); /* 1 transpose. */ + success &= ASSERT_EQL(caEditDist("kitten", "kittens", 0, 0), 1, "%d"); /* 1 insert (end). */ + success &= ASSERT_EQL(caEditDist("kitten", "kitte", 0, 0), 1, "%d"); /* 1 delete (end). */ + success &= ASSERT_EQL(caEditDist("kitten", "kittem", 0, 0), 1, "%d"); /* 1 replace (end). */ + success &= ASSERT_EQL(caEditDist("kitten", "kittne", 0, 0), 1, "%d"); /* 1 transpose (end). */ /** Alternate words. **/ - success &= EXPECT_EQL(caEditDist("lawn", "flown", 0, 0), 2, "%d"); /* 1 insert and one replace. */ - success &= EXPECT_EQL(caEditDist("hello", "hello!", 0, 0), 1, "%d"); /* 1 insert (end). */ - success &= EXPECT_EQL(caEditDist("zert", "zerf", 0, 0), 1, "%d"); /* 1 replace (end). */ - success &= EXPECT_EQL(caEditDist("llearr", "lear", 0, 0), 2, "%d"); /* 2 deletes (start & end). */ + success &= ASSERT_EQL(caEditDist("lawn", "flown", 0, 0), 2, "%d"); /* 1 insert and one replace. */ + success &= ASSERT_EQL(caEditDist("hello", "hello!", 0, 0), 1, "%d"); /* 1 insert (end). */ + success &= ASSERT_EQL(caEditDist("zert", "zerf", 0, 0), 1, "%d"); /* 1 replace (end). */ + success &= ASSERT_EQL(caEditDist("llearr", "lear", 0, 0), 2, "%d"); /* 2 deletes (start & end). */ /** Long strings for testing edge cases. **/ char* str1 = "This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string..."; @@ -55,35 +55,35 @@ static bool doTest(void) char* str3 = "This is quite a lengthy string. I do not expect the function to compute any longer string since this one is a full 254 characters. That is plenty, even if someone adds many contact details to their record!! Thus, this test should cover most cases we see."; /** Test edge cases. **/ - success &= EXPECT_EQL(caEditDist("", "", 0, 0), 0, "%d"); /* Identical, empty string: 0 edits. */ - success &= EXPECT_EQL(caEditDist(str1, str1, 0, 0), 0, "%d"); /* Identical, very long strings. */ - success &= EXPECT_EQL(caEditDist(str1, str2, 0, 0), 0, "%d"); /* Identical, very long strings (different pointers). */ - success &= EXPECT_EQL(caEditDist(str2, str3, 0, 0), 133, "%d"); /* 133 edits. */ + success &= ASSERT_EQL(caEditDist("", "", 0, 0), 0, "%d"); /* Identical, empty string: 0 edits. */ + success &= ASSERT_EQL(caEditDist(str1, str1, 0, 0), 0, "%d"); /* Identical, very long strings. */ + success &= ASSERT_EQL(caEditDist(str1, str2, 0, 0), 0, "%d"); /* Identical, very long strings (different pointers). */ + success &= ASSERT_EQL(caEditDist(str2, str3, 0, 0), 133, "%d"); /* 133 edits. */ /** Empty string comparsions. **/ - success &= EXPECT_EQL(caEditDist(str1, "", 0, 0), (int)strlen(str1), "%d"); - success &= EXPECT_EQL(caEditDist(str2, "", 0, 0), (int)strlen(str2), "%d"); - success &= EXPECT_EQL(caEditDist(str3, "", 0, 0), (int)strlen(str3), "%d"); + success &= ASSERT_EQL(caEditDist(str1, "", 0, 0), (int)strlen(str1), "%d"); + success &= ASSERT_EQL(caEditDist(str2, "", 0, 0), (int)strlen(str2), "%d"); + success &= ASSERT_EQL(caEditDist(str3, "", 0, 0), (int)strlen(str3), "%d"); /** Specifying lengths with overflows. **/ - success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 13, 13), 0, "%d"); - success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 13, 0), 9, "%d"); - success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 0, 13), 6, "%d"); - success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 0, 0), 3, "%d"); - success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 19, 0), 3, "%d"); - success &= EXPECT_EQL(caEditDist("A string with edits", "A string without edits", 0, 19), 6, "%d"); + success &= ASSERT_EQL(caEditDist("A string with edits", "A string without edits", 13, 13), 0, "%d"); + success &= ASSERT_EQL(caEditDist("A string with edits", "A string without edits", 13, 0), 9, "%d"); + success &= ASSERT_EQL(caEditDist("A string with edits", "A string without edits", 0, 13), 6, "%d"); + success &= ASSERT_EQL(caEditDist("A string with edits", "A string without edits", 0, 0), 3, "%d"); + success &= ASSERT_EQL(caEditDist("A string with edits", "A string without edits", 19, 0), 3, "%d"); + success &= ASSERT_EQL(caEditDist("A string with edits", "A string without edits", 0, 19), 6, "%d"); /** Test for errors in identical pointer optimizations with specified lengths. **/ char identical_string[] = "Identical String!"; - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 0), 0, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 17), 0, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 17, 0), 0, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 16), 1, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 8), 9, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 5, 6), 1, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 16, 13), 3, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 0, 1), 16, "%d"); - success &= EXPECT_EQL(caEditDist(identical_string, identical_string, 1, 0), 16, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 0, 0), 0, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 0, 17), 0, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 17, 0), 0, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 0, 16), 1, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 0, 8), 9, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 5, 6), 1, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 16, 13), 3, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 0, 1), 16, "%d"); + success &= ASSERT_EQL(caEditDist(identical_string, identical_string, 1, 0), 16, "%d"); /** Clean up. **/ free(str2); diff --git a/centrallix-lib/tests/test_clusters_01.c b/centrallix-lib/tests/test_clusters_01.c index 37890547a..2acee0142 100644 --- a/centrallix-lib/tests/test_clusters_01.c +++ b/centrallix-lib/tests/test_clusters_01.c @@ -37,9 +37,9 @@ static bool doTest(void) memset(AAA, 'A', num_edits); memset(BBB, 'B', num_edits); AAA[num_edits] = BBB[num_edits] = '\0'; - success &= EXPECT_EQL(caEditDist(AAA, "", 0, 0), num_edits, "%d"); - success &= EXPECT_EQL(caEditDist("", BBB, 0, 0), num_edits, "%d"); - success &= EXPECT_EQL(caEditDist(AAA, BBB, 0, 0), num_edits, "%d"); + success &= ASSERT_EQL(caEditDist(AAA, "", 0, 0), num_edits, "%d"); + success &= ASSERT_EQL(caEditDist("", BBB, 0, 0), num_edits, "%d"); + success &= ASSERT_EQL(caEditDist(AAA, BBB, 0, 0), num_edits, "%d"); return success; } diff --git a/centrallix-lib/tests/test_clusters_02.c b/centrallix-lib/tests/test_clusters_02.c index eec411550..9b3363561 100644 --- a/centrallix-lib/tests/test_clusters_02.c +++ b/centrallix-lib/tests/test_clusters_02.c @@ -39,22 +39,22 @@ static bool doTest(void) #define vec(s) STORE(caBuildVector(s)) /** Edge case: Null string. **/ - success &= EXPECT_EQL(caBuildVector(NULL), NULL, "%p"); + success &= ASSERT_EQL(caBuildVector(NULL), NULL, "%p"); /** Edge case: Empty string. **/ - success &= EXPECT_VEC_EQL(vec(""), ((int[]){-172, 11, -78})); + success &= ASSERT_VEC_EQL(vec(""), ((int[]){-172, 11, -78})); /** Single letter cases. **/ - success &= EXPECT_VEC_EQL(vec("a"), ((int[]){-204, 12, -25, 12, -20})); - success &= EXPECT_VEC_EQL(vec("b"), ((int[]){-151, 13, -11, 13, -87})); - success &= EXPECT_VEC_EQL(vec("v"), ((int[]){-221, 7, -19, 7, -9})); + success &= ASSERT_VEC_EQL(vec("a"), ((int[]){-204, 12, -25, 12, -20})); + success &= ASSERT_VEC_EQL(vec("b"), ((int[]){-151, 13, -11, 13, -87})); + success &= ASSERT_VEC_EQL(vec("v"), ((int[]){-221, 7, -19, 7, -9})); /** Multi-letter cases. **/ - success &= EXPECT_VEC_EQL(vec("def"), ((int[]){-79, 4, -51, 2, -4, 7, -64, 9, -49})); - success &= EXPECT_VEC_EQL(vec("vec"), ((int[]){-37, 1, -175, 12, -18, 6, -8, 7, -9})); + success &= ASSERT_VEC_EQL(vec("def"), ((int[]){-79, 4, -51, 2, -4, 7, -64, 9, -49})); + success &= ASSERT_VEC_EQL(vec("vec"), ((int[]){-37, 1, -175, 12, -18, 6, -8, 7, -9})); /** White space and punctuation should be ignored. **/ - success &= EXPECT_VEC_EQL(vec("Yippee!!!"), vec(">>->y i!&P^^_pe$/\n?e")); + success &= ASSERT_VEC_EQL(vec("Yippee!!!"), vec(">>->y i!&P^^_pe$/\n?e")); /** Clean up using the free list. **/ if (index >= max_index) diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c index f93bc5f59..3d15d09cc 100644 --- a/centrallix-lib/tests/test_clusters_03.c +++ b/centrallix-lib/tests/test_clusters_03.c @@ -45,22 +45,22 @@ static bool doTest(void) }) /** Basic tests of cosine similarity. **/ - success &= EXPECT_RANGE(cos_cmp("hello", "hello"), 0.999, 1.0, "%g"); - success &= EXPECT_RANGE(cos_cmp("hello", "zephora"), 0.0, 0.001, "%g"); - success &= EXPECT_RANGE(cos_cmp("hello", "hello world"), 0.6, 0.7, "%g"); - success &= EXPECT_RANGE(cos_cmp("hello there", "hellow there"), 0.9, 1.0, "%g"); + success &= ASSERT_RANGE(cos_cmp("hello", "hello"), 0.999, 1.0, "%g"); + success &= ASSERT_RANGE(cos_cmp("hello", "zephora"), 0.0, 0.001, "%g"); + success &= ASSERT_RANGE(cos_cmp("hello", "hello world"), 0.6, 0.7, "%g"); + success &= ASSERT_RANGE(cos_cmp("hello there", "hellow there"), 0.9, 1.0, "%g"); /** Tests on fabricated contact information. */ /*** All email addresses and phone numbers are imaginary and were *** fabricated for the purposes of this test. ***/ - success &= EXPECT_RANGE(cos_cmp("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470"), 0.49, 0.54, "%g"); - success &= EXPECT_RANGE(cos_cmp("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189"), 0.45, 0.50, "%g"); - success &= EXPECT_RANGE(cos_cmp("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791"), 0.425, 0.475, "%g"); - success &= EXPECT_RANGE(cos_cmp("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791"), 0.94, 0.99, "%g"); - success &= EXPECT_RANGE(cos_cmp("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149"), 0.575, 0.625, "%g"); - success &= EXPECT_RANGE(cos_cmp("This is an identical case", "This is an identical case"), 0.975, 1.00, "%g"); - success &= EXPECT_RANGE(cos_cmp("Samuel", "Alex"), 0.00, 0.025, "%g"); + success &= ASSERT_RANGE(cos_cmp("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470"), 0.49, 0.54, "%g"); + success &= ASSERT_RANGE(cos_cmp("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189"), 0.45, 0.50, "%g"); + success &= ASSERT_RANGE(cos_cmp("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791"), 0.425, 0.475, "%g"); + success &= ASSERT_RANGE(cos_cmp("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791"), 0.94, 0.99, "%g"); + success &= ASSERT_RANGE(cos_cmp("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149"), 0.575, 0.625, "%g"); + success &= ASSERT_RANGE(cos_cmp("This is an identical case", "This is an identical case"), 0.975, 1.00, "%g"); + success &= ASSERT_RANGE(cos_cmp("Samuel", "Alex"), 0.00, 0.025, "%g"); /** Clean up scope. **/ #undef STORE diff --git a/centrallix-lib/tests/test_clusters_04.c b/centrallix-lib/tests/test_clusters_04.c index c7d9f53a2..7c4de302a 100644 --- a/centrallix-lib/tests/test_clusters_04.c +++ b/centrallix-lib/tests/test_clusters_04.c @@ -32,33 +32,33 @@ static bool doTest(void) bool success = true; /** Basic tests of Levenshtein edit distance similarity. **/ - success &= EXPECT_RANGE(caLevCompare("hello", "hello"), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(caLevCompare("hello", "hello!"), 0.8, 1.0, "%g"); - success &= EXPECT_RANGE(caLevCompare("hello", "asdfkh"), 0.0, 0.1, "%g"); - success &= EXPECT_RANGE(caLevCompare("hello", "aaaaaaaaaaaaaaaaa"), 0.0, 0.1, "%g"); - success &= EXPECT_RANGE(caLevCompare("hello", "nope"), 0.0, 0.2, "%g"); - success &= EXPECT_RANGE(caLevCompare("hello", "noepo"), 0.15, 0.25, "%g"); - success &= EXPECT_RANGE(caLevCompare("below", "hello!"), 0.4, 0.6, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "smitten"), 0.65, 0.85, "%g"); - success &= EXPECT_RANGE(caLevCompare("hello", "bobbobbobbob"), 0.0, 0.1, "%g"); - success &= EXPECT_RANGE(caLevCompare("hello", ""), 0.0, 0.05, "%g"); - success &= EXPECT_RANGE(caLevCompare("", ""), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(caLevCompare("blooooop", "blob"), 0.3, 0.5, "%g"); - success &= EXPECT_RANGE(caLevCompare("", "!"), 0.0, 0.01, "%g"); - success &= EXPECT_RANGE(caLevCompare("h", "h"), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(caLevCompare("hi", "hi"), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "hello"), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "hello!"), 0.8, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "asdfkh"), 0.0, 0.1, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "aaaaaaaaaaaaaaaaa"), 0.0, 0.1, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "nope"), 0.0, 0.2, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "noepo"), 0.15, 0.25, "%g"); + success &= ASSERT_RANGE(caLevCompare("below", "hello!"), 0.4, 0.6, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "smitten"), 0.65, 0.85, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "bobbobbobbob"), 0.0, 0.1, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", ""), 0.0, 0.05, "%g"); + success &= ASSERT_RANGE(caLevCompare("", ""), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("blooooop", "blob"), 0.3, 0.5, "%g"); + success &= ASSERT_RANGE(caLevCompare("", "!"), 0.0, 0.01, "%g"); + success &= ASSERT_RANGE(caLevCompare("h", "h"), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("hi", "hi"), 0.99, 1.0, "%g"); /** Kitten tests with specific edit operations. **/ - success &= EXPECT_RANGE(caLevCompare("kitten", "kitten"), 0.99, 1.0, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "skitten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "itten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "mitten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "smitten"), 0.7, 0.8, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "iktten"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "kittens"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "kitte"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "kittem"), 0.8, 0.9, "%g"); - success &= EXPECT_RANGE(caLevCompare("kitten", "kittne"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kitten"), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "skitten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "itten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "mitten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "smitten"), 0.7, 0.8, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "iktten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kittens"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kitte"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kittem"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kittne"), 0.8, 0.9, "%g"); return success; } diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index 3886eba68..d2cde6bb4 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -66,46 +66,46 @@ static bool doTest(void) bool success = true; /** Check error cases. **/ - success &= EXPECT_STR_EQL(caMostSimilar(NULL, (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", NULL, 2, caLevCompare, 0.0), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, NULL, 0.0), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 1.1), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, -0.1), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, INFINITY), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, -INFINITY), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, NAN), NULL); + success &= ASSERT_STR_EQL(caMostSimilar(NULL, (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str", NULL, 2, caLevCompare, 0.0), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, NULL, 0.0), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 1.1), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, -0.1), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, INFINITY), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, -INFINITY), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, NAN), NULL); /** Simple test cases. **/ - success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); - success &= EXPECT_STR_EQL(caMostSimilar("kitten", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); - success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, caLevCompare, 0.0), "str2"); + success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); + success &= ASSERT_STR_EQL(caMostSimilar("kitten", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); + success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, caLevCompare, 0.0), "str2"); /** Many, identically similar options. */ - success &= EXPECT_STR_EQL(caMostSimilar("kitten", + success &= ASSERT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "itten", "mitten", "iktten", "kittens", "kitte", "kittem", "kittne"}, 8, caLevCompare, 0.0), "skitten"); /** Pointer-perfect handling. **/ char* target = "string"; - success &= EXPECT_EQL((char*)caMostSimilar(target, (void*[]){"str", target}, 2, caLevCompare, 0.0), target, "%s"); + success &= ASSERT_EQL((char*)caMostSimilar(target, (void*[]){"str", target}, 2, caLevCompare, 0.0), target, "%s"); /** List overflow. **/ - success &= EXPECT_STR_EQL(caMostSimilar("target", (void*[]){"str1", "targets", "target", "walmart"}, 2, caLevCompare, 0.0), "targets"); + success &= ASSERT_STR_EQL(caMostSimilar("target", (void*[]){"str1", "targets", "target", "walmart"}, 2, caLevCompare, 0.0), "targets"); /** Length detection on null-terminated lists. **/ - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1", NULL}, 0, caLevCompare, 0.0), "str1"); - success &= EXPECT_STR_EQL(caMostSimilar("str", (void*[]){NULL}, 0, caLevCompare, 0.0), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string", NULL}, 0, caLevCompare, 0.5), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("target", (void*[]){"str1", "targets", "target", NULL}, 2, caLevCompare, 0.0), "targets"); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){"str_abc", "str1", NULL}, 0, caLevCompare, 0.0), "str1"); + success &= ASSERT_STR_EQL(caMostSimilar("str", (void*[]){NULL}, 0, caLevCompare, 0.0), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string", NULL}, 0, caLevCompare, 0.5), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("target", (void*[]){"str1", "targets", "target", NULL}, 2, caLevCompare, 0.0), "targets"); /** Threshold exceeded. **/ - success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.0), "blooooop"); - success &= EXPECT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.5), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("hello", (void*[]){"bane", "noepo", "stars"}, 3, caLevCompare, 0.0), "noepo"); - success &= EXPECT_STR_EQL(caMostSimilar("hello", (void*[]){"bane", "noepo", "stars"}, 3, caLevCompare, 0.25), NULL); - success &= EXPECT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "fit"}, 2, caLevCompare, 0.0), "skitten"); - success &= EXPECT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "fit"}, 2, caLevCompare, 0.9), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.0), "blooooop"); + success &= ASSERT_STR_EQL(caMostSimilar("blob", (void*[]){"blooooop", "targets", "string"}, 3, caLevCompare, 0.5), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("hello", (void*[]){"bane", "noepo", "stars"}, 3, caLevCompare, 0.0), "noepo"); + success &= ASSERT_STR_EQL(caMostSimilar("hello", (void*[]){"bane", "noepo", "stars"}, 3, caLevCompare, 0.25), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "fit"}, 2, caLevCompare, 0.0), "skitten"); + success &= ASSERT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "fit"}, 2, caLevCompare, 0.9), NULL); /** Make an array to STORE() pointers to vectors so we can free them. **/ const unsigned int max_index = 32u; @@ -119,20 +119,20 @@ static bool doTest(void) pVector held = vec("held"), zephora = vec("zephora"), hexza = vec("hexza"); pVector hello_there = vec("hello there"), hello_world = vec("hello world"); pVector hellow_there = vec("hellow there"); - success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){fellow, felon, hello, held}, 4, caCosCompare, 0.0), hello); - success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora, hello_world, hexza}, 3, caCosCompare, 0.0), hello_world); - success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora, hello_world}, 1, caCosCompare, 0.0), zephora); - success &= EXPECT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora}, 1, caCosCompare, 0.0), zephora); - success &= EXPECT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there, hexza}, 4, caCosCompare, 0.0), hellow_there); - success &= EXPECT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, caCosCompare, 0.0), hello_world); - success &= EXPECT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, caCosCompare, 0.8), NULL); + success &= ASSERT_VEC_EQL(caMostSimilar(hello, (void*[]){fellow, felon, hello, held}, 4, caCosCompare, 0.0), hello); + success &= ASSERT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora, hello_world, hexza}, 3, caCosCompare, 0.0), hello_world); + success &= ASSERT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora, hello_world}, 1, caCosCompare, 0.0), zephora); + success &= ASSERT_VEC_EQL(caMostSimilar(hello, (void*[]){zephora}, 1, caCosCompare, 0.0), zephora); + success &= ASSERT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there, hexza}, 4, caCosCompare, 0.0), hellow_there); + success &= ASSERT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, caCosCompare, 0.0), hello_world); + success &= ASSERT_VEC_EQL(caMostSimilar(hello_there, (void*[]){hello_world, zephora, hellow_there}, 2, caCosCompare, 0.8), NULL); /** Special characters (ignored by the similarity function). **/ pVector yip = vec("Yippee!!!"); pVector str1 = vec("@*#((%^!&@*-+!"), str2 = vec(">>->y i!&P^^_pe$/\n?e"), str3 = vec("yip"); - success &= EXPECT_VEC_EQL(yip, str2); - success &= EXPECT_VEC_EQL(caMostSimilar(yip, (void*[]){str1, str2, str3}, 3, caCosCompare, 0.0), str2); - success &= EXPECT_VEC_EQL(caMostSimilar(yip, (void*[]){str1, str2, str3}, 3, caCosCompare, 1.0), str2); + success &= ASSERT_VEC_EQL(yip, str2); + success &= ASSERT_VEC_EQL(caMostSimilar(yip, (void*[]){str1, str2, str3}, 3, caCosCompare, 0.0), str2); + success &= ASSERT_VEC_EQL(caMostSimilar(yip, (void*[]){str1, str2, str3}, 3, caCosCompare, 1.0), str2); /** Clean up scope. **/ #undef STORE @@ -164,8 +164,8 @@ static bool doTest(void) if (check(xhAdd(&sim_table, "str1|str", (void*)&str1_str)) != 0) return false; if (check(xhAdd(&sim_table, "str1|str2", (void*)&str1_str2)) != 0) return false; if (check(xhAdd(&sim_table, "str1|eight", (void*)&str1_eight)) != 0) return false; - success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.0), "eight"); - success &= EXPECT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.9), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.0), "eight"); + success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.9), NULL); if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; /** Nans are skipped. **/ @@ -173,8 +173,8 @@ static bool doTest(void) if (check(xhAdd(&sim_table, "val|nan", (void*)&val_nan)) != 0) return false; if (check(xhAdd(&sim_table, "val|vals", (void*)&val_vals)) != 0) return false; if (check(xhAdd(&sim_table, "val|val", (void*)&val_val)) != 0) return false; - success &= EXPECT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.0), "nan"); - success &= EXPECT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.9), NULL); + success &= ASSERT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.0), "nan"); + success &= ASSERT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.9), NULL); if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; /** Clean up. **/ diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index bba789efb..aa11118e4 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -44,13 +44,13 @@ static void freePairs(pXArray xPairs) check(xaDeInit(xPairs)); } -#define EXPECT_PAIR(Pair, k1, k2, sim_min, sim_max) \ +#define ASSERT_PAIR(Pair, k1, k2, sim_min, sim_max) \ ({ \ bool success = true; \ pPair d = (Pair); \ - success &= EXPECT_EQL(d->i, k1, "%u"); \ - success &= EXPECT_EQL(d->j, k2, "%u"); \ - success &= EXPECT_RANGE(d->similarity, sim_min, sim_max, "%g"); \ + success &= ASSERT_EQL(d->i, k1, "%u"); \ + success &= ASSERT_EQL(d->j, k2, "%u"); \ + success &= ASSERT_RANGE(d->similarity, sim_min, sim_max, "%g"); \ success; \ }) @@ -70,20 +70,20 @@ static bool doTest(void) }; /** Check error cases. **/ - success &= EXPECT_EQL(caCompleteSearch(NULL, 6, caLevCompare, 0.8, NULL), NULL, "%p"); - success &= EXPECT_EQL(caCompleteSearch(data, 0, caLevCompare, 0.8, NULL), NULL, "%p"); - success &= EXPECT_EQL(caCompleteSearch(data, 6, NULL, 0.8, NULL), NULL, "%p"); - success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, 1.1, NULL), NULL, "%p"); - success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, -0.1, NULL), NULL, "%p"); - success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, INFINITY, NULL), NULL, "%p"); - success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, -INFINITY, NULL), NULL, "%p"); - success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, NAN, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(NULL, 6, caLevCompare, 0.8, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 0, caLevCompare, 0.8, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 6, NULL, 0.8, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 6, caLevCompare, 1.1, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 6, caLevCompare, -0.1, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 6, caLevCompare, INFINITY, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 6, caLevCompare, -INFINITY, NULL), NULL, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 6, caLevCompare, NAN, NULL), NULL, "%p"); /** Test complete search. **/ { XArray xPairs; if (check(xaInit(&xPairs, 4)) != 0) return false; - success &= EXPECT_EQL(caCompleteSearch(data, 6, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); + success &= ASSERT_EQL(caCompleteSearch(data, 6, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) { @@ -96,10 +96,10 @@ static bool doTest(void) } } qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); - success &= EXPECT_EQL(xPairs.nItems, 3, "%d"); - success &= EXPECT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); - success &= EXPECT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); - success &= EXPECT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); + success &= ASSERT_EQL(xPairs.nItems, 3, "%d"); + success &= ASSERT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); + success &= ASSERT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); + success &= ASSERT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); freePairs(&xPairs); } @@ -107,7 +107,7 @@ static bool doTest(void) { XArray xPairs; if (check(xaInit(&xPairs, 4)) != 0) return false; - success &= EXPECT_EQL(caSlidingSearch(data, 6, 5, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); + success &= ASSERT_EQL(caSlidingSearch(data, 6, 5, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) { @@ -120,10 +120,10 @@ static bool doTest(void) } } qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); - success &= EXPECT_EQL(xPairs.nItems, 2, "%d"); - success &= EXPECT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); - // success &= EXPECT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ - success &= EXPECT_PAIR(Pairs[1], 1, 5, 0.8, 1.0); + success &= ASSERT_EQL(xPairs.nItems, 2, "%d"); + success &= ASSERT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); + // success &= ASSERT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ + success &= ASSERT_PAIR(Pairs[1], 1, 5, 0.8, 1.0); freePairs(&xPairs); } @@ -131,7 +131,7 @@ static bool doTest(void) { XArray xPairs; if (check(xaInit(&xPairs, 4)) != 0) return false; - success &= EXPECT_EQL(caSlidingSearch(data, 6, 2, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); + success &= ASSERT_EQL(caSlidingSearch(data, 6, 2, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) { @@ -144,10 +144,10 @@ static bool doTest(void) } } qsort(Pairs, xPairs.nItems, sizeof(pPair), cmp_Pairs); - success &= EXPECT_EQL(xPairs.nItems, 1, "%d"); - success &= EXPECT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); - // success &= EXPECT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ - // success &= EXPECT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); /* Sliding search misses this pair. */ + success &= ASSERT_EQL(xPairs.nItems, 1, "%d"); + success &= ASSERT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); + // success &= ASSERT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ + // success &= ASSERT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); /* Sliding search misses this pair. */ freePairs(&xPairs); } From f791c316498213c6fb259f703b8e01eff4efe73c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 10:17:29 -0600 Subject: [PATCH 268/361] Improve spacing in test_clusters_04.c. --- centrallix-lib/tests/test_clusters_04.c | 56 ++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_04.c b/centrallix-lib/tests/test_clusters_04.c index 7c4de302a..3b6ddc36b 100644 --- a/centrallix-lib/tests/test_clusters_04.c +++ b/centrallix-lib/tests/test_clusters_04.c @@ -30,36 +30,36 @@ static bool doTest(void) { bool success = true; - + /** Basic tests of Levenshtein edit distance similarity. **/ - success &= ASSERT_RANGE(caLevCompare("hello", "hello"), 0.99, 1.0, "%g"); - success &= ASSERT_RANGE(caLevCompare("hello", "hello!"), 0.8, 1.0, "%g"); - success &= ASSERT_RANGE(caLevCompare("hello", "asdfkh"), 0.0, 0.1, "%g"); - success &= ASSERT_RANGE(caLevCompare("hello", "aaaaaaaaaaaaaaaaa"), 0.0, 0.1, "%g"); - success &= ASSERT_RANGE(caLevCompare("hello", "nope"), 0.0, 0.2, "%g"); - success &= ASSERT_RANGE(caLevCompare("hello", "noepo"), 0.15, 0.25, "%g"); - success &= ASSERT_RANGE(caLevCompare("below", "hello!"), 0.4, 0.6, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "smitten"), 0.65, 0.85, "%g"); - success &= ASSERT_RANGE(caLevCompare("hello", "bobbobbobbob"), 0.0, 0.1, "%g"); - success &= ASSERT_RANGE(caLevCompare("hello", ""), 0.0, 0.05, "%g"); - success &= ASSERT_RANGE(caLevCompare("", ""), 0.99, 1.0, "%g"); - success &= ASSERT_RANGE(caLevCompare("blooooop", "blob"), 0.3, 0.5, "%g"); - success &= ASSERT_RANGE(caLevCompare("", "!"), 0.0, 0.01, "%g"); - success &= ASSERT_RANGE(caLevCompare("h", "h"), 0.99, 1.0, "%g"); - success &= ASSERT_RANGE(caLevCompare("hi", "hi"), 0.99, 1.0, "%g"); - + success &= ASSERT_RANGE(caLevCompare("hello", "hello"), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "hello!"), 0.8, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "asdfkh"), 0.0, 0.1, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "aaaaaaaaaaaaaaaaa"), 0.0, 0.1, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "nope"), 0.0, 0.2, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "noepo"), 0.15, 0.25, "%g"); + success &= ASSERT_RANGE(caLevCompare("below", "hello!"), 0.4, 0.6, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "smitten"), 0.65, 0.85, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", "bobbobbobbob"), 0.0, 0.1, "%g"); + success &= ASSERT_RANGE(caLevCompare("hello", ""), 0.0, 0.05, "%g"); + success &= ASSERT_RANGE(caLevCompare("", ""), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("blooooop", "blob"), 0.3, 0.5, "%g"); + success &= ASSERT_RANGE(caLevCompare("", "!"), 0.0, 0.01, "%g"); + success &= ASSERT_RANGE(caLevCompare("h", "h"), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("hi", "hi"), 0.99, 1.0, "%g"); + /** Kitten tests with specific edit operations. **/ - success &= ASSERT_RANGE(caLevCompare("kitten", "kitten"), 0.99, 1.0, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "skitten"), 0.8, 0.9, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "itten"), 0.8, 0.9, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "mitten"), 0.8, 0.9, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "smitten"), 0.7, 0.8, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "iktten"), 0.8, 0.9, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "kittens"), 0.8, 0.9, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "kitte"), 0.8, 0.9, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "kittem"), 0.8, 0.9, "%g"); - success &= ASSERT_RANGE(caLevCompare("kitten", "kittne"), 0.8, 0.9, "%g"); - + success &= ASSERT_RANGE(caLevCompare("kitten", "kitten"), 0.99, 1.0, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "skitten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "itten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "mitten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "smitten"), 0.7, 0.8, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "iktten"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kittens"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kitte"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kittem"), 0.8, 0.9, "%g"); + success &= ASSERT_RANGE(caLevCompare("kitten", "kittne"), 0.8, 0.9, "%g"); + return success; } From 75510214c196cbf8e43cf14d9262bcdc30512c89 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 10:18:23 -0600 Subject: [PATCH 269/361] Remove check.h usage in tests. --- centrallix-lib/tests/test_clusters_05.c | 21 ++++++++++----------- centrallix-lib/tests/test_clusters_06.c | 17 ++++++++--------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index d2cde6bb4..9f0779035 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -22,7 +22,6 @@ /** Test dependencies. **/ #include "test_utils.h" -#include "check.h" #include "xhash.h" /** Tested module. **/ @@ -155,30 +154,30 @@ static bool doTest(void) /** Set up the mock similarity function. **/ XHashTable sim_table; - if (check(xhInit(&sim_table, 64, 0)) != 0) return false; + if (!ASSERT_EQL(xhInit(&sim_table, 64, 0), 0, "%d")) return false; mock_sims = &sim_table; success_ptr = &success; /** Completely different strings are similar. **/ double str1_str = 0.2, str1_str2 = 0.1, str1_eight = 0.8; - if (check(xhAdd(&sim_table, "str1|str", (void*)&str1_str)) != 0) return false; - if (check(xhAdd(&sim_table, "str1|str2", (void*)&str1_str2)) != 0) return false; - if (check(xhAdd(&sim_table, "str1|eight", (void*)&str1_eight)) != 0) return false; + success &= ASSERT_EQL(xhAdd(&sim_table, "str1|str", (void*)&str1_str), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&sim_table, "str1|str2", (void*)&str1_str2), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&sim_table, "str1|eight", (void*)&str1_eight), 0, "%d"); success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.0), "eight"); success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.9), NULL); - if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; + success &= ASSERT_EQL(xhClear(&sim_table, do_nothing, NULL), 0, "%d"); /** Nans are skipped. **/ double val_nan = 0.8, val_vals = NAN, val_val = 0.2; - if (check(xhAdd(&sim_table, "val|nan", (void*)&val_nan)) != 0) return false; - if (check(xhAdd(&sim_table, "val|vals", (void*)&val_vals)) != 0) return false; - if (check(xhAdd(&sim_table, "val|val", (void*)&val_val)) != 0) return false; + success &= ASSERT_EQL(xhAdd(&sim_table, "val|nan", (void*)&val_nan), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&sim_table, "val|vals", (void*)&val_vals), 0, "%d"); + success &= ASSERT_EQL(xhAdd(&sim_table, "val|val", (void*)&val_val), 0, "%d"); success &= ASSERT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.0), "nan"); success &= ASSERT_STR_EQL(caMostSimilar("val", (void*[]){"val", "vals", "nan"}, 3, getSimMock, 0.9), NULL); - if (check(xhClear(&sim_table, do_nothing, NULL)) != 0) return false; + success &= ASSERT_EQL(xhClear(&sim_table, do_nothing, NULL), 0, "%d"); /** Clean up. **/ - if (check(xhDeInit(&sim_table)) != 0) return false; + success &= ASSERT_EQL(xhDeInit(&sim_table), 0, "%d"); return success; } diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index aa11118e4..566c6109b 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -21,7 +21,6 @@ #include /** Test dependencies. **/ -#include "check.h" #include "newmalloc.h" #include "test_utils.h" @@ -36,12 +35,12 @@ static int cmp_Pairs(const void* v1, const void* v2) return Pair1->i - Pair2->i; } -static void freePairs(pXArray xPairs) +static bool freePairs(pXArray xPairs) { /** The search functions hand ownership of each pair to the caller. **/ while (xPairs->nItems > 0) nmFree(xPairs->Items[--xPairs->nItems], sizeof(Pair)); - check(xaDeInit(xPairs)); + return ASSERT_EQL(xaDeInit(xPairs), 0, "%d"); } #define ASSERT_PAIR(Pair, k1, k2, sim_min, sim_max) \ @@ -82,7 +81,7 @@ static bool doTest(void) /** Test complete search. **/ { XArray xPairs; - if (check(xaInit(&xPairs, 4)) != 0) return false; + if (!ASSERT_EQL(xaInit(&xPairs, 4), 0, "%d")) return false; success &= ASSERT_EQL(caCompleteSearch(data, 6, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) @@ -100,13 +99,13 @@ static bool doTest(void) success &= ASSERT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); success &= ASSERT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); success &= ASSERT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); - freePairs(&xPairs); + success &= freePairs(&xPairs); } /** Test sliding search: Large window. **/ { XArray xPairs; - if (check(xaInit(&xPairs, 4)) != 0) return false; + if (!ASSERT_EQL(xaInit(&xPairs, 4), 0, "%d")) return false; success &= ASSERT_EQL(caSlidingSearch(data, 6, 5, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) @@ -124,13 +123,13 @@ static bool doTest(void) success &= ASSERT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); // success &= ASSERT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ success &= ASSERT_PAIR(Pairs[1], 1, 5, 0.8, 1.0); - freePairs(&xPairs); + success &= freePairs(&xPairs); } /** Test sliding search: Small window. **/ { XArray xPairs; - if (check(xaInit(&xPairs, 4)) != 0) return false; + if (!ASSERT_EQL(xaInit(&xPairs, 4), 0, "%d")) return false; success &= ASSERT_EQL(caSlidingSearch(data, 6, 2, caLevCompare, 0.8, &xPairs), &xPairs, "%p"); pPair* Pairs = (pPair*)xPairs.Items; for (unsigned int i = 0u; i < xPairs.nItems; i++) @@ -148,7 +147,7 @@ static bool doTest(void) success &= ASSERT_PAIR(Pairs[0], 0, 1, 0.8, 1.0); // success &= ASSERT_PAIR(Pairs[1], 0, 5, 0.8, 1.0); /* Sliding search misses this pair. */ // success &= ASSERT_PAIR(Pairs[2], 1, 5, 0.8, 1.0); /* Sliding search misses this pair. */ - freePairs(&xPairs); + success &= freePairs(&xPairs); } return success; From 28e2f1e99d29350eef8e886a853af3c69ebf3ef6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 10:18:31 -0600 Subject: [PATCH 270/361] Remove check.h usage in xarray.c. --- centrallix-lib/src/xarray.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/src/xarray.c b/centrallix-lib/src/xarray.c index 9fb94dba0..2cce14e5a 100644 --- a/centrallix-lib/src/xarray.c +++ b/centrallix-lib/src/xarray.c @@ -6,7 +6,6 @@ #include #include #include "xarray.h" -#include "check.h" #include "newmalloc.h" /************************************************************************/ @@ -424,7 +423,7 @@ xaTrim(pXArray this) /** Allocate the new internal items array. **/ const size_t new_size = this->nItems * sizeof(void*); - void* new_items = checkPtr(nmSysRealloc(this->Items, new_size)); + void* new_items = nmSysRealloc(this->Items, new_size); if (new_items == NULL) return -1; /** Update the struct. **/ @@ -445,7 +444,7 @@ void** xaToArray(pXArray this) { const size_t size = this->nItems * sizeof(void*); - void** result = checkPtr(nmSysMalloc(size)); + void** result = nmSysMalloc(size); if (result == NULL) return NULL; memcpy(result, this->Items, size); From 9770c54df8c11cfc65ffd95640ed972dac788b75 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 12:02:07 -0600 Subject: [PATCH 271/361] Update error handling in clusters.c. --- centrallix-lib/src/clusters.c | 221 ++++++++++++++++++++++++++-------- 1 file changed, 172 insertions(+), 49 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 55149873f..b1e4951dc 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -42,11 +42,12 @@ #include #include -#include "check.h" #include "clusters.h" #include "expect.h" +#include "mtsession.h" #include "newmalloc.h" #include "range.h" +#include "warn.h" #include "xarray.h" /** This file has additional documentation in string_similarity.md. **/ @@ -159,8 +160,13 @@ caBuildVector(const char* str) /** Allocate memory to store the characters. **/ unsigned int num_chars = 0u; - chars = checkPtr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); - if (UNLIKELY(chars == NULL)) goto err_free; + const size_t chars_size = (strlen(str) + 2u) * sizeof(unsigned char); + chars = nmSysMalloc(chars_size); + if (UNLIKELY(chars == NULL)) + { + mssError(1, "CA", "nmSysMalloc(%zu) failed.", chars_size); + goto err_free; + } /** Store characters. **/ chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ @@ -183,10 +189,16 @@ caBuildVector(const char* str) } chars[num_chars++] = CA_BOUNDARY_CHAR; /* Ending boundary character. */ + /** Allocate space for char pair hashes. **/ + const size_t char_pairs_size = num_chars * sizeof(CharPair); + char_pairs = nmSysMalloc(char_pairs_size); + if (UNLIKELY(char_pairs == NULL)) + { + mssError(1, "CA", "nmSysMalloc(%zu) failed.", char_pairs_size); + goto err_free; + } /** Compute character pair hashes. **/ - char_pairs = checkPtr(nmSysMalloc(num_chars * sizeof(CharPair))); - if (UNLIKELY(char_pairs == NULL)) goto err_free; const unsigned int num_pairs = num_chars - 1u; for (unsigned int i = 0u; i < num_pairs; i++) { @@ -209,8 +221,13 @@ caBuildVector(const char* str) /** Allocate space for the sparse vector. **/ - sparse_vector = checkPtr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); - if (sparse_vector == NULL) goto err_free; + const size_t sparse_vector_size = (num_pairs * 2u + 1u) * sizeof(int); + sparse_vector = nmSysMalloc(sparse_vector_size); + if (UNLIKELY(sparse_vector == NULL)) + { + mssError(1, "CA", "nmSysMalloc(%zu) failed.", sparse_vector_size); + goto err_free; + } /** Build the sparse vector from the character pairs. **/ unsigned int cur = 0u, dim = 0u; @@ -247,8 +264,13 @@ caBuildVector(const char* str) /** Trim extra space wasted by identical hashes. **/ - trimmed_sparse_vector = checkPtr(nmSysRealloc(sparse_vector, cur * sizeof(int))); - if (trimmed_sparse_vector == NULL) goto err_free; + const size_t trimmed_sparse_vector_size = cur * sizeof(int); + trimmed_sparse_vector = nmSysRealloc(sparse_vector, trimmed_sparse_vector_size); + if (UNLIKELY(trimmed_sparse_vector == NULL)) + { + mssError(1, "CA", "nmSysMalloc(%zu) failed.", trimmed_sparse_vector_size); + goto err_free; + } sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ /** Return the result. **/ @@ -517,12 +539,25 @@ caEditDist(const char* str1, const char* str2, const size_t str1_length, const s ***/ const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; - lev_matrix = checkPtr(nmSysMalloc((str1_len + 1) * sizeof(unsigned int*))); - if (lev_matrix == NULL) goto end; + const size_t lev_matrix_size = (str1_len + 1) * sizeof(unsigned int*); + const size_t lev_matrix_entry_size = (str2_len + 1) * sizeof(unsigned int); + lev_matrix = nmSysMalloc(lev_matrix_size); + if (UNLIKELY(lev_matrix == NULL)) + { + mssError(1, "CA", "nmSysMalloc(%zu) failed.", lev_matrix_size); + goto end; + } for (unsigned int i = 0u; i < str1_len + 1u; i++) { - lev_matrix[i] = checkPtr(nmSysMalloc((str2_len + 1) * sizeof(unsigned int))); - if (lev_matrix[i] == NULL) goto end; + lev_matrix[i] = nmSysMalloc(lev_matrix_entry_size); + if (UNLIKELY(lev_matrix[i] == NULL)) + { + mssError(1, "CA", + "nmSysMalloc(%zu) failed for entry #%u/%zu.", + lev_matrix_entry_size, i + 1u, str1_len + 1u + ); + goto end; + } } /*** Base case #0: @@ -587,14 +622,23 @@ caEditDist(const char* str1, const char* str2, const size_t str1_length, const s } result = (int)unsigned_result; - /** Cleanup. **/ end: - if (lev_matrix != NULL) + if (UNLIKELY(result < 0)) + { + mssError(0, "CA", + "caEditDist(\"%s\", \"%s\", %zu, %zu) failed.", + str1, str2, str1_length, str2_length + ); + } + + /** Cleanup. **/ + if (LIKELY(lev_matrix != NULL)) { for (unsigned int i = 0u; i < str1_len + 1u; i++) { - if (lev_matrix[i] == NULL) break; - else nmSysFree(lev_matrix[i]); + if (LIKELY(lev_matrix[i] != NULL)) + nmSysFree(lev_matrix[i]); + else break; } nmSysFree(lev_matrix); } @@ -622,20 +666,31 @@ caEditDist(const char* str1, const char* str2, const size_t str1_length, const s double caCosCompare(void* v1, void* v2) { - /** Input validation checks. **/ + /** Input edge cases. **/ if (v1 == NULL || v2 == NULL) return 0.0; if (v1 == v2) return 1.0; - /** Input validation checks. **/ + /** Empty vector edge cases. **/ const pVector vec1 = v1, vec2 = v2; const bool v1_empty = (vec1 == NULL || caIsEmpty(vec1) || caHasNoPairs(vec1)); const bool v2_empty = (vec2 == NULL || caIsEmpty(vec2) || caHasNoPairs(vec2)); if (v1_empty && v2_empty) return 1.0; if (v1_empty && !v2_empty) return 0.0; if (!v1_empty && v2_empty) return 0.0; - - /** Apply rounding to avoid annoying floating point issues before returning. **/ - return round(ca_i_sparseSimilarity(vec1, vec2) * 1000000.0) / 1000000.0; + + const double similarity = ca_i_sparseSimilarity(vec1, vec2); + if (UNLIKELY(isnan(similarity))) + { + mssError(1, "CA", "ca_i_sparseSimilarity(%p, %p) failed.", vec1, vec2); + goto err; + } + + /** Apply rounding to avoid annoying floating point issues before returning. **/ + return round(similarity * 1000000.0) / 1000000.0; + + err: + mssError(0, "CA", "caCosCompare(%p, %p) failed.", v1, v2); + return NAN; } /*** Compares two strings using their Levenshtein edit distance to compute a @@ -653,14 +708,18 @@ caCosCompare(void* v1, void* v2) *** family without needing a messy typecast to avoid the compiler warning. *** However, behavior is undefined if `v1` and `v2` are not `char*`s. *** - *** @param str1 A `char*` to the first string to compare. - *** @param str2 A `char*` to the second string to compare. + *** @param s1 A `char*` to the first string to compare. + *** @param s2 A `char*` to the second string to compare. *** @returns The Levenshtein similarity between the two strings, *** or NAN on failure. ***/ double -caLevCompare(void* str1, void* str2) +caLevCompare(void* s1, void* s2) { + /** Convert types. **/ + const char* str1 = s1; + const char* str2 = s2; + /** Input validation checks. **/ if (str1 == NULL || str2 == NULL) return 0.0; if (str1 == str2) return 1.0; @@ -672,15 +731,29 @@ caLevCompare(void* str1, void* str2) if (len1 != 0lu && len2 == 0lu) return 0.0; if (len1 == 0lu && len2 != 0lu) return 0.0; + /*** It's difficult to know if we are the first to detect an error + *** later in this function, so clear the error stack before hand + *** to avoid stale error data. + ***/ + mssClearError(); + /** Compute levenshtein edit distance. **/ - const int edit_dist = checkPos(caEditDist((const char*)str1, (const char*)str2, len1, len2)); - if (edit_dist < 0) return NAN; + const int edit_dist = caEditDist((const char*)str1, (const char*)str2, len1, len2); + if (UNLIKELY(edit_dist < 0)) goto err; /** Normalize edit distance into a similarity measure. **/ const double normalized_similarity = 1.0 - (double)edit_dist / (double)max(len1, len2); - /** Apply rounding to avoid annoying floating point issues before returning. **/ - return round(normalized_similarity * 1000000.0) / 1000000.0; + /** Apply rounding to avoid annoying floating point issues. **/ + const double rounded_similarity = round(normalized_similarity * 1000000.0) / 1000000.0; + if (UNLIKELY(isnan(rounded_similarity))) goto err; + + /** Success. **/ + return rounded_similarity; + + err: + mssError(0, "CA", "caLevCompare(\"%s\", \"%s\") failed.", str1, str2); + return NAN; } /*** Check if two sparse vectors are identical, typically used for debugging @@ -734,9 +807,13 @@ ca_i_getClusterSize( *** loop in caKmeans(). Also, caKmeans() may be called multiple *** times with the same k value, increasing this benefit. ***/ - cluster_sums = checkPtr(nmMalloc(num_clusters * sizeof(double))); - cluster_counts = checkPtr(nmMalloc(num_clusters * sizeof(unsigned int))); - if (cluster_sums == NULL || cluster_counts == NULL) goto end; + cluster_sums = nmMalloc(num_clusters * sizeof(double)); + cluster_counts = nmMalloc(num_clusters * sizeof(unsigned int)); + if (UNLIKELY(cluster_sums == NULL || cluster_counts == NULL)) + { + mssError(1, "CA", "Allocation failed for %u clusters.", num_clusters); + goto end; + } for (unsigned int i = 0u; i < num_clusters; i++) { cluster_sums[i] = 0.0; @@ -767,6 +844,14 @@ ca_i_getClusterSize( result = cluster_total / num_valid_clusters; end: + if (UNLIKELY(isnan(result))) + { + mssError(0, "CA", + "ca_i_getClusterSize() failed on %u vectors and %u clusters.", + num_vectors, num_clusters + ); + } + /** Clean up. **/ if (cluster_sums != NULL) nmFree(cluster_sums, num_clusters * sizeof(double)); if (cluster_counts != NULL) nmFree(cluster_counts, num_clusters * sizeof(unsigned int)); @@ -821,22 +906,42 @@ caKmeans( pCentroid* new_centroids = NULL; unsigned int cluster_counts[num_clusters]; + /** Edge cases. **/ + if (UNLIKELY(false + || vectors == NULL + || labels == NULL + || vector_sims == NULL + || num_vectors == 0u + || num_clusters == 0u + || max_iter == 0u + || isnan(min_improvement) + )) + goto end; + /** Initialize labels. **/ memset(labels, 0u, num_vectors * sizeof(unsigned int)); /** Allocate space to store centroids and new_centroids. **/ /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ const size_t centroids_size = num_clusters * sizeof(pCentroid); - centroids = checkPtr(nmMalloc(centroids_size)); - new_centroids = checkPtr(nmMalloc(centroids_size)); - if (centroids == NULL || new_centroids == NULL) goto end; + centroids = nmMalloc(centroids_size); + new_centroids = nmMalloc(centroids_size); + if (centroids == NULL || new_centroids == NULL) + { + mssError(1, "CA", "nmMalloc(%zu) failed.", centroids_size); + goto end; + } memset(centroids, 0, centroids_size); memset(new_centroids, 0, centroids_size); for (unsigned int i = 0u; i < num_clusters; i++) { - centroids[i] = checkPtr(nmMalloc(CENTROID_SIZE)); - new_centroids[i] = checkPtr(nmMalloc(CENTROID_SIZE)); - if (centroids[i] == NULL || new_centroids[i] == NULL) goto end; + centroids[i] = nmMalloc(CENTROID_SIZE); + new_centroids[i] = nmMalloc(CENTROID_SIZE); + if (centroids[i] == NULL || new_centroids[i] == NULL) + { + mssError(1, "CA", "nmMalloc(%zu) failed.", CENTROID_SIZE); + goto end; + } memset(centroids[i], 0, CENTROID_SIZE); memset(new_centroids[i], 0, CENTROID_SIZE); } @@ -924,8 +1029,8 @@ caKmeans( /** Is there enough improvement? **/ if (min_improvement <= -1.0) continue; /** Skip check if it will never end the loop. **/ - const double average_cluster_size = checkDouble(ca_i_getClusterSize(vectors, num_vectors, labels, centroids, num_clusters)); - if (isnan(average_cluster_size)) goto end; + const double average_cluster_size = ca_i_getClusterSize(vectors, num_vectors, labels, centroids, num_clusters); + if (UNLIKELY(isnan(average_cluster_size))) goto end; const double improvement = old_average_cluster_size - average_cluster_size; if (improvement < min_improvement) break; old_average_cluster_size = average_cluster_size; @@ -942,6 +1047,15 @@ caKmeans( successful = true; end: + if (!successful) + { + mssError(0, "CA", + "caKmeans(%p, %u, %u, %u, %lf, %p, %p, %s) failed.", + vectors, num_vectors, num_clusters, max_iter, min_improvement, + labels, vector_sims, (auto_seed) ? "true" : "false" + ); + } + /** Clean up. **/ if (centroids != NULL) { @@ -1055,8 +1169,12 @@ caSlidingSearch( { /** Guess that we will need space for two pairs per data point. **/ const int guess_size = num_data * 2; - pairs = checkPtr(xaNew(guess_size)); - if (pairs == NULL) goto err; + pairs = xaNew(guess_size); + if (UNLIKELY(pairs == NULL)) + { + mssError(1, "CA", "xaNew(%d)", guess_size); + goto err; + } } const int num_starting_pairs = pairs->nItems; @@ -1067,20 +1185,25 @@ caSlidingSearch( const unsigned int window_end = min(i + window_size, num_data); for (unsigned int j = window_start; j < window_end; j++) { - const double sim = checkDouble(similarity(data[i], data[j])); - if (isnan(sim) || sim < 0.0 || 1.0 < sim) + const double sim = similarity(data[i], data[j]); + if (UNLIKELY(isnan(sim) || sim < 0.0 || 1.0 < sim)) { - fprintf(stderr, "Invalid similarity %g.\n", sim); + mssError(0, "CA", "Invalid similarity %g.", sim); goto err_free; } if (sim > threshold) /* Pair found! */ { - pPair pair = (pPair)checkPtr(nmMalloc(sizeof(Pair))); - if (pair == NULL) goto err_free; + const size_t pair_size = sizeof(Pair); + pPair pair = nmMalloc(pair_size); + if (UNLIKELY(pair == NULL)) + { + mssError(1, "CA", "nmMalloc(%zu)", pair_size); + goto err_free; + } pair->i = i; pair->j = j; pair->similarity = sim; - if (checkPos(xaAddItem(pairs, (void*)pair)) < 0) goto err_free; + warnNeg(xaAddItem(pairs, (void*)pair)); } } } @@ -1092,7 +1215,7 @@ caSlidingSearch( /** Error cleanup: Free the pairs that we added to the XArray. **/ while (pairs->nItems > num_starting_pairs) nmFree(pairs->Items[--pairs->nItems], sizeof(Pair)); - if (maybe_pairs == NULL) check(xaFree(pairs)); /* Failure ignored. */ + if (maybe_pairs == NULL) warnFail(xaFree(pairs)); err: return NULL; From 144217e914136138fc4f43229f14e08a2848319d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 14:49:33 -0600 Subject: [PATCH 272/361] Add xsCharAt() to xstring. --- centrallix-lib/include/xstring.h | 3 ++- centrallix-lib/src/xstring.c | 24 +++++++++++++++++++++++- centrallix-sysdoc/Libraries/xstring.md | 8 ++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/include/xstring.h b/centrallix-lib/include/xstring.h index 683b0e05d..2ac01b5f7 100644 --- a/centrallix-lib/include/xstring.h +++ b/centrallix-lib/include/xstring.h @@ -13,7 +13,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -54,6 +54,7 @@ int xsCopy(pXString this, char* text, int len); char* xsString(pXString this); char* xsStringEnd(pXString this); int xsLength(pXString this); +char xsCharAt(pXString this, int pos); int xsPrintf(pXString this, char* fmt, ...); int xsConcatPrintf(pXString this, char* fmt, ...); int xsWrite(pXString this, char* buf, int len, int offset, int flags); diff --git a/centrallix-lib/src/xstring.c b/centrallix-lib/src/xstring.c index 51df377c2..1444341ff 100644 --- a/centrallix-lib/src/xstring.c +++ b/centrallix-lib/src/xstring.c @@ -18,7 +18,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -870,6 +870,28 @@ xsLength(pXString this) } +/*** xsCharAt - returns the character at the given position in the string, or + *** '\0' if the position is outside of it. This is the bounds checked way to + *** read one character, in place of indexing the buffer from xsString(). + ***/ +char +xsCharAt(pXString this, int pos) + { + CXSEC_ENTRY(XS_FN_KEY); + ASSERTMAGIC(this, MGK_XSTRING); + CXSEC_VERIFY(*this); + + if (pos < 0 || pos >= this->Length) + { + CXSEC_EXIT(XS_FN_KEY); + return '\0'; + } + + CXSEC_EXIT(XS_FN_KEY); + return this->String[pos]; + } + + /*** xs_internal_Grow - grow function needed by QPrintf ***/ int diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 2523ccf2f..442104fab 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -65,6 +65,7 @@ - [xsGenPrintf()](#xsgenprintf) - [xsString()](#xsstring) - [xsLength()](#xslength) + - [xsCharAt()](#xscharat) - [xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf()](#xsqprintf_va-xsqprintf--xsconcatqprintf) @@ -276,6 +277,13 @@ xsLength(pXString this); This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. +## xsCharAt() +```c +char xsCharAt(pXString this, int pos); +``` +This function returns the character at `pos`, or `'\0'` if `pos` falls outside the string. Use it instead of indexing the buffer from [`xsString()`](#xsstring), which is not bounds checked. Note that a position past the end and a negative position both read as `'\0'`, so a caller walking off either end of the string gets the same result as they would from reading the~~~~ null-terminator. + + ## xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf() ```c int xsQPrintf_va(pXString this, char* fmt, va_list va); From 032a05e98d64fdf0b26bdee2a4fbc5a054f9e151 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 15:06:51 -0600 Subject: [PATCH 273/361] Add xsConcatenateLiteral() to xstring. --- centrallix-lib/include/xstring.h | 13 ++++++++++++- centrallix-sysdoc/Libraries/xstring.md | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/include/xstring.h b/centrallix-lib/include/xstring.h index 2ac01b5f7..bc3f54dea 100644 --- a/centrallix-lib/include/xstring.h +++ b/centrallix-lib/include/xstring.h @@ -74,7 +74,18 @@ int xsConcatQPrintf(pXString this, char* fmt, ...); pXString xsNew(); void xsFree(pXString this); +/*** xsConcatenateLiteral - concatenates a string literal, taking its length + *** from sizeof() at compile time (faster than measuring with strlen() at + *** run time). + *** + *** Note: The "" causes a compiler error a pointer is passed because it's + *** length is not known at compile time. Use xsConcatenate() for these. + *** + *** Note: The length covers the whole literal, so an embedded null ('\0') is + *** concatenated rather than treated as the end of the text. + ***/ +#define xsConcatenateLiteral(this, lit) xsConcatenate((this), (lit), (int)(sizeof("" lit) - 1)) + #define XS_U_SEEK 2 #endif /* _XSTRING_H */ - diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 442104fab..19570e618 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -48,6 +48,7 @@ - [xsDeInit()](#xsdeinit) - [xsCheckAlloc()](#xscheckalloc) - [xsConcatenate()](#xsconcatenate) + - [xsConcatenateLiteral()](#xsconcatenateliteral) - [xsCopy()](#xscopy) - [xsStringEnd()](#xsstringend) - [xsConcatPrintf()](#xsconcatprintf) @@ -145,6 +146,21 @@ This function concatenates the `text` string onto the end of the XString's value ``` +## xsConcatenateLiteral() +```c +#define xsConcatenateLiteral(this, lit) /* ... */ +``` +This macro wraps [`xsConcatenate()`](#xsconcatenate) for the common case of adding a string literal. It takes the length from `sizeof(lit)` at compile time, so the literal is not measured with `strlen()` on every call. + +```c +xsConcatenateLiteral(&xs, "SELECT * FROM "); /* Instead of passing -1. */ +``` + +The argument must be a string literal. Passing a pointer is a compile error, because `sizeof()` on a pointer would give the size of the pointer rather than the length of the text. A macro that expands to a literal is fine, as are adjacent literals (`"a" "b"`). + +Because the length comes from `sizeof()`, it covers the whole literal. An embedded null (`'\0'`) is therefore concatenated like any other character, where `xsConcatenate()` with `len` of -1 would stop at it. + + ## xsCopy() ```c int xsCopy(pXString this, char* text, int len); From f4b2c2bcc1db29d4d15459ccdebe2d99b2bfbcd8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 15:16:58 -0600 Subject: [PATCH 274/361] Replace meta strings with XStrings in double_metaphone.c. Implement XStrings for string handling and update helper functions to use them. Add a few calls to warn functions. Remove meta strings. Remove all calls to check functions. --- centrallix/utility/double_metaphone.c | 809 ++++++++++++-------------- 1 file changed, 366 insertions(+), 443 deletions(-) diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index d0cf4e8dc..00e1897e0 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -70,9 +70,9 @@ /* improved version of the original Metaphone algorithm */ /* written by Philips'. This implementation was written by */ /* Maurice Aubrey for C/C++ with bug fixes provided by */ -/* Kevin Atkinson. It was revised by Israel Fuller to */ -/* better align with the Centrallix coding style and */ -/* standards so that it could be included here. */ +/* Kevin Atkinson. It was heavily revised by Israel Fuller */ +/* to align with the Centrallix coding style and make use */ +/* of the centrallix libraries. */ /************************************************************************/ /*** Note to future programmers reading this file (by Israel Fuller): @@ -99,231 +99,137 @@ #include #include +#include #include #include #include #include -#include "cxlib/check.h" #include "cxlib/newmalloc.h" -#include "cxlib/strtcpy.h" +#include "cxlib/xstring.h" +#include "cxlib/warn.h" #include "cxlib/expect.h" -typedef struct - { - char* str; - size_t length; - size_t bufsize; - int free_str_on_destroy; - } - MetaString, *pMetaString; - -/*** Allocates a new MetaString. +/*** Convert all characters of an XString to uppercase. *** - *** @param init_str The initial size of the string. - *** @returns The new MetaString, or NULL if an error occurs. + *** @param s The XString being modified. + *** @returns 0 if successful, or -1 if an error occurs. ***/ -pMetaString -meta_i_newString(const char* init_str) +int +meta_i_makeUpper(pXString s) { - pMetaString s; - char empty_string[] = ""; - - s = (pMetaString)checkPtr(nmSysMalloc(sizeof(MetaString))); - if (UNLIKELY(s == NULL)) goto err_free; - - if (init_str == NULL) - init_str = empty_string; + /** Handle edge cases. **/ + if (UNLIKELY(s == NULL)) return -1; - s->length = strlen(init_str); - /** Preallocate a bit more for potential growth. **/ - s->bufsize = s->length + 7u; + /** Get the xstring character buffer. **/ + char* buf = xsString(s); + const int length = xsLength(s); + if (UNLIKELY(buf == NULL)) return -1; + if (UNLIKELY(length < 0)) return -1; - s->str = (char*)checkPtr(nmSysMalloc(s->bufsize * sizeof(char))); - if (UNLIKELY(s->str == NULL)) goto err_free; - - strtcpy(s->str, init_str, s->bufsize); - s->free_str_on_destroy = 1; + /** Uppercase each character. **/ + for (int i = 0; i < length; i++) + buf[i] = (char)toupper(buf[i]); - return s; - - err_free: - if (s != NULL) - { - if (s->str != NULL) nmSysFree(s->str); - nmSysFree(s); - } - - return NULL; + return 0; } -/*** Frees a MetaString. - *** - *** @param s The MetaString. +/*** @param s The XString being checked. + *** @param pos The character location to check within the XString. + *** @returns The character at the position in the XString, or + *** '\0' if the position is not in the XString. ***/ -void -meta_i_destroyString(pMetaString s) +char +meta_i_getCharAt(pXString s, unsigned int pos) { - if (UNLIKELY(s == NULL)) - return; + /** Handle edge cases. **/ + if (UNLIKELY(s == NULL)) return '\0'; - if (s->free_str_on_destroy && s->str != NULL) - nmSysFree(s->str); - - nmSysFree(s); - - return; - } - -/*** Increases a MetaString's buffer size. - *** - *** @param s The pMetaString being modified. - *** @param chars_needed Minimum number of characters to increase buffer size. - *** @returns 0 if successful, or -1 if an error occurs. - ***/ -int -meta_i_increaseBuffer(pMetaString s, const size_t chars_needed) - { - s->bufsize += chars_needed + 8u; - s->str = checkPtr(nmSysRealloc(s->str, s->bufsize * sizeof(char))); - if (UNLIKELY(s->str == NULL)) return -1; + /** A position past INT_MAX has wrapped, so it is out of bounds. **/ + if (UNLIKELY(pos > (unsigned int)INT_MAX)) return '\0'; - return 0; + return xsCharAt(s, (int)pos); } -/*** Convert all characters of a MetaString to uppercase. +/*** Checks if a character in an XString is a vowel. *** - *** @param s The MetaString being modified. - ***/ -void -meta_i_makeUpper(pMetaString s) - { - for (char* i = s->str; i[0] != '\0'; i++) - *i = (char)toupper(*i); - - return; - } - -/*** @param s The MetaString being checked. - *** @param pos The character location to check within the MetaString. - *** @returns 1 if the location is out of bounds for the MetaString, - *** 0 otherwise. + *** @param s The XString being checked. + *** @param pos The character location to check within the XString. ***/ bool -meta_i_isOutOfBounds(pMetaString s, unsigned int pos) +meta_i_isVowel(pXString s, unsigned int pos) { - return (s->length <= pos); - } - -/*** Checks if a character in a MetaString is a vowel. - *** - *** @param s The MetaString being checked. - *** @param pos The character location to check within the MetaString. - ***/ -bool -meta_i_isVowel(pMetaString s, unsigned int pos) - { - if (UNLIKELY(meta_i_isOutOfBounds(s, pos))) return 0; - - const char c = *(s->str + pos); + const char c = meta_i_getCharAt(s, pos); return ((c == 'A') || (c == 'E') || (c == 'I') || (c == 'O') || (c == 'U') || (c == 'Y')); } -/*** Search a MetaString for "W", "K", "CZ", or "WITZ", which indicate that the +/*** Search an XString for "W", "K", "CZ", or "WITZ", which indicate that the *** string is Slavo Germanic. *** - *** @param s The MetaString to be searched. - *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. + *** @param s The XString to be searched. + *** @returns 1 if the XString is Slavo Germanic, or 0 otherwise. ***/ bool -meta_i_isSlavoGermanic(pMetaString s) +meta_i_isSlavoGermanic(pXString s) { - return (strstr(s->str, "W") != NULL) - || (strstr(s->str, "K") != NULL) - || (strstr(s->str, "CZ") != NULL) - || (strstr(s->str, "WITZ") != NULL); - } - -/*** @param s The MetaString being checked. - *** @param pos The character location to check within the MetaString. - *** @returns The character at the position in the MetaString, or - *** '\0' if the position is not in the MetaString. - ***/ -char -meta_i_getCharAt(pMetaString s, unsigned int pos) - { - return (UNLIKELY(meta_i_isOutOfBounds(s, pos))) ? '\0' : ((char) *(s->str + pos)); + /** Handle edge cases. **/ + if (UNLIKELY(s == NULL)) return false; + + return (xsFind(s, "W", 1, 0) >= 0) + || (xsFind(s, "K", 1, 0) >= 0) + || (xsFind(s, "CZ", 2, 0) >= 0) + || (xsFind(s, "WITZ", 4, 0) >= 0); } /*** Checks for to see if any of a list of strings appear in a the given - *** MetaString after the given start position. + *** XString after the given start position. *** *** @attention - Note that the START value is 0 based. *** - *** @param s The MetaString being modified. + *** @param s The XString being checked. *** @param start The zero-based start of at which to begin searching - *** within the MetaString. - *** @param length The length of the character strings being checked. + *** within the XString. *** @returns 1 if any of the character sequences appear after the start - *** in the MetaString and 0 otherwise. + *** in the XString and 0 otherwise. ***/ bool -meta_i_isStrAt(pMetaString s, unsigned int start, ...) +meta_i_isStrAt(pXString s, unsigned int start, ...) { va_list ap; bool found = false; - /** Should never happen. **/ - if (UNLIKELY(meta_i_isOutOfBounds(s, start))) - return false; - - const char* pos = (s->str + start); va_start(ap, start); char* test; do { + /** An empty string terminates the argument list. **/ test = va_arg(ap, char*); - if (test[0] != '\0' && (strncmp(pos, test, strlen(test)) == 0)) + if (test[0] == '\0') + break; + + /** Reading through meta_i_getCharAt() cannot run off the end. **/ + int i; + for (i = 0; test[i] != '\0'; i++) + { + if (meta_i_getCharAt(s, start + (unsigned int)i) != test[i]) + break; + } + if (test[i] == '\0') { found = true; break; } } - while (test[0] != '\0'); + while (true); va_end(ap); return found; } -/*** Adds a string to a MetaString, expanding the MetaString if needed. - *** - *** @param s The MetaString being modified. - *** @param new_str The string being added. - *** @returns 0 if successful, or -1 if an error occurs. - ***/ -int -meta_i_addStr(pMetaString s, const char* new_str) - { - if (UNLIKELY(new_str == NULL)) - return -1; - - /** Increase the buffer to the required size. **/ - const size_t add_length = strlen(new_str); - const size_t new_length = s->length + add_length + 1; - if (UNLIKELY(new_length > s->bufsize) && check(meta_i_increaseBuffer(s, add_length)) != 0) - return -1; - - /** Write the data to the buffer. **/ - strtcat(s->str, new_str, s->bufsize); - s->length += add_length; - - return 0; - } - /*** Computes double metaphone. *** *** Example Usage: @@ -344,7 +250,17 @@ int metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) { int ret = -1; + XString original, primary, secondary; + /** xsInit() always leaves a buffer, so NULL marks one it never reached. **/ + original.String = NULL; + primary.String = NULL; + secondary.String = NULL; + + if (UNLIKELY(xsInit(&original) != 0)) goto end_free; + if (UNLIKELY(xsInit(&primary) != 0)) goto end_free; + if (UNLIKELY(xsInit(&secondary) != 0)) goto end_free; + /** Edge cases. **/ if (UNLIKELY(str == NULL)) { @@ -372,38 +288,32 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) unsigned int current = 0; const unsigned int last = (unsigned int)(length - 1); - /** Pad original so we can index beyond end. **/ - pMetaString original = checkPtr(meta_i_newString(str)); - if (UNLIKELY(original == NULL)) goto end_free; - meta_i_makeUpper(original); - if (check(meta_i_addStr(original, " ")) != 0) goto end_free; - - /** Allocate the primary and secondary output strings. **/ - pMetaString primary = checkPtr(meta_i_newString("")); - pMetaString secondary = checkPtr(meta_i_newString("")); - if (UNLIKELY(primary == NULL || secondary == NULL)) goto end_free; + /** Uppercase the input, and pad it so we can index beyond the end. **/ + if (xsConcatenate(&original, (char*)str, (int)length) < 0) goto end_free; + if (meta_i_makeUpper(&original) != 0) goto end_free; + if (xsConcatenateLiteral(&original, " ") < 0) goto end_free; /** Skip these if they are at start of a word. **/ - if (meta_i_isStrAt(original, 0, "GN", "KN", "PN", "WR", "PS", "")) + if (meta_i_isStrAt(&original, 0, "GN", "KN", "PN", "WR", "PS", "")) current += 1; /** Initial 'X' is pronounced 'Z' e.g. 'Xavier' **/ - const char first_char = meta_i_getCharAt(original, 0); + const char first_char = meta_i_getCharAt(&original, 0); if (first_char == 'X') { - if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; /* 'Z' maps to 'S' */ - if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; /* 'Z' maps to 'S' */ + if (xsConcatenateLiteral(&secondary, "S") < 0) goto end_free; current += 1; } /** Precomputing this is useful. **/ - const bool is_slavo_germanic = meta_i_isSlavoGermanic(original); + const bool is_slavo_germanic = meta_i_isSlavoGermanic(&original); /** Main loop. **/ while (current < length) { - const char cur_char = meta_i_getCharAt(original, current); - const char next_char = meta_i_getCharAt(original, current + 1); + const char cur_char = meta_i_getCharAt(&original, current); + const char next_char = meta_i_getCharAt(&original, current + 1); switch (cur_char) { case 'A': @@ -416,8 +326,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (current == 0) { /** All init vowels now map to 'A'. **/ - if (check(meta_i_addStr(primary, "A") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "A") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "A") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "A") < 0) goto end_free; } current += 1; break; @@ -426,8 +336,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) case 'B': { /** "-mb", e.g", "dumb", already skipped over... **/ - if (check(meta_i_addStr(primary, "P") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "P") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "P") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "P") < 0) goto end_free; current += (next_char == 'B') ? 2 : 1; break; @@ -438,46 +348,46 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) /** Various germanic. **/ if ( (current > 1) - && !meta_i_isVowel(original, current - 2) - && meta_i_isStrAt(original, (current - 1), "ACH", "") - && meta_i_getCharAt(original, current + 2) != 'I' + && !meta_i_isVowel(&original, current - 2) + && meta_i_isStrAt(&original, (current - 1), "ACH", "") + && meta_i_getCharAt(&original, current + 2) != 'I' && ( - meta_i_getCharAt(original, current + 2) != 'E' - || meta_i_isStrAt(original, (current - 2), "BACHER", "MACHER", "") + meta_i_getCharAt(&original, current + 2) != 'E' + || meta_i_isStrAt(&original, (current - 2), "BACHER", "MACHER", "") ) ) { - if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; current += 2; break; } /** Special case 'caesar' **/ - if (current == 0 && meta_i_isStrAt(original, current, "CAESAR", "")) + if (current == 0 && meta_i_isStrAt(&original, current, "CAESAR", "")) { - if (check(meta_i_addStr(primary, "S") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "S") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "S") < 0) goto end_free; current += 2; break; } /** Italian 'chianti' **/ - if (meta_i_isStrAt(original, current, "CHIA", "")) + if (meta_i_isStrAt(&original, current, "CHIA", "")) { - if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; current += 2; break; } - if (meta_i_isStrAt(original, current, "CH", "")) + if (meta_i_isStrAt(&original, current, "CH", "")) { /** Find 'michael' **/ - if (current > 0 && meta_i_isStrAt(original, current, "CHAE", "")) + if (current > 0 && meta_i_isStrAt(&original, current, "CHAE", "")) { - if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; current += 2; break; } @@ -485,52 +395,52 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) /** Greek roots e.g. 'chemistry', 'chorus' **/ if ( current == 0 - && meta_i_isStrAt(original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") - && !meta_i_isStrAt(original, 0, "CHORE", "") + && meta_i_isStrAt(&original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") + && !meta_i_isStrAt(&original, 0, "CHORE", "") ) { - if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; current += 2; break; } /** Germanic, greek, or otherwise 'ch' for 'kh' sound. */ if ( - meta_i_isStrAt(original, 0, "SCH", "VAN ", "VON ", "") + meta_i_isStrAt(&original, 0, "SCH", "VAN ", "VON ", "") /** 'architect but not 'arch', 'orchestra', 'orchid' **/ - || meta_i_isStrAt(original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") - || meta_i_isStrAt(original, (current + 2), "T", "S", "") + || meta_i_isStrAt(&original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") + || meta_i_isStrAt(&original, (current + 2), "T", "S", "") || ( - (current == 0 || meta_i_isStrAt(original, (current - 1), "A", "O", "U", "E", "")) + (current == 0 || meta_i_isStrAt(&original, (current - 1), "A", "O", "U", "E", "")) /** e.g., 'wachtler', 'wechsler', but not 'tichner' **/ - && meta_i_isStrAt(original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") + && meta_i_isStrAt(&original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") ) ) { - if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; } else { if (current > 0) { - if (meta_i_isStrAt(original, 0, "MC", "")) + if (meta_i_isStrAt(&original, 0, "MC", "")) { /* e.g., "McHugh" */ - if (check(meta_i_addStr(primary, "K") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; } else { - if (check(meta_i_addStr(primary, "X") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "K") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "X") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; } } else { - if (check(meta_i_addStr(primary, "X") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "X") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; } } current += 2; @@ -538,97 +448,97 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) } /** e.g, 'czerny' **/ - if (meta_i_isStrAt(original, current, "CZ", "") - && !meta_i_isStrAt(original, (current - 2), "WICZ", "")) + if (meta_i_isStrAt(&original, current, "CZ", "") + && !meta_i_isStrAt(&original, (current - 2), "WICZ", "")) { - if (check(meta_i_addStr(primary, "S") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; current += 2; break; } /** e.g., 'focaccia' **/ - if (meta_i_isStrAt(original, (current + 1), "CIA", "")) + if (meta_i_isStrAt(&original, (current + 1), "CIA", "")) { - if (check(meta_i_addStr(primary, "X") != 0)) goto end_free; - if (check(meta_i_addStr(secondary, "X") != 0)) goto end_free; + if (xsConcatenateLiteral(&primary, "X") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; current += 3; break; } /** Double 'C' rule. **/ if ( - meta_i_isStrAt(original, current, "CC", "") + meta_i_isStrAt(&original, current, "CC", "") && !(current == 1 && first_char == 'M') /* McClellan exception. */ ) { /** 'bellocchio' but not 'bacchus' **/ if ( - meta_i_isStrAt(original, (current + 2), "I", "E", "H", "") - && !meta_i_isStrAt(original, (current + 2), "HU", "") + meta_i_isStrAt(&original, (current + 2), "I", "E", "H", "") + && !meta_i_isStrAt(&original, (current + 2), "HU", "") ) { /** 'accident', 'accede' 'succeed' **/ if ( - (current == 1 && meta_i_getCharAt(original, current - 1) == 'A') - || meta_i_isStrAt(original, (current - 1), "UCCEE", "UCCES", "") + (current == 1 && meta_i_getCharAt(&original, current - 1) == 'A') + || meta_i_isStrAt(&original, (current - 1), "UCCEE", "UCCES", "") ) { - if (check(meta_i_addStr(primary, "KS")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "KS")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "KS") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "KS") < 0) goto end_free; /** 'bacci', 'bertucci', other italian **/ } else { - if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "X") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; } current += 3; break; } else { /** Pierce's rule **/ - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; current += 2; break; } } - if (meta_i_isStrAt(original, current, "CK", "CG", "CQ", "")) + if (meta_i_isStrAt(&original, current, "CK", "CG", "CQ", "")) { - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; current += 2; break; } - if (meta_i_isStrAt(original, current, "CI", "CE", "CY", "")) + if (meta_i_isStrAt(&original, current, "CI", "CE", "CY", "")) { /* Italian vs. English */ - if (meta_i_isStrAt(original, current, "CIO", "CIE", "CIA", "")) + if (meta_i_isStrAt(&original, current, "CIO", "CIE", "CIA", "")) { - if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; } else { - if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "S") < 0) goto end_free; } current += 2; break; } /** else **/ - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; /** Name sent in 'mac caffrey', 'mac gregor **/ - if (meta_i_isStrAt(original, (current + 1), " C", " Q", " G", "")) + if (meta_i_isStrAt(&original, (current + 1), " C", " Q", " G", "")) current += 3; - else if (meta_i_isStrAt(original, (current + 1), "C", "K", "Q", "") - && !meta_i_isStrAt(original, (current + 1), "CE", "CI", "")) + else if (meta_i_isStrAt(&original, (current + 1), "C", "K", "Q", "") + && !meta_i_isStrAt(&original, (current + 1), "CE", "CI", "")) current += 2; else current += 1; @@ -637,37 +547,37 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) case 'D': { - if (meta_i_isStrAt(original, current, "DG", "")) + if (meta_i_isStrAt(&original, current, "DG", "")) { - if (meta_i_isStrAt(original, (current + 2), "I", "E", "Y", "")) + if (meta_i_isStrAt(&original, (current + 2), "I", "E", "Y", "")) { /** e.g. 'edge' **/ - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "J") < 0) goto end_free; current += 3; break; } else { /** e.g. 'edgar' **/ - if (check(meta_i_addStr(primary, "TK")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "TK")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "TK") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "TK") < 0) goto end_free; current += 2; break; } } - if (meta_i_isStrAt(original, current, "DT", "DD", "")) + if (meta_i_isStrAt(&original, current, "DT", "DD", "")) { - if (check(meta_i_addStr(primary, "T")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "T") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "T") < 0) goto end_free; current += 2; break; } /** else **/ - if (check(meta_i_addStr(primary, "T")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "T") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "T") < 0) goto end_free; current += 1; break; } @@ -675,8 +585,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) case 'F': { current += (next_char == 'F') ? 2 : 1; - if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "F") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "F") < 0) goto end_free; break; } @@ -685,10 +595,10 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (next_char == 'H') { /** 'Vghee' */ - if (current > 0 && !meta_i_isVowel(original, (current - 1))) + if (current > 0 && !meta_i_isVowel(&original, (current - 1))) { - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; current += 2; break; } @@ -698,15 +608,15 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) /** 'ghislane', 'ghiradelli' **/ if (current == 0) { - if (meta_i_getCharAt(original, (current + 2)) == 'I') + if (meta_i_getCharAt(&original, (current + 2)) == 'I') { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "J") < 0) goto end_free; } else { - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; } current += 2; break; @@ -715,11 +625,11 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if ( /** Parker's rule (with some further refinements) - e.g., 'hugh' **/ - (current > 1 && meta_i_isStrAt(original, (current - 2), "B", "H", "D", "")) + (current > 1 && meta_i_isStrAt(&original, (current - 2), "B", "H", "D", "")) /** e.g., 'bough' **/ - || (current > 2 && meta_i_isStrAt(original, (current - 3), "B", "H", "D", "")) + || (current > 2 && meta_i_isStrAt(&original, (current - 3), "B", "H", "D", "")) /** e.g., 'broughton' **/ - || (current > 3 && meta_i_isStrAt(original, (current - 4), "B", "H", "")) + || (current > 3 && meta_i_isStrAt(&original, (current - 4), "B", "H", "")) ) { current += 2; @@ -730,17 +640,17 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) /** e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough' **/ if ( current > 2 - && meta_i_getCharAt(original, (current - 1)) == 'U' - && meta_i_isStrAt(original, (current - 3), "C", "G", "L", "R", "T", "") + && meta_i_getCharAt(&original, (current - 1)) == 'U' + && meta_i_isStrAt(&original, (current - 3), "C", "G", "L", "R", "T", "") ) { - if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "F") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "F") < 0) goto end_free; } - else if (current > 0 && meta_i_getCharAt(original, (current - 1)) != 'I') + else if (current > 0 && meta_i_getCharAt(&original, (current - 1)) != 'I') { - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; } current += 2; @@ -750,26 +660,26 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (next_char == 'N') { - if (current == 1 && !is_slavo_germanic && meta_i_isVowel(original, 0)) + if (current == 1 && !is_slavo_germanic && meta_i_isVowel(&original, 0)) { - if (check(meta_i_addStr(primary, "KN")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "N")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "KN") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "N") < 0) goto end_free; } else /** not e.g. 'cagney' **/ if ( next_char != 'Y' && !is_slavo_germanic - && !meta_i_isStrAt(original, (current + 2), "EY", "") + && !meta_i_isStrAt(&original, (current + 2), "EY", "") ) { - if (check(meta_i_addStr(primary, "N")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "KN")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "N") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "KN") < 0) goto end_free; } else { - if (check(meta_i_addStr(primary, "KN")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "KN")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "KN") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "KN") < 0) goto end_free; } current += 2; break; @@ -778,11 +688,11 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) /** 'tagliaro' **/ if ( !is_slavo_germanic - && meta_i_isStrAt(original, (current + 1), "LI", "") + && meta_i_isStrAt(&original, (current + 1), "LI", "") ) { - if (check(meta_i_addStr(primary, "KL")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "L")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "KL") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "L") < 0) goto end_free; current += 2; break; } @@ -793,58 +703,58 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) && ( next_char == 'Y' || meta_i_isStrAt( - original, (current + 1), + &original, (current + 1), "ES", "EP", "EB", "EL", "EY", "IB", "IL", "IN", "IE", "EI", "ER", "" ) ) ) { - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "J") < 0) goto end_free; current += 2; break; } /** -ger-, -gy- **/ if ( - (next_char == 'Y' || meta_i_isStrAt(original, (current + 1), "ER", "")) + (next_char == 'Y' || meta_i_isStrAt(&original, (current + 1), "ER", "")) /** Exceptions. **/ - && !meta_i_isStrAt(original, 0, "DANGER", "RANGER", "MANGER", "") - && !meta_i_isStrAt(original, (current - 1), "E", "I", "RGY", "OGY", "") + && !meta_i_isStrAt(&original, 0, "DANGER", "RANGER", "MANGER", "") + && !meta_i_isStrAt(&original, (current - 1), "E", "I", "RGY", "OGY", "") ) { - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "J") < 0) goto end_free; current += 2; break; } /** Italian e.g, 'biaggi' **/ if ( - meta_i_isStrAt(original, (current + 1), "E", "I", "Y", "") - || meta_i_isStrAt(original, (current - 1), "AGGI", "OGGI", "") + meta_i_isStrAt(&original, (current + 1), "E", "I", "Y", "") + || meta_i_isStrAt(&original, (current - 1), "AGGI", "OGGI", "") ) { /** Obvious germanic. **/ - if (meta_i_isStrAt(original, 0, "SCH", "VAN ", "VON ", "") - || meta_i_isStrAt(original, (current + 1), "ET", "")) + if (meta_i_isStrAt(&original, 0, "SCH", "VAN ", "VON ", "") + || meta_i_isStrAt(&original, (current + 1), "ET", "")) { - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; } else { /** Always soft, if french ending. **/ - if (meta_i_isStrAt(original, (current + 1), "IER ", "")) + if (meta_i_isStrAt(&original, (current + 1), "IER ", "")) { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "J") < 0) goto end_free; } else { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; } } current += 2; @@ -852,8 +762,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) } current += (next_char == 'G') ? 2 : 1; - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; break; } @@ -861,12 +771,12 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) { /** Only keep if first & before vowel or between 2 vowels. **/ if ( - (current == 0 || meta_i_isVowel(original, (current - 1))) - && meta_i_isVowel(original, current + 1) + (current == 0 || meta_i_isVowel(&original, (current - 1))) + && meta_i_isVowel(&original, current + 1) ) { - if (check(meta_i_addStr(primary, "H")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "H") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "H") < 0) goto end_free; current += 2; } else /* also takes care of 'HH' */ @@ -877,23 +787,23 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) case 'J': { /** Obvious spanish, 'jose', 'san jacinto' **/ - const bool has_jose_next = meta_i_isStrAt(original, current, "JOSE", ""); - const bool starts_with_san = meta_i_isStrAt(original, 0, "SAN ", ""); + const bool has_jose_next = meta_i_isStrAt(&original, current, "JOSE", ""); + const bool starts_with_san = meta_i_isStrAt(&original, 0, "SAN ", ""); if (has_jose_next || starts_with_san) { if ( starts_with_san /** I don't know what this condition means. **/ - || (current == 0 && meta_i_getCharAt(original, current + 4) == ' ') + || (current == 0 && meta_i_getCharAt(&original, current + 4) == ' ') ) { - if (check(meta_i_addStr(primary, "H")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "H") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "H") < 0) goto end_free; } else { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "H") < 0) goto end_free; } current += 1; break; @@ -901,8 +811,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (current == 0 && !has_jose_next) { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; /* Yankelovich/Jankelowicz */ - if (check(meta_i_addStr(secondary, "A")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; /* Yankelovich/Jankelowicz */ + if (xsConcatenateLiteral(&secondary, "A") < 0) goto end_free; } else { @@ -910,28 +820,28 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if ( !is_slavo_germanic && (next_char == 'A' || next_char == 'O') - && meta_i_isVowel(original, (current - 1)) + && meta_i_isVowel(&original, (current - 1)) ) { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "H")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "H") < 0) goto end_free; } else { if (current == last) { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "") < 0) goto end_free; } else { if ( - !meta_i_isStrAt(original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") - && !meta_i_isStrAt(original, (current - 1), "S", "K", "L", "") + !meta_i_isStrAt(&original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") + && !meta_i_isStrAt(&original, (current - 1), "S", "K", "L", "") ) { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "J") < 0) goto end_free; } } } @@ -944,8 +854,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) case 'K': { current += (next_char == 'K') ? 2 : 1; - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; break; } @@ -957,19 +867,19 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if ( ( current == length - 3 - && meta_i_isStrAt(original, (current - 1), "ILLO", "ILLA", "ALLE", "") + && meta_i_isStrAt(&original, (current - 1), "ILLO", "ILLA", "ALLE", "") ) || ( - meta_i_isStrAt(original, (current - 1), "ALLE", "") + meta_i_isStrAt(&original, (current - 1), "ALLE", "") && ( - meta_i_isStrAt(original, (last - 1), "AS", "OS", "") - || meta_i_isStrAt(original, last, "A", "O", "") + meta_i_isStrAt(&original, (last - 1), "AS", "OS", "") + || meta_i_isStrAt(&original, last, "A", "O", "") ) ) ) { - if (check(meta_i_addStr(primary, "L")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "L") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "") < 0) goto end_free; current += 2; break; } @@ -977,8 +887,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) } else current += 1; - if (check(meta_i_addStr(primary, "L")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "L")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "L") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "L") < 0) goto end_free; break; } @@ -986,22 +896,22 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) { current += ( ( - meta_i_isStrAt(original, (current - 1), "UMB", "") - && (current + 1 == last || meta_i_isStrAt(original, (current + 2), "ER", "")) + meta_i_isStrAt(&original, (current - 1), "UMB", "") + && (current + 1 == last || meta_i_isStrAt(&original, (current + 2), "ER", "")) ) /** 'dumb','thumb' **/ || next_char == 'M' ) ? 2 : 1; - if (check(meta_i_addStr(primary, "M")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "M")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "M") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "M") < 0) goto end_free; break; } case 'N': { current += (next_char == 'N') ? 2 : 1; - if (check(meta_i_addStr(primary, "N")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "N")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "N") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "N") < 0) goto end_free; break; } @@ -1009,24 +919,24 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) { if (next_char == 'H') { - if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "F") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "F") < 0) goto end_free; current += 2; break; } /** Also account for "campbell", "raspberry" **/ - current += (meta_i_isStrAt(original, (current + 1), "P", "B", "")) ? 2 : 1; - if (check(meta_i_addStr(primary, "P")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "P")) != 0) goto end_free; + current += (meta_i_isStrAt(&original, (current + 1), "P", "B", "")) ? 2 : 1; + if (xsConcatenateLiteral(&primary, "P") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "P") < 0) goto end_free; break; } case 'Q': { current += (next_char == 'Q') ? 2 : 1; - if (check(meta_i_addStr(primary, "K")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "K")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; break; } @@ -1036,12 +946,12 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) const bool no_primary = ( !is_slavo_germanic && current == last - && meta_i_isStrAt(original, (current - 2), "IE", "") - && !meta_i_isStrAt(original, (current - 4), "ME", "MA", "") + && meta_i_isStrAt(&original, (current - 2), "IE", "") + && !meta_i_isStrAt(&original, (current - 4), "ME", "MA", "") ); - if (check(meta_i_addStr(primary, (no_primary) ? "" : "R")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "R")) != 0) goto end_free; + if (xsConcatenate(&primary, (no_primary) ? "" : "R", -1) < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "R") < 0) goto end_free; current += (next_char == 'R') ? 2 : 1; break; } @@ -1049,69 +959,69 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) case 'S': { /** Special cases 'island', 'isle', 'carlisle', 'carlysle' **/ - if (meta_i_isStrAt(original, (current - 1), "ISL", "YSL", "")) + if (meta_i_isStrAt(&original, (current - 1), "ISL", "YSL", "")) { current += 1; break; } /** Special case 'sugar-' **/ - if (current == 0 && meta_i_isStrAt(original, current, "SUGAR", "")) + if (current == 0 && meta_i_isStrAt(&original, current, "SUGAR", "")) { - if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "X") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "S") < 0) goto end_free; current += 1; break; } - if (meta_i_isStrAt(original, current, "SH", "")) + if (meta_i_isStrAt(&original, current, "SH", "")) { - const bool germanic = meta_i_isStrAt(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); - const char* sound = (germanic) ? "S" : "X"; - if (check(meta_i_addStr(primary, sound)) != 0) goto end_free; - if (check(meta_i_addStr(secondary, sound)) != 0) goto end_free; + const bool germanic = meta_i_isStrAt(&original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); + char* sound = (germanic) ? "S" : "X"; + if (xsConcatenate(&primary, sound, -1) < 0) goto end_free; + if (xsConcatenate(&secondary, sound, -1) < 0) goto end_free; current += 2; break; } /** Italian & Armenian. **/ - if (meta_i_isStrAt(original, current, "SIO", "SIA", "SIAN", "")) + if (meta_i_isStrAt(&original, current, "SIO", "SIA", "SIAN", "")) { - if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, (is_slavo_germanic) ? "S" : "X")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenate(&secondary, (is_slavo_germanic) ? "S" : "X", -1) < 0) goto end_free; current += 3; break; } /** german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' **/ /** also, -sz- in slavic language although in hungarian it is pronounced 's' **/ - if (current == 0 && meta_i_isStrAt(original, (current + 1), "M", "N", "L", "W", "")) + if (current == 0 && meta_i_isStrAt(&original, (current + 1), "M", "N", "L", "W", "")) { - if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; current += 1; break; } - if (meta_i_isStrAt(original, (current + 1), "Z", "")) + if (meta_i_isStrAt(&original, (current + 1), "Z", "")) { - if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; current += 2; break; } - if (meta_i_isStrAt(original, current, "SC", "")) + if (meta_i_isStrAt(&original, current, "SC", "")) { /** Schlesinger's rule. **/ - if (meta_i_getCharAt(original, current + 2) == 'H') + if (meta_i_getCharAt(&original, current + 2) == 'H') { /** Dutch origin, e.g. 'school', 'schooner' **/ - if (meta_i_isStrAt(original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) + if (meta_i_isStrAt(&original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) { /** 'schermerhorn', 'schenker' **/ - const bool x_sound = meta_i_isStrAt(original, (current + 3), "ER", "EN", ""); - if (check(meta_i_addStr(primary, (x_sound) ? "X" : "SK")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "SK")) != 0) goto end_free; + const bool x_sound = meta_i_isStrAt(&original, (current + 3), "ER", "EN", ""); + if (xsConcatenate(&primary, (x_sound) ? "X" : "SK", -1) < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "SK") < 0) goto end_free; current += 3; break; } @@ -1119,66 +1029,66 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) { const bool s_sound = ( current == 0 - && !meta_i_isVowel(original, 3) - && meta_i_getCharAt(original, 3) != 'W' + && !meta_i_isVowel(&original, 3) + && meta_i_getCharAt(&original, 3) != 'W' ); - if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, (s_sound) ? "S" : "X")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "X") < 0) goto end_free; + if (xsConcatenate(&secondary, (s_sound) ? "S" : "X", -1) < 0) goto end_free; current += 3; break; } } /** Default case. **/ - const char* sound = (meta_i_isStrAt(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; - if (check(meta_i_addStr(primary, sound)) != 0) goto end_free; - if (check(meta_i_addStr(secondary, sound)) != 0) goto end_free; + char* sound = (meta_i_isStrAt(&original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; + if (xsConcatenate(&primary, sound, -1) < 0) goto end_free; + if (xsConcatenate(&secondary, sound, -1) < 0) goto end_free; current += 3; break; } /** French e.g. 'resnais', 'artois' **/ - const bool no_primary = (current == last && meta_i_isStrAt(original, (current - 2), "AI", "OI", "")); - if (check(meta_i_addStr(primary, (no_primary) ? "" : "S")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "S")) != 0) goto end_free; - current += (meta_i_isStrAt(original, (current + 1), "S", "Z", "")) ? 2 : 1; + const bool no_primary = (current == last && meta_i_isStrAt(&original, (current - 2), "AI", "OI", "")); + if (xsConcatenate(&primary, (no_primary) ? "" : "S", -1) < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "S") < 0) goto end_free; + current += (meta_i_isStrAt(&original, (current + 1), "S", "Z", "")) ? 2 : 1; break; } case 'T': { - if (meta_i_isStrAt(original, current, "TIA", "TCH", "TION", "")) + if (meta_i_isStrAt(&original, current, "TIA", "TCH", "TION", "")) { - if (check(meta_i_addStr(primary, "X")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "X")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "X") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; current += 3; break; } - if (meta_i_isStrAt(original, current, "TH", "TTH", "")) + if (meta_i_isStrAt(&original, current, "TH", "TTH", "")) { /** Special case 'thomas', 'thames' or germanic. **/ char* primary_char = ( - meta_i_isStrAt(original, (current + 2), "OM", "AM", "") - || meta_i_isStrAt(original, 0, "SCH", "VAN ", "VON ", "") + meta_i_isStrAt(&original, (current + 2), "OM", "AM", "") + || meta_i_isStrAt(&original, 0, "SCH", "VAN ", "VON ", "") ) ? "T" : "0"; /* Zero, not O. */ - if (check(meta_i_addStr(primary, primary_char)) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; + if (xsConcatenate(&primary, primary_char, -1) < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "T") < 0) goto end_free; current += 2; break; } - if (check(meta_i_addStr(primary, "T")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "T")) != 0) goto end_free; - current += (meta_i_isStrAt(original, (current + 1), "T", "D", "")) ? 2 : 1; + if (xsConcatenateLiteral(&primary, "T") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "T") < 0) goto end_free; + current += (meta_i_isStrAt(&original, (current + 1), "T", "D", "")) ? 2 : 1; break; } case 'V': { - if (check(meta_i_addStr(primary, "F")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "F") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "F") < 0) goto end_free; current += (next_char == 'V') ? 2 : 1; break; } @@ -1186,39 +1096,39 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) case 'W': { /** Can also be in middle of word. **/ - if (meta_i_isStrAt(original, current, "WR", "")) + if (meta_i_isStrAt(&original, current, "WR", "")) { - if (check(meta_i_addStr(primary, "R")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "R")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "R") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "R") < 0) goto end_free; current += 2; break; } - const bool next_is_vowel = meta_i_isVowel(original, current + 1); - if (current == 0 && (next_is_vowel || meta_i_isStrAt(original, current, "WH", ""))) + const bool next_is_vowel = meta_i_isVowel(&original, current + 1); + if (current == 0 && (next_is_vowel || meta_i_isStrAt(&original, current, "WH", ""))) { /** Wasserman should match Vasserman. **/ - if (check(meta_i_addStr(primary, "A")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, (next_is_vowel) ? "F" : "A")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "A") < 0) goto end_free; + if (xsConcatenate(&secondary, (next_is_vowel) ? "F" : "A", -1) < 0) goto end_free; } /** Arnow should match Arnoff. **/ - if ((current == last && meta_i_isVowel(original, current - 1)) - || meta_i_isStrAt(original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") - || meta_i_isStrAt(original, 0, "SCH", "") + if ((current == last && meta_i_isVowel(&original, current - 1)) + || meta_i_isStrAt(&original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") + || meta_i_isStrAt(&original, 0, "SCH", "") ) { - if (check(meta_i_addStr(primary, "")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "F")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "F") < 0) goto end_free; current += 1; break; } /** Polish e.g. 'filipowicz' **/ - if (meta_i_isStrAt(original, current, "WICZ", "WITZ", "")) + if (meta_i_isStrAt(&original, current, "WICZ", "WITZ", "")) { - if (check(meta_i_addStr(primary, "TS")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "FX")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "TS") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "FX") < 0) goto end_free; current += 4; break; } @@ -1234,17 +1144,17 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) const bool silent = ( current == last && ( - meta_i_isStrAt(original, (current - 2), "AU", "OU", "") - || meta_i_isStrAt(original, (current - 3), "IAU", "EAU", "") + meta_i_isStrAt(&original, (current - 2), "AU", "OU", "") + || meta_i_isStrAt(&original, (current - 3), "IAU", "EAU", "") ) ); if (!silent) { - if (check(meta_i_addStr(primary, "KS")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "KS")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "KS") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "KS") < 0) goto end_free; } - current += (meta_i_isStrAt(original, (current + 1), "C", "X", "")) ? 2 : 1; + current += (meta_i_isStrAt(&original, (current + 1), "C", "X", "")) ? 2 : 1; break; } @@ -1253,18 +1163,18 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) /** Chinese pinyin e.g. 'zhao' **/ if (next_char == 'H') { - if (check(meta_i_addStr(primary, "J")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, "J")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "J") < 0) goto end_free; + if (xsConcatenateLiteral(&secondary, "J") < 0) goto end_free; current += 2; break; } const bool has_t_sound = ( - meta_i_isStrAt(original, (current + 1), "ZO", "ZI", "ZA", "") - || (is_slavo_germanic && current > 0 && meta_i_getCharAt(original, (current - 1)) != 'T') + meta_i_isStrAt(&original, (current + 1), "ZO", "ZI", "ZA", "") + || (is_slavo_germanic && current > 0 && meta_i_getCharAt(&original, (current - 1)) != 'T') ); - if (check(meta_i_addStr(primary, "S")) != 0) goto end_free; - if (check(meta_i_addStr(secondary, (has_t_sound) ? "TS" : "S")) != 0) goto end_free; + if (xsConcatenateLiteral(&primary, "S") < 0) goto end_free; + if (xsConcatenate(&secondary, (has_t_sound) ? "TS" : "S", -1) < 0) goto end_free; current += (next_char == 'Z') ? 2 : 1; break; } @@ -1274,18 +1184,31 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) } } - /** Write the output strings. **/ - *primary_code = primary->str; - *secondary_code = secondary->str; - primary->free_str_on_destroy = 0; - secondary->free_str_on_destroy = 0; + /** Get the output strings. **/ + char* primary_str = xsString(&primary); + char* secondary_str = xsString(&secondary); + if (UNLIKELY(primary_str == NULL || secondary_str == NULL)) goto end_free; + + /** Allocate buffers for returning the output strings. **/ + void* primary_code_buf = nmSysStrdup(primary_str); + void* secondary_code_buf = nmSysStrdup(secondary_str); + if (UNLIKELY(primary_code_buf == NULL || secondary_code_buf == NULL)) + { + if (primary_code_buf != NULL) nmSysFree(primary_code_buf); + if (secondary_code_buf != NULL) nmSysFree(secondary_code_buf); + goto end_free; + } + + /** Success. **/ + *primary_code = primary_code_buf; + *secondary_code = secondary_code_buf; ret = 0; end_free: if (UNLIKELY(ret != 0)) fprintf(stderr, "Error: metaDoubleMetaphone() failed (error code %d).\n", ret); - meta_i_destroyString(original); - meta_i_destroyString(primary); - meta_i_destroyString(secondary); + if (secondary.String != NULL) warnFail(xsDeInit(&secondary)); + if (primary.String != NULL) warnFail(xsDeInit(&primary)); + if (original.String != NULL) warnFail(xsDeInit(&original)); return ret; } From 79f0e6657171c337472e67be8b3d28e731160764 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 15:24:26 -0600 Subject: [PATCH 275/361] Update my explanation comment. --- centrallix/utility/double_metaphone.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 00e1897e0..2082667d3 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -77,20 +77,17 @@ /*** Note to future programmers reading this file (by Israel Fuller): *** - *** This file was copied from a GitHub Repo with proper licensing (in case - *** you didn't read the legal stuff above), so feel free to check it out. + *** This file was copied from a GitHub Repo with licensing (see above). *** - *** As for this code, I've modified it to use styling and memory allocation - *** consistent with the rest of the Centrallix codebase. Also, I have added - *** documentation comments and extensive test cases (at the end of the file), - *** however, these reflect my own (possibly incorrect) understanding, which - *** might not line up with the original author. + *** As for this code, I've modified it to use styling, memory management, and + *** libraries used to be consistent Centrallix. I also wrote comments and + *** tests based on my own understanding, so they might not accurately reflect + *** the original author's intent. *** - *** To be honest, though, trying to make this code as readable as possible - *** was very challenging due to all the messy boolean algebra. If there is - *** ever a professional linguist reading this, please factor out some of the - *** logic into local variables with descriptive names so that the rest of us - *** can read this code without our eyes glazing over. + *** To be honest, trying to make this code as readable as possible was by no + *** means easy, due to the complex boolean algebra. If a linguist ever reads + *** this, please factor out some logic into local variables with descriptive + *** names so that the rest of us can read this without our eyes glazing over. *** *** If you have any questions, please feel free to reach out to me or Greg. *** From 0927f755c0493946b3205e0406ec7c29cb415073 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 15:34:06 -0600 Subject: [PATCH 276/361] Improve metaphone code style. Add static to internal functions. Add docs showing that the caller owns the metaphone strings on success. Remove the dead slavo-germanic "WITZ" branch. Fix toupper() called on a signed char. Fix broken citation links in copyright notices. Fix missing #include "double_metaphone.h". Clean up unused #includes. --- centrallix/include/double_metaphone.h | 5 ++++- centrallix/utility/double_metaphone.c | 31 ++++++++++++++++----------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h index e27d2d52d..3ba0972c7 100644 --- a/centrallix/include/double_metaphone.h +++ b/centrallix/include/double_metaphone.h @@ -37,7 +37,7 @@ /* b) the "Artistic License" (5). */ /* */ /* Citations: */ -/* 1: https://github.com/gitpan/Text-metaDoubleMetaphone */ +/* 1: https://github.com/gitpan/Text-DoubleMetaphone */ /* 2: https://dev.perl.org/licenses/gpl1.html */ /* 3: http://www.fsf.org */ /* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ @@ -78,6 +78,9 @@ /* standards so that it could be included here. */ /************************************************************************/ +/*** Computes double metaphone. On success the caller owns both strings and + *** must release them with nmSysFree(). See double_metaphone.c for details. + ***/ int metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code); #endif /* End of .h file. */ diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 2082667d3..783d8f3a2 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -34,7 +34,7 @@ /* b) the "Artistic License" (5). */ /* */ /* Citations: */ -/* 1: https://github.com/gitpan/Text-metaDoubleMetaphone */ +/* 1: https://github.com/gitpan/Text-DoubleMetaphone */ /* 2: https://dev.perl.org/licenses/gpl1.html */ /* 3: http://www.fsf.org */ /* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ @@ -94,7 +94,6 @@ *** Original Source: https://github.com/gitpan/Text-DoubleMetaphone ***/ -#include #include #include #include @@ -106,13 +105,14 @@ #include "cxlib/xstring.h" #include "cxlib/warn.h" #include "cxlib/expect.h" +#include "double_metaphone.h" /*** Convert all characters of an XString to uppercase. *** *** @param s The XString being modified. *** @returns 0 if successful, or -1 if an error occurs. ***/ -int +static int meta_i_makeUpper(pXString s) { /** Handle edge cases. **/ @@ -126,7 +126,7 @@ meta_i_makeUpper(pXString s) /** Uppercase each character. **/ for (int i = 0; i < length; i++) - buf[i] = (char)toupper(buf[i]); + buf[i] = (char)toupper((unsigned char)buf[i]); return 0; } @@ -136,7 +136,7 @@ meta_i_makeUpper(pXString s) *** @returns The character at the position in the XString, or *** '\0' if the position is not in the XString. ***/ -char +static char meta_i_getCharAt(pXString s, unsigned int pos) { /** Handle edge cases. **/ @@ -153,7 +153,7 @@ meta_i_getCharAt(pXString s, unsigned int pos) *** @param s The XString being checked. *** @param pos The character location to check within the XString. ***/ -bool +static bool meta_i_isVowel(pXString s, unsigned int pos) { const char c = meta_i_getCharAt(s, pos); @@ -162,13 +162,13 @@ meta_i_isVowel(pXString s, unsigned int pos) (c == 'O') || (c == 'U') || (c == 'Y')); } -/*** Search an XString for "W", "K", "CZ", or "WITZ", which indicate that the +/*** Search an XString for "W", "K", or "CZ", which indicate that the *** string is Slavo Germanic. *** *** @param s The XString to be searched. *** @returns 1 if the XString is Slavo Germanic, or 0 otherwise. ***/ -bool +static bool meta_i_isSlavoGermanic(pXString s) { /** Handle edge cases. **/ @@ -176,8 +176,7 @@ meta_i_isSlavoGermanic(pXString s) return (xsFind(s, "W", 1, 0) >= 0) || (xsFind(s, "K", 1, 0) >= 0) - || (xsFind(s, "CZ", 2, 0) >= 0) - || (xsFind(s, "WITZ", 4, 0) >= 0); + || (xsFind(s, "CZ", 2, 0) >= 0); } /*** Checks for to see if any of a list of strings appear in a the given @@ -191,7 +190,7 @@ meta_i_isSlavoGermanic(pXString s) *** @returns 1 if any of the character sequences appear after the start *** in the XString and 0 otherwise. ***/ -bool +static bool meta_i_isStrAt(pXString s, unsigned int start, ...) { va_list ap; @@ -228,12 +227,20 @@ meta_i_isStrAt(pXString s, unsigned int start, ...) } /*** Computes double metaphone. + *** + *** On success the caller owns both strings and must release them with + *** nmSysFree(). On failure neither pointer is written. *** *** Example Usage: *** ```c *** char* primary_code; *** char* secondary_code; - *** metaDoubleMetaphone(input, &primary_code, &secondary_code); + *** if (metaDoubleMetaphone(input, &primary_code, &secondary_code) != 0) return -1; + *** + *** printf("%s %s\n", primary_code, secondary_code); + *** + *** nmSysFree(primary_code); + *** nmSysFree(secondary_code); *** ``` *** *** @param str The string to compute. From 08730a8c85b1ee44e54959081b9a555032a7387e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 15:41:58 -0600 Subject: [PATCH 277/361] Improve metaphone comments. --- centrallix/utility/double_metaphone.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 783d8f3a2..9010f0d5d 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -256,7 +256,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) int ret = -1; XString original, primary, secondary; - /** xsInit() always leaves a buffer, so NULL marks one it never reached. **/ + /** xsInit() always sets .String, so NULL marks an uninitialized xstring. **/ original.String = NULL; primary.String = NULL; secondary.String = NULL; From 75526a2e0e6b77fc10046efb364624f9f527a1a2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 15:49:59 -0600 Subject: [PATCH 278/361] Remove pointer address printing from errors. Pointer addresses are unhelpful and printing them might be a security concern. The info was trimmed or replaced with better info. --- centrallix-lib/src/clusters.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index b1e4951dc..d3b2e9518 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -681,7 +681,7 @@ caCosCompare(void* v1, void* v2) const double similarity = ca_i_sparseSimilarity(vec1, vec2); if (UNLIKELY(isnan(similarity))) { - mssError(1, "CA", "ca_i_sparseSimilarity(%p, %p) failed.", vec1, vec2); + mssError(1, "CA", "ca_i_sparseSimilarity() failed."); goto err; } @@ -689,7 +689,7 @@ caCosCompare(void* v1, void* v2) return round(similarity * 1000000.0) / 1000000.0; err: - mssError(0, "CA", "caCosCompare(%p, %p) failed.", v1, v2); + mssError(0, "CA", "caCosCompare() failed."); return NAN; } @@ -1050,9 +1050,15 @@ caKmeans( if (!successful) { mssError(0, "CA", - "caKmeans(%p, %u, %u, %u, %lf, %p, %p, %s) failed.", - vectors, num_vectors, num_clusters, max_iter, min_improvement, - labels, vector_sims, (auto_seed) ? "true" : "false" + "caKmeans(%s, %u, %u, %u, %lf, %s, %s, %s) failed.", + (vectors == NULL) ? "NULL" : "vectors", + num_vectors, + num_clusters, + max_iter, + min_improvement, + (labels == NULL) ? "NULL" : "labels", + (vector_sims == NULL) ? "NULL" : "vector_sims", + (auto_seed) ? "true" : "false" ); } From 3947616d9b2cfd68cd04b4b2ff7e97b87ff6d8b7 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 16:01:57 -0600 Subject: [PATCH 279/361] Update error handling in stparse.c. --- centrallix/utility/stparse.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index e71760f88..dc8f61544 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -8,7 +8,7 @@ #include "cxlib/exception.h" #include "stparse_ne.h" #include "stparse.h" -#include "cxlib/check.h" +#include "cxlib/warn.h" #include "cxlib/mtsession.h" #include "cxlib/xstring.h" #include "cxlib/newmalloc.h" @@ -606,8 +606,12 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio ASSERTMAGIC(this, MGK_STRUCTINF); /** Get the correct expression **/ - find_exp = checkPtr(stGetExpression(this, nval)); - if (find_exp == NULL) goto end; + find_exp = stGetExpression(this, nval); + if (UNLIKELY(find_exp == NULL)) + { + mssError(1, "ST", "stGetExpression(this, %d) failed.", nval); + goto end; + } /** expression code? **/ if (type == DATA_T_CODE && (find_exp->Flags & (EXPR_F_RUNCLIENT | EXPR_F_RUNSERVER))) @@ -622,16 +626,25 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio ***/ if ((domain != 0 || (find_exp->ObjCoverageMask & (EXPR_MASK_EXTREF | EXPR_MASK_INDETERMINATE))) && !(find_exp->Flags & EXPR_F_RUNCLIENT)) { - if (!objlist) + if (objlist == NULL) { - my_objlist = checkPtr(expCreateParamList()); + my_objlist = expCreateParamList(); + if (UNLIKELY(my_objlist == NULL)) + { + mssError(1, "ST", "expCreateParamList() failed."); + goto end; + } my_objlist->Session = sess; } if (domain != 0) expBindExpression(find_exp, my_objlist, domain); - expEvalTree(find_exp, my_objlist); - if (!objlist) - expFreeParamList(my_objlist); + if (expEvalTree(find_exp, my_objlist) < 0) + { + mssError(0, "ST", "Failed to evaluate expression tree."); + goto end; + } + if (objlist == NULL) + warnFail(expFreeParamList(my_objlist)); } /** Correct type requested? **/ From fbeac7f48830ac5c79b08562d4f9e1ecd3e1aea6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 21 Sep 2026 16:25:22 -0600 Subject: [PATCH 280/361] Update error handling in exp_functions.c. --- centrallix/expression/exp_functions.c | 166 +++++++++++++++++++------- 1 file changed, 124 insertions(+), 42 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 51d44afd6..c44233697 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -62,7 +62,6 @@ #include #include -#include "cxlib/check.h" #include "cxlib/clusters.h" #include "cxlib/expect.h" #include "cxlib/mtask.h" @@ -521,13 +520,22 @@ exp_fn_i_allocResultString(pExpression tree, const size_t required_space) else { /** We need to allocate new memory. **/ - char* result = checkPtr(nmSysMalloc(required_space * sizeof(char))); - if (result == NULL) return -1; + const size_t required_size = required_space * sizeof(char); + char* result = nmSysMalloc(required_size); + if (UNLIKELY(result == NULL)) + { + mssError(1, "EXP", "nmSysMalloc(%zu) failed.", required_size); + goto err; + } tree->String = result; tree->Alloc = 1; } - return 0; + return 0; + + err: + mssError(0, "EXP", "Failed to allocate result string."); + return -1; } @@ -1596,8 +1604,12 @@ exp_fn_lztrim(pExpression tree) } /** Extract the arg string. **/ - pExpression maybe_str = checkPtr(tree->Children.Items[0]); - if (UNLIKELY(maybe_str == NULL)) return -1; + pExpression maybe_str = tree->Children.Items[0]; + if (UNLIKELY(maybe_str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument.", tree->Name); + return -1; + } if (maybe_str->Flags & EXPR_F_NULL) { /** Propagate null values. **/ @@ -1605,8 +1617,12 @@ exp_fn_lztrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = checkPtr(maybe_str->String); - if (UNLIKELY(str == NULL)) return -1; + char* str = maybe_str->String; + if (UNLIKELY(str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument string.", tree->Name); + return -1; + } /*** We don't need to allocate new memory or copy anything because we *** can simply point to the first character in the previous string @@ -1641,8 +1657,12 @@ exp_fn_ltrim(pExpression tree) } /** Extract the arg string. **/ - pExpression maybe_str = checkPtr(tree->Children.Items[0]); - if (UNLIKELY(maybe_str == NULL)) return -1; + pExpression maybe_str = tree->Children.Items[0]; + if (UNLIKELY(maybe_str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument.", tree->Name); + return -1; + } if (maybe_str->Flags & EXPR_F_NULL) { /** Propagate null values. **/ @@ -1650,8 +1670,12 @@ exp_fn_ltrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = checkPtr(maybe_str->String); - if (UNLIKELY(str == NULL)) return -1; + char* str = maybe_str->String; + if (UNLIKELY(str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument string.", tree->Name); + return -1; + } /*** We don't need to allocate new memory or copy anything because we *** can simply point to the first character in the previous string @@ -1687,8 +1711,12 @@ exp_fn_rtrim(pExpression tree) } /** Extract the arg string. **/ - pExpression maybe_str = checkPtr(tree->Children.Items[0]); - if (UNLIKELY(maybe_str == NULL)) return -1; + pExpression maybe_str = tree->Children.Items[0]; + if (UNLIKELY(maybe_str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument.", tree->Name); + return -1; + } if (maybe_str->Flags & EXPR_F_NULL) { /** Propagate null values. **/ @@ -1696,8 +1724,12 @@ exp_fn_rtrim(pExpression tree) tree->DataType = DATA_T_STRING; return 0; } - char* str = checkPtr(maybe_str->String); - if (UNLIKELY(str == NULL)) return -1; + char* str = maybe_str->String; + if (UNLIKELY(str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument string.", tree->Name); + return -1; + } /** Trim spaces from the end of the string. **/ /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ @@ -1713,7 +1745,7 @@ exp_fn_rtrim(pExpression tree) } /** We need to copy to remove spaces (str is owned by a child expression). **/ - if (check(exp_fn_i_allocResultString(tree, n + 1)) != 0) return -1; + if (exp_fn_i_allocResultString(tree, n + 1) != 0) return -1; memcpy(tree->String, str, n); tree->String[n] = '\0'; @@ -1734,6 +1766,11 @@ exp_fn_trim(pExpression tree) /** Temporarily override the arg0 str pointer with the result from ltrim(). **/ pExpression arg0 = tree->Children.Items[0]; + if (UNLIKELY(arg0 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument.", tree->Name); + return -1; + } char* arg0_str = arg0->String; arg0->String = tree->String; tree->Alloc = 0; @@ -3817,7 +3854,7 @@ exp_fn_i_doMath(pExpression tree, double (*math)(), int arg_num) double n[4] = {0.0, 0.0, 0.0, 0.0}; for (int i = 0; i < arg_num; i++) { - if (check(exp_fn_i_getNumber(tree->Children.Items[i], &(n[i]))) != 0) + if (exp_fn_i_getNumber(tree->Children.Items[i], &(n[i])) != 0) { mssError(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); return -1; @@ -3865,14 +3902,14 @@ exp_fn_log(pExpression tree) /** Extract args. **/ double number, base; - if (check(exp_fn_i_getNumber(checkPtr(tree->Children.Items[0]), &number)) != 0) + if (UNLIKELY(exp_fn_i_getNumber(tree->Children.Items[0], &number) != 0)) { mssError(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); return -1; } if (tree->Children.nItems > 1) { - if (check(exp_fn_i_getNumber(checkPtr(tree->Children.Items[1]), &base)) != 0) + if (UNLIKELY(exp_fn_i_getNumber(tree->Children.Items[1], &base) != 0)) { mssError(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); return -1; @@ -4565,18 +4602,27 @@ exp_fn_metaphone(pExpression tree) goto end_free; } - /** Extract string param. **/ - pExpression maybe_str = checkPtr(tree->Children.Items[0]); - if (UNLIKELY(maybe_str == NULL)) goto end_free; + /** Extract the arg string. **/ + pExpression maybe_str = tree->Children.Items[0]; + if (UNLIKELY(maybe_str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument.", tree->Name); + goto end_free; + } if (maybe_str->Flags & EXPR_F_NULL) { + /** Propagate null values. **/ tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_STRING; ret = 0; goto end_free; } - const char* str = checkPtr(maybe_str->String); - if (UNLIKELY(str == NULL)) goto end_free; + char* str = maybe_str->String; + if (UNLIKELY(str == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument string.", tree->Name); + goto end_free; + } const size_t str_len = strlen(str); if (UNLIKELY(str_len == 0u)) { @@ -4598,7 +4644,7 @@ exp_fn_metaphone(pExpression tree) /** Store the results. **/ store_data:; const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; - if (check(exp_fn_i_allocResultString(tree, length)) != 0) goto end_free; + if (exp_fn_i_allocResultString(tree, length) != 0) goto end_free; sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); tree->DataType = DATA_T_STRING; ret = 0; @@ -4637,18 +4683,36 @@ exp_fn_compare(pExpression tree) } /** Extract strings. **/ - pExpression maybe_str1 = checkPtr(tree->Children.Items[0]); - pExpression maybe_str2 = checkPtr(tree->Children.Items[1]); - if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) goto err; + pExpression maybe_str1 = tree->Children.Items[0]; + pExpression maybe_str2 = tree->Children.Items[1]; + if (UNLIKELY(maybe_str1 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument.", tree->Name); + goto err; + } + if (UNLIKELY(maybe_str2 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get second argument.", tree->Name); + goto err; + } if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_DOUBLE; return 0; } - char* str1 = checkPtr(maybe_str1->String); - char* str2 = checkPtr(maybe_str2->String); - if (UNLIKELY(str1 == NULL || str2 == NULL)) goto err; + char* str1 = maybe_str1->String; + char* str2 = maybe_str2->String; + if (UNLIKELY(str1 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument string.", tree->Name); + goto err; + } + if (UNLIKELY(str2 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get second argument string.", tree->Name); + goto err; + } /** Handle either cos_compare() or lev_compare(). **/ if (tree->Name[0] == 'c') @@ -4656,8 +4720,8 @@ exp_fn_compare(pExpression tree) int ret; /** Build vectors. **/ - const pVector v1 = checkPtr(caBuildVector(str1)); - const pVector v2 = checkPtr(caBuildVector(str2)); + const pVector v1 = caBuildVector(str1); + const pVector v2 = caBuildVector(str2); if (UNLIKELY(v1 == NULL || v2 == NULL)) { mssError(1, "EXP", @@ -4682,7 +4746,7 @@ exp_fn_compare(pExpression tree) } else { /* lev_compare() */ - const double lev_sim = checkDouble(caLevCompare(str1, str2)); + const double lev_sim = caLevCompare(str1, str2); if (UNLIKELY(isnan(lev_sim))) { mssError(1, "EXP", @@ -4718,22 +4782,40 @@ exp_fn_levenshtein(pExpression tree) } /** Extract strings. **/ - pExpression maybe_str1 = checkPtr(tree->Children.Items[0]); - pExpression maybe_str2 = checkPtr(tree->Children.Items[1]); - if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) return -1; + pExpression maybe_str1 = tree->Children.Items[0]; + pExpression maybe_str2 = tree->Children.Items[1]; + if (UNLIKELY(maybe_str1 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument.", tree->Name); + return -1; + } + if (UNLIKELY(maybe_str2 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get second argument.", tree->Name); + return -1; + } if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { tree->Flags |= EXPR_F_NULL; tree->DataType = DATA_T_INTEGER; return 0; } - char* str1 = checkPtr(maybe_str1->String); - char* str2 = checkPtr(maybe_str2->String); - if (UNLIKELY(str1 == NULL || str2 == NULL)) return -1; + char* str1 = maybe_str1->String; + char* str2 = maybe_str2->String; + if (UNLIKELY(str1 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get first argument string.", tree->Name); + return -1; + } + if (UNLIKELY(str2 == NULL)) + { + mssError(1, "EXP", "%s(?): Failed to get second argument string.", tree->Name); + return -1; + } /** Compute edit distance. **/ /** Length 0 is provided for both strings so that the function will compute it for us. **/ - const int edit_dist = checkPos(caEditDist(str1, str2, 0lu, 0lu)); + const int edit_dist = caEditDist(str1, str2, 0lu, 0lu); if (UNLIKELY(edit_dist < 0)) { mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.", tree->Name, str1, str2); From 508a4084ac17285c7f026a770e96db9f7375eb7b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 12:45:58 -0600 Subject: [PATCH 281/361] Disable warnings in cluster tests that spam them. --- centrallix-lib/tests/test_clusters_03.c | 4 ++++ centrallix-lib/tests/test_clusters_04.c | 4 ++++ centrallix-lib/tests/test_clusters_05.c | 4 ++++ centrallix-lib/tests/test_clusters_06.c | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c index 3d15d09cc..ef98d0552 100644 --- a/centrallix-lib/tests/test_clusters_03.c +++ b/centrallix-lib/tests/test_clusters_03.c @@ -22,6 +22,7 @@ /** Test dependencies. **/ #include "test_utils.h" +#include "warn.h" /** Tested module. **/ #include "clusters.h" @@ -86,6 +87,9 @@ static bool doTest(void) long long test(char** tname) { + /** Hide warning noise when this test deliberately causes failures. **/ + WarnPrintEnabled = 0; + *tname = "cluster-03 caCosCompare()"; return loopTest(doTest) * 11; } diff --git a/centrallix-lib/tests/test_clusters_04.c b/centrallix-lib/tests/test_clusters_04.c index 3b6ddc36b..6ea63b5c3 100644 --- a/centrallix-lib/tests/test_clusters_04.c +++ b/centrallix-lib/tests/test_clusters_04.c @@ -22,6 +22,7 @@ /** Test dependencies. **/ #include "test_utils.h" +#include "warn.h" /** Tested module. **/ #include "clusters.h" @@ -65,6 +66,9 @@ static bool doTest(void) long long test(char** tname) { + /** Hide warning noise when this test deliberately causes failures. **/ + WarnPrintEnabled = 0; + *tname = "cluster-04 caLevCompare()"; return loopTest(doTest) * 25; } diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index 9f0779035..9f4354db1 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -22,6 +22,7 @@ /** Test dependencies. **/ #include "test_utils.h" +#include "warn.h" #include "xhash.h" /** Tested module. **/ @@ -184,6 +185,9 @@ static bool doTest(void) long long test(char** tname) { + /** Hide warning noise when this test deliberately causes failures. **/ + WarnPrintEnabled = 0; + *tname = "cluster-05 caMostSimilar()"; return loopTest(doTest) * 39; } diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index 566c6109b..75c3db472 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -23,6 +23,7 @@ /** Test dependencies. **/ #include "newmalloc.h" #include "test_utils.h" +#include "warn.h" /** Tested module. **/ #include "clusters.h" @@ -155,6 +156,9 @@ static bool doTest(void) long long test(char** tname) { + /** Hide warning noise when this test deliberately causes failures. **/ + WarnPrintEnabled = 0; + *tname = "cluster-06 Searching"; return loopTest(doTest) * 3; } From 3e8244d91b0cb49a4c9ad61750ffadbd1cc6d8ad Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 12:50:09 -0600 Subject: [PATCH 282/361] Overhaul error handling in objdrv_cluster.c. --- centrallix/osdrivers/objdrv_cluster.c | 1579 ++++++++++++++++++------- 1 file changed, 1164 insertions(+), 415 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index c8fbef71c..5d6ae51ee 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -44,13 +44,14 @@ #include #include -#include "cxlib/check.h" #include "cxlib/clusters.h" #include "cxlib/expect.h" #include "cxlib/magic.h" #include "cxlib/mtsession.h" #include "cxlib/newmalloc.h" +#include "cxlib/strtcpy.h" #include "cxlib/util.h" +#include "cxlib/warn.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "expression.h" @@ -835,7 +836,7 @@ cluster_i_parseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) return ALGORITHM_NULL; } - /** Parse known clustering algorithms. **/ + /** Check for any known clustering algorithms. **/ if (strcasecmp(algorithm, "none") == 0) return ALGORITHM_NONE; if (strcasecmp(algorithm, "sliding-window") == 0) return ALGORITHM_SLIDING_WINDOW; if (strcasecmp(algorithm, "k-means") == 0) return ALGORITHM_KMEANS; @@ -892,7 +893,7 @@ cluster_i_parseSimilarityMeasure(pStructInf inf, pParamObjects param_list) return SIMILARITY_NULL; } - /** Parse known clustering algorithms. **/ + /** Check for any known similarity measure. **/ if (strcasecmp(measure, "cosine") == 0) return SIMILARITY_COSINE; if (strcasecmp(measure, "levenshtein") == 0) return SIMILARITY_LEVENSHTEIN; @@ -934,19 +935,51 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) char* buf = NULL; pSourceData source_data = NULL; - /** Magic checks. **/ + /** Edge cases. **/ + if (UNLIKELY(inf == NULL)) + { + mssError(1, "Cluster", "Failed to parse source data from NULL struct inf."); + return NULL; /* Skip error handler, which expects a valid struct inf. */ + } ASSERTMAGIC(inf, MGK_STRUCTINF); + if (UNLIKELY(param_list == NULL)) + { + mssError(1, "Cluster", "Failed to parse source data with NULL param_list."); + goto err_free; + } + if (UNLIKELY(path == NULL)) + { + mssError(1, "Cluster", "Failed to parse source data with NULL path."); + goto err_free; + } + + /*** Knowing that an error has started and the error stack must be + *** cleared is sometimes not possible in this function, so clear it + *** while we know no errors are happening. **/ + mssClearError(); /** Allocate SourceData. **/ - source_data = (pSourceData)checkPtr(nmMalloc(sizeof(SourceData))); - if (UNLIKELY(source_data == NULL)) goto err_free; + source_data = nmMalloc(sizeof(SourceData)); + if (UNLIKELY(source_data == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(SourceData)); + goto err_free; + } memset(source_data, 0, sizeof(SourceData)); SETMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Initialize obvious values for SourceData. **/ - source_data->Name = (char*)checkPtr(nmSysStrdup(inf->Name)); - if (UNLIKELY(source_data->Name == NULL)) goto err_free; - if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; + source_data->Name = nmSysStrdup(inf->Name); + if (UNLIKELY(source_data->Name == NULL)) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", inf->Name); + goto err_free; + } + if (UNLIKELY(objCurrentDate(&source_data->DateCreated) != 0)) + { + mssError(1, "Cluster", "objCurrentDate() failed."); + goto err_free; + } /** Get source. **/ if (UNLIKELY(stGetObjAttrValueOSML( @@ -958,9 +991,17 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) param_list->Session, param_list, EXPR_F_RUNSERVER - ) != 0)) goto err_free; - source_data->SourcePath = (char*)checkPtr(nmSysStrdup(buf)); - if (UNLIKELY(source_data->SourcePath == NULL)) goto err_free; + ) != 0)) + { + mssError(0, "Cluster", "Failed to get required 'source' attribute."); + goto err_free; + } + source_data->SourcePath = nmSysStrdup(buf); + if (UNLIKELY(source_data->SourcePath == NULL)) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", buf); + goto err_free; + } /** Get the attribute name to use when querying keys from the source. **/ if (UNLIKELY(stGetObjAttrValueOSML( @@ -972,9 +1013,17 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) param_list->Session, param_list, EXPR_F_RUNSERVER - ) != 0)) goto err_free; - source_data->KeyAttr = (char*)checkPtr(nmSysStrdup(buf)); - if (UNLIKELY(source_data->KeyAttr == NULL)) goto err_free; + ) != 0)) + { + mssError(0, "Cluster", "Failed to get required 'key_attr' attribute."); + goto err_free; + } + source_data->KeyAttr = nmSysStrdup(buf); + if (UNLIKELY(source_data->KeyAttr == NULL)) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", buf); + goto err_free; + } /** Get the attribute name to use for querying data from the source. **/ if (UNLIKELY(stGetObjAttrValueOSML( @@ -986,17 +1035,30 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) param_list->Session, param_list, EXPR_F_RUNSERVER - ) != 0)) goto err_free; - source_data->DataAttr = (char*)checkPtr(nmSysStrdup(buf)); - if (UNLIKELY(source_data->DataAttr == NULL)) goto err_free; + ) != 0)) + { + mssError(0, "Cluster", "Failed to get required 'data_attr' attribute."); + goto err_free; + } + source_data->DataAttr = nmSysStrdup(buf); + if (UNLIKELY(source_data->DataAttr == NULL)) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", buf); + goto err_free; + } /** Create cache entry key. **/ const size_t len = strlen(path) + strlen(source_data->SourcePath) + strlen(source_data->KeyAttr) + strlen(source_data->DataAttr) + 5lu; - source_data->CacheKey = (char*)checkPtr(nmSysMalloc(len * sizeof(char))); - if (UNLIKELY(source_data->CacheKey == NULL)) goto err_free; + const size_t cache_key_size = len * sizeof(char); + source_data->CacheKey = nmSysMalloc(cache_key_size); + if (UNLIKELY(source_data->CacheKey == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", cache_key_size); + goto err_free; + } snprintf(source_data->CacheKey, len, "%s?%s->%s:%s", path, source_data->SourcePath, source_data->KeyAttr, source_data->DataAttr @@ -1017,14 +1079,20 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) } /** Cache miss: Add the new object to the cache for next time. **/ - if (check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey, (void*)source_data)) != 0) + if (xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey, (void*)source_data) != 0) + { + mssError(1, "Cluster", + "Failed to add source data to cache hash table with cache_key: \"%s\".", + source_data->CacheKey + ); goto err_free; + } /** Success. **/ return source_data; - /** Error handling. **/ err_free: + /** Error handling. **/ if (source_data != NULL) { if (source_data->CacheKey != NULL) nmSysFree(source_data->CacheKey); @@ -1062,27 +1130,57 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData XArray sub_clusters = {0}; char* cache_key = NULL; - if (thExcessiveRecursion()) + /** Edge cases. **/ + if (UNLIKELY(inf == NULL)) + { + mssError(1, "Cluster", "Failed to parse cluster data from NULL struct inf."); + return NULL; /* Skip error handler, which expects a valid struct inf. */ + } + ASSERTMAGIC(inf, MGK_STRUCTINF); + if (UNLIKELY(param_list == NULL)) + { + mssError(1, "Cluster", "Failed to parse cluster data from NULL param_list."); + goto err_free; + } + + /** Recursion check. **/ + if (UNLIKELY(thExcessiveRecursion())) { mssError(1, "Cluster", "Resource exhaustion occurred while parsing cluster data."); goto err_free; } /** Verify source_data value. **/ - if (UNLIKELY(source_data == NULL)) goto err_free; + if (UNLIKELY(source_data == NULL)) + { + mssError(1, "Cluster", "Failed to parse cluster data from NULL source data."); + goto err_free; + } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Allocate space for data struct. **/ - cluster_data = checkPtr(nmMalloc(sizeof(ClusterData))); - if (UNLIKELY(cluster_data == NULL)) goto err_free; + cluster_data = nmMalloc(sizeof(ClusterData)); + if (UNLIKELY(cluster_data == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(ClusterData)); + goto err_free; + } memset(cluster_data, 0, sizeof(ClusterData)); SETMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); /** Basic fields. **/ - cluster_data->Name = checkPtr(nmSysStrdup(inf->Name)); - if (UNLIKELY(cluster_data->Name == NULL)) goto err_free; + cluster_data->Name = nmSysStrdup(inf->Name); + if (UNLIKELY(cluster_data->Name == NULL)) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", inf->Name); + goto err_free; + } cluster_data->SourceData = source_data; - if (check(objCurrentDate(&cluster_data->DateCreated)) != 0) goto err_free; + if (UNLIKELY(objCurrentDate(&cluster_data->DateCreated) != 0)) + { + mssError(1, "Cluster", "objCurrentDate() failed."); + goto err_free; + } /** Get algorithm. **/ cluster_data->ClusterAlgorithm = cluster_i_parseClusteringAlgorithm(inf, param_list); @@ -1097,7 +1195,11 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Get similarity_measure. **/ cluster_data->SimilarityMeasure = cluster_i_parseSimilarityMeasure(inf, param_list); - if (UNLIKELY(cluster_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; + if (UNLIKELY(cluster_data->SimilarityMeasure == SIMILARITY_NULL)) + { + mssError(0, "Cluster", "Failed to parse similarity measure."); + goto err_free; + } /** Handle sliding window case. **/ if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) @@ -1116,7 +1218,11 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData param_list->Session, param_list, EXPR_F_RUNSERVER - ) != 0)) goto err_free; + ) != 0)) + { + mssError(0, "Cluster", "Failed to get required 'window_size' attribute."); + goto err_free; + } if (window_size < 1) { mssError(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); @@ -1139,7 +1245,11 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData param_list->Session, param_list, EXPR_F_RUNSERVER - ) != 0)) goto err_free; + ) != 0)) + { + mssError(0, "Cluster", "Failed to get required 'num_clusters' attribute."); + goto err_free; + } if (num_clusters < 2) { mssError(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); @@ -1171,7 +1281,11 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData goto err_free; } } - else goto err_free; + else + { + mssError(0, "Cluster", "Failed to check for optional 'min_improvement' attribute."); + goto err_free; + } /** Get max_iterations. **/ int max_iterations; @@ -1195,7 +1309,11 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData } cluster_data->MaxIterations = (unsigned int)max_iterations; } - else goto err_free; + else + { + mssError(0, "Cluster", "Failed to check for optional 'max_iterations' attribute."); + goto err_free; + } /** Get seed. **/ int seed; @@ -1219,23 +1337,36 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData } cluster_data->Seed = (unsigned int)seed; } - else goto err_free; + else + { + mssError(0, "Cluster", "Failed to check for optional 'seed' attribute."); + goto err_free; + } /** Search for sub-clusters. **/ - if (check(xaInit(&sub_clusters, CI_INITIAL_SUBCLUSTERS)) != 0) goto err_free; + if (xaInit(&sub_clusters, CI_INITIAL_SUBCLUSTERS)) + { + mssError(1, "Cluster", + "Failed to allocate an XArray of %d subclusters.", + CI_INITIAL_SUBCLUSTERS + ); + goto err_free; + } for (unsigned int i = 0u; i < inf->nSubInf; i++) { - pStructInf sub_inf = checkPtr(inf->SubInf[i]); + pStructInf sub_inf = inf->SubInf[i]; if (UNLIKELY(sub_inf == NULL)) { - mssError(1, "Cluster", "Failed to get %uth subinf.", i); - goto err_free; + mssError(1, "Cluster", "Failed to get subinf #%u/%u.", i + 1, inf->nSubInf); + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf. */ } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; + if (UNLIKELY(name == NULL)) + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->name. */ /** Handle various struct types. **/ - const int struct_type = checkPos(stStructType(sub_inf)); + const int struct_type = stStructType(sub_inf); switch (struct_type) { case ST_T_ATTRIB: @@ -1252,7 +1383,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData } if (is_valid) continue; /* Next inf. */ - /** Give the user a warning, and attempt to give them a hint. **/ + /** Give the user a warning, and attempt to give a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); if (cluster_i_tryHint(name, CLUSTER_ATTRS, N_INPUT_CLUSTER_ATTRS)); else if (strcasecmp(name, "k") == 0) cluster_i_giveHint("num_clusters"); @@ -1264,11 +1395,15 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData case ST_T_SUBGROUP: { /** Select array by group type. **/ - char* group_type = checkPtr(sub_inf->UsrType); - if (group_type == NULL) goto err_free; + char* group_type = sub_inf->UsrType; + if (UNLIKELY(group_type == NULL)) + { + mssError(1, "Cluster", "Failed to get group type."); + goto err_sub_inf; + } if (strcmp(group_type, "cluster/cluster") != 0) { - mssError(1, "Cluster", + fprintf(stderr, "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", name, group_type, inf->Name ); @@ -1278,38 +1413,63 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Subcluster found. **/ pClusterData sub_cluster = cluster_i_parseClusterData(sub_inf, param_list, source_data); - if (sub_cluster == NULL) goto err_free; + if (UNLIKELY(sub_cluster == NULL)) goto err_sub_inf; sub_cluster->Parent = cluster_data; - if (checkPos(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; + if (UNLIKELY(xaAddItem(&sub_clusters, sub_cluster) < 0)) + { + mssError(1, "Cluster", "Failed to add parsed subcluster to XArray."); + goto err_sub_inf; + } break; } default: { - mssError(1, "Cluster", - "Warning: Unknown struct type %d in cluster \"%s\".", - struct_type, inf->Name + fprintf(stderr, + "Warning: Unknown struct type %d in cluster data.\n", + struct_type ); - goto err_free; + continue; /* Skip it. */ } } + + /** Success. **/ + continue; + + err_sub_inf: + mssError(0, "Cluster", + "Failed to parse \"%s\", the #%u/%u subinf of %s.", + sub_inf->Name, i + 1, inf->nSubInf, inf->Name + ); + goto err_free; } + + /** Post sub-inf parsing cleanup. */ cluster_data->nSubClusters = sub_clusters.nItems; - cluster_data->SubClusters = (ClusterData**)checkPtr(xaToArray(&sub_clusters)); - if (UNLIKELY(cluster_data->SubClusters == NULL)) goto err_free; - check(xaDeInit(&sub_clusters)); /* Failure ignored. */ + cluster_data->SubClusters = (ClusterData**)xaToArray(&sub_clusters); + if (UNLIKELY(cluster_data->SubClusters == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster data array."); + goto err_free; + } + warnFail(xaDeInit(&sub_clusters)); sub_clusters.nAlloc = 0; - /** Create the cache_key. **/ parsing_done:; + /** Create the cache_key. **/ switch (cluster_data->ClusterAlgorithm) { case ALGORITHM_NONE: { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 8lu; - cache_key = checkPtr(nmSysMalloc(len * sizeof(char))); - if (UNLIKELY(cache_key == NULL)) goto err_free; + const size_t cache_key_size = len * sizeof(char); + cache_key = nmSysMalloc(cache_key_size); + if (UNLIKELY(cache_key == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", cache_key_size); + goto err_free; + } snprintf(cache_key, len, "%s/%s?%u", source_data->CacheKey, cluster_data->Name, @@ -1321,8 +1481,13 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData case ALGORITHM_SLIDING_WINDOW: { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 16lu; - cache_key = checkPtr(nmSysMalloc(len * sizeof(char))); - if (UNLIKELY(cache_key == NULL)) goto err_free; + const size_t cache_key_size = len * sizeof(char); + cache_key = nmSysMalloc(cache_key_size); + if (UNLIKELY(cache_key == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", cache_key_size); + goto err_free; + } snprintf(cache_key, len, "%s/%s?%u&%u&%u", source_data->CacheKey, cluster_data->Name, @@ -1336,8 +1501,13 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData default: { const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 32lu; - cache_key = checkPtr(nmSysMalloc(len * sizeof(char))); - if (UNLIKELY(cache_key == NULL)) goto err_free; + const size_t cache_key_size = len * sizeof(char); + cache_key = nmSysMalloc(cache_key_size); + if (UNLIKELY(cache_key == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", cache_key_size); + goto err_free; + } snprintf(cache_key, len, "%s/%s?%u&%u&%u&%g&%u", source_data->CacheKey, cluster_data->Name, @@ -1367,11 +1537,20 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData } /** Cache miss. **/ - if (check(xhAdd(&ClusterDriverCaches.ClusterDataCache, cache_key, (void*)cluster_data)) != 0) goto err_free; + if (xhAdd(&ClusterDriverCaches.ClusterDataCache, cache_key, (void*)cluster_data) != 0) + { + mssError(1, "Cluster", + "Failed to add cluster data to cache hash table with cache_key: \"%s\".", + cache_key + ); + goto err_free; + } + + /** Success. **/ return cluster_data; - /** Error cleanup. **/ err_free: + /** Error cleanup. **/ if (cache_key != NULL) nmSysFree(cache_key); if (sub_clusters.nAlloc != 0) @@ -1382,12 +1561,12 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData if (cur == NULL) break; cluster_i_freeClusterData(cur, true); } - check(xaDeInit(&sub_clusters)); /* Failure ignored. */ + warnFail(xaDeInit(&sub_clusters)); } if (cluster_data != NULL) cluster_i_freeClusterData(cluster_data, false); - mssError(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); + mssError(0, "Cluster", "Failed to parse cluster from group \"%s\":\"%s\".", inf->Name, inf->UsrType); return NULL; } @@ -1408,22 +1587,51 @@ static pSearchData cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) { pSearchData search_data = NULL; - char* key = NULL; + char* cache_key = NULL; + /** Edge cases. **/ + if (UNLIKELY(inf == NULL)) + { + mssError(1, "Cluster", "Failed to parse search data from NULL struct inf."); + return NULL; /* Skip error handler, which expects a valid struct inf. */ + } + ASSERTMAGIC(inf, MGK_STRUCTINF); + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "Failed to parse search data from NULL node_data."); + goto err_free; + } + /** Extract values. **/ - pParamObjects param_list = checkPtr(node_data->ParamList); - if (UNLIKELY(param_list == NULL)) goto err_free; + pParamObjects param_list = node_data->ParamList; + if (UNLIKELY(param_list == NULL)) + { + mssError(1, "Cluster", "Node data has NULL ParamList!"); + goto err_free; + } /** Allocate space for search struct. **/ - search_data = checkPtr(nmMalloc(sizeof(SearchData))); - if (UNLIKELY(search_data == NULL)) goto err_free; + search_data = nmMalloc(sizeof(SearchData)); + if (UNLIKELY(search_data == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(SearchData)); + goto err_free; + } memset(search_data, 0, sizeof(SearchData)); SETMAGIC(search_data, MGK_CL_SEARCH_DATA); /** Get basic information. **/ - search_data->Name = checkPtr(nmSysStrdup(inf->Name)); - if (UNLIKELY(search_data->Name == NULL)) goto err_free; - if (check(objCurrentDate(&search_data->DateCreated)) != 0) goto err_free; + search_data->Name = nmSysStrdup(inf->Name); + if (UNLIKELY(search_data->Name == NULL)) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", inf->Name); + goto err_free; + } + if (UNLIKELY(objCurrentDate(&search_data->DateCreated) != 0)) + { + mssError(1, "Cluster", "objCurrentDate() failed."); + goto err_free; + } /** Search for the source cluster. **/ char* source_cluster_name; @@ -1436,7 +1644,11 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) param_list->Session, param_list, EXPR_F_RUNSERVER - ) != 0)) goto err_free; + ) != 0)) + { + mssError(0, "Cluster", "Failed to get required 'source' attribute for search."); + goto err_free; + } for (unsigned int i = 0; i < node_data->nClusterDatas; i++) { pClusterData cluster_data = node_data->ClusterDatas[i]; @@ -1448,14 +1660,17 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) break; } - /** Note: Subclusters should probably be parsed here, if they were implemented. **/ + /** Note: Subclusters should probably be investigated here, if they were implemented. **/ } /** Did we find the requested source? **/ if (UNLIKELY(search_data->SourceCluster == NULL)) { /** Print error. **/ - mssError(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); + mssError(1, "Cluster", + "Could not find cluster \"%s\" for search \"%s\".", + source_cluster_name, search_data->Name + ); /** Attempt to give a hint. **/ char* cluster_names[node_data->nClusterDatas]; @@ -1477,7 +1692,11 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) param_list->Session, param_list, EXPR_F_RUNSERVER - ) != 0)) goto err_free; + ) != 0)) + { + mssError(0, "Cluster", "Failed to get required 'threshold' attribute for search."); + goto err_free; + } if (UNLIKELY(search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold)) { mssError(1, "Cluster", @@ -1489,23 +1708,28 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) /** Get similarity measure. **/ search_data->SimilarityMeasure = cluster_i_parseSimilarityMeasure(inf, param_list); - if (UNLIKELY(search_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; + if (UNLIKELY(search_data->SimilarityMeasure == SIMILARITY_NULL)) + { + mssError(0, "Cluster", "Failed to parse similarity measure."); + goto err_free; + } /** Check for additional data to warn the user about. **/ for (unsigned int i = 0u; i < inf->nSubInf; i++) { - pStructInf sub_inf = checkPtr(inf->SubInf[i]); + pStructInf sub_inf = inf->SubInf[i]; if (UNLIKELY(sub_inf == NULL)) { mssError(1, "Cluster", "Failed to get subinf #%u/%u.", i + 1, inf->nSubInf); - goto err_free; + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf. */ } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); - char* name = checkPtr(sub_inf->Name); - if (UNLIKELY(name == NULL)) goto err_free; + char* name = sub_inf->Name; + if (UNLIKELY(name == NULL)) + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->name. */ /** Handle various struct types. **/ - const int struct_type = checkPos(stStructType(sub_inf)); + const int struct_type = stStructType(sub_inf); switch (struct_type) { case ST_T_ATTRIB: @@ -1522,7 +1746,7 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) } if (is_valid) continue; /* Next inf. */ - /** Give the user a warning, and attempt to give them a hint. **/ + /** Give the user a warning, and attempt to give a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); cluster_i_tryHint(name, SEARCH_ATTRS, N_INPUT_SEARCH_ATTRS); @@ -1532,8 +1756,12 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) case ST_T_SUBGROUP: { /** The spec does not specify any valid sub-groups for searches. **/ - char* group_type = checkPtr(sub_inf->UsrType); - if (UNLIKELY(group_type == NULL)) goto err_free; + char* group_type = sub_inf->UsrType; + if (UNLIKELY(group_type == NULL)) + { + mssError(1, "Cluster", "Failed to get group type."); + goto err_sub_inf; + } fprintf(stderr, "Warning: Unknown group [\"%s\" : \"%s\"] in search \"%s\".\n", name, group_type, inf->Name @@ -1543,22 +1771,37 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) default: { - mssError(1, "Cluster", - "Warning: Unknown struct type %d in search \"%s\".", - struct_type, inf->Name + fprintf(stderr, + "Warning: Unknown struct type %d in search data.\n", + struct_type ); - goto err_free; + continue; /* Skip it. */ } } + + /** Success. **/ + continue; + + err_sub_inf: + mssError(0, "Cluster", + "Failed to parse \"%s\", the #%u/%u subinf of %s.", + sub_inf->Name, i + 1, inf->nSubInf, inf->Name + ); + goto err_free; } /** Create cache entry key. **/ - char* source_key = search_data->SourceCluster->CacheKey; - const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; - key = checkPtr(nmSysMalloc(len * sizeof(char))); - if (UNLIKELY(key == NULL)) goto err_free; - snprintf(key, len, "%s/%s?%g&%u", - source_key, + char* source_cache_key = search_data->SourceCluster->CacheKey; + const size_t cache_key_len = strlen(source_cache_key) + strlen(search_data->Name) + 16lu; + const size_t cache_key_size = cache_key_len * sizeof(char); + cache_key = nmSysMalloc(cache_key_size); + if (UNLIKELY(cache_key == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", cache_key_size); + goto err_free; + } + snprintf(cache_key, cache_key_len, "%s/%s?%g&%u", + source_cache_key, search_data->Name, search_data->Threshold, search_data->SimilarityMeasure @@ -1566,32 +1809,39 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) pXHashTable search_cache = &ClusterDriverCaches.SearchDataCache; /** Check for a cached version. **/ - pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); + pSearchData search_maybe = (pSearchData)xhLookup(search_cache, cache_key); if (search_maybe != NULL) { /* Cache hit. */ ASSERTMAGIC(search_maybe, MGK_CL_SEARCH_DATA); /** Free the parsed search that we no longer need. **/ if (LIKELY(search_data != NULL)) cluster_i_freeSearchData(search_data); - if (LIKELY(key != NULL)) nmSysFree(key); + if (LIKELY(cache_key != NULL)) nmSysFree(cache_key); /** Return the cached search. **/ return search_maybe; } /** Cache miss. **/ - if (check(xhAdd(search_cache, key, (void*)search_data)) != 0) + if (xhAdd(search_cache, cache_key, (void*)search_data) != 0) + { + mssError(1, "Cluster", + "Failed to add search data to cache hash table with cache_key: \"%s\".", + cache_key + ); goto err_free; + } /** Done. **/ return search_data; - /** Error cleanup. **/ err_free: - if (search_data != NULL) cluster_i_freeSearchData(search_data); - mssError(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); + /** Error cleanup. **/ + if (search_data != NULL) cluster_i_freeSearchData(search_data); + if (cache_key != NULL) nmSysFree(cache_key); + return NULL; } @@ -1618,28 +1868,54 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) XArray cluster_infs = {0}; XArray search_infs = {0}; - /** Magic. **/ + /** Edge cases. **/ + if (UNLIKELY(inf == NULL)) + { + mssError(1, "Cluster", "Failed to parse node data from NULL struct inf."); + return NULL; /* Skip error handler, which expects a valid struct inf. */ + } ASSERTMAGIC(inf, MGK_STRUCTINF); + if (UNLIKELY(parent == NULL)) + { + mssError(1, "Cluster", "Failed to parse node data from NULL parent object."); + return NULL; /* Skip error handler, which expects a valid path. */ + } ASSERTMAGIC(parent, MGK_OBJECT); /** Get file path. **/ - char* path = checkPtr(objFilePath(parent)); - if (UNLIKELY(path == NULL)) goto err_free; + char* path = objFilePath(parent); + if (UNLIKELY(path == NULL)) + { + mssError(1, "Cluster", "Failed to get parent file path."); + goto err_free; + } /** Allocate node struct data. **/ - node_data = checkPtr(nmMalloc(sizeof(NodeData))); - if (UNLIKELY(node_data == NULL)) goto err_free; + node_data = nmMalloc(sizeof(NodeData)); + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(NodeData)); + goto err_free; + } memset(node_data, 0, sizeof(NodeData)); SETMAGIC(node_data, MGK_CL_NODE_DATA); node_data->Parent = parent; /** Set up param list. **/ - node_data->ParamList = checkPtr(expCreateParamList()); - if (UNLIKELY(node_data->ParamList == NULL)) goto err_free; - node_data->ParamList->Session = checkPtr(parent->Session); - if (UNLIKELY(node_data->ParamList->Session == NULL)) goto err_free; + node_data->ParamList = expCreateParamList(); + if (UNLIKELY(node_data->ParamList == NULL)) + { + mssError(1, "Cluster", "expCreateParamList() failed."); + goto err_free; + } + node_data->ParamList->Session = parent->Session; + if (UNLIKELY(node_data->ParamList->Session == NULL)) + { + mssError(1, "Cluster", "Failed to get session because parent->Session is NULL."); + goto err_free; + } ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); - if (ret != 0) + if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", "Failed to add parameters to the param list scope (error code %d).", ret); goto err_free; @@ -1660,22 +1936,28 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) } /** Detect relevant groups. **/ - if (check(xaInit(¶m_infs, CI_INITIAL_INFS)) != 0) goto err_free; - if (check(xaInit(&cluster_infs, CI_INITIAL_INFS)) != 0) goto err_free; - if (check(xaInit(&search_infs, CI_INITIAL_INFS)) != 0) goto err_free; + if (xaInit(¶m_infs, CI_INITIAL_INFS) != 0 + || xaInit(&cluster_infs, CI_INITIAL_INFS) != 0 + || xaInit(&search_infs, CI_INITIAL_INFS) != 0 + ) { + mssError(1, "Cluster", "Failed to initialize an XArray of size %u.", CI_INITIAL_INFS); + goto err_free; + } for (unsigned int i = 0u; i < inf->nSubInf; i++) { - pStructInf sub_inf = checkPtr(inf->SubInf[i]); + pStructInf sub_inf = inf->SubInf[i]; if (UNLIKELY(sub_inf == NULL)) { - mssError(1, "Cluster", "Failed to get %uth subinf.", i); - goto err_free; + mssError(1, "Cluster", "Failed to get subinf #%u/%u.", i + 1, inf->nSubInf); + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf. */ } ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; + if (UNLIKELY(name == NULL)) + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->name. */ /** Handle various struct types. **/ - const int struct_type = checkPos(stStructType(sub_inf)); + const int struct_type = stStructType(sub_inf); switch (struct_type) { case ST_T_ATTRIB: @@ -1692,7 +1974,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) } if (is_valid) continue; /* Next inf. */ - /** Give the user a warning, and attempt to give them a hint. **/ + /** Give the user a warning, and attempt to give a hint. **/ fprintf(stderr, "Warning: Unknown attribute '%s' in cluster driver root node \"%s\".\n", name, inf->Name); cluster_i_tryHint(name, ROOT_ATTRS, N_INPUT_ROOT_ATTRS); @@ -1701,26 +1983,39 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) case ST_T_SUBGROUP: { - char* group_type = checkPtr(sub_inf->UsrType); - if (UNLIKELY(group_type == NULL)) goto err_free; + char* group_type = sub_inf->UsrType; + if (UNLIKELY(group_type == NULL)) + { + mssError(1, "Cluster", "Failed to get group type."); + goto err_sub_inf; + } if (strcmp(group_type, "cluster/parameter") == 0) { - if (checkPos(xaAddItem(¶m_infs, sub_inf) < 0)) - goto err_free; + if (UNLIKELY(xaAddItem(¶m_infs, sub_inf) < 0)) + { + mssError(1, "Cluster", "Failed to add detected param inf to XArray."); + goto err_sub_inf; + } } else if (strcmp(group_type, "cluster/cluster") == 0) { - if (checkPos(xaAddItem(&cluster_infs, sub_inf) < 0)) - goto err_free; + if (UNLIKELY(xaAddItem(&cluster_infs, sub_inf) < 0)) + { + mssError(1, "Cluster", "Failed to add detected cluster inf to XArray."); + goto err_sub_inf; + } } else if (strcmp(group_type, "cluster/search") == 0) { - if (checkPos(xaAddItem(&search_infs, sub_inf) < 0)) - goto err_free; + if (UNLIKELY(xaAddItem(&search_infs, sub_inf) < 0)) + { + mssError(1, "Cluster", "Failed to add detected search inf to XArray."); + goto err_sub_inf; + } } else { - /** Give the user a warning, and attempt to give them a hint. **/ + /** Give the user a warning, and attempt to give a hint. **/ fprintf(stderr, "Warning: Unknown group type \"%s\" on group \"%s\".\n", group_type, sub_inf->Name @@ -1737,13 +2032,23 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) default: { - mssError(1, "Cluster", - "Warning: Unknown struct type %d in search \"%s\".", - struct_type, inf->Name + fprintf(stderr, + "Warning: Unknown struct type %d in node data.\n", + struct_type ); - goto err_free; + continue; /* Skip it. */ } } + + /** Success. **/ + continue; + + err_sub_inf: + mssError(0, "Cluster", + "Failed to parse \"%s\", the #%u/%u subinf of %s.", + sub_inf->Name, i + 1, inf->nSubInf, inf->Name + ); + goto err_free; } /** Extract OpenCtl for use below. **/ @@ -1759,8 +2064,12 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) /** Allocate space to store params. **/ node_data->nParams = param_infs.nItems; const size_t params_size = node_data->nParams * sizeof(pParam); - node_data->Params = checkPtr(nmSysMalloc(params_size)); - if (node_data->Params == NULL) goto err_free; + node_data->Params = nmSysMalloc(params_size); + if (UNLIKELY(node_data->Params == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", params_size); + goto err_free; + } memset(node_data->Params, 0, params_size); /** Iterate over each param in the structure file. **/ @@ -1780,8 +2089,15 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) /** Check each provided param to see if the user provided value. **/ for (unsigned int j = 0u; j < num_provided_params; j++) { - pStruct provided_param = checkPtr(provided_params[j]); - if (UNLIKELY(provided_param == NULL)) goto err_free; + pStruct provided_param = provided_params[j]; + if (UNLIKELY(provided_param == NULL)) + { + mssError(1, "Cluster", + "Failed to get param #%u/%u.", + j + 1, num_provided_params + ); + goto err_free; + } /** If this provided param value isn't for the param, ignore it. **/ if (UNLIKELY(strcmp(provided_param->Name, param->Name) != 0)) continue; @@ -1817,7 +2133,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) goto err_free; } } - check(xaDeInit(¶m_infs)); /* Failure ignored. */ + warnFail(xaDeInit(¶m_infs)); param_infs.nAlloc = 0; /*** Iterate over provided parameters to warn the user if they @@ -1825,9 +2141,24 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) ***/ for (unsigned int i = 0u; i < num_provided_params; i++) { - pStruct provided_param = checkPtr(provided_params[i]); - if (UNLIKELY(provided_param == NULL)) goto err_free; + pStruct provided_param = provided_params[i]; + if (UNLIKELY(provided_param == NULL)) + { + mssError(1, "Cluster", + "Failed to get provided param #%u/%u.", + i + 1, num_provided_params + ); + goto err_free; + } char* provided_name = provided_param->Name; + if (UNLIKELY(provided_name == NULL)) + { + mssError(1, "Cluster", + "Failed to get provided param name from param #%u/%u.", + i + 1, num_provided_params + ); + goto err_free; + } /** Look to see if this provided param actually exists for this driver instance. **/ bool param_exists = false; @@ -1847,8 +2178,13 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) ); /** Attempt hint. **/ - char** param_names = checkPtr(nmSysMalloc(node_data->nParams * sizeof(char*))); - if (UNLIKELY(param_names == NULL)) goto err_free; + const size_t param_name_size = node_data->nParams * sizeof(char*); + char** param_names = nmSysMalloc(param_name_size); + if (UNLIKELY(param_names == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", param_name_size); + goto err_free; + } for (unsigned int j = 0u; j < node_data->nParams; j++) param_names[j] = node_data->Params[j]->Name; cluster_i_tryHint(provided_name, param_names, node_data->nParams); @@ -1858,24 +2194,36 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) /** Parse source data. **/ node_data->SourceData = cluster_i_parseSourceData(inf, node_data->ParamList, path); - if (UNLIKELY(node_data->SourceData == NULL)) goto err_free; + if (UNLIKELY(node_data->SourceData == NULL)) + { + mssError(0, "Cluster", "Failed to parse source data."); + goto err_free; + } /** Parse each cluster. **/ node_data->nClusterDatas = cluster_infs.nItems; if (LIKELY(node_data->nClusterDatas > 0)) { const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); - node_data->ClusterDatas = checkPtr(nmSysMalloc(clusters_size)); - if (UNLIKELY(node_data->ClusterDatas == NULL)) goto err_free; + node_data->ClusterDatas = nmSysMalloc(clusters_size); + if (UNLIKELY(node_data->ClusterDatas == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", clusters_size); + goto err_free; + } memset(node_data->ClusterDatas, 0, clusters_size); for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) { node_data->ClusterDatas[i] = cluster_i_parseClusterData(cluster_infs.Items[i], node_data->ParamList, node_data->SourceData); - if (node_data->ClusterDatas[i] == NULL) goto err_free; + if (UNLIKELY(node_data->ClusterDatas[i] == NULL)) + { + mssError(0, "Cluster", "Failed to parse cluster data."); + goto err_free; + } } } else node_data->ClusterDatas = NULL; - check(xaDeInit(&cluster_infs)); /* Failure ignored. */ + warnFail(xaDeInit(&cluster_infs)); cluster_infs.nAlloc = 0; /** Parse each search. **/ @@ -1883,28 +2231,38 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) if (LIKELY(node_data->nSearchDatas > 0)) { const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); - node_data->SearchDatas = checkPtr(nmSysMalloc(searches_size)); - if (UNLIKELY(node_data->SearchDatas == NULL)) goto err_free; + node_data->SearchDatas = nmSysMalloc(searches_size); + if (UNLIKELY(node_data->SearchDatas == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", searches_size); + goto err_free; + } memset(node_data->SearchDatas, 0, searches_size); for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) { node_data->SearchDatas[i] = cluster_i_parseSearchData(search_infs.Items[i], node_data); - if (node_data->SearchDatas[i] == NULL) goto err_free; + if (UNLIKELY(node_data->SearchDatas[i] == NULL)) + { + mssError(0, "Cluster", "Failed to parse search data."); + goto err_free; + } } } else node_data->SearchDatas = NULL; - check(xaDeInit(&search_infs)); /* Failure ignored. */ + warnFail(xaDeInit(&search_infs)); search_infs.nAlloc = 0; /** Success. **/ return node_data; err_free: - if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); /* Failure ignored. */ - if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); /* Failure ignored. */ - if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); /* Failure ignored. */ - if (node_data != NULL) cluster_i_freeNodeData(node_data); mssError(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); + + /** Clean up. **/ + if (param_infs.nAlloc != 0) warnFail(xaDeInit(¶m_infs)); + if (cluster_infs.nAlloc != 0) warnFail(xaDeInit(&cluster_infs)); + if (search_infs.nAlloc != 0) warnFail(xaDeInit(&search_infs)); + if (node_data != NULL) cluster_i_freeNodeData(node_data); return NULL; } @@ -2011,7 +2369,7 @@ cluster_i_freeSourceData(pSourceData source_data) static void cluster_i_freeClusterData(pClusterData cluster_data, bool recursive) { - if (thExcessiveRecursion()) + if (UNLIKELY(thExcessiveRecursion())) { mssError(1, "Cluster", "Resource exhaustion occurred while freeing cluster datas."); return; @@ -2201,9 +2559,9 @@ cluster_i_clearCaches(void) /*** Free caches in reverse of the order they are created in case *** cached data relies on its source during the freeing process. ***/ - check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, cluster_i_cacheFreeSearch, NULL)); /* Failure ignored. */ - check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, cluster_i_cacheFreeCluster, NULL)); /* Failure ignored. */ - check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, cluster_i_cacheFreeSourceData, NULL)); /* Failure ignored. */ + warnFail(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, cluster_i_cacheFreeSearch, NULL)); + warnFail(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, cluster_i_cacheFreeCluster, NULL)); + warnFail(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, cluster_i_cacheFreeSourceData, NULL)); return; } @@ -2276,7 +2634,7 @@ cluster_i_sizeOfSourceData(pSourceData source_data) static size_t cluster_i_sizeOfClusterData(pClusterData cluster_data, bool recursive) { - if (thExcessiveRecursion()) + if (UNLIKELY(thExcessiveRecursion())) { mssError(1, "Cluster", "Resource exhaustion occurred while counting size of cluster data structs."); return 0u; @@ -2368,8 +2726,12 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) XArray data_xarray = {0}; XArray vector_xarray = {0}; - /** Guard segfaults. **/ - if (checkPtr(source_data) == NULL) goto end_free; + /** Edge cases. **/ + if (UNLIKELY(source_data == NULL)) + { + mssError(1, "Cluster", "Failed to compute source data on NULL source data struct."); + return -1; /* Skip error handler, which expects a valid source data struct. */ + } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** If the vectors are already computed, we're done. **/ @@ -2380,7 +2742,11 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) } /** Record the date and time. **/ - if (check(objCurrentDate(&source_data->DateComputed)) != 0) goto end_free; + if (UNLIKELY(objCurrentDate(&source_data->DateComputed) != 0)) + { + mssError(1, "Cluster", "objCurrentDate() failed."); + goto end_free; + } /** Open the source path specified by the .cluster file. **/ obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); @@ -2399,9 +2765,13 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) } /** Initialize an xarray to store the retrieved data. **/ - if (check(xaInit(&key_xarray, CI_INITIAL_SOURCE_DATAS)) != 0) goto end_free; - if (check(xaInit(&data_xarray, CI_INITIAL_SOURCE_DATAS)) != 0) goto end_free; - if (check(xaInit(&vector_xarray, CI_INITIAL_SOURCE_DATAS)) != 0) goto end_free; + if (xaInit(&key_xarray, CI_INITIAL_SOURCE_DATAS) != 0 + || xaInit(&data_xarray, CI_INITIAL_SOURCE_DATAS) != 0 + || xaInit(&vector_xarray, CI_INITIAL_SOURCE_DATAS) != 0 + ) { + mssError(1, "Cluster", "Failed to initialize an XArray of size %d.", CI_INITIAL_SOURCE_DATAS); + goto end_free; + } /** Fetch data and build vectors. **/ pObject entry; @@ -2521,26 +2891,47 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) } /** Store values. **/ - key_dup = checkPtr(nmSysStrdup(key)); - if (key_dup == NULL) goto entry_free; - data_dup = checkPtr(nmSysStrdup(data)); - if (data_dup == NULL) goto entry_free; + key_dup = nmSysStrdup(key); + if (key_dup == NULL) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", key); + goto entry_free; + } + data_dup = nmSysStrdup(data); + if (data_dup == NULL) + { + mssError(1, "Cluster", "nmSysStrdup(\"%s\") failed.", data); + goto entry_free; + } /** Hand each value to its xarray, which owns it from then on. **/ - if (checkPos(xaAddItem(&key_xarray, (void*)key_dup)) < 0) goto entry_free; + if (xaAddItem(&key_xarray, (void*)key_dup) < 0) + { + mssError(1, "Cluster", "Failed to add key_dup to XArray."); + goto entry_free; + } key_dup = NULL; - if (checkPos(xaAddItem(&data_xarray, (void*)data_dup)) < 0) goto entry_free; + if (xaAddItem(&data_xarray, (void*)data_dup) < 0) + { + mssError(1, "Cluster", "Failed to add data_dup to XArray."); + goto entry_free; + } data_dup = NULL; - if (checkPos(xaAddItem(&vector_xarray, (void*)vector)) < 0) goto entry_free; + if (xaAddItem(&vector_xarray, (void*)vector) < 0) + { + mssError(1, "Cluster", "Failed to add vector to XArray."); + goto entry_free; + } vector = NULL; entry_ok = true; entry_free: /** Clean up owned memory, then fail if an error occurred. **/ - if (vector != NULL) caFreeVector(vector); - if (key_dup != NULL) nmSysFree(key_dup); - if (data_dup != NULL) nmSysFree(data_dup); - check(objClose(entry)); /* Failure ignored. */ + if (UNLIKELY(vector != NULL)) caFreeVector(vector); + if (UNLIKELY(key_dup != NULL)) nmSysFree(key_dup); + if (UNLIKELY(data_dup != NULL)) nmSysFree(data_dup); + warnFail(objClose(entry)); + if (UNLIKELY(!entry_ok)) goto end_free; } @@ -2552,19 +2943,31 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) } /** Trim and store the keys, data strings, and vectors. **/ - source_data->Keys = (char**)checkPtr(xaToArray(&key_xarray)); - if (UNLIKELY(source_data->Keys == NULL)) goto end_free; - source_data->Strings = (char**)checkPtr(xaToArray(&data_xarray)); - if (UNLIKELY(source_data->Strings == NULL)) goto end_free; - source_data->Vectors = (int**)checkPtr(xaToArray(&vector_xarray)); - if (UNLIKELY(source_data->Vectors == NULL)) goto end_free; + source_data->Keys = (char**)xaToArray(&key_xarray); + if (UNLIKELY(source_data->Keys == NULL)) + { + mssError(1, "Cluster", "xaToArray(&key_xarray) failed."); + goto end_free; + } + source_data->Strings = (char**)xaToArray(&data_xarray); + if (UNLIKELY(source_data->Strings == NULL)) + { + mssError(1, "Cluster", "xaToArray(&data_xarray) failed."); + goto end_free; + } + source_data->Vectors = (int**)xaToArray(&vector_xarray); + if (UNLIKELY(source_data->Vectors == NULL)) + { + mssError(1, "Cluster", "xaToArray(&vector_xarray) failed."); + goto end_free; + } /** The stored arrays own the values now, so only free the xarrays. **/ - check(xaDeInit(&key_xarray)); /* Failure ignored. */ + warnFail(xaDeInit(&key_xarray)); key_xarray.nAlloc = 0; - check(xaDeInit(&data_xarray)); /* Failure ignored. */ + warnFail(xaDeInit(&data_xarray)); data_xarray.nAlloc = 0; - check(xaDeInit(&vector_xarray)); /* Failure ignored. */ + warnFail(xaDeInit(&vector_xarray)); vector_xarray.nAlloc = 0; /** Success. **/ @@ -2613,7 +3016,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) char* key = key_xarray.Items[i]; if (key != NULL) nmSysFree(key); } - check(xaDeInit(&key_xarray)); /* Failure ignored. */ + warnFail(xaDeInit(&key_xarray)); } if (UNLIKELY(data_xarray.nAlloc != 0)) { @@ -2622,7 +3025,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) char* str = data_xarray.Items[i]; if (str != NULL) nmSysFree(str); } - check(xaDeInit(&data_xarray)); /* Failure ignored. */ + warnFail(xaDeInit(&data_xarray)); } if (UNLIKELY(vector_xarray.nAlloc != 0)) { @@ -2631,12 +3034,12 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) pVector vec = vector_xarray.Items[i]; if (vec != NULL) caFreeVector(vec); } - check(xaDeInit(&vector_xarray)); /* Failure ignored. */ + warnFail(xaDeInit(&vector_xarray)); } /** Clean up query & object structs. **/ - if (LIKELY(query != NULL)) check(objQueryClose(query)); /* Failure ignored. */ - if (LIKELY(obj != NULL)) check(objClose(obj)); /* Failure ignored. */ + if (LIKELY(query != NULL)) warnFail(objQueryClose(query)); + if (LIKELY(obj != NULL)) warnFail(objClose(obj)); /** Return the function status code. **/ return (successful) ? 0 : -1; @@ -2660,26 +3063,45 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) size_t clusters_size = -1; size_t sims_size = -1; - /** Guard segfaults. **/ - if (checkPtr(cluster_data) == NULL || checkPtr(node_data) == NULL) goto err_free; + /** Edge cases. **/ + if (UNLIKELY(cluster_data == NULL)) + { + mssError(1, "Cluster", "Failed to compute cluster data on NULL cluster data struct."); + return -1; /* Skip error handler, which expects a valid cluster data struct. */ + } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "Failed to compute cluster data with NULL node data struct."); + goto err_free; + } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** If the clusters are already computed, we're done. **/ if (LIKELY(cluster_data->Clusters != NULL)) return 0; /** Make source data available. **/ - pSourceData source_data = checkPtr(node_data->SourceData); + pSourceData source_data = node_data->SourceData; if (UNLIKELY(source_data == NULL)) { - mssError(1, "Cluster", "Failed to get source data for cluster computation."); + mssError(1, "Cluster", "Failed to get source data for cluster computation."); + goto err_free; + } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** Ensure that we have computed SourceData vectors for computing clusters. **/ + pParamObjects param_list = node_data->ParamList; + if (UNLIKELY(param_list == NULL)) + { + mssError(1, "Cluster", "Failed to get param list from driver node data."); + goto err_free; + } + pObjSession session = param_list->Session; + if (UNLIKELY(session == NULL)) + { + mssError(1, "Cluster", "Failed to get session from param list."); goto err_free; } - ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); - - /** We need the SourceData vectors to compute clusters. **/ - pObjSession session = checkPtr(node_data->ParamList->Session); - if (UNLIKELY(session == NULL)) goto err_free; ASSERTMAGIC(session, MGK_OBJSESSION); if (UNLIKELY(cluster_i_computeSourceData(source_data, session) != 0)) { @@ -2689,15 +3111,27 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Record the date and time. **/ - if (check(objCurrentDate(&cluster_data->DateComputed)) != 0) goto err_free; + if (UNLIKELY(objCurrentDate(&cluster_data->DateComputed) != 0)) + { + mssError(1, "Cluster", "objCurrentDate() failed."); + goto err_free; + } /** Allocate static memory for finding clusters. **/ clusters_size = cluster_data->nClusters * sizeof(Cluster); sims_size = source_data->nDatas * sizeof(double); - cluster_data->Clusters = checkPtr(nmSysMalloc(clusters_size)); - cluster_data->Sims = checkPtr(nmSysMalloc(sims_size)); - if (UNLIKELY(cluster_data->Clusters == NULL)) goto err_free; - if (UNLIKELY(cluster_data->Sims == NULL)) goto err_free; + cluster_data->Clusters = nmSysMalloc(clusters_size); + cluster_data->Sims = nmSysMalloc(sims_size); + if (UNLIKELY(cluster_data->Clusters == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", clusters_size); + goto err_free; + } + if (UNLIKELY(cluster_data->Sims == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", sims_size); + goto err_free; + } memset(cluster_data->Clusters, 0, clusters_size); memset(cluster_data->Sims, 0, sims_size); @@ -2714,9 +3148,14 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) SETMAGIC(only_cluster, MGK_CL_CLUSTER); /** Add all data points to that cluster. **/ - only_cluster->Size = source_data->nDatas; - only_cluster->Indexes = checkPtr(nmSysMalloc(only_cluster->Size * sizeof(int))); - if (UNLIKELY(only_cluster->Indexes == NULL)) goto err_free; + const size_t indexes_size = source_data->nDatas * sizeof(int); + only_cluster->Size = source_data->nDatas; + only_cluster->Indexes = nmSysMalloc(indexes_size); + if (UNLIKELY(only_cluster->Indexes == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", indexes_size); + goto err_free; + } for (unsigned int i = 0u; i < only_cluster->Size; i++) only_cluster->Indexes[i] = i; @@ -2746,8 +3185,12 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate labels. Note: caKmeans() initializes labels for us. **/ const size_t labels_size = source_data->nDatas * sizeof(unsigned int); - labels = checkPtr(nmSysMalloc(labels_size)); - if (UNLIKELY(labels == NULL)) goto err_cleanup; + labels = nmSysMalloc(labels_size); + if (UNLIKELY(labels == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", labels_size); + goto err_cleanup; + } /** Handle seed for caKmeans(). **/ const bool auto_seed = (cluster_data->Seed == CI_NO_SEED); @@ -2782,15 +3225,30 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) /** Allocate temporary xArrays for tracking the indices stored in each cluster. **/ for (unsigned int i = 0u; i < cluster_data->nClusters; i++) - if (check(xaInit(&indexes_in_cluster[i], CI_INITIAL_POINTS_PER_CLUSTER)) != 0) + { + if (UNLIKELY(xaInit(&indexes_in_cluster[i], CI_INITIAL_POINTS_PER_CLUSTER) != 0)) { + mssError(1, "Cluster", + "Failed to initialize XArray for cluster #%u/%u.", + i + 1, cluster_data->nClusters + ); memset(&indexes_in_cluster[i], 0, sizeof(XArray)); goto err_cleanup; } + } /** Iterate through each label and add the index of the data to the specified cluster. **/ for (unsigned long long i = 0llu; i < source_data->nDatas; i++) - if (checkPos(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_cleanup; + { + if (xaAddItem(&indexes_in_cluster[labels[i]], (void*)i) < 0) + { + mssError(1, "Cluster", + "Failed to add index #%llu/%u to XArray for cluster #%u/%u.", + i + 1llu, source_data->nDatas, labels[i] + 1, cluster_data->nClusters + ); + goto err_cleanup; + } + } /** Free unused data. **/ nmSysFree(labels); @@ -2806,8 +3264,13 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) /** Store the data in the cluster. **/ cluster->Size = indexes_in_this_cluster->nItems; if (cluster->Size == 0) goto cluster_cleanup; /* Not a failure, but the array still needs to be freed. */ - cluster->Indexes = checkPtr(nmSysMalloc(cluster->Size * sizeof(unsigned int*))); - if (UNLIKELY(cluster->Indexes == NULL)) goto err_cleanup; + const size_t indexes_size = cluster->Size * sizeof(unsigned int*); + cluster->Indexes = nmSysMalloc(indexes_size); + if (UNLIKELY(cluster->Indexes == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", indexes_size); + goto err_cleanup; + } for (unsigned int i = 0u; i < indexes_in_this_cluster->nItems; i++) { const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[i]; @@ -2825,7 +3288,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) } cluster_cleanup: - check(xaDeInit(indexes_in_this_cluster)); /* Failure ignored. */ + warnFail(xaDeInit(indexes_in_this_cluster)); indexes_in_this_cluster->Items = NULL; } @@ -2838,7 +3301,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) for (unsigned int i = 0u; i < cluster_data->nClusters; i++) { if (indexes_in_cluster[i].Items == NULL) continue; - check(xaDeInit(&indexes_in_cluster[i])); /* Failure ignored. */ + warnFail(xaDeInit(&indexes_in_cluster[i])); } goto err_free; } @@ -2909,16 +3372,25 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) { pXArray pairs = NULL; - /** Guard segfaults. **/ - if (checkPtr(search_data) == NULL || checkPtr(node_data) == NULL) goto err_free; + /** Edge cases. **/ + if (UNLIKELY(search_data == NULL)) + { + mssError(1, "Cluster", "Failed to compute search data on NULL search data struct."); + return -1; /* Skip error handler, which expects a valid search data struct. */ + } ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "Failed to compute search data with NULL node data struct."); + goto err_free; + } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** If the clusters are already computed, we're done. **/ if (LIKELY(search_data->Pairs != NULL)) return 0; /** We need the cluster data to be computed before we search it. **/ - pClusterData cluster_data = checkPtr(search_data->SourceCluster); + pClusterData cluster_data = search_data->SourceCluster; if (UNLIKELY(cluster_data == NULL)) { mssError(1, "Cluster", "Failed to get cluster data for search computation."); @@ -2936,11 +3408,19 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); /** Record the date and time. **/ - if (check(objCurrentDate(&search_data->DateComputed)) != 0) goto err_free; + if (UNLIKELY(objCurrentDate(&search_data->DateComputed) != 0)) + { + mssError(1, "Cluster", "objCurrentDate() failed."); + goto err_free; + } /** Get the comparison function based on the similarity measure. **/ - double (*similarity_function)(void *, void *) = checkPtr(cluster_i_similarityMeasureToFunction(search_data->SimilarityMeasure)); - if (UNLIKELY(similarity_function == NULL)) goto err_free; + double (*similarity_function)(void *, void *) = cluster_i_similarityMeasureToFunction(search_data->SimilarityMeasure); + if (UNLIKELY(similarity_function == NULL)) + { + mssError(1, "Cluster", "Failed to get similarity measure function."); + goto err_free; + } /** Get a pointer to the data that will be used for the search. **/ void** data = NULL; @@ -2964,14 +3444,14 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) ***/ /** Execute sliding search. **/ - pairs = checkPtr(caSlidingSearch( + pairs = caSlidingSearch( data, source_data->nDatas, cluster_data->WindowSize, similarity_function, search_data->Threshold, NULL - )); + ); if (UNLIKELY(pairs == NULL)) { mssError(1, "Cluster", @@ -2985,8 +3465,12 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) { /** Initialize the pairs array with a size of double the amount of data. **/ const int guess_size = search_data->SourceCluster->SourceData->nDatas * 2; - pairs = checkPtr(xaNew(guess_size)); - if (UNLIKELY(pairs == NULL)) goto err_free; + pairs = xaNew(guess_size); + if (UNLIKELY(pairs == NULL)) + { + mssError(1, "Cluster", "xaNew(%d) failed.", guess_size); + goto err_free; + } /** Iterate over each cluster. **/ for (unsigned int i = 0u; i < cluster_data->nClusters; i++) @@ -3001,8 +3485,13 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) if (cluster_data->nClusters > 1) { /** Allocate space. **/ - filtered_data = checkPtr(nmSysMalloc(cluster->Size * sizeof(void*))); - if (filtered_data == NULL) goto err_free; + const size_t filtered_data_size = cluster->Size * sizeof(void*); + filtered_data = nmSysMalloc(filtered_data_size); + if (UNLIKELY(filtered_data == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", filtered_data_size); + goto err_free; + } free_filtered_data = true; /** Add filtered data. **/ @@ -3011,13 +3500,13 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) } /** Execute complete search. **/ - const pXArray cluster_pairs = checkPtr(caCompleteSearch( + const pXArray cluster_pairs = caCompleteSearch( filtered_data, cluster->Size, similarity_function, search_data->Threshold, NULL - )); + ); if (free_filtered_data) nmSysFree(filtered_data); if (UNLIKELY(cluster_pairs == NULL)) { @@ -3034,9 +3523,13 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) const pPair pair = (pPair)cluster_pairs->Items[i]; pair->i = cluster->Indexes[pair->i]; pair->j = cluster->Indexes[pair->j]; - if (checkPos(xaAddItem(pairs, pair)) < 0) goto err_free; + if (UNLIKELY(xaAddItem(pairs, pair) < 0)) + { + mssError(1, "Cluster", "Failed to add new pair to pairs XArray."); + goto err_free; + } } - check(xaFree(cluster_pairs)); /* Failure ignored. */ + warnFail(xaFree(cluster_pairs)); } } @@ -3048,16 +3541,24 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) *** were computed, but nmSysMalloc(0) may return NULL, so we *** allocate a 1 byte memory section to be a marker. ***/ - search_data->Pairs = checkPtr(nmSysMalloc(1)); - if (search_data->Pairs == NULL) goto err_free; - check(xaFree(pairs)); /* Failure ignored. */ + search_data->Pairs = nmSysMalloc(1); + if (UNLIKELY(search_data->Pairs == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(1) failed."); + goto err_free; + } + warnFail(xaFree(pairs)); pairs = NULL; } else { - search_data->Pairs = (pPair*)checkPtr(xaToArray(pairs)); - if (search_data->Pairs == NULL) goto err_free; - check(xaFree(pairs)); /* Failure ignored. */ + search_data->Pairs = (pPair*)xaToArray(pairs); + if (UNLIKELY(search_data->Pairs == NULL)) + { + mssError(1, "Cluster", "xaToArray(pairs) failed."); + goto err_free; + } + warnFail(xaFree(pairs)); pairs = NULL; } @@ -3077,7 +3578,7 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) if (pairs->Items[i] != NULL) nmFree(pairs->Items[i], sizeof(Pair)); else break; } - check(xaFree(pairs)); /* Failure ignored. */ + warnFail(xaFree(pairs)); } mssError(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); @@ -3101,15 +3602,25 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) static int cluster_i_getParamType(void* inf_v, const char* attr_name) { - pNodeData node_data = checkPtr(inf_v); - if (UNLIKELY(node_data == NULL)) return -1; + /** Edge cases. **/ + pNodeData node_data = inf_v; + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "Failed to get param type from NULL struct inf."); + return -1; + } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + if (UNLIKELY(attr_name == NULL)) + { + mssError(1, "Cluster", "Failed to get param type for null attr name."); + return -1; + } /** Find the parameter. **/ for (unsigned int i = 0; i < node_data->nParams; i++) { - const pParam param = checkPtr(node_data->Params[i]); - if (UNLIKELY(param == NULL)) continue; + const pParam param = warnNull(node_data->Params[i]); + if (UNLIKELY(param == NULL)) continue; /* Skip it. */ if (strcmp(param->Name, attr_name) != 0) continue; /** Parameter found. **/ @@ -3147,9 +3658,19 @@ cluster_i_getParamType(void* inf_v, const char* attr_name) static int cluster_i_getParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) { - pNodeData node_data = checkPtr(inf_v); - if (UNLIKELY(node_data == NULL)) goto err; + /** Edge cases. **/ + pNodeData node_data = inf_v; + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "Failed to get param type from NULL struct inf."); + return -1; + } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + if (UNLIKELY(attr_name == NULL)) + { + mssError(1, "Cluster", "Failed to get param type for null attr name."); + return -1; + } /** Find the parameter. **/ for (unsigned int i = 0; i < node_data->nParams; i++) @@ -3168,7 +3689,14 @@ cluster_i_getParamValue(void* inf_v, char* attr_name, int datatype, pObjData val } /** Return param value. **/ - if (check(objCopyData(&(param->Value->Data), val, datatype)) != 0) goto err; + if (UNLIKELY(objCopyData(&(param->Value->Data), val, datatype) != 0)) + { + mssError(1, "Cluster", + "Failed to copy param data of type %s (%d).", + objTypeToStr(datatype), datatype + ); + goto err; + } return 0; } @@ -3222,11 +3750,11 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Update statistics. **/ ClusterStatistics.OpenCalls++; - /** Guard segfaults. **/ + /** Edge cases. **/ if (UNLIKELY(parent == NULL)) { mssError(0, "Cluster", "Warning: Call to clusterOpen(NULL, ...);\n"); - return NULL; /* The function failure assumes a valid parent, skip it. */ + return NULL; /* Skip error handler, which expects a valid parent. */ } ASSERTMAGIC(parent, MGK_OBJECT); @@ -3272,8 +3800,12 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Allocate driver instance data. **/ - driver_data = checkPtr(nmMalloc(sizeof(DriverData))); - if (UNLIKELY(driver_data == NULL)) goto err_free; + driver_data = nmMalloc(sizeof(DriverData)); + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(DriverData)); + goto err_free; + } memset(driver_data, 0, sizeof(DriverData)); SETMAGIC(driver_data, MGK_CL_DRIVER_DATA); driver_data->NodeData = node_data; @@ -3412,15 +3944,20 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb int clusterClose(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) return -1; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to close NULL driver object."); + return -1; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); /** Update statistics. **/ ClusterStatistics.CloseCalls++; /** Unlink the driver's node data. **/ - pNodeData node_data = checkPtr(driver_data->NodeData); /* Failure ignored. */ + pNodeData node_data = warnNull(driver_data->NodeData); ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); if (UNLIKELY(node_data != NULL && --node_data->OpenCount == 0)) cluster_i_freeNodeData(driver_data->NodeData); @@ -3456,10 +3993,15 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) mssClearError(); /** Get driver data. **/ - pDriverData driver_data = checkPtr(inf_v); - if (driver_data == NULL) goto err_free; + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to open a query on a NULL driver object."); + return NULL; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + /** Fail on target types that don't support queries. **/ if (driver_data->TargetType != TARGET_SEARCH && driver_data->TargetType != TARGET_CLUSTER && driver_data->TargetType != TARGET_NODE) @@ -3476,14 +4018,19 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) ClusterStatistics.OpenQueryCalls++; /** Allocate memory for the query. **/ - query_data = checkPtr(nmMalloc(sizeof(ClusterQuery))); - if (UNLIKELY(query_data == NULL)) goto err_free; + query_data = nmMalloc(sizeof(ClusterQuery)); + if (UNLIKELY(query_data == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(ClusterQuery)); + goto err_free; + } /** Initialize the query. **/ SETMAGIC(query_data, MGK_CL_QUERY_DATA); query_data->DriverData = (pDriverData)inf_v; query_data->RowIndex = 0u; + /** Success. **/ return query_data; err_free: @@ -3513,22 +4060,38 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) pDriverData result_data = NULL; /** Unpack data into local variables. **/ - pQueryData query_data = checkPtr(qy_v); - if (UNLIKELY(query_data == NULL)) goto err_free; + pQueryData query_data = qy_v; + if (UNLIKELY(query_data == NULL)) + { + mssError(1, "Cluster", "Failed to fetch from NULL query object."); + goto err_free; + } ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); - pDriverData driver_data = checkPtr(query_data->DriverData); - if (UNLIKELY(driver_data == NULL)) goto err_free; + pDriverData driver_data = query_data->DriverData; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to fetch from query object with NULL driver data."); + goto err_free; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); - pNodeData node_data = checkPtr(driver_data->NodeData); - if (UNLIKELY(node_data == NULL)) goto err_free; + pNodeData node_data = driver_data->NodeData; + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "Failed to fetch from query object with NULL node data."); + goto err_free; + } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Update statistics. **/ ClusterStatistics.FetchCalls++; /** Allocate result struct. **/ - result_data = checkPtr(nmMalloc(sizeof(DriverData))); - if (UNLIKELY(result_data == NULL)) goto err_free; + result_data = nmMalloc(sizeof(DriverData)); + if (UNLIKELY(result_data == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(DriverData)); + goto err_free; + } /** Default initialization. **/ SETMAGIC(result_data, MGK_CL_DRIVER_DATA); @@ -3575,8 +4138,12 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_CLUSTER: { /** Ensure the required data is computed. **/ - pClusterData target = (pClusterData)checkPtr(driver_data->TargetData); - if (UNLIKELY(target == NULL)) goto err_free; + pClusterData target = (pClusterData)driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster target for fetch."); + goto err_free; + } ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (UNLIKELY(cluster_i_computeClusterData(target, node_data) != 0)) { @@ -3597,8 +4164,12 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) case TARGET_SEARCH: { /** Ensure the required data is computed. **/ - pSearchData target = (pSearchData)checkPtr(driver_data->TargetData); - if (UNLIKELY(target == NULL)) goto err_free; + pSearchData target = (pSearchData)driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get search target for fetch."); + goto err_free; + } ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (UNLIKELY(cluster_i_computeSearchData(target, node_data) != 0)) { @@ -3657,7 +4228,11 @@ clusterQueryClose(void* qy_v, pObjTrxTree* oxt) { /** Cast the query data. **/ pQueryData query_data = qy_v; - if (checkPtr(qy_v) == NULL) return -1; + if (UNLIKELY(query_data == NULL)) + { + mssError(1, "Cluster", "Failed to close NULL query object."); + return -1; + } ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); /** Free the query data. **/ @@ -3681,10 +4256,16 @@ clusterQueryClose(void* qy_v, pObjTrxTree* oxt) int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) { - /** Extract target type from driver data. **/ - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) goto err; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get an attribute type from a NULL driver object."); + return -1; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + /** Extract target type from driver data. **/ const TargetType target_type = driver_data->TargetType; /** Update statistics. **/ @@ -3767,9 +4348,6 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) } return DATA_T_UNAVAILABLE; - - err: - return -1; } @@ -3798,10 +4376,16 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { TargetType target_type = -1; - /** Extract target type from driver data. **/ - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) goto err; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get an attribute value from a NULL driver object."); + return -1; /* Skip the error handler which expects a valid inf_v struct. */ + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + /** Extract target type from driver data. **/ target_type = driver_data->TargetType; /** Update statistics. **/ @@ -3835,8 +4419,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { case TARGET_NODE: { - pSourceData source_data = checkPtr(driver_data->TargetData); - if (source_data == NULL) goto err; + pSourceData source_data = driver_data->TargetData; + if (UNLIKELY(source_data == NULL)) + { + mssError(1, "Cluster", "Failed to get source data."); + goto err; + } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); val->String = source_data->Name; break; @@ -3845,8 +4433,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: { - pClusterData cluster_data = checkPtr(driver_data->TargetData); - if (cluster_data == NULL) goto err; + pClusterData cluster_data = driver_data->TargetData; + if (UNLIKELY(cluster_data == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster data."); + goto err; + } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); val->String = cluster_data->Name; break; @@ -3855,8 +4447,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: { - pSearchData search_data = checkPtr(driver_data->TargetData); - if (search_data == NULL) goto err; + pSearchData search_data = driver_data->TargetData; + if (UNLIKELY(search_data == NULL)) + { + mssError(1, "Cluster", "Failed to get search data."); + goto err; + } ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); val->String = search_data->Name; break; @@ -3940,8 +4536,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: { - pClusterData cluster_data = checkPtr(driver_data->TargetData); - if (cluster_data == NULL) goto err; + pClusterData cluster_data = driver_data->TargetData; + if (UNLIKELY(cluster_data == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster data."); + goto err; + } ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); if (cluster_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ else val->DateTime = &cluster_data->DateCreated; @@ -3951,8 +4551,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: { - pSearchData search_data = checkPtr(driver_data->TargetData); - if (search_data == NULL) goto err; + pSearchData search_data = driver_data->TargetData; + if (UNLIKELY(search_data == NULL)) + { + mssError(1, "Cluster", "Failed to get search data."); + goto err; + } ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); if (search_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ else val->DateTime = &search_data->DateCreated; @@ -3975,22 +4579,36 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: { - pClusterData target = checkPtr((pClusterData)driver_data->TargetData); - if (target == NULL) goto err; + pClusterData target = driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster data target."); + goto err; + } ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); - if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ + + if (UNLIKELY(target->DateComputed.Value == 0)) + return 1; /* DateComputed not set: return null */ else val->DateTime = &target->DateComputed; + return 0; } case TARGET_SEARCH: case TARGET_SEARCH_ENTRY: { - pSearchData target = checkPtr((pSearchData)driver_data->TargetData); - if (target == NULL) goto err; + pSearchData target = driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get search data target."); + goto err; + } ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ + + if (UNLIKELY(target->DateComputed.Value == 0)) + return 1; /* DateComputed not set: return null */ else val->DateTime = &target->DateComputed; + return 0; } } @@ -4005,8 +4623,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { case TARGET_NODE: { - pSourceData source_data = checkPtr(driver_data->TargetData); - if (source_data == NULL) goto err; + pSourceData source_data = driver_data->TargetData; + if (UNLIKELY(source_data == NULL)) + { + mssError(1, "Cluster", "Failed to get source data."); + goto err; + } ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); if (strcmp(attr_name, "source") == 0) @@ -4029,8 +4651,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER: { - pClusterData target = checkPtr(driver_data->TargetData); - if (target == NULL) goto err; + pClusterData target = driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster data target."); + goto err; + } ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); if (strcmp(attr_name, "algorithm") == 0) @@ -4072,8 +4698,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH: { - pSearchData target = checkPtr(driver_data->TargetData); - if (target == NULL) goto err; + pSearchData target = driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get search data target."); + goto err; + } ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); if (strcmp(attr_name, "source") == 0) @@ -4095,8 +4725,12 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_CLUSTER_ENTRY: { - pClusterData target = checkPtr(driver_data->TargetData); - if (target == NULL) goto err; + pClusterData target = driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster data target."); + goto err; + } ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; ASSERTMAGIC(target_cluster, MGK_CL_CLUSTER); @@ -4111,13 +4745,24 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO nmFree(vec, sizeof(StringVec)); } - /** Allocate and initialize the requested data. **/ - vec = val->StringVec = checkPtr(nmMalloc(sizeof(StringVec))); - if (vec == NULL) goto err; + /** Allocate a string vec for the requested data. **/ + vec = val->StringVec = nmMalloc(sizeof(StringVec)); + if (UNLIKELY(vec == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(StringVec)); + goto err; + } memset(vec, 0, sizeof(StringVec)); + + /** Initialize the string vec. **/ + const size_t strings_size = target_cluster->Size * sizeof(char*); + vec->Strings = nmSysMalloc(strings_size); + if (UNLIKELY(vec->Strings == NULL)) + { + mssError(1, "Cluster", "nmSysMalloc(%zu) failed.", strings_size); + goto err; + } vec->nStrings = target_cluster->Size; - vec->Strings = checkPtr(nmSysMalloc(target_cluster->Size * sizeof(char*))); - if (vec->Strings == NULL) goto err; for (unsigned int i = 0u; i < target_cluster->Size; i++) vec->Strings[i] = target->SourceData->Strings[target_cluster->Indexes[i]]; @@ -4129,11 +4774,19 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO case TARGET_SEARCH_ENTRY: { - pSearchData target = checkPtr(driver_data->TargetData); - if (target == NULL) goto err; + pSearchData target = driver_data->TargetData; + if (UNLIKELY(target == NULL)) + { + mssError(1, "Cluster", "Failed to get search data target."); + goto err; + } ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); - pPair target_dup = checkPtr(target->Pairs[driver_data->TargetIndex]); - if (target_dup == NULL) goto err; + pPair target_dup = target->Pairs[driver_data->TargetIndex]; + if (UNLIKELY(target_dup == NULL)) + { + mssError(1, "Cluster", "Failed to get target duplicate."); + goto err; + } if (strcmp(attr_name, "sim") == 0) { @@ -4197,15 +4850,25 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) pObjPresentationHints hints = NULL; pParamObjects tmp_list = NULL; - /** Extract target type from driver data. **/ - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) goto err_free; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get presentation hints from a NULL driver object."); + return NULL; /* Skip the error handler, which expects a valid inf_v struct. */ + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + /** Extract target type from driver data. **/ const TargetType target_type = driver_data->TargetType; /** Malloc presentation hints struct. **/ - hints = checkPtr(nmMalloc(sizeof(ObjPresentationHints))); - if (UNLIKELY(hints == NULL)) goto err_free; + hints = nmMalloc(sizeof(ObjPresentationHints)); + if (UNLIKELY(hints == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(ObjPresentationHints)); + goto err_free; + } memset(hints, 0, sizeof(ObjPresentationHints)); /** Hints that are the same for all attributes. **/ @@ -4215,8 +4878,12 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) hints->StyleMask |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; /** Temporary param list for compiling expressions. **/ - tmp_list = checkPtr(expCreateParamList()); - if (UNLIKELY(hints == NULL)) goto err_free; + tmp_list = expCreateParamList(); + if (UNLIKELY(tmp_list == NULL)) + { + mssError(1, "Cluster", "expCreateParamList() failed."); + goto err_free; + } /** Search for the requested attribute through attributes common to all instances. **/ if (strcmp(attr_name, "name") == 0) @@ -4242,17 +4909,17 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) } if (strcmp(attr_name, "internal_type") == 0) { - if (check(xaInit(&(hints->EnumList), 5)) == 0) + if (warnFail(xaInit(&(hints->EnumList), 5)) == 0) { - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ - checkPos(xaAddItem(&(hints->EnumList), checkPtr(nmSysStrdup("search/entry")))); /* Failure ignored. */ + warnNeg(xaAddItem(&(hints->EnumList), warnNull(nmSysStrdup("system/cluster")))); + warnNeg(xaAddItem(&(hints->EnumList), warnNull(nmSysStrdup("cluster/cluster")))); + warnNeg(xaAddItem(&(hints->EnumList), warnNull(nmSysStrdup("cluster/entry")))); + warnNeg(xaAddItem(&(hints->EnumList), warnNull(nmSysStrdup("cluster/search")))); + warnNeg(xaAddItem(&(hints->EnumList), warnNull(nmSysStrdup("search/entry")))); } hints->Length = 16; hints->VisualLength = 16; - hints->FriendlyName = checkPtr(nmSysStrdup("Internal Type")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Internal Type")); hints->Style |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; hints->StyleMask |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; goto end; @@ -4269,7 +4936,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = 24; hints->VisualLength = 20; - hints->Format = checkPtr(nmSysStrdup("datetime")); /* Failure ignored. */ + hints->Format = warnNull(nmSysStrdup("datetime")); goto end; } else @@ -4287,21 +4954,21 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = _PC_PATH_MAX; hints->VisualLength = 64; - hints->FriendlyName = checkPtr(nmSysStrdup("Source Path")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Source Path")); goto end; } if (strcmp(attr_name, "key_attr") == 0) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = checkPtr(nmSysStrdup("Key Attribute Name")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Key Attribute Name")); goto end; } if (strcmp(attr_name, "data_attr") == 0) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = checkPtr(nmSysStrdup("Data Attribute Name")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Data Attribute Name")); goto end; } break; @@ -4310,59 +4977,59 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "num_clusters") == 0) { /** Min and max values. **/ - hints->MinValue = checkPtr(expCompileExpression("1", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MaxValue = checkPtr(expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = warnNull(expCompileExpression("1", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MaxValue = warnNull(expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - hints->FriendlyName = checkPtr(nmSysStrdup("Number of Clusters")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Number of Clusters")); goto end; } if (strcmp(attr_name, "min_improvement") == 0) { /** Min and max values. **/ - hints->DefaultExpr = checkPtr(expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MIN_IMPROVEMENT), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->DefaultExpr = warnNull(expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MIN_IMPROVEMENT), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MinValue = warnNull(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MaxValue = warnNull(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = checkPtr(nmSysStrdup("Minimum Improvement Threshold")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Minimum Improvement Threshold")); goto end; } if (strcmp(attr_name, "max_iterations") == 0) { /** Min and max values. **/ - hints->DefaultExpr = checkPtr(expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MAX_ITERATIONS), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MinValue = checkPtr(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MaxValue = checkPtr(expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->DefaultExpr = warnNull(expCompileExpression(CI_STRINGIFY_CONSTANT(CI_DEFAULT_MAX_ITERATIONS), tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MinValue = warnNull(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MaxValue = warnNull(expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Other hints. **/ hints->Length = 8; hints->VisualLength = 4; - hints->FriendlyName = checkPtr(nmSysStrdup("Maximum Iterations")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Maximum Iterations")); goto end; } if (strcmp(attr_name, "algorithm") == 0) { /** Enum values. **/ - if (check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)) == 0) + if (warnFail(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)) == 0) { for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) { - char* cluster_string = checkPtr(nmSysStrdup(cluster_i_clusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]))); + char* cluster_string = warnNull(nmSysStrdup(cluster_i_clusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]))); if (cluster_string == NULL) continue; /* Skip this. */ - checkPos(xaAddItem(&(hints->EnumList), cluster_string)); /* Failure ignored. */ + warnNeg(xaAddItem(&(hints->EnumList), cluster_string)); } } /** Min and max values. **/ - hints->MinValue = checkPtr(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = warnNull(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); char buf[8]; snprintf(buf, sizeof(buf), "%u", N_CLUSTERING_ALGORITHMS - 1u); - hints->MaxValue = checkPtr(expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = warnNull(expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Display flags. **/ hints->Style |= OBJ_PH_STYLE_BUTTONS; @@ -4371,7 +5038,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 24; hints->VisualLength = 20; - hints->FriendlyName = checkPtr(nmSysStrdup("Clustering Algorithm")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Clustering Algorithm")); goto end; } /** Fall-through: Start of overlapping region. **/ @@ -4380,13 +5047,13 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) if (strcmp(attr_name, "similarity_measure") == 0) { /** Enum values. **/ - if (check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)) == 0) + if (warnFail(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)) == 0) { for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) { - char* similarity_string = checkPtr(nmSysStrdup(cluster_i_similarityMeasureToString(ALL_SIMILARITY_MEASURES[i]))); + char* similarity_string = warnNull(nmSysStrdup(cluster_i_similarityMeasureToString(ALL_SIMILARITY_MEASURES[i]))); if (similarity_string == NULL) continue; /* Skip this. */ - checkPos(xaAddItem(&(hints->EnumList), similarity_string)); /* Failure ignored. */ + warnNeg(xaAddItem(&(hints->EnumList), similarity_string)); } } @@ -4395,15 +5062,15 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; /** Min and max values. **/ - hints->MinValue = checkPtr(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = warnNull(expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); char buf[8]; snprintf(buf, sizeof(buf), "%u", N_SIMILARITY_MEASURES - 1u); - hints->MaxValue = checkPtr(expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MaxValue = warnNull(expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Other hints. **/ hints->Length = 32; hints->VisualLength = 20; - hints->FriendlyName = checkPtr(nmSysStrdup("Similarity Measure")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Similarity Measure")); goto end; } @@ -4414,19 +5081,19 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = 64; hints->VisualLength = 32; - hints->FriendlyName = checkPtr(nmSysStrdup("Source Cluster Name")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Source Cluster Name")); goto end; } if (strcmp(attr_name, "threshold") == 0) { /** Min and max values. **/ - hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = warnNull(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MaxValue = warnNull(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = checkPtr(nmSysStrdup("Similarity Threshold")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Similarity Threshold")); goto end; } break; @@ -4438,19 +5105,19 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** Other hints. **/ hints->Length = 65536; hints->VisualLength = 256; - hints->FriendlyName = checkPtr(nmSysStrdup("Cluster Data")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Cluster Data")); goto end; } if (strcmp(attr_name, "sim") == 0) { /** Min and max values. **/ - hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = warnNull(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MaxValue = warnNull(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = checkPtr(nmSysStrdup("Similarity")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Similarity")); goto end; } break; @@ -4462,26 +5129,26 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = checkPtr(nmSysStrdup("Key 1")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Key 1")); goto end; } if (strcmp(attr_name, "key2") == 0) { hints->Length = 255; hints->VisualLength = 32; - hints->FriendlyName = checkPtr(nmSysStrdup("Key 2")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Key 2")); goto end; } if (strcmp(attr_name, "sim") == 0) { /** Min and max values. **/ - hints->MinValue = checkPtr(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ - hints->MaxValue = checkPtr(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /* Failure ignored. */ + hints->MinValue = warnNull(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); + hints->MaxValue = warnNull(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); /** Other hints. **/ hints->Length = 16; hints->VisualLength = 8; - hints->FriendlyName = checkPtr(nmSysStrdup("Similarity")); /* Failure ignored. */ + hints->FriendlyName = warnNull(nmSysStrdup("Similarity")); goto end; } break; @@ -4495,23 +5162,23 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) /** No checks matched the requested attribute. **/ cluster_i_unknownAttribute(attr_name, driver_data->TargetType); - err_free: - /** Error cleanup. **/ - if (hints != NULL) nmFree(hints, sizeof(ObjPresentationHints)); - hints = NULL; - + err_free:; /** Construct the clearest error message that we can. **/ char* name = NULL; char* internal_type = NULL; - check(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); /* Failure ignored. */ - check(clusterGetAttrValue(inf_v, "internal_type", DATA_T_STRING, POD(&internal_type), NULL)); /* Failure ignored. */ + warnFail(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); + warnFail(clusterGetAttrValue(inf_v, "internal_type", DATA_T_STRING, POD(&internal_type), NULL)); mssError(0, "Cluster", "Failed to get presentation hints for '%s' on object '%s' : \"%s\".", attr_name, name, internal_type ); + /** Error cleanup. **/ + if (hints != NULL) nmFree(hints, sizeof(ObjPresentationHints)); + hints = NULL; + end: - if (tmp_list != NULL) check(expFreeParamList(tmp_list)); /* Failure ignored. */ + if (tmp_list != NULL) warnFail(expFreeParamList(tmp_list)); return hints; } @@ -4530,8 +5197,13 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) return NULL; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get first attribute from NULL driver object."); + return NULL; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); driver_data->TargetAttrIndex = 0u; @@ -4553,8 +5225,13 @@ clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) return NULL; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get next attribute from NULL driver object."); + return NULL; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); const unsigned int i = driver_data->TargetAttrIndex++; @@ -4585,11 +5262,22 @@ clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) int clusterInfo(void* inf_v, pObjectInfo info) { - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) goto err; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster info for NULL cluster object."); + goto err; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); - pNodeData node_data = checkPtr(driver_data->NodeData); - if (UNLIKELY(node_data == NULL)) goto err; + + /** Get node data. **/ + pNodeData node_data = driver_data->NodeData; + if (UNLIKELY(node_data == NULL)) + { + mssError(1, "Cluster", "Failed to get node data from driver data."); + goto err; + } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); /** Reset flags buffer. **/ @@ -4627,8 +5315,12 @@ clusterInfo(void* inf_v, pObjectInfo info) case TARGET_SEARCH: { - pSearchData search_data = checkPtr(driver_data->TargetData); - if (UNLIKELY(search_data == NULL)) goto err; + pSearchData search_data = driver_data->TargetData; + if (UNLIKELY(search_data == NULL)) + { + mssError(1, "Cluster", "Failed to get search data."); + goto err; + } info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; if (search_data->Pairs != NULL) @@ -4678,8 +5370,13 @@ clusterInfo(void* inf_v, pObjectInfo info) char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) return NULL; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get first method from NULL driver object."); + return NULL; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); driver_data->TargetMethodIndex = 0u; @@ -4701,11 +5398,16 @@ clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) { - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) return NULL; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to get next method from NULL driver object."); + return NULL; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); - if (driver_data->TargetMethodIndex >= METHOD_NAMES_COUNT) return NULL; + if (driver_data->TargetMethodIndex >= METHOD_NAMES_COUNT) return NULL; return METHOD_NAMES[driver_data->TargetMethodIndex++]; } @@ -4851,8 +5553,13 @@ cluster_i_cacheFreeSearch(pXHashEntry entry, void* unused) int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) { - pDriverData driver_data = checkPtr(inf_v); - if (UNLIKELY(driver_data == NULL)) goto err; + /** Get driver data. **/ + pDriverData driver_data = inf_v; + if (UNLIKELY(driver_data == NULL)) + { + mssError(1, "Cluster", "Failed to execute method on NULL driver object."); + return -1; + } ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); /** Cache management method. **/ @@ -4886,32 +5593,44 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (show) { - /** Print cache info table. **/ + /** Print cache info table headers. **/ int ret = 0; unsigned int source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; - bool failed = false; printf("\nShowing cache for "); if (path != NULL) printf("\"%s\":\n", path); else printf("all files:\n"); printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Entry CacheKey"); - failed |= (check(xhForEach( + + /** Print source data cache. **/ + if (UNLIKELY(xhForEach( &ClusterDriverCaches.SourceDataCache, cluster_i_printEntry, (int)CI_SOURCE_DATA, &source_bytes, &skip_uncomputed, path - )) != 0); - failed |= (check(xhForEach( + )) != 0) + { + mssError(0, "Cluster", "Failed to print source data cache."); + ret = -1; + } + + /** Print cluster data cache. **/ + if (UNLIKELY(xhForEach( &ClusterDriverCaches.ClusterDataCache, cluster_i_printEntry, (int)CI_CLUSTER_DATA, &cluster_bytes, &skip_uncomputed, path - )) != 0); - failed |= (check(xhForEach( + )) != 0) + { + mssError(0, "Cluster", "Failed to print cluster data cache."); + ret = -1; + } + + /** Print search data cache. **/ + if (UNLIKELY(xhForEach( &ClusterDriverCaches.SearchDataCache, cluster_i_printEntry, (int)CI_SEARCH_DATA, &search_bytes, &skip_uncomputed, path - )) != 0); - if (failed) + )) != 0) { - mssError(0, "Cluster", "Unexpected error occurred while showing caches."); + mssError(0, "Cluster", "Failed to print search data cache."); ret = -1; } @@ -4920,7 +5639,8 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree + (unsigned int)ClusterDriverCaches.SourceDataCache.nItems + (unsigned int)ClusterDriverCaches.ClusterDataCache.nItems + (unsigned int)ClusterDriverCaches.SearchDataCache.nItems; - if (total_caches < skip_uncomputed) printf("All caches skipped, nothing to show...\n"); + if (total_caches < skip_uncomputed) + printf("All caches skipped, nothing to show...\n"); /** Print stats. **/ char buf[SNPRINT_BYTES_BUF_SIZE]; @@ -4936,7 +5656,8 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree printf("%-8s %-4d %-12s\n\n", "Total", total_caches, buf); /** Print skip stats (if anything was skipped.) **/ - if (skip_uncomputed > 1llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); + if (skip_uncomputed > 1llu) + printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); return ret; } @@ -5102,20 +5823,44 @@ int clusterInitialize(void) { /** Allocate the driver. **/ - pObjDriver drv = checkPtr(nmMalloc(sizeof(ObjDriver))); - if (UNLIKELY(drv == NULL)) goto err_free; + pObjDriver drv = nmMalloc(sizeof(ObjDriver)); + if (UNLIKELY(drv == NULL)) + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(ObjDriver)); + goto err_free; + } memset(drv, 0, sizeof(ObjDriver)); /** Initialize caches. **/ // memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); - if (check(xhInit(&ClusterDriverCaches.SourceDataCache, CI_CACHE_HASHTABLE_ROWS, 0)) != 0) goto err_free; - if (check(xhInit(&ClusterDriverCaches.ClusterDataCache, CI_CACHE_HASHTABLE_ROWS, 0)) != 0) goto err_free; - if (check(xhInit(&ClusterDriverCaches.SearchDataCache, CI_CACHE_HASHTABLE_ROWS, 0)) != 0) goto err_free; + if (UNLIKELY(xhInit(&ClusterDriverCaches.SourceDataCache, CI_CACHE_HASHTABLE_ROWS, 0) != 0)) + { + mssError(1, "Cluster", "Failed to allocate the hash table for the source data cache."); + goto err_free; + } + if (UNLIKELY(xhInit(&ClusterDriverCaches.ClusterDataCache, CI_CACHE_HASHTABLE_ROWS, 0) != 0)) + { + mssError(1, "Cluster", "Failed to allocate the hash table for the cluster data cache."); + goto err_free; + } + if (UNLIKELY(xhInit(&ClusterDriverCaches.SearchDataCache, CI_CACHE_HASHTABLE_ROWS, 0) != 0)) + { + mssError(1, "Cluster", "Failed to allocate the hash table for the search data cache."); + goto err_free; + } /** Setup the structure. **/ - if (checkPtr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; - if (check(xaInit(&drv->RootContentTypes, 1)) != 0) goto err_free; - if (checkPos(xaAddItem(&drv->RootContentTypes, "system/cluster")) < 0) goto err_free; + strtcpy(drv->Name, "cluster - Clustering Driver", sizeof(drv->Name)); + if (UNLIKELY(xaInit(&drv->RootContentTypes, 1) != 0)) + { + mssError(1, "Cluster", "Failed to allocate the XArray table for the driver's root content types."); + goto err_free; + } + if (UNLIKELY(xaAddItem(&drv->RootContentTypes, "system/cluster") < 0)) + { + mssError(1, "Cluster", "Failed to add \"system/cluster\" to the drv->RootContentTypes XArray."); + goto err_free; + } drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ @@ -5149,7 +5894,11 @@ clusterInitialize(void) drv->GetQueryIdentityPath = NULL; /** Register the driver. **/ - if (check(objRegisterDriver(drv)) != 0) goto err_free; + if (objRegisterDriver(drv) != 0) + { + mssError(1, "Cluster", "Failed to register driver."); + goto err_free; + } /** Register structs used in this project with the newmalloc memory management system. **/ nmRegister(sizeof(SourceData), "ClusterSourceData"); @@ -5166,12 +5915,12 @@ clusterInitialize(void) err_free: /** Error cleanup. **/ - if (ClusterDriverCaches.SourceDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SourceDataCache)); /* Failure ignored. */ - if (ClusterDriverCaches.ClusterDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.ClusterDataCache)); /* Failure ignored. */ - if (ClusterDriverCaches.SearchDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SearchDataCache)); /* Failure ignored. */ + if (ClusterDriverCaches.SourceDataCache.nRows != 0) warnFail(xhDeInit(&ClusterDriverCaches.SourceDataCache)); + if (ClusterDriverCaches.ClusterDataCache.nRows != 0) warnFail(xhDeInit(&ClusterDriverCaches.ClusterDataCache)); + if (ClusterDriverCaches.SearchDataCache.nRows != 0) warnFail(xhDeInit(&ClusterDriverCaches.SearchDataCache)); if (drv != NULL) { - if (drv->RootContentTypes.nAlloc != 0) check(xaDeInit(&drv->RootContentTypes)); /* Failure ignored. */ + if (drv->RootContentTypes.nAlloc != 0) warnFail(xaDeInit(&drv->RootContentTypes)); nmFree(drv, sizeof(ObjDriver)); } From 5f272f0e1f473fc68359a1268d48e752d6a59f7c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 13:53:58 -0600 Subject: [PATCH 283/361] Remove code that frees clusters owned by the cluster cache. --- centrallix/osdrivers/objdrv_cluster.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 5d6ae51ee..649704b6f 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1553,16 +1553,8 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData /** Error cleanup. **/ if (cache_key != NULL) nmSysFree(cache_key); - if (sub_clusters.nAlloc != 0) - { - for (unsigned int i = 0u; i < sub_clusters.nItems; i++) - { - pClusterData cur = sub_clusters.Items[i]; - if (cur == NULL) break; - cluster_i_freeClusterData(cur, true); - } - warnFail(xaDeInit(&sub_clusters)); - } + /** The cluster cache owns the subclusters so we only free the XArray. **/ + if (sub_clusters.nAlloc != 0) warnFail(xaDeInit(&sub_clusters)); if (cluster_data != NULL) cluster_i_freeClusterData(cluster_data, false); From ec9454f5364caa81e207c0baee03d343b2caebcd Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 16:51:14 -0600 Subject: [PATCH 284/361] Improve comments. --- centrallix/osdrivers/objdrv_cluster.c | 56 +++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 649704b6f..98f41c3a1 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -732,7 +732,10 @@ int clusterCommit(void* inf_v, pObjTrxTree *oxt); /** ANCHOR[id=parsing] **/ // LINK #functions -/** Format a hint to give to the user. **/ +/*** Format a hint to print to the user. + *** + *** @param hint The text of the guess to print as a hint. + ***/ static void cluster_i_giveHint(const char* hint) { fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); @@ -5406,7 +5409,20 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions -/** Intended for use in `xhForEach()`. **/ +/*** Prints a hash table entry that is assumed to be from one of the caches. + *** + *** @attention - Intended for use in `xhForEach()`. + *** + *** @param entry The hash table entry to print. + *** @param args Several arguments that control the printing: + *** - The type of driver struct data being printed: either source data, + *** cluster data, or search data. + *** - A pointer to an unsigned int storing the total number of bytes + *** used by the entry printed. + *** - A pointer to ?. + *** @returns 0 if successful, + *** -1 if an error occurs. + ***/ static int cluster_i_printEntry(pXHashEntry entry, va_list args) { @@ -5486,17 +5502,26 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) goto increment_total; no_print: - (*less_ptr)++; + if (less_ptr != NULL) + (*less_ptr)++; increment_total: - *total_bytes_ptr += bytes; + if (total_bytes_ptr != NULL) + *total_bytes_ptr += bytes; return 0; } // LINK #functions -/** Intended for use in `xhClearKeySafe()`. **/ +/*** Free the source data stored in a hash table entry that is assumed to be + *** from the source data cache. + *** + *** @attention - Intended for use in `xhClearKeySafe()`. + *** + *** @param entry The hash table entry to use for freeing. + *** @param unused An unused pointer. + ***/ static void cluster_i_cacheFreeSourceData(pXHashEntry entry, void* unused) { @@ -5509,7 +5534,14 @@ cluster_i_cacheFreeSourceData(pXHashEntry entry, void* unused) // LINK #functions -/** Intended for use in `xhClearKeySafe()`. **/ +/*** Free the cluster data stored in a hash table entry that is assumed to be + *** from the cluster data cache. + *** + *** @attention - Intended for use in `xhClearKeySafe()`. + *** + *** @param entry The hash table entry to use for freeing. + *** @param unused An unused pointer. + ***/ static void cluster_i_cacheFreeCluster(pXHashEntry entry, void* unused) { @@ -5522,7 +5554,14 @@ cluster_i_cacheFreeCluster(pXHashEntry entry, void* unused) // LINK #functions -/** Intended for use in `xhClearKeySafe()`. **/ +/*** Free the search data stored in a hash table entry that is assumed to be + *** from the search data cache. + *** + *** @attention - Intended for use in `xhClearKeySafe()`. + *** + *** @param entry The hash table entry to use for freeing. + *** @param unused An unused pointer. + ***/ static void cluster_i_cacheFreeSearch(pXHashEntry entry, void* unused) { @@ -5581,7 +5620,8 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree show = true; path = objFilePath(driver_data->NodeData->Parent); } - if (strcmp(param->String, "show_all") == 0) show = true; + if (strcmp(param->String, "show_all") == 0) + show = true; if (show) { From 1a29da8016fd5b0f683e547b970254abee88919d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 17:01:27 -0600 Subject: [PATCH 285/361] Add error checking for strtcpy(). --- centrallix/osdrivers/objdrv_cluster.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 98f41c3a1..40e4690cd 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -5882,7 +5882,11 @@ clusterInitialize(void) } /** Setup the structure. **/ - strtcpy(drv->Name, "cluster - Clustering Driver", sizeof(drv->Name)); + if (strtcpy(drv->Name, "cluster - Clustering Driver", sizeof(drv->Name)) < 0) + { + mssError(1, "Cluster", "Failed to write driver name."); + goto err_free; + } if (UNLIKELY(xaInit(&drv->RootContentTypes, 1) != 0)) { mssError(1, "Cluster", "Failed to allocate the XArray table for the driver's root content types."); From ad15ebf4e2e3bc7a18add6be37d00aeabd9c31d9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 17:08:39 -0600 Subject: [PATCH 286/361] Update to use unsigned long instead of unsigned long long for cluster indicies that may be stored as pointers to support 32-bit platforms. --- centrallix/osdrivers/objdrv_cluster.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 40e4690cd..1e0033a4a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3233,7 +3233,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) } /** Iterate through each label and add the index of the data to the specified cluster. **/ - for (unsigned long long i = 0llu; i < source_data->nDatas; i++) + for (unsigned long i = 0lu; i < source_data->nDatas; i++) { if (xaAddItem(&indexes_in_cluster[labels[i]], (void*)i) < 0) { @@ -3268,7 +3268,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) } for (unsigned int i = 0u; i < indexes_in_this_cluster->nItems; i++) { - const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[i]; + const unsigned long index = (unsigned long)indexes_in_this_cluster->Items[i]; if (UNLIKELY(index > __UINT32_MAX__)) { mssError(1, "Cluster", From 1eb05329aaea4b05b876bde66176204c790a8e8c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 17:10:02 -0600 Subject: [PATCH 287/361] Add the pSimilarityFn type. --- centrallix-lib/include/clusters.h | 11 ++++++++--- centrallix-lib/src/clusters.c | 6 +++--- centrallix/osdrivers/objdrv_cluster.c | 7 ++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index b83862965..fb32423f8 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -129,6 +129,11 @@ int caKmeans( }) #endif +/*** A function which takes two data items and returns their similarity, a + *** value between `0.0` (completely different) and `1.0` (identical). + ***/ +typedef double (*pSimilarityFn)(void*, void*); + /** Comparison functions (see ca_search()). **/ double caCosCompare(void* v1, void* v2); double caLevCompare(void* str1, void* str2); @@ -139,19 +144,19 @@ void* caMostSimilar( void* target, void** data, const unsigned int num_data, - double (*similarity)(void*, void*), + pSimilarityFn similarity, const double threshold); pXArray caSlidingSearch( void** data, const unsigned int num_data, const unsigned int window_size, - double (*similarity)(void*, void*), + pSimilarityFn similarity, const double threshold, pXArray maybe_pairs); pXArray caCompleteSearch( void** data, const unsigned int num_data, - double (*similarity)(void*, void*), + pSimilarityFn similarity, const double threshold, pXArray maybe_pairs); diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index d3b2e9518..e22d8d601 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -1105,7 +1105,7 @@ caMostSimilar( void* target, void** data, const unsigned int num_data, - double (*similarity)(void*, void*), + pSimilarityFn similarity, const double threshold) { void* most_similar = NULL; @@ -1155,7 +1155,7 @@ caSlidingSearch( void** data, const unsigned int num_data, const unsigned int window_size, - double (*similarity)(void*, void*), + pSimilarityFn similarity, const double threshold, pXArray maybe_pairs) { @@ -1245,7 +1245,7 @@ pXArray caCompleteSearch( void** data, const unsigned int num_data, - double (*similarity)(void*, void*), + pSimilarityFn similarity, const double threshold, pXArray maybe_pairs) { diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 1e0033a4a..f963ec302 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -173,7 +173,8 @@ cluster_i_similarityMeasureToString(SimilarityMeasure similarity_measure) *** if an error occurs, in which case it always calls mssError() to give *** an error message. ***/ -double (*cluster_i_similarityMeasureToFunction(SimilarityMeasure similarity_measure))(void*, void*) +pSimilarityFn +cluster_i_similarityMeasureToFunction(SimilarityMeasure similarity_measure) { switch (similarity_measure) { @@ -3410,10 +3411,10 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) } /** Get the comparison function based on the similarity measure. **/ - double (*similarity_function)(void *, void *) = cluster_i_similarityMeasureToFunction(search_data->SimilarityMeasure); + pSimilarityFn similarity_function = cluster_i_similarityMeasureToFunction(search_data->SimilarityMeasure); if (UNLIKELY(similarity_function == NULL)) { - mssError(1, "Cluster", "Failed to get similarity measure function."); + mssError(0, "Cluster", "Failed to get similarity measure function."); goto err_free; } From 6d0cc6e2dcc982e934fb844b18b2f6de5008c673 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 22 Sep 2026 17:32:57 -0600 Subject: [PATCH 288/361] Fix queries from node targets incrementing the index even if no entry was consumed. --- centrallix/osdrivers/objdrv_cluster.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index f963ec302..4cc68ef43 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4104,7 +4104,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { case TARGET_NODE: { - unsigned int index = query_data->RowIndex++; + unsigned int index = query_data->RowIndex + 1; /** Fetch a cluster at the current index. **/ const unsigned int n_cluster_datas = node_data->nClusterDatas; @@ -4113,6 +4113,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) /** Fetch a cluster. **/ result_data->TargetType = TARGET_CLUSTER; result_data->TargetData = node_data->ClusterDatas[index]; + query_data->RowIndex++; /* Consume fetched entry. */ break; } else index -= n_cluster_datas; @@ -4124,6 +4125,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) /** Fetch a search. **/ result_data->TargetType = TARGET_SEARCH; result_data->TargetData = node_data->SearchDatas[index]; + query_data->RowIndex++; /* Consume fetched entry. */ break; } else index -= n_search_datas; From 05ac6ef0a1dc8e87f763a1b0fb5a5f2e52314b8e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 14:27:01 -0600 Subject: [PATCH 289/361] Modify date_computed to be null on the target node instead of undefined. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 4cc68ef43..1819b3591 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4572,7 +4572,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO { case TARGET_NODE: /** Attribute is not defined for this target type. **/ - goto err; + return 1; case TARGET_CLUSTER: case TARGET_CLUSTER_ENTRY: From ee3086c9e694a8d400b54cf23f7575c87f8718bb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 14:27:05 -0600 Subject: [PATCH 290/361] Restructure confusing last_modification code. --- centrallix/osdrivers/objdrv_cluster.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 1819b3591..e5812be58 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4511,17 +4511,6 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO return 0; } - /** Last modification is not implemented. **/ - if (strcmp(attr_name, "last_modification") == 0) - { - if (target_type == TARGET_CLUSTER - || target_type == TARGET_CLUSTER_ENTRY - || target_type == TARGET_SEARCH - || target_type == TARGET_SEARCH_ENTRY) - goto date_computed; - else return 1; /* null */ - } - /** Handle date_created. **/ if (strcmp(attr_name, "date_created") == 0) { @@ -4565,9 +4554,9 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO } /** Handle date_computed. **/ - if (strcmp(attr_name, "date_computed") == 0) + if (strcmp(attr_name, "last_modification") == 0 + || strcmp(attr_name, "date_computed") == 0) { - date_computed: switch (target_type) { case TARGET_NODE: From d411e84ca81ac3cbd53285f706e75cbfce15c9b5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 14:33:23 -0600 Subject: [PATCH 291/361] Fix an off-by-one when fetching objects from the cluster driver root node. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index e5812be58..46a4083e0 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4104,7 +4104,7 @@ clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) { case TARGET_NODE: { - unsigned int index = query_data->RowIndex + 1; + unsigned int index = query_data->RowIndex; /** Fetch a cluster at the current index. **/ const unsigned int n_cluster_datas = node_data->nClusterDatas; From a69e620c425afd8c9b62ba9333b6cb99b12cf118 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 14:51:23 -0600 Subject: [PATCH 292/361] Improve confusing logic for skipping uncomputed caches. --- centrallix/osdrivers/objdrv_cluster.c | 56 ++++++++++++++++----------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 46a4083e0..d46abf311 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -5411,7 +5411,8 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) *** cluster data, or search data. *** - A pointer to an unsigned int storing the total number of bytes *** used by the entry printed. - *** - A pointer to ?. + *** - A pointer to an unsigned long long that counts how many uncomputed + *** caches have been skipped. Specify NULL to include uncomputed caches. *** @returns 0 if successful, *** -1 if an error occurs. ***/ @@ -5423,9 +5424,9 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) void* data = entry->Data; /** Extract args. **/ - CIDataType data_type = (CIDataType)va_arg(args, int); + CIDataType data_type = (CIDataType)va_arg(args, int); unsigned int* total_bytes_ptr = va_arg(args, unsigned int*); - unsigned long long* less_ptr = va_arg(args, unsigned long long*); + unsigned long long* num_uncomputed_skipped_ptr = va_arg(args, unsigned long long*); char* path = va_arg(args, char*); /** If a path is provided, check that it matches the start of the key. **/ @@ -5444,8 +5445,10 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) /** Compute size. **/ bytes = cluster_i_sizeOfSourceData(source_data); - /** If less is specified, skip uncomputed source. **/ - if (*less_ptr > 0llu && source_data->Vectors == NULL) goto no_print; + /** If num_uncomputed_skipped_ptr is specified, skip uncomputed source. **/ + if (num_uncomputed_skipped_ptr != NULL + && source_data->Vectors == NULL + ) goto no_print; /** Compute printing information. **/ type = "Source"; @@ -5459,8 +5462,10 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) /** Compute size. **/ bytes = cluster_i_sizeOfClusterData(cluster_data, false); - /** If less is specified, skip uncomputed source. **/ - if (*less_ptr > 0llu && cluster_data->Clusters == NULL) goto no_print; + /** If num_uncomputed_skipped_ptr is specified, skip uncomputed source. **/ + if (num_uncomputed_skipped_ptr != NULL + && cluster_data->Clusters == NULL + ) goto no_print; /** Compute printing information. **/ type = "Cluster"; @@ -5474,8 +5479,10 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) /** Compute size. **/ bytes = cluster_i_sizeOfSearchData(search_data); - /** If less is specified, skip uncomputed source. **/ - if (*less_ptr > 0llu && search_data->Pairs == NULL) goto no_print; + /** If num_uncomputed_skipped_ptr is specified, skip uncomputed source. **/ + if (num_uncomputed_skipped_ptr != NULL + && search_data->Pairs == NULL + ) goto no_print; /** Compute printing information. **/ type = "Search"; @@ -5494,8 +5501,8 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) goto increment_total; no_print: - if (less_ptr != NULL) - (*less_ptr)++; + if (num_uncomputed_skipped_ptr != NULL) + (*num_uncomputed_skipped_ptr)++; increment_total: if (total_bytes_ptr != NULL) @@ -5600,14 +5607,13 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree } /** 'show' and 'show_all'. **/ - bool show = false; - unsigned long long skip_uncomputed = 0llu; + bool show = false, skip_uncomputed = false; if (strcmp(param->String, "show_less") == 0) { - /** Specify show_less to skip uncomputed caches. **/ - skip_uncomputed = 1ull; + /** Specifying show_less skips uncomputed caches. **/ + skip_uncomputed = true; } - if (skip_uncomputed == 1ull || strcmp(param->String, "show") == 0) + if (skip_uncomputed || strcmp(param->String, "show") == 0) { show = true; path = objFilePath(driver_data->NodeData->Parent); @@ -5615,6 +5621,12 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (strcmp(param->String, "show_all") == 0) show = true; + /** Declare local variables to store skip counts. **/ + unsigned long long num_uncomputed_skipped = 0; + unsigned long long* num_uncomputed_skipped_ptr = (skip_uncomputed) + ? &num_uncomputed_skipped + : NULL; + if (show) { /** Print cache info table headers. **/ @@ -5629,7 +5641,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (UNLIKELY(xhForEach( &ClusterDriverCaches.SourceDataCache, cluster_i_printEntry, - (int)CI_SOURCE_DATA, &source_bytes, &skip_uncomputed, path + (int)CI_SOURCE_DATA, &source_bytes, num_uncomputed_skipped_ptr, path )) != 0) { mssError(0, "Cluster", "Failed to print source data cache."); @@ -5640,7 +5652,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (UNLIKELY(xhForEach( &ClusterDriverCaches.ClusterDataCache, cluster_i_printEntry, - (int)CI_CLUSTER_DATA, &cluster_bytes, &skip_uncomputed, path + (int)CI_CLUSTER_DATA, &cluster_bytes, num_uncomputed_skipped_ptr, path )) != 0) { mssError(0, "Cluster", "Failed to print cluster data cache."); @@ -5651,7 +5663,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree if (UNLIKELY(xhForEach( &ClusterDriverCaches.SearchDataCache, cluster_i_printEntry, - (int)CI_SEARCH_DATA, &search_bytes, &skip_uncomputed, path + (int)CI_SEARCH_DATA, &search_bytes, num_uncomputed_skipped_ptr, path )) != 0) { mssError(0, "Cluster", "Failed to print search data cache."); @@ -5663,7 +5675,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree + (unsigned int)ClusterDriverCaches.SourceDataCache.nItems + (unsigned int)ClusterDriverCaches.ClusterDataCache.nItems + (unsigned int)ClusterDriverCaches.SearchDataCache.nItems; - if (total_caches < skip_uncomputed) + if (skip_uncomputed && total_caches <= num_uncomputed_skipped) printf("All caches skipped, nothing to show...\n"); /** Print stats. **/ @@ -5680,8 +5692,8 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree printf("%-8s %-4d %-12s\n\n", "Total", total_caches, buf); /** Print skip stats (if anything was skipped.) **/ - if (skip_uncomputed > 1llu) - printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); + if (num_uncomputed_skipped > 0) + printf("Skipped %llu uncomputed caches.\n\n", num_uncomputed_skipped); return ret; } From 5a004548648ede32876760933f032e47dc39bf4c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:18:44 -0600 Subject: [PATCH 293/361] Fix typos and mistakes (thanks Opus 5.5). --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/include/xstring.h | 2 +- centrallix-lib/src/clusters.c | 27 +++--- centrallix-lib/src/xhash.c | 2 +- centrallix-lib/tests/test_clusters_00.c | 2 +- centrallix-os/cluster-schema.cluster | 4 +- centrallix-sysdoc/GCC_Dependencies.md | 2 +- centrallix-sysdoc/Libraries/mtask.md | 10 +-- centrallix-sysdoc/Libraries/mtsession.md | 16 ++-- centrallix-sysdoc/Libraries/newmalloc.md | 2 +- centrallix-sysdoc/Libraries/xarray.md | 12 +-- centrallix-sysdoc/Libraries/xhash.md | 4 +- centrallix-sysdoc/Libraries/xstring.md | 18 ++-- centrallix-sysdoc/OSDriver_Authoring.md | 54 ++++++------ centrallix-sysdoc/string_similarity.md | 4 +- centrallix/expression/exp_functions.c | 8 +- centrallix/include/double_metaphone.h | 4 +- centrallix/objectsystem/obj_datatypes.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 108 ++++++++++++----------- centrallix/utility/double_metaphone.c | 16 ++-- centrallix/utility/stparse.c | 6 +- 21 files changed, 154 insertions(+), 151 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index fb32423f8..8fbf87e2a 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -28,7 +28,7 @@ /* Module: clusters.c, clusters.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ -/* Description Clustering library used to cluster and search data with */ +/* Description: Clustering library used to cluster and search data with */ /* cosine or Levenshtein (aka. edit distance) similarity */ /* measures. Used by the "clustering driver". */ /* For more information on how to use this library, see */ diff --git a/centrallix-lib/include/xstring.h b/centrallix-lib/include/xstring.h index bc3f54dea..6ed2371a9 100644 --- a/centrallix-lib/include/xstring.h +++ b/centrallix-lib/include/xstring.h @@ -78,7 +78,7 @@ void xsFree(pXString this); *** from sizeof() at compile time (faster than measuring with strlen() at *** run time). *** - *** Note: The "" causes a compiler error a pointer is passed because it's + *** Note: The "" causes a compiler error if a pointer is passed because its *** length is not known at compile time. Use xsConcatenate() for these. *** *** Note: The length covers the whole literal, so an embedded null ('\0') is diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index e22d8d601..d0c29928b 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -25,7 +25,7 @@ /* Module: clusters.c, clusters.h */ /* Author: Israel Fuller */ /* Creation: September 29, 2025 */ -/* Description Clustering library used to cluster and search data with */ +/* Description: Clustering library used to cluster and search data with */ /* cosine or Levenshtein (aka. edit distance) similarity */ /* measures. Used by the "clustering driver". */ /* For more information on how to use this library, see */ @@ -268,7 +268,7 @@ caBuildVector(const char* str) trimmed_sparse_vector = nmSysRealloc(sparse_vector, trimmed_sparse_vector_size); if (UNLIKELY(trimmed_sparse_vector == NULL)) { - mssError(1, "CA", "nmSysMalloc(%zu) failed.", trimmed_sparse_vector_size); + mssError(1, "CA", "nmSysRealloc(%zu) failed.", trimmed_sparse_vector_size); goto err_free; } sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ @@ -299,7 +299,7 @@ caFreeVector(pVector sparse_vector) /*** Parse a token from a sparsely allocated vector and write its value to *** `token_value`. The number of dimensions consumed in the process is - *** written `dims_consumed`. + *** written to `dims_consumed`. *** *** @param token The sparse vector token to be parsed. *** @param dims_consumed The location to store the number of dimensions @@ -457,7 +457,7 @@ ca_i_sparseSimilarity(const pVector v1, const pVector v2) /** Optimization: Skip computing magnitudes for completely different vectors. **/ if (dot_product == 0u) return 0.0; - /** Return the difference score. **/ + /** Return the similarity score. **/ return (double)dot_product / (ca_i_magnitudeSparse(v1) * ca_i_magnitudeSparse(v2)); } @@ -466,7 +466,7 @@ ca_i_sparseSimilarity(const pVector v1, const pVector v2) *** *** @param v1 Sparse vector #1. *** @param v2 Sparse vector #2. - *** @returns Similarity between 0 and 1 where + *** @returns Difference between 0 and 1 where *** 1 indicates completely different and *** 0 indicates identical. ***/ @@ -499,7 +499,7 @@ ca_i_sparseSimilarityToCentroid(const pVector v1, const pCentroid c1) dim += dims_consumed; } - /** Return the difference score. **/ + /** Return the similarity score. **/ return dot_product / (ca_i_magnitudeSparse(v1) * ca_i_magnitudeDense(c1)); } @@ -507,7 +507,7 @@ ca_i_sparseSimilarityToCentroid(const pVector v1, const pCentroid c1) *** allocated centroid by subtracting their dot product from 1.0. *** *** @param v1 Sparse vector #1. - *** @param c1 Dense centroid #2. + *** @param c1 Dense centroid #1. *** @returns Difference between 0 and 1 where *** 1 indicates completely different and *** 0 indicates identical. @@ -616,7 +616,7 @@ caEditDist(const char* str1, const char* str2, const size_t str1_length, const s if (unsigned_result > INT_MAX) { fprintf(stderr, - "Warning: Integer overflow detected in caEditDist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", + "Warning: Integer overflow detected in caEditDist(\"%s\", \"%s\", %zu, %zu) = %u > %d\n", str1, str2, str1_length, str2_length, unsigned_result, INT_MAX ); } @@ -699,14 +699,14 @@ caCosCompare(void* v1, void* v2) *** either OR BOTH strings are NULL, this function returns `0.0`. *** *** @attention - Note: Unlike `caCosCompare()`, punctuation, whitespace, - *** etc. are NOT ignored. In fact, this functions supports strings that + *** etc. are NOT ignored. In fact, this function supports strings that *** contain ANY valid ASCII characters other than the NULL-terminator, *** which is used to terminate the string. *** *** @attention - This function takes `void*` instead of `char*` so that it *** can be used as the similarity function in the ca_search() function *** family without needing a messy typecast to avoid the compiler warning. - *** However, behavior is undefined if `v1` and `v2` are not `char*`s. + *** However, behavior is undefined if `s1` and `s2` are not `char*`s. *** *** @param s1 A `char*` to the first string to compare. *** @param s2 A `char*` to the second string to compare. @@ -879,7 +879,7 @@ ca_i_getClusterSize( *** each clustering iteration. If this is not met, the iterations stop. *** Pass -1.0 to disable this and iterate for as long as the centroids keep *** changing (or until `max_iter` is reached). - *** @param labels Initialized by this function to stores the final cluster + *** @param labels Initialized by this function to store the final cluster *** identities of the vectors after clustering is completed. Each value *** will be `0 <= n < num_clusters`. This buffer can be uninitialized. *** @param vector_sims An array of num_vectors elements, allocated by the @@ -910,7 +910,6 @@ caKmeans( if (UNLIKELY(false || vectors == NULL || labels == NULL - || vector_sims == NULL || num_vectors == 0u || num_clusters == 0u || max_iter == 0u @@ -1178,7 +1177,7 @@ caSlidingSearch( pairs = xaNew(guess_size); if (UNLIKELY(pairs == NULL)) { - mssError(1, "CA", "xaNew(%d)", guess_size); + mssError(1, "CA", "xaNew(%d) failed.", guess_size); goto err; } } @@ -1203,7 +1202,7 @@ caSlidingSearch( pPair pair = nmMalloc(pair_size); if (UNLIKELY(pair == NULL)) { - mssError(1, "CA", "nmMalloc(%zu)", pair_size); + mssError(1, "CA", "nmMalloc(%zu) failed.", pair_size); goto err_free; } pair->i = i; diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index 2fdeb7246..72b293bf4 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -339,7 +339,7 @@ xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, va_list), ...) *** behavior). *** @param free_fn A pointer to a free function, which is passed each hash *** entry struct and is responsible for freeing the key, the value, and - *** any data the they own (but not the passed entry itself). The function + *** any data they own (but not the passed entry itself). The function *** is also passed a `void*` argument, which is `free_arg`. *** @param free_arg The void pointer value passed to the free function. *** @returns 0 if successful, or diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c index d28052c99..86d84f3e9 100644 --- a/centrallix-lib/tests/test_clusters_00.c +++ b/centrallix-lib/tests/test_clusters_00.c @@ -60,7 +60,7 @@ static bool doTest(void) success &= ASSERT_EQL(caEditDist(str1, str2, 0, 0), 0, "%d"); /* Identical, very long strings (different pointers). */ success &= ASSERT_EQL(caEditDist(str2, str3, 0, 0), 133, "%d"); /* 133 edits. */ - /** Empty string comparsions. **/ + /** Empty string comparisons. **/ success &= ASSERT_EQL(caEditDist(str1, "", 0, 0), (int)strlen(str1), "%d"); success &= ASSERT_EQL(caEditDist(str2, "", 0, 0), (int)strlen(str2), "%d"); success &= ASSERT_EQL(caEditDist(str3, "", 0, 0), (int)strlen(str3), "%d"); diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index af69b23d5..019a6823d 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -25,7 +25,7 @@ file_name "system/cluster" num_clusters : uint > 1 // (probably a parameter) ?min_improvement : double && -1.0 <= x <= 1.0 // default: 0.0001 ?max_iterations : uint // default: 64 - ?window_size : uint > 0 // required for algorithm = sliding_window. + ?window_size : uint > 0 // required for algorithm = sliding-window. ?overlap_size : double && 0.0 <= x <= 1.0 // default: 0.0, only allowed for algorithm = k-means | k-means++ | k-medoids, not implemented ?seed : uint > 0 // default: Dynamic seed. @@ -41,7 +41,7 @@ file_name "system/cluster" } ... - search_name "system/search" + search_name "cluster/search" { source : string ⊂ [cluster_name, ...] similarity_measure : "cosine" | "levenshtein" diff --git a/centrallix-sysdoc/GCC_Dependencies.md b/centrallix-sysdoc/GCC_Dependencies.md index fde389c90..f4a26f7fb 100644 --- a/centrallix-sysdoc/GCC_Dependencies.md +++ b/centrallix-sysdoc/GCC_Dependencies.md @@ -2,7 +2,7 @@ Author: Israel Fuller -Date: Descember 4, 2025 +Date: December 4, 2025 ## Table of Contents - [GCC Dependencies](#gcc-dependencies) diff --git a/centrallix-sysdoc/Libraries/mtask.md b/centrallix-sysdoc/Libraries/mtask.md index 47510ca04..1fc2595a2 100644 --- a/centrallix-sysdoc/Libraries/mtask.md +++ b/centrallix-sysdoc/Libraries/mtask.md @@ -57,7 +57,7 @@ The `MTASK` module provides simple and easy TCP/IP connectivity. It includes ma ## netConnectTCP() ```c -pFile netConnectTCP(char* host_name, char* service_name, int flags); +pFile netConnectTCP(const char* host_name, const char* service_name, int flags); ``` This function creates a client socket and connects it to a server on a given TCP service/port and host name. It takes the following three parameters: - `host_name`: The host name or ascii string for the host's ip address. @@ -73,17 +73,17 @@ This function returns the connection file descriptor if successful, or `NULL` if ```c int netCloseTCP(pFile net_filedesc, int linger_msec, int flags); ``` -This function closes a network connection (either a TCP listening, server, or client socket). It will also optionally waits up to `linger_msec` milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If `linger_msec` is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately prior to the close, offering to linger for a few more seconds (perhaps 5 or 10 by specifying 5000 or 10000 msec) can be a good idea. +This function closes a network connection (either a TCP listening, server, or client socket). It will also optionally wait up to `linger_msec` milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If `linger_msec` is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately prior to the close, offering to linger for a few more seconds (perhaps 5 or 10 by specifying 5000 or 10000 msec) can be a good idea. ## fdWrite() ```c -int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags); +int fdWrite(pFile filedesc, const char* buffer, int length, int offset, int flags); ``` -This function writes data to an open file descriptor, from a given `buffer` and `length` of data to write. It also takes an optional seek `offset` and and `flags`, which can be zero or more of: +This function writes data to an open file descriptor, from a given `buffer` and `length` of data to write. It also takes an optional seek `offset` and `flags`, which can be zero or more of: - `FD_U_NOBLOCK` - If the write can't be performed immediately, don't perform it at all. - `FD_U_SEEK` - The `offset` value is valid. Seek to it before writing. Not allowed for network connections. -- `FD_U_PACKET` - *ALL* of the data specified by `length` in `buffer` must be written. Normal `write()` semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. +- `FD_U_PACKET` - *ALL* the data specified by `length` in `buffer` must be written. Normal `write()` semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. ## fdRead() diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md index 50c8d66c5..e6ec53cf7 100644 --- a/centrallix-sysdoc/Libraries/mtsession.md +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -69,26 +69,26 @@ This function initializes the session manager and sets global variables used in ```c char* mssUserName(); ``` -This function returns the current user name, or `NULL` an error occurs. +This function returns the current user name, or `NULL` if an error occurs. ## mssPassword() ```c char* mssPassword(); ``` -This function returns the current user's password that they used to log into Centrallix, or `NULL` an error occurs. +This function returns the current user's password that they used to log into Centrallix, or `NULL` if an error occurs. ## mssSetParam() ```c -int mssSetParam(char* paramname, char* param); +int mssSetParam(char* paramname, void* param); ``` -This function sets the session parameter of the provided name (`paramname`) to the provided value (`param`). The parameter MUST be a string value. This function returns 0 if successful, or -1 an error occurs. +This function sets the session parameter of the provided name (`paramname`) to the provided value (`param`). The parameter MUST be a string value. This function returns 0 if successful, or -1 if an error occurs. ## mssGetParam() ```c -char* mssGetParam(char* paramname); +void* mssGetParam(char* paramname); ``` Returns the value of a session parameter of the provided name (`paramname`), or `NULL` if an error occurs. Common session parameters include: - `dfmt`: The current date format. @@ -100,18 +100,18 @@ Returns the value of a session parameter of the provided name (`paramname`), or ```c void mssError(int clr, char* module, char* message, ...); ``` -Formats and caches an error message for return to the user. This function returns 0 if successful, or -1 if an error occurred. +Formats and caches an error message for return to the user. | Parameter | Type | Description | --------- | ------------- | ------------ -| crl | int | If set to 1, all previous error messages are cleared. Set this when the error is initially discovered and no other module is likely to have made a relevant `mssError()` call for the current error. +| clr | int | If set to 1, all previous error messages are cleared. Set this when the error is initially discovered and no other module is likely to have made a relevant `mssError()` call for the current error. | module | char* | A two-to-five letter abbreviation of the module reporting the error. This is typically the module or driver's abbreviation prefix in full uppercase letters (although that is not required). This is intended to help the developer find the source of the error faster. | message | char* | A string error message, accepting format specifiers like `%d` and `%s` which are supplied by the argument list, similar to `printf()`. | ... | ... | Parameters for the formatting. Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch the errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. -The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ as an expression being compiled? etc.). However, it is far easier to give too little information that too much, so it can often be best to err on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. +The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ was an expression being compiled? etc.). However, it is far easier to give too little information than too much, so it can often be best to err on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. - 📖 **Note**: The `mssError()` routines do not cause the calling function to return or exit. The function must still clean up after itself and return an appropriate value (such as `-1` or `NULL`) to indicate failure. diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index 3e4b7541a..0ccab1603 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -111,7 +111,7 @@ NewMalloc subsystem statistics: ```c void nmRegister(int size, char* name); ``` -Registers an inteligent name for block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production usecases, but using it can make tracking down memory leaks easier. +Registers an intelligent name for block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production use cases, but using it can make tracking down memory leaks easier. This function is usually called in a module's `Initialize()` function on each of the structures the module uses internally. diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 47857d700..3ccf4760a 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -127,12 +127,12 @@ This function returns 0 on success, or -1 if an error occurs. ```c int xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen); ``` -This function adds an item to a sorted XArray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers are stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). +This function adds an item to a sorted XArray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). ## xaAddItemSortedInt32() ```c -int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset) +int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset); ``` This function works the same as [`xaAddItemSorted()`](#xaadditemsorted), except that the sort key is a signed 32-bit integer (an `int` on supported platforms) located `keyoffset` bytes into the structure, rather than a byte string of a given length. This function _will_ follow the pointers stored in the array, so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). Because the comparison is numeric rather than bytewise, the ordering is correct for negative values and is unaffected by the host byte order. @@ -141,7 +141,7 @@ This function returns the index of the last item in the array (not the index the ## xaGetItem() ```c -void* xaGetItem(pXArray this, int index) +void* xaGetItem(pXArray this, int index); ``` This function returns an item given a specific index into the XArray, or `NULL` if the index is out of bounds. If the bounds check needs to be omitted for performance and the caller can otherwise verify that no out of bounds read is possible (e.g. because they are iterating from 0 to `XArray->nItems`), the caller should access `XArray->Items` directly. Either way, the result may need to be typecasted or stored in a variable of a specific type for it to be useable, and error checking for `NULL` values should be used. @@ -179,7 +179,7 @@ This function works the same as [`xaFindItem()`](#xafinditem), however it iterat ## xaRemoveItem() ```c -int xaRemoveItem(pXArray this, int index) +int xaRemoveItem(pXArray this, int index); ``` This function removes an item from the XArray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. @@ -207,13 +207,13 @@ This function returns the number of items in the XArray, or -1 on error. It is ## xaInsertBefore() ```c -int xaInsertBefore(pXArray this, int index, void* item) +int xaInsertBefore(pXArray this, int index, void* item); ``` This function inserts an item before the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. ## xaInsertAfter() ```c -int xaInsertAfter(pXArray this, int index, void* item) +int xaInsertAfter(pXArray this, int index, void* item); ``` This function inserts an item after the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index of the inserted value on success, or -1 if an error occurs. diff --git a/centrallix-sysdoc/Libraries/xhash.md b/centrallix-sysdoc/Libraries/xhash.md index a06907686..7124f31be 100644 --- a/centrallix-sysdoc/Libraries/xhash.md +++ b/centrallix-sysdoc/Libraries/xhash.md @@ -70,7 +70,7 @@ Initialize the random number table for hash computation, returning 0 on success ```c int xhInit(pXHashTable this, int rows, int keylen); ``` -This function initializes a hash table, setting the number of rows and the key length. Specify a `keylen` of 0 for for variable length keys (aka. null-terminated strings). The `rows` should be an odd number, preferably prime (although that isn't required). `rows` **SHOULD NOT** be a power of 2. Providing this value allows the caller to optimize it based on how much data they expect to be stored in the hash table. If this value is set to 1, the hash search degenerates to a linear array search with extra overhead. Thus, the value should be large enough to comfortably accommodate the elements with minimal collisions. Typical values include 31, 251, or 255 (though 255 is not prime). +This function initializes a hash table, setting the number of rows and the key length. Specify a `keylen` of 0 for variable length keys (aka. null-terminated strings). The `rows` should be an odd number, preferably prime (although that isn't required). `rows` **SHOULD NOT** be a power of 2. Providing this value allows the caller to optimize it based on how much data they expect to be stored in the hash table. If this value is set to 1, the hash search degenerates to a linear array search with extra overhead. Thus, the value should be large enough to comfortably accommodate the elements with minimal collisions. Typical values include 31, 251, or 255 (though 255 is not prime). ## xhDeInit() @@ -84,7 +84,7 @@ This function deinitializes a hash table struct, freeing all rows. Note that th ```c int xhAdd(pXHashTable this, char* key, char* data); ``` -Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that they item is hashed, as they are assumed to be the responsibility of the caller. This function returns 0 on success, or -1 if an error occurs. +Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that the item is hashed, as they are assumed to be the responsibility of the caller. This function returns 0 on success, or -1 if an error occurs. ## xhRemove() diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 19570e618..6632bb9c5 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -75,12 +75,12 @@ The xstring (xs) module is used for managing growable strings. It is based on a - 📖 **Note**: The contents of the XString can be easily referenced with the `xstring->String` field in the xstring struct. -- ⚠️ **Warning**: Do not mix calls to [`xsNew()`](#xsnew)/[`xsFree()`](#xsfree) with calls to [`xsInit()`](#xsinit)/[`xsDeInit()`](#xsdeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. +- ⚠️ **Warning**: Do not mix calls to [`xsNew()`](#xsnew)/[`xsFree()`](#xsfree) with calls to [`xsInit()`](#xsinit)/[`xsDeInit()`](#xsdeinit). Every struct allocated using new must be freed, and every struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. ## xsNew() ```c -pXString xsNew() +pXString xsNew(); ``` This function allocates a new XString structure to contain a new, empty string, initializing it with [`xsInit()`](#xsinit) before returning. It uses [`nmMalloc()`](newmalloc.md#nmmalloc) because the XString struct is always a consistent size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. @@ -138,11 +138,11 @@ This function concatenates the `text` string onto the end of the XString's value XString xs; xsInit(&xs); xsConcatenate(&xs, "This is the first sentence. ", -1); - int offset = xsStringEnd(&xs) - xs->String; /* Stores index offset. */ + int offset = xsStringEnd(&xs) - xs.String; /* Stores index offset. */ xsConcatenate(&xs, "This is the second sentence.", -1); /** Print will probably work fine. **/ - printf("A pointer to the second sentence is '%s'\n", xs->String + offset); + printf("A pointer to the second sentence is '%s'\n", xs.String + offset); ``` @@ -227,21 +227,21 @@ This function trims whitespace characters (spaces, tabs, newlines, and line feed ## xsFind() ```c -int xsFind(pXString this, char* find, int findlen, int offset) +int xsFind(pXString this, char* find, int findlen, int offset); ``` This function searches for a specific string (`find`) in the xstring, starting at the provided `offset`. `findlen` is the length of the provided string, allowing it to include null characters (pass -1 to have the length calculated using `strlen(find)`). This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). ## xsFindRev() ```c -int xsFindRev(pXString this, char* find, int findlen, int offset) +int xsFindRev(pXString this, char* find, int findlen, int offset); ``` This function works the same as [`xsFind()`](#xsfind) except that it searches from the end of the string, resulting in better performance if the value is closer to the end of the string. This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). ## xsSubst() ```c -int xsSubst(pXString this, int offset, int len, char* rep, int replen) +int xsSubst(pXString this, int offset, int len, char* rep, int replen); ``` This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsreplace), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified in order to handle null characters. Pass -1 for `len` or `replen` to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. @@ -288,7 +288,7 @@ This function returns the stored string after checking for various errors, or re ## xsLength() ```c -xsLength(pXString this); +int xsLength(pXString this); ``` This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. @@ -297,7 +297,7 @@ This function returns the length of the string in constant time (since this valu ```c char xsCharAt(pXString this, int pos); ``` -This function returns the character at `pos`, or `'\0'` if `pos` falls outside the string. Use it instead of indexing the buffer from [`xsString()`](#xsstring), which is not bounds checked. Note that a position past the end and a negative position both read as `'\0'`, so a caller walking off either end of the string gets the same result as they would from reading the~~~~ null-terminator. +This function returns the character at `pos`, or `'\0'` if `pos` falls outside the string. Use it instead of indexing the buffer from [`xsString()`](#xsstring), which is not bounds checked. Note that a position past the end and a negative position both read as `'\0'`, so a caller walking off either end of the string gets the same result as they would from reading the null-terminator. ## xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf() diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index fcc750757..61a530935 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -161,7 +161,7 @@ The structure of the subtree beneath the node object is entirely up to the drive - Content, which can be read similar to reading a file. - Query data, allowing the object to be queried for information. -Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradigm, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traversal of its child objects, although queries are not required to be implemented this way. +Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradigm, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traverse its child objects, although queries are not required to be implemented this way. Below is an example of the Sybase driver's node object and its subtrees of child objects (defined in `objdrv_sybase.c`): @@ -511,7 +511,7 @@ The `Write()` function is very similar to the `Read()` function above, allowing ```c void* xxxOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); ``` -The `OpenQuery()` function opens a new query instance struct for fetching query results from a specific driver instance. Queries are often used to enumerate an object's child objects, although this is not a requirement. Queries may include specific criteria, and the driver may decide to intelligently handle them (either manually or, more often, by passing them on to a lower level driver or database) or simply to enumerating all results with its query functions. In the latter case, the OSML layer will filter results and only return objects that match the criteria to the user. +The `OpenQuery()` function opens a new query instance struct for fetching query results from a specific driver instance. Queries are often used to enumerate an object's child objects, although this is not a requirement. Queries may include specific criteria, and the driver may decide to intelligently handle them (either manually or, more often, by passing them on to a lower level driver or database) or simply enumerate all results with its query functions. In the latter case, the OSML layer will filter results and only return objects that match the criteria to the user. `OpenQuery()` is passed three parameters: @@ -529,7 +529,7 @@ The `query : pObjQuery` parameter contains several useful fields: | query->SortBy[] | void*[] (pExpression[]) | An array of expressions giving the various components of the sorting criteria. | query->Flags | int | The driver should set and/or clear the `OBJ_QY_F_FULLQUERY` and `OBJ_QY_F_FULLSORT` flags, if needed. -The `OBJ_QY_F_FULLQUERY` flag indicates that the driver will handle the full `where` clause specified in `query->Tree`. Even if this flag is not specified, the driver is still free to use the provided `where` clause to pre-filter data, which improves performance when the Object System does its final filtering. However, setting this flag disables the Object System filtering because it promises that the driver will _always_ handle _all_ filtering for _every_ valid queries. +The `OBJ_QY_F_FULLQUERY` flag indicates that the driver will handle the full `where` clause specified in `query->Tree`. Even if this flag is not specified, the driver is still free to use the provided `where` clause to pre-filter data, which improves performance when the Object System does its final filtering. However, setting this flag disables the Object System filtering because it promises that the driver will _always_ handle _all_ filtering for _every_ valid query. The `OBJ_QY_F_FULLSORT` flag indicates that the driver will handle all sorting for the data specified in `query->SortBy[]`. @@ -547,7 +547,7 @@ Deletes results in the query result set, optionally matching a certain criteria. | Parameter | Type | Description | --------- | ------------- | ------------ -| qy_v | void* | A query instance pointer (returned from `QueryOpen()`). +| qy_v | void* | A query instance pointer (returned from `OpenQuery()`). | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. `QueryDelete()` returns 0 to indicate a successful deletion, or -1 to indicate failure, in which case `mssError()` should be called before returning. @@ -647,7 +647,7 @@ This function should return `DATA_T_UNAVAILABLE` if the requested attribute does For example, calling the following on any driver should return `DATA_T_STRING`. ```c -int datatype = driver->GetAttrType(inf_v, 'name', oxt); +int datatype = driver->GetAttrType(inf_v, "name", oxt); ``` @@ -655,7 +655,7 @@ int datatype = driver->GetAttrType(inf_v, 'name', oxt); ```c int xxxGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); ``` -The `GetAttrValue()` function takes four parameters: +The `GetAttrValue()` function takes five parameters: | Parameter | Type | Description | --------- | ------------- | ------------ @@ -691,7 +691,7 @@ This function should return 0 on success, 1 if the value is `NULL` or undefined printf("Object name: \"%s\"\n", name); ``` -- 📖 **Note**: In legacy code, a type cast `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more information). +- 📖 **Note**: In legacy code, a type cast `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible with the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more information). ### Function: SetAttrValue() @@ -774,7 +774,7 @@ The `PresentationHints()` function takes three parameters: | attr_name | char* | The name of the requested attribute. | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -The returns a new pObjPresentationHints struct on success, or NULL to indicate an error, in which case `mssError()` should be called before returning. This struct should be allocated using `nmMalloc()`, and memset to zero, like this: +This function returns a new pObjPresentationHints struct on success, or NULL to indicate an error, in which case `mssError()` should be called before returning. This struct should be allocated using `nmMalloc()`, and memset to zero, like this: ```c pObjPresentationHints hints = nmMalloc(sizeof(ObjPresentationHints)); if (hints == NULL) goto error_handling; @@ -792,7 +792,7 @@ The return value, `hints : ObjPresentationHints`, contains the following useful - `hints->AllowChars : char*`: An array of all valid characters for a string attribute, NULL to allow all characters. - `hints->BadChars : char*`: An array of all invalid characters for a string attribute. If a character appears in both `hints->BadChars` and `hints->AllowChars`, the character should be rejected. - `hints->Length : int`: The maximum length of data that can be included in a string attribute. -- `hints->VisualLength : int`: The length that the attribute should be displayed if it is show to the user. +- `hints->VisualLength : int`: The length that the attribute should be displayed if it is shown to the user. - `hints->VisualLength2 : int`: The number of lines to use in a multi-line edit box for the attribute. - `hints->BitmaskRO : unsigned int`: If the value is an integer that represents a bit mask, _this_ bit mask shows which bits of that bitmask are read-only. - `hints->Style : int`: Style flags, documented below. @@ -805,10 +805,10 @@ The return value, `hints : ObjPresentationHints`, contains the following useful - ⚠️ **Warning**: Behavior is undefined if: - The data is longer than length. -The `hints->Style` field can be set with several useful flags. To specify that a flag is not set (e.g. to specify explicitly that a field does allow `NULL`s), set the corresponding bit in the `hints->StyleMask` field while leaving the the bit in the `hints->Style` field set to 0. +The `hints->Style` field can be set with several useful flags. To specify that a flag is not set (e.g. to specify explicitly that a field does allow `NULL`s), set the corresponding bit in the `hints->StyleMask` field while leaving the bit in the `hints->Style` field set to 0. The following macros are provided for setting style flags: -- `OBJ_PH_STYLE_BITMASK`: The items in `hints->EnumList` or `hints->EnumQuery` are bit masked. +- `OBJ_PH_STYLE_BITMASK`: The items in `hints->EnumList` or `hints->EnumQuery` are bit-masked. - `OBJ_PH_STYLE_LIST`: List-style presentation should be used for the values of an enum attribute. - `OBJ_PH_STYLE_BUTTONS`: Radio buttons or check boxes should be used for the presentation of enum attribute values. - `OBJ_PH_STYLE_NOTNULL`: The attribute does not allow `NULL` values. @@ -940,7 +940,7 @@ In this case, the new structure file will have the type: `"system/structure"`. ### st_node: snWriteNode() ```c -int snWriteNode(pSnNode node); +int snWriteNode(pObject obj, pSnNode node); ``` The `snWriteNode()` function writes a node's internal data back out to the node file, if the node's status (`node->Status`) is set to `SN_NS_DIRTY`. Otherwise, `snWriteNode()` does nothing. @@ -960,7 +960,7 @@ int snGetSerial(pSnNode node); ``` The `snGetSerial()` function returns the serial number of the node. -Each time the node is re-read because of modifications to the node file or is written with because `snWriteNode()` was called after modifications to the internal structure, the serial number is increased. This is a good way for a driver to determine if the node file has changed so it can refresh internal cached data. +Each time the node is re-read because of modifications to the node file or is written because `snWriteNode()` was called after modifications to the internal structure, the serial number is increased. This is a good way for a driver to determine if the node file has changed so it can refresh internal cached data. ### st_node: snGetLastModification() @@ -983,9 +983,9 @@ To use this module, include the file `stparse.h`, which includes the following f ```c int stStructType(pStructInf this); ``` -The `stStructType()` function returns the struct type of the past `pStructInf` parameter, which is either `ST_T_ATTRIB` or `ST_T_SUBGROUP` (see above). +The `stStructType()` function returns the struct type of the passed `pStructInf` parameter, which is either `ST_T_ATTRIB` or `ST_T_SUBGROUP` (see above). -- ⚠️ **Warning**: The node object root of type `ST_T_STRUCT` will return `ST_T_SUBGROUP` from this function. In most cases, treating this node as ust another subgroup simplifies logic for the caller. However, if you wish to avoid this behavior, read `inf->Type` (see [stparse: Using Fields Directly](#stparse-using-fields-directly) for more info). +- ⚠️ **Warning**: The node object root of type `ST_T_STRUCT` will return `ST_T_SUBGROUP` from this function. In most cases, treating this node as just another subgroup simplifies logic for the caller. However, if you wish to avoid this behavior, read `inf->Type` (see [stparse: Using Fields Directly](#stparse-using-fields-directly) for more info). ### stparse: stLookup() @@ -1003,7 +1003,7 @@ This function gets the value of the given attribute in an `ST_T_ATTRIB` node. I This function returns -1 if the attribute value did not exist, if the wrong type was requested, or if 'inf' was `NULL`. -It is common practice to use `stLookup()` and `stAttrValue()` or `stGetExpression()` (see below) together to retrieve values, for example (where `inf` is a `pStructInfo` variable from somewhere): +It is common practice to use `stLookup()` and `stAttrValue()` or `stGetExpression()` (see below) together to retrieve values, for example (where `inf` is a `pStructInf` variable from somewhere): ```c char* ptr; @@ -1057,7 +1057,7 @@ This function is used to free a `StructInf` tree node. This also recursively fr ### stparse: Using Fields Directly It is also common practice to bypass the stparse functions entirely and access the elements of the `StructInf` struct directly, which is allowed. (See `stparse.h` for more information about this structure.) -For example (assuming `inf` is a `pStructInfo` variable in scope): +For example (assuming `inf` is a `pStructInf` variable in scope): ```c for (unsigned int i = 0u; i < inf->nSubInf; i++) { @@ -1131,14 +1131,14 @@ This function is similar to [`expCompileExpression()`](#expcompileexpression), e ### expPodToExpression() ```c -pExpression expPodToExpression(pObjData pod, int type, pExpression provided_exp) +pExpression expPodToExpression(pObjData pod, int type, pExpression provided_exp); ``` This function builds an expression node from a single piece of data, passed using the `pObjData` of the given datatype. This function can be used to initialize a provided expression (`provided_exp`), or it will allocate a new one if none is provided (aka. `provided_exp` is `NULL`). For example, the following code creates an expression representing the integer 1. ```c int value = 1; -pExpression exp = expPodToExpression(POD(value), DATA_T_INTEGER, NULL); +pExpression exp = expPodToExpression(POD(&value), DATA_T_INTEGER, NULL); ``` This function returns a pointer to the expression if successful, or `NULL` if an error occurs. @@ -1226,7 +1226,7 @@ The functions use the following parameters: - `v : void*` is the object provided in `expAddParamToList()` (or a similar function). - `attr_name : char*` is the string name for the requested attribute. - `datatype : int` is the data type for the requested attribute. -- `val : pObjectData` is either a buffer in which to store the requested data (`cluster_i_getParamValue()`) or a buffer containing data that will be copied to the parameter `cluster_i_setParamValue()`. +- `val : pObjData` is either a buffer in which to store the requested data (`cluster_i_getParamValue()`) or a buffer containing data that will be copied to the parameter `cluster_i_setParamValue()`. The functions return the following values: - The `cluster_i_getParamType()` function returns the datatype on success (e.g. `DATA_T_INTEGER`), or -1 if an error occurs. @@ -1237,7 +1237,7 @@ The `expSetParamFunctions()` function returns 0 if the functions were set succes ### expReverseEvalTree() ```c -int expReverseEvalTree(pExpression tree, pParamObjects objlist)l +int expReverseEvalTree(pExpression tree, pParamObjects objlist); ``` This function reverse-evaluates a tree. @@ -1329,7 +1329,7 @@ This function opens a lexer session, using a file descriptor as its source. Som | `MLX_F_NODISCARD` | Attempt to unread unused buffered data rather than discarding it, allowing the calling function to continue reading with `fdRead()` or another lexer session after the last token is read and the session is closed. The lexer `fdRead()`s in 2k or so chunks for performance, and normally discards this data when done, causing future file descriptors to start at an undefined file location. | `MLX_F_DBLBRACE` | Treat `{{` and `}}` as double brace tokens, not two single brace tokens. | `MLX_F_NOUNESC` | Do not remove escapes in strings. -| `MLX_F_SSTRING` | Differentiate between strings values using `""` and `''`. +| `MLX_F_SSTRING` | Differentiate between string values using `""` and `''`. This function returns a pointer to the new lexer session if successful, or `NULL` if an error occurs. @@ -1391,7 +1391,7 @@ Returns the type of the next token in the token stream. Valid token types are: ```c char* mlxStringVal(pLxSession this, int* alloc); ``` -This function gets the string value of the current token. If `alloc` is `NULL`, an internal buffer is returned, which the caller _should not free_. Fails and returns null if the A token longer than 255 characters (`MLX_STRVAL`) does not fit that buffer, so the call fails and returns `NULL`. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always_ allocate memory for the string, whether long or short. +This function gets the string value of the current token. If `alloc` is `NULL`, an internal buffer is returned, which the caller _should not free_. A token longer than 255 characters (`MLX_STRVAL`) does not fit that buffer, so the call fails and returns `NULL`. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always_ allocate memory for the string, whether long or short. This routine works no matter what the token type, and returns a string representation of the token if not `MLX_TOK_STRING`. @@ -1470,13 +1470,13 @@ MLX: Error at line ## ## VII Driver Testing -This section contains a list of things that can be done to test an objectsystem driver and ensure that it preforms all basic operations correctly, using the [test_obj command line interface](https://www.centrallix.net/docs/docs.php?t=1.3.1%20test_obj%20Command-Line). +This section contains a list of things that can be done to test an objectsystem driver and ensure that it performs all basic operations correctly, using the [test_obj command line interface](https://www.centrallix.net/docs/docs.php?t=1.3.1%20test_obj%20Command-Line). It is strongly recommended to test for invalid reads, writes, frees, and memory leaks during each of these by watching memory utilization using nmDeltas() during repetitive operations (e.g., nmDeltas(), open, close, nmDeltas(), open, close, and then nmDeltas() again). Testing for more general memory bugs using the "valgrind" tool is also strongly encouraged, via running these various tests in test_obj while test_obj is running under valgrind. To properly test under Valgrind, centrallix-lib must be compiled with the configure flag `--enable-valgrind-integration` turned on. This disables `nmMalloc()` block caching (so that valgrind can properly detect memory leaks and free memory reuse), and it provides better information to valgrind's analyzer regarding MTASK threads. -Magic number checking on data structures is encouraged. To use magic number checking, determine a magic number value for each of your structures, and add a #define for that constant in your code. The magic number should be a 32-bit integer, possibly with 0x00 in either the 2nd or 3rd byte of the integer. Many existing magic number values can be found in [magic.h](../centrallix-lib/include/magic.h). The 32-bit integer is placed as the first element of the structure, and set using the `SETMAGIC()` macro, then tested using the macros `ASSERTMAGIC()` macro or, less commonly, `ASSERTNOTMAGIC()`. Common times to `ASSERTMAGIC()` include: +Magic number checking on data structures is encouraged. To use magic number checking, determine a magic number value for each of your structures, and add a #define for that constant in your code. The magic number should be a 32-bit integer, possibly with 0x00 in either the 2nd or 3rd byte of the integer. Many existing magic number values can be found in [magic.h](../centrallix-lib/include/magic.h). The 32-bit integer is placed as the first element of the structure, and set using the `SETMAGIC()` macro, then tested using the `ASSERTMAGIC()` macro or, less commonly, `ASSERTNOTMAGIC()`. Common times to `ASSERTMAGIC()` include: - Any time a pointer to the structure crosses an interface boundary. - At the entry to internal methods/functions. - When traversing linked lists of data structures. @@ -1507,7 +1507,7 @@ The term "**MAY**" refers to optional, but permissible, behavior. ### B. Attributes -1. The driver MUST NOT return system attributes (name, inner_type, etc) when enumerating with `xxxGetFirst()`/`xxxNextAttr()`. +1. The driver MUST NOT return system attributes (name, inner_type, etc) when enumerating with `xxxGetFirstAttr()`/`xxxGetNextAttr()`. 2. The driver MAY choose not to handle `xxxGetAttrType` on the system attributes. The OSML handles this. @@ -1556,7 +1556,7 @@ The term "**MAY**" refers to optional, but permissible, behavior. 6. Drivers which connect to resources which are able to perform sorting and/or selection (filtering) of records or objects SHOULD use the [`OBJ_QY_F_FULLSORT`](#function-openquery) and [`OBJ_QY_F_FULLQUERY`](#function-openquery) flags. Further, they SHOULD pass on the sorting and filtering expressions to the remote resource so that resource can optimize sorting and/or filtering as needed. -7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then `OBJ_QY_F_FULLSORT` and/or `OBJ_QY_F_FULL`- QUERY MUST NOT be used. However, the remote resource MAY still provide partial sorting and/or selection of data. +7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then `OBJ_QY_F_FULLSORT` and/or `OBJ_QY_F_FULLQUERY` MUST NOT be used. However, the remote resource MAY still provide partial sorting and/or selection of data. 8. Drivers SHOULD NOT use `OBJ_QY_F_FULLSORT` and `OBJ_QY_F_FULLQUERY` if there is no advantage to letting the resource perform these operations (usually, however, if the resource provides such functionality, there is advantage to letting the resource perform those operations. However, the coding burden to provide the filtering and sorting expressions to the resource, and in the correct format for the resource, may be not worth the work). diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 7717a2a8c..7b4b5f7da 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -87,7 +87,7 @@ const char BOUNDARY_CHAR = ('a' - 1); // aka. '`' Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296` possible character pairs. If the number of characters in the `CHAR_SET` ever needed to be expanded - for example, to include all UTF-8 characters - this number would quickly explode exponentially to utterly infeasible proportions. Thus, a hashing algorithm is employed to hash each character pair down to a more reasonable number of dimensions (which can be accessed with the `CA_NUM_DIMS` macro). ### String Vectors -Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies to based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). +Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. @@ -148,7 +148,7 @@ For problems where a sorting algorithm exists which can mitigate the above issue ### K-means Fuzzy Clustering One of the biggest downsides with k-means is that it creates very arbitrary boundaries between clusters. Elements on either side of these boundaries may be highly similar, but if comparisons only occur within a cluster, these similar entries will be missed. The problem becomes more extreme as a higher k value (more clusters) is used, creating more arbitrary boundaries. This drawback is probably the main reason that clustering sacrifices some accuracy over searching every element. -Running the entire search multiple types may allow some of these to be found because the initial cluster locations are random. This approach is partially implemented for duplicate searching because the algorithm runs nightly anyway, so a simple upsert (**UP**date existing entries; in**SERT** new entries) slightly reduces this problem. However, this solution is obviously far from ideal. +Running the entire search multiple times may allow some of these to be found because the initial cluster locations are random. This approach is partially implemented for duplicate searching because the algorithm runs nightly anyway, so a simple upsert (**UP**date existing entries; in**SERT** new entries) slightly reduces this problem. However, this solution is obviously far from ideal. If the clustering could be expanded with an additional step that makes clusters larger, adding elements from other clusters to them, this might effectively mitigate the issue. It may also allow developers to use larger numbers of clusters, improving performance as well as accuracy. Further research is needed to verify the effectiveness of this approach before an implementation is written. diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index c44233697..e20a0bc5d 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -83,11 +83,11 @@ *** @param Datatypes An array of datatypes (terminated with a -1). Set to NULL *** to accept any datatype as valid for this argument. *** @param Flags Flags to require other properties about an argument. If the - *** flag a required behavior for specific types, the requirement will be + *** flag is a required behavior for specific types, the requirement will be *** skipped for other types. *** *** Valid Flags: - *** - `EXP_ARG_OPTIONAL`: The arg is optional. It is not valid a required + *** - `EXP_ARG_OPTIONAL`: The arg is optional. It is not valid to have a required *** argument after an optional one. *** - `EXP_ARG_NOT_NULL`: Expect the arg to not be null. *** - `EXP_ARG_FORCE_TYPE`: Run type check on null args (not recommended). @@ -1589,7 +1589,7 @@ int exp_fn_reverse(pExpression tree, pParamObjects objlist, pExpression i0, pExp return 0; } -/** Leading zero trim. */ +/** Leading zero trim. **/ int exp_fn_lztrim(pExpression tree) { @@ -1682,7 +1682,7 @@ exp_fn_ltrim(pExpression tree) *** that isn't trimmed. ***/ - /** Iterate until we find the a character that isn't a space. **/ + /** Iterate until we find a character that isn't a space. **/ /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ while (*str == ' ') str++; diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h index 3ba0972c7..78eef04f1 100644 --- a/centrallix/include/double_metaphone.h +++ b/centrallix/include/double_metaphone.h @@ -23,7 +23,7 @@ /* has been included below for the convenience of the reader. This */ /* information was collected and saved on September 5th, 2025 and may */ /* differ from current information. For the most up to date copy of */ -/* this information, please use the link provided above. */ +/* this information, please use the link provided above. */ /* */ /* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ /* */ @@ -71,7 +71,7 @@ /* Lawrence Philips which he published in the June, 2000 */ /* issue of C/C++ Users Journal. Double Metaphone is an */ /* improved version of the original Metaphone algorithm */ -/* written by Philips'. This implementation was written by */ +/* written by Philips. This implementation was written by */ /* Maurice Aubrey for C/C++ with bug fixes provided by */ /* Kevin Atkinson. It was revised by Israel Fuller to */ /* better align with the Centrallix coding style and */ diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index 23b8b74f1..e63cfbdf5 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -132,7 +132,7 @@ char* obj_default_null_fmt = "NULL"; /*** Parse the given string into a datatype. The case of the first character *** is ignored, but all other characters must be capitalized correctly. *** - *** @attention - This function is optimized to prevent performance hits + *** @attention - This function is optimized to prevent performance hits in *** situations where it may need to be called many thousands of times. *** *** @param str The string to be parsed to a datatype. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index d46abf311..c4b13e8b0 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -428,9 +428,9 @@ typedef struct *** @param SourceData Pointer to the source data that this cluster uses. *** *** @skip --> Computed Data. - *** @param Clusters An array of length nClusters, NULL if the clusters have - *** been computed yet. - *** @param Sims An array of nClusters elements, where index i stores the + *** @param Clusters An array of length nClusters, NULL if the clusters have + *** not been computed yet. + *** @param Sims An array of nDatas elements, where index i stores the *** similarity of vector i to its assigned cluster, NULL if the clusters *** have not been computed yet. *** @@ -522,18 +522,18 @@ typedef struct _SEARCH *** @note When a .cluster file is opened, there will be only one node for *** that file. However, in the course of the query, many driver instance *** structs using this one node may be created thanks to functions such as - ***`clusterQueryFetch()`, and closed by with functions like `clusterClose()`. + *** `clusterQueryFetch()`, and closed with functions like `clusterClose()`. *** *** @param SourceData Data from the provided source. *** @param Params A pParam array storing the params in the .cluster file. *** @param nParams The number of specified params. *** @param ParamList A "scope" for resolving parameter values during parsing. *** @param ClusterDatas A pCluster array for the clusters in the .cluster file, - *** NULL if `nClusters == 0`. + *** NULL if `nClusterDatas == 0`. *** @param nClusterDatas The number of specified clusters. *** @param SearchDatas A SearchData array for the searches in the .cluster file. - *** @param nSearches The number of specified searches. - *** @param nSearchDatas The parent object used to open this NodeData instance. + *** @param nSearchDatas The number of specified searches. + *** @param Parent The parent object used to open this NodeData instance. *** @param OpenCount The number of open driver instances that are using the *** NodeData struct. When this reaches 0, the struct should be freed. *** @param Magic A magic value for detecting memory corruption. @@ -581,9 +581,9 @@ typedef struct _NODE *** decrement `NodeData->OpenCount`, freeing the NodeData struct as well *** if it reaches 0, to prevent memory leaks. *** - *** @param NodeData The associated node data struct. While many driver - *** struct instances pointing to one NodeData at a time, but each driver - *** instance always points to singular NodeData struct. + *** @param NodeData The associated node data struct. Many driver struct + *** instances may point to one NodeData at a time, but each driver + *** instance always points to a singular NodeData struct. *** @param TargetType The type of data targeted (see above). *** @param TargetData If target type is: *** ```txt @@ -592,7 +592,7 @@ typedef struct _NODE *** Search or SearchEntry: A pointer to the targeted search. *** ``` *** @param TargetAttrIndex An index into an attribute list (for GetNextAttr()). - *** @param TargetMethodIndex An index into an method list (for GetNextMethod()). + *** @param TargetMethodIndex An index into a method list (for GetNextMethod()). *** @param Magic A magic value for detecting memory corruption. ***/ typedef struct _DRIVER @@ -605,7 +605,7 @@ typedef struct _DRIVER unsigned char TargetAttrIndex; unsigned char TargetMethodIndex; TargetType TargetType; - /** 1 bytes of auto-padding. **/ + /** 1 byte of auto-padding. **/ } DriverData, *pDriverData; @@ -745,7 +745,7 @@ static void cluster_i_giveHint(const char* hint) } -/*** Given the user a hint when they specify an invalid string for an attribute +/*** Give the user a hint when they specify an invalid string for an attribute *** where we know the list of valid strings. The hint is only displayed if *** their string is close enough to a valid string. *** @@ -818,7 +818,7 @@ cluster_i_unknownAttribute(char* attr_name, const TargetType target_type) *** @param inf A parsed pStructInf. *** @param param_list The param objects that function as a kind of "scope" for *** evaluating parameter variables in the structure file. - *** @returns The data algorithm, or ALGORITHM_NULL on failure. + *** @returns The clustering algorithm, or ALGORITHM_NULL on failure. ***/ static ClusterAlgorithm cluster_i_parseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) @@ -1449,7 +1449,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData goto err_free; } - /** Post sub-inf parsing cleanup. */ + /** Post sub-inf parsing cleanup. **/ cluster_data->nSubClusters = sub_clusters.nItems; cluster_data->SubClusters = (ClusterData**)xaToArray(&sub_clusters); if (UNLIKELY(cluster_data->SubClusters == NULL)) @@ -1532,7 +1532,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData { /* Cache hit. */ ASSERTMAGIC(cluster_maybe, MGK_CL_CLUSTER_DATA); - /** Free the parsed cluster that we no longer need. */ + /** Free the parsed cluster that we no longer need. **/ if (LIKELY(cluster_data != NULL)) cluster_i_freeClusterData(cluster_data, false); if (LIKELY(cache_key != NULL)) nmSysFree(cache_key); @@ -2624,7 +2624,7 @@ cluster_i_sizeOfSourceData(pSourceData source_data) *** is ignored. *** *** @param cluster_data The cluster data struct to be queried. - *** @param recursive Whether to recursively free subclusters. + *** @param recursive Whether to recursively include subcluster sizes. *** @returns The size in bytes of the struct and all internal allocated data. ***/ static size_t @@ -2650,8 +2650,9 @@ cluster_i_sizeOfClusterData(pClusterData cluster_data, bool recursive) { for (unsigned int i = 0u; i < cluster_data->nClusters; i++) size += cluster_data->Clusters[i].Size * (sizeof(int)); - size += cluster_data->nClusters * (sizeof(Cluster) + sizeof(double)); + size += cluster_data->nClusters * sizeof(Cluster); } + if (cluster_data->Sims != NULL) size += cluster_data->SourceData->nDatas * sizeof(double); if (cluster_data->SubClusters != NULL) { if (recursive) @@ -2688,7 +2689,7 @@ cluster_i_sizeOfSearchData(pSearchData search_data) } ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); - unsigned int size = 0u; + size_t size = 0u; if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); if (search_data->Pairs != NULL) size += search_data->nPairs * (sizeof(pPair) + sizeof(Pair)); size += sizeof(SearchData); @@ -2706,10 +2707,10 @@ cluster_i_sizeOfSearchData(pSearchData search_data) *** *** @attention - Promises that mssError() will be invoked on failure. *** - *** @param source_data The pSourceData who's attributes should be computed. + *** @param source_data The pSourceData whose attributes should be computed. *** @param session The current session, used to open the data source. *** @returns 0 if successful, or - *** -1 other value on failure. + *** -1 on failure. ***/ static int cluster_i_computeSourceData(pSourceData source_data, pObjSession session) @@ -2792,7 +2793,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(data_datatype != DATA_T_STRING)) { mssError(1, "Cluster", - "Type for data of entry #%d was %s instead of String:\n", + "Type for data of entry #%d was %s instead of String.", vector_xarray.nItems, objTypeToStr(data_datatype) ); goto entry_free; @@ -2859,19 +2860,19 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) if (UNLIKELY(key_datatype != DATA_T_STRING)) { mssError(1, "Cluster", - "Type of key on entry #%d was %s instead of String:", + "Type of key on entry #%d was %s instead of String.", vector_xarray.nItems, objTypeToStr(key_datatype) ); goto entry_free; } - /** key value: Get value from provided data source. **/ + /** Key value: Get value from provided data source. **/ char* key; ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); if (UNLIKELY(ret != 0)) { mssError(0, "Cluster", - "Failed to value for key on entry #%d (error code: %d).", + "Failed to get value for key on entry #%d (error code: %d).", vector_xarray.nItems, ret ); goto entry_free; @@ -2934,7 +2935,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) source_data->nDatas = vector_xarray.nItems; if (UNLIKELY(source_data->nDatas == 0)) { - mssError(0, "Cluster", "Data source path did not contain any valid data:\n"); + mssError(0, "Cluster", "Data source path did not contain any valid data."); goto end_free; } @@ -3048,7 +3049,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) *** *** @attention - Promises that mssError() will be invoked on failure. *** - *** @param cluster_data The pClusterData who's attributes should be computed. + *** @param cluster_data The pClusterData whose attributes should be computed. *** @param node_data The current pNodeData, used to get vectors to cluster. *** @returns 0 if successful, or *** -1 other value on failure. @@ -3206,7 +3207,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) if (UNLIKELY(kmeans_result != 0)) { mssError(1, "Cluster", - "kmeans(pVector[], %u, %u, %u, %lf, labels[], similarities[], %s) failed (error code: %d).", + "caKmeans(pVector[], %u, %u, %u, %lf, labels[], similarities[], %s) failed (error code: %d).", source_data->nDatas, cluster_data->nClusters, cluster_data->MaxIterations, @@ -3239,8 +3240,8 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) if (xaAddItem(&indexes_in_cluster[labels[i]], (void*)i) < 0) { mssError(1, "Cluster", - "Failed to add index #%llu/%u to XArray for cluster #%u/%u.", - i + 1llu, source_data->nDatas, labels[i] + 1, cluster_data->nClusters + "Failed to add index #%lu/%u to XArray for cluster #%u/%u.", + i + 1lu, source_data->nDatas, labels[i] + 1, cluster_data->nClusters ); goto err_cleanup; } @@ -3260,7 +3261,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) /** Store the data in the cluster. **/ cluster->Size = indexes_in_this_cluster->nItems; if (cluster->Size == 0) goto cluster_cleanup; /* Not a failure, but the array still needs to be freed. */ - const size_t indexes_size = cluster->Size * sizeof(unsigned int*); + const size_t indexes_size = cluster->Size * sizeof(unsigned int); cluster->Indexes = nmSysMalloc(indexes_size); if (UNLIKELY(cluster->Indexes == NULL)) { @@ -3316,7 +3317,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) err_free: if (cluster_data->Sims != NULL) { - nmFree(cluster_data->Sims, sims_size); + nmSysFree(cluster_data->Sims); cluster_data->Sims = NULL; } @@ -3354,11 +3355,11 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) // LINK #functions /*** Ensures that the computed attributes for `search_data` are computed, - *** running the a search with the specified similarity measure if necessary. + *** running a search with the specified similarity measure if necessary. *** *** @attention - Promises that mssError() will be invoked on failure. *** - *** @param cluster_data The pSearchData who's attributes should be computed. + *** @param search_data The pSearchData whose attributes should be computed. *** @param node_data The current pNodeData, used to get vectors to cluster. *** @returns 0 if successful, or *** -1 other value on failure. @@ -3382,7 +3383,7 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); - /** If the clusters are already computed, we're done. **/ + /** If the pairs are already computed, we're done. **/ if (LIKELY(search_data->Pairs != NULL)) return 0; /** We need the cluster data to be computed before we search it. **/ @@ -3658,13 +3659,13 @@ cluster_i_getParamValue(void* inf_v, char* attr_name, int datatype, pObjData val pNodeData node_data = inf_v; if (UNLIKELY(node_data == NULL)) { - mssError(1, "Cluster", "Failed to get param type from NULL struct inf."); + mssError(1, "Cluster", "Failed to get param value from NULL struct inf."); return -1; } ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); if (UNLIKELY(attr_name == NULL)) { - mssError(1, "Cluster", "Failed to get param type for null attr name."); + mssError(1, "Cluster", "Failed to get param value for null attr name."); return -1; } @@ -3749,7 +3750,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb /** Edge cases. **/ if (UNLIKELY(parent == NULL)) { - mssError(0, "Cluster", "Warning: Call to clusterOpen(NULL, ...);\n"); + mssError(0, "Cluster", "Call to clusterOpen(NULL, ...);"); return NULL; /* Skip error handler, which expects a valid parent. */ } ASSERTMAGIC(parent, MGK_OBJECT); @@ -3880,7 +3881,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb pSearchData search_data = node_data->SearchDatas[i]; ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); - /** Skip clusters with the wrong name. **/ + /** Skip searches with the wrong name. **/ if (strcmp(search_data->Name, target_name) != 0) continue; /** Target found: Search **/ @@ -4016,10 +4017,10 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) /** Allocate memory for the query. **/ query_data = nmMalloc(sizeof(ClusterQuery)); if (UNLIKELY(query_data == NULL)) - { - mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(ClusterQuery)); - goto err_free; - } + { + mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(ClusterQuery)); + goto err_free; + } /** Initialize the query. **/ SETMAGIC(query_data, MGK_CL_QUERY_DATA); @@ -4399,7 +4400,10 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO /** Type check. **/ const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE || expected_datatype < 0)) + { cluster_i_unknownAttribute(attr_name, driver_data->TargetType); + goto err; + } if (UNLIKELY(datatype != expected_datatype)) { mssError(1, "Cluster", @@ -4708,6 +4712,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO val->Double = target->Threshold; return 0; } + break; } case TARGET_CLUSTER_ENTRY: @@ -4886,7 +4891,6 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) goto end; } if (strcmp(attr_name, "inner_type") == 0 - || strcmp(attr_name, "inner_type") == 0 || strcmp(attr_name, "outer_type") == 0 || strcmp(attr_name, "content_type") == 0 || strcmp(attr_name, "last_modification") == 0) @@ -4912,7 +4916,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) goto end; } - /** Handle date created and date computed. */ + /** Handle date created and date computed. **/ if (strcmp(attr_name, "date_created") == 0 || strcmp(attr_name, "date_computed") == 0) { @@ -5336,7 +5340,7 @@ clusterInfo(void* inf_v, pObjectInfo info) return 0; err: - mssError(0, "Cluster", "Failed execute get info."); + mssError(0, "Cluster", "Failed to execute get info."); return -1; } @@ -5373,8 +5377,8 @@ clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions -/*** Returns the name of the next method that one can get from - *** this driver instance (using `GetAttrType()` and `GetAttrValue()`). +/*** Returns the name of the next method that one can execute from + *** this driver instance (using `clusterExecuteMethod()`). *** Uses an internal variable (`TargetMethodIndex`) used to maintain *** the state of this iteration over repeated calls. *** @@ -5462,7 +5466,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) /** Compute size. **/ bytes = cluster_i_sizeOfClusterData(cluster_data, false); - /** If num_uncomputed_skipped_ptr is specified, skip uncomputed source. **/ + /** If num_uncomputed_skipped_ptr is specified, skip uncomputed cluster. **/ if (num_uncomputed_skipped_ptr != NULL && cluster_data->Clusters == NULL ) goto no_print; @@ -5479,7 +5483,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) /** Compute size. **/ bytes = cluster_i_sizeOfSearchData(search_data); - /** If num_uncomputed_skipped_ptr is specified, skip uncomputed source. **/ + /** If num_uncomputed_skipped_ptr is specified, skip uncomputed search. **/ if (num_uncomputed_skipped_ptr != NULL && search_data->Pairs == NULL ) goto no_print; @@ -5753,7 +5757,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree cluster_i_tryHint(method_name, METHOD_NAMES, METHOD_NAMES_COUNT); err: - mssError(0, "Cluster", "Failed execute command."); + mssError(0, "Cluster", "Failed to execute command."); return -1; } @@ -5964,7 +5968,7 @@ clusterInitialize(void) nmFree(drv, sizeof(ObjDriver)); } - mssError(1, "Cluster", "Failed to initialize cluster driver.\n"); + mssError(1, "Cluster", "Failed to initialize cluster driver."); return -1; } diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 9010f0d5d..26c686cf6 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -20,7 +20,7 @@ /* has been included below for the convenience of the reader. This */ /* information was collected and saved on September 5th, 2025 and may */ /* differ from current information. For the most up to date copy of */ -/* this information, please use the link provided above. */ +/* this information, please use the link provided above. */ /* */ /* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ /* */ @@ -68,7 +68,7 @@ /* Lawrence Philips which he published in the June, 2000 */ /* issue of C/C++ Users Journal. Double Metaphone is an */ /* improved version of the original Metaphone algorithm */ -/* written by Philips'. This implementation was written by */ +/* written by Philips. This implementation was written by */ /* Maurice Aubrey for C/C++ with bug fixes provided by */ /* Kevin Atkinson. It was heavily revised by Israel Fuller */ /* to align with the Centrallix coding style and make use */ @@ -179,15 +179,15 @@ meta_i_isSlavoGermanic(pXString s) || (xsFind(s, "CZ", 2, 0) >= 0); } -/*** Checks for to see if any of a list of strings appear in a the given - *** XString after the given start position. +/*** Checks to see if any of a list of strings appear in the given + *** XString at the given start position. *** *** @attention - Note that the START value is 0 based. *** *** @param s The XString being checked. - *** @param start The zero-based start of at which to begin searching + *** @param start The zero-based position at which to begin searching *** within the XString. - *** @returns 1 if any of the character sequences appear after the start + *** @returns 1 if any of the character sequences appear at the start *** in the XString and 0 otherwise. ***/ static bool @@ -409,7 +409,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) break; } - /** Germanic, greek, or otherwise 'ch' for 'kh' sound. */ + /** Germanic, greek, or otherwise 'ch' for 'kh' sound. **/ if ( meta_i_isStrAt(&original, 0, "SCH", "VAN ", "VON ", "") /** 'architect but not 'arch', 'orchestra', 'orchid' **/ @@ -598,7 +598,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) { if (next_char == 'H') { - /** 'Vghee' */ + /** 'Vghee' **/ if (current > 0 && !meta_i_isVowel(&original, (current - 1))) { if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index dc8f61544..08e427014 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -572,8 +572,8 @@ stGetObjAttrValue(pStructInf this, char* attrname, int type, pObjData value) return stGetObjAttrValueOSML(this, attrname, type, value, 0, NULL, NULL, 0); } -/*** stGetAttrValueOSML - return the value of an expression, evaluated - *** in the context of an OSML session. +/*** stGetObjAttrValueOSML - return the value of a named attribute, + *** evaluated in the context of an OSML session. ***/ int stGetObjAttrValueOSML(pStructInf this, char* attrname, int type, pObjData pod, int nval, pObjSession sess, pParamObjects objlist, int domain) @@ -584,7 +584,7 @@ stGetObjAttrValueOSML(pStructInf this, char* attrname, int type, pObjData pod, i return -1; } - /** Look up the requested inf. */ + /** Look up the requested inf. **/ pStructInf target_inf = stLookup(this, attrname); if (target_inf == NULL) return 1; /* Null value for nonexistent attribute. */ From f582bbff8a0b59a3e77bd4ee43cd4ad93b0b8e94 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:19:59 -0600 Subject: [PATCH 294/361] Fix cmp_Pairs() comparing pointers instead of the pointed data in test_clusters_06.c. --- centrallix-lib/tests/test_clusters_06.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index 75c3db472..2a2df3f3d 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -31,9 +31,10 @@ static int cmp_Pairs(const void* v1, const void* v2) { - const Pair* Pair1 = v1; - const Pair* Pair2 = v2; - return Pair1->i - Pair2->i; + const Pair* Pair1 = *(const pPair*)v1; + const Pair* Pair2 = *(const pPair*)v2; + if (Pair1->i != Pair2->i) return (int)Pair1->i - (int)Pair2->i; + return (int)Pair1->j - (int)Pair2->j; } static bool freePairs(pXArray xPairs) From a1b1057b457e7ba7c886a2fe704949e7a02b69e3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:41:45 -0600 Subject: [PATCH 295/361] Update a comment about how caCosCompare()'s function signature needs to match pSimilarityFn. --- centrallix-lib/src/clusters.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index d0c29928b..037aa6659 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -654,10 +654,10 @@ caEditDist(const char* str1, const char* str2, const size_t str1_length, const s *** @attention - Note: Punctuation, whitespace, etc. are ignored due to how *** cosine vector hashing is implemented. *** - *** @attention - This function takes `void*` instead of `pVector` so that it - *** can be used as the similarity function in the ca_search() function - *** family without needing a messy typecast to avoid the compiler warning. - *** However, behavior is undefined if `v1` and `v2` are not `pVector`s. + *** @attention - This function takes `void*` instead of `pVector` to match + *** the `pSimilarityFn` signature expected by search functions like + *** `caCompleteSearch()`. However, behavior is undefined if `v1` or `v2` + *** are not `pVector`s. *** *** @param v1 A `pVector` to the first string to compare. *** @param v2 A `pVector` to the second string to compare. From bd76d704c13661acafc8aed0e8ed1525787c7ab4 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:42:14 -0600 Subject: [PATCH 296/361] Improve money format printing in 2 errors. --- centrallix/expression/exp_functions.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index e20a0bc5d..9ef9e5114 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -292,16 +292,16 @@ exp_fn_i_verifyArg(const char* fn_name, pExpression arg, const ArgExpect* arg_ex if (arg_expect->Flags & EXP_ARG_POSITIVE && value->WholePart < 0) { mssError(1, "EXP", - "%s(...): Expects positive money value but got $%d.%g.", - fn_name, value->WholePart, (double)value->FractionPart / 100.0 + "%s(...): Expects positive money value but got %s.", + fn_name, objFormatMoneyTmp(value, NULL) ); return -1; } if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->WholePart > 0) { mssError(1, "EXP", - "%s(...): Expects negative money value but got $%d.%g.", - fn_name, value->WholePart, (double)value->FractionPart / 100.0 + "%s(...): Expects negative money value but got %s.", + fn_name, objFormatMoneyTmp(value, NULL) ); return -1; } From d1e5325643cb5767f399cdd4afcad9694d009a09 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:42:46 -0600 Subject: [PATCH 297/361] Remove an extra -lm that was being added to the makefile. --- centrallix-lib/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index deb8aff8f..5afb32f72 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -59,7 +59,7 @@ LIBS = @LIBS@ -lm ## PROFILE=@PROFILE@ COVERAGE=@COVERAGE@ -CFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall -Wformat-nonliteral $(PROFILE) $(COVERAGE) -g -lm +CFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall -Wformat-nonliteral $(PROFILE) $(COVERAGE) -g MTCFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall -Wformat-nonliteral $(PROFILE) $(COVERAGE) -g -O0 TCFLAGS=$(patsubst -DNDEBUG,,$(CFLAGS)) -DCX_TESTING From f6b28de7ad5dc94238d64f2ad04c373a0792df2c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:47:55 -0600 Subject: [PATCH 298/361] Update test performance multiplier. --- centrallix-lib/tests/test_clusters_05.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index 9f4354db1..bb761ee9f 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -189,5 +189,5 @@ long long test(char** tname) WarnPrintEnabled = 0; *tname = "cluster-05 caMostSimilar()"; - return loopTest(doTest) * 39; + return loopTest(doTest) * 38; } From bb40fa69b559b6ed3bc01a3c11f97487f780eda8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:51:12 -0600 Subject: [PATCH 299/361] Fix all remaining stale mentions of ca_search(). --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/src/clusters.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 8fbf87e2a..8f5c135f1 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -134,7 +134,7 @@ int caKmeans( ***/ typedef double (*pSimilarityFn)(void*, void*); -/** Comparison functions (see ca_search()). **/ +/** Comparison functions (matching the pSimilarityFn signature). **/ double caCosCompare(void* v1, void* v2); double caLevCompare(void* str1, void* str2); bool caEql(pVector v1, pVector v2); diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 037aa6659..0052c4777 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -703,10 +703,10 @@ caCosCompare(void* v1, void* v2) *** contain ANY valid ASCII characters other than the NULL-terminator, *** which is used to terminate the string. *** - *** @attention - This function takes `void*` instead of `char*` so that it - *** can be used as the similarity function in the ca_search() function - *** family without needing a messy typecast to avoid the compiler warning. - *** However, behavior is undefined if `s1` and `s2` are not `char*`s. + *** @attention - This function takes `void*` instead of `char*` to match + *** the `pSimilarityFn` signature expected by search functions like + *** `caCompleteSearch()`. However, behavior is undefined if `v1` or `v2` + *** are not `char*`s. *** *** @param s1 A `char*` to the first string to compare. *** @param s2 A `char*` to the second string to compare. From 5d22dcc23346f69c9d31e153248a8f9bf883c665 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:52:22 -0600 Subject: [PATCH 300/361] Reorder caEql() to a more intuitive location in the clusters module. --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/src/clusters.c | 48 +++++++++++++++---------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 8f5c135f1..f355c46af 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -97,6 +97,7 @@ int caEditDist(const char* str1, const char* str2, const size_t str1_length, con /** Vector functions. **/ pVector caBuildVector(const char* str); +bool caEql(pVector v1, pVector v2); unsigned int caSparseLen(const pVector vector); void caPrintVector(const pVector vector); void caFreeVector(pVector sparse_vector); @@ -137,7 +138,6 @@ typedef double (*pSimilarityFn)(void*, void*); /** Comparison functions (matching the pSimilarityFn signature). **/ double caCosCompare(void* v1, void* v2); double caLevCompare(void* str1, void* str2); -bool caEql(pVector v1, pVector v2); /** Similarity search functions. **/ void* caMostSimilar( diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 0052c4777..2f2153947 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -297,6 +297,30 @@ caFreeVector(pVector sparse_vector) return; } +/*** Check if two sparse vectors are identical, typically used for debugging + *** or testing. True if both vectors are `NULL`, but false if one is and the + *** other is not. + *** + *** @param v1 The first vector. + *** @param v2 The second vector. + *** @returns true if they are equal, + *** false if any element is different. + ***/ +bool +caEql(pVector v1, pVector v2) + { + /** Edge cases. **/ + if (v1 == v2) return true; + if (v1 == NULL || v2 == NULL) return false; + + const unsigned int len = caSparseLen(v1); + if (len != caSparseLen(v2)) return false; + for (unsigned int i = 0u; i < len; i++) + if (v1[i] != v2[i]) return false; + + return true; + } + /*** Parse a token from a sparsely allocated vector and write its value to *** `token_value`. The number of dimensions consumed in the process is *** written to `dims_consumed`. @@ -756,30 +780,6 @@ caLevCompare(void* s1, void* s2) return NAN; } -/*** Check if two sparse vectors are identical, typically used for debugging - *** or testing. True if both vectors are `NULL`, but false if one is and the - *** other is not. - *** - *** @param v1 The first vector. - *** @param v2 The second vector. - *** @returns true if they are equal, - *** false if any element is different. - ***/ -bool -caEql(pVector v1, pVector v2) - { - /** Edge cases. **/ - if (v1 == v2) return true; - if (v1 == NULL || v2 == NULL) return false; - - const unsigned int len = caSparseLen(v1); - if (len != caSparseLen(v2)) return false; - for (unsigned int i = 0u; i < len; i++) - if (v1[i] != v2[i]) return false; - - return true; - } - /*** Calculate the average size of all clusters in a set of vectors. *** *** @param vectors The vectors of the dataset (allocated sparsely). From 8163bab18c3b542c1561714f1249fe2b0a7eb42e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:54:22 -0600 Subject: [PATCH 301/361] Replace _PC_PATH_MAX with OBJSYS_MAX_PATH, the actual max path length in the object system. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index c4b13e8b0..28d7eb342 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4943,7 +4943,7 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) case TARGET_NODE: if (strcmp(attr_name, "source") == 0) { - hints->Length = _PC_PATH_MAX; + hints->Length = OBJSYS_MAX_PATH; hints->VisualLength = 64; hints->FriendlyName = warnNull(nmSysStrdup("Source Path")); goto end; From 0f650fcb2b95de33059a08efebc4e7f90fbd17e9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 15:58:19 -0600 Subject: [PATCH 302/361] Fix a memory leak when xaAddItem() fails. --- centrallix/osdrivers/objdrv_cluster.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 28d7eb342..c6a777372 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3523,6 +3523,11 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) if (UNLIKELY(xaAddItem(pairs, pair) < 0)) { mssError(1, "Cluster", "Failed to add new pair to pairs XArray."); + + /** Free renamining items. **/ + for (unsigned int j = i; j < cluster_pairs->nItems; j++) + nmFree(cluster_pairs->Items[j], sizeof(Pair)); + warnFail(xaFree(cluster_pairs)); goto err_free; } } From cf43c1b3975812500a2706b45d8585a4ef7bfb30 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:04:59 -0600 Subject: [PATCH 303/361] Fix clusterCommit() returning a successful error code despite calling mssError() because it isn't implemented. --- centrallix/osdrivers/objdrv_cluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index c6a777372..9d553ab4a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -5851,7 +5851,7 @@ clusterCommit(void* inf_v, pObjTrxTree* oxt) { mssError(1, "Cluster", "clusterCommit() not implemented because clusters are immutable."); - return 0; + return -1; } From 00c214f061a8b0a94fe521bed9042bbefd1371b3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:05:46 -0600 Subject: [PATCH 304/361] Remove mentions of cluster entry similarities, which were scrapped and never implemented. --- centrallix/osdrivers/objdrv_cluster.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 9d553ab4a..dada76b26 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -5104,18 +5104,6 @@ clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) hints->FriendlyName = warnNull(nmSysStrdup("Cluster Data")); goto end; } - if (strcmp(attr_name, "sim") == 0) - { - /** Min and max values. **/ - hints->MinValue = warnNull(expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); - hints->MaxValue = warnNull(expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0)); - - /** Other hints. **/ - hints->Length = 16; - hints->VisualLength = 8; - hints->FriendlyName = warnNull(nmSysStrdup("Similarity")); - goto end; - } break; } From 48bbede24f4668fd56a9bb53e2cc5645eda9b97b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:06:31 -0600 Subject: [PATCH 305/361] Add a memset() for ClusterQuery even though each field is initialized manually in case new fields are added. --- centrallix/osdrivers/objdrv_cluster.c | 1 + 1 file changed, 1 insertion(+) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index dada76b26..193f7003f 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -4026,6 +4026,7 @@ clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) mssError(1, "Cluster", "nmMalloc(%zu) failed.", sizeof(ClusterQuery)); goto err_free; } + memset(query_data, 0, sizeof(ClusterQuery)); /** Initialize the query. **/ SETMAGIC(query_data, MGK_CL_QUERY_DATA); From ff9b8698dceca1b901b0af3eb387f73047e32ce1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:09:00 -0600 Subject: [PATCH 306/361] Fix a confusing error message. --- centrallix/osdrivers/objdrv_cluster.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 193f7003f..6c27f7894 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2075,8 +2075,8 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) if (UNLIKELY(param == NULL)) { mssError(0, "Cluster", - "Failed to create param from inf for param #%u: %s", - i, ((pStructInf)param_infs.Items[i])->Name + "Failed to create param from inf for param #%u/%u: %s", + i + 1, node_data->nParams, ((pStructInf)param_infs.Items[i])->Name ); goto err_free; } From a9942a0645f533151409edeb8cbd68df7a6ad2a1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:30:51 -0600 Subject: [PATCH 307/361] Improve some wording in string_similarity.md. --- centrallix-sysdoc/string_similarity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 7b4b5f7da..c55efaed8 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -89,7 +89,7 @@ Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296 ### String Vectors Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). -Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. +Remember that the first and last characters form a pairs with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real use cases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string (this allows us to get large performance and memory improvements with [Sparse Vectors](#sparse-vectors)). ### Sparse Vectors As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. From 69c0c735be75d3aa11ec89092719af9927b80372 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:31:02 -0600 Subject: [PATCH 308/361] Improve spacing in test_expfn_log_00.to. --- centrallix/tests/test_expfn_log_00.to | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix/tests/test_expfn_log_00.to b/centrallix/tests/test_expfn_log_00.to index c73140236..993064a87 100644 --- a/centrallix/tests/test_expfn_log_00.to +++ b/centrallix/tests/test_expfn_log_00.to @@ -48,8 +48,8 @@ query select 'log(1e10)-' = (log(10000000000.0) == ln(10000000000.0)) -- Additional double/int mixed cases query select 'ln(2.718281828)' = round(ln(2.718281828), 8) -- Expect ~1 (close to e). -query select 'log10(3.14159)' = round(log10(3.14159), 8) -- Expect ~0.49715. -query select 'log(10, 1.1)' = round(log(1.1, 10), 8) -- Expect 0.04139289. -query select 'log(1.1, 10)' = round(log(10, 1.1), 8) -- Expect 24.15885793. -query select 'log(10, 0.001)' = round(log(0.001, 10), 8) -- Expect ~-0.33333333... -query select 'log(0.1, 1000)' = round(log(1000, 0.1), 8) -- Expect ~-0.33333333... +query select 'log10(3.14159)' = round(log10(3.14159), 8) -- Expect ~0.49715. +query select 'log(1.1, 10)' = round(log(1.1, 10), 8) -- Expect 0.04139269. +query select 'log(10, 1.1)' = round(log(10, 1.1), 8) -- Expect 24.15885793. +query select 'log(0.001, 10)' = round(log(0.001, 10), 8) -- Expect -3.0. +query select 'log(1000, 0.1)' = round(log(1000, 0.1), 8) -- Expect -3.0. From 53d75d455bbd411821d7c191558e9aa95b37354b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:31:23 -0600 Subject: [PATCH 309/361] Fix more typos and mistakes. --- centrallix-sysdoc/Libraries/xstring.md | 2 +- centrallix-sysdoc/OSDriver_Authoring.md | 10 +++++----- centrallix/osdrivers/objdrv_cluster.c | 6 +++--- centrallix/tests/test_expfn_metaphone_00.to | 2 +- centrallix/tests/test_expfn_trim_00.cmp | 2 +- centrallix/tests/test_expfn_trim_00.to | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 6632bb9c5..cea36a3e9 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -290,7 +290,7 @@ This function returns the stored string after checking for various errors, or re ```c int xsLength(pXString this); ``` -This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. +This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `-1` if an error occurs. ## xsCharAt() diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 61a530935..3ab2b9c9f 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -154,7 +154,7 @@ Once a file is opened, the driver should organize provided data into a tree-stru A driver can be opened multiple times, leading one driver to have multiple "node" objects, also called instances. Typically, each "node" object relates to a particular instance of a resource. For example, say you are designing a driver to access MySQL databases. You could design the driver file to describe a MySQL instance. Thus, the node object for this driver could have children for each database in that instance (e.g. `Kardia_DB`, `mysql`, and even the system databases used by MySQL to manage the database internals). Another design would be for each driver file to describe one MySQL database. Thus, you could make a `Kardia_DB` file to access that database, and the children of that node object would be each table in the database. A third design option would be for each driver file to describe a MySQL table. Thus, you make a `p_partner` file to access members of the partner table, a `p_contact_info` file to access contact info for partners, etc. with each node object having children for the rows in the table. This last option would require the developer to create a _lot_ of files (and would probably also make joins hard to implement), so in this case, it's probably not the best. Ultimately, though, these design choices are up to the driver author. -an instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. +An instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. The structure of the subtree beneath the node object is entirely up to the drivers' author to determine; the OSML does not impose any structural restrictions on such subtrees. Each object within this structure (e.g. `/example.qy`) can have three types of readable data: - Child objects (e.g. `/rows`) which can have their own data. @@ -163,7 +163,7 @@ The structure of the subtree beneath the node object is entirely up to the drive Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradigm, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traverse its child objects, although queries are not required to be implemented this way. -Below is an example of the Sybase driver's node object and its subtrees of child objects (defined in `objdrv_sybase.c`): +Below is an example of the MySQL driver's node object and its subtrees of child objects (defined in `objdrv_mysql.c`): ```sh Kardia_DB (type = "application/mysql") @@ -298,7 +298,7 @@ The `name` field is a 64 character buffer (allowing names up to 63 characters, w For example: ```c -if (strcpy(drv->Name, "SYBD - Sybase Database Driver") == NULL) goto error_handling; +strcpy(drv->Name, "SYBD - Sybase Database Driver"); ``` #### RootContentTypes @@ -635,7 +635,7 @@ The `last_modification : DATA_T_DATETIME` attribute is a sixth, optional attribu ```c int xxxGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); ``` -The `GetAttrType()` function returns DATA_T_xxx value for the datatype of the requested. It takes three parameters: +The `GetAttrType()` function returns DATA_T_xxx value for the datatype of the requested attribute. It takes three parameters: | Parameter | Type | Description | --------- | ------------- | ------------ @@ -686,7 +686,7 @@ This function should return 0 on success, 1 if the value is `NULL` or undefined - 📖 **Note**: The caller can use the `POD(x)` macro to typecast appropriate pointers to the `pObjData` pointer. For example: ```c char* name; - if (xxxGetAttrValue(obj, "name", DATA_T_STRING, POD(&name)) != 0) + if (objGetAttrValue(obj, "name", DATA_T_STRING, POD(&name)) != 0) goto error_handling; printf("Object name: \"%s\"\n", name); ``` diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 6c27f7894..f9f74664a 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3052,7 +3052,7 @@ cluster_i_computeSourceData(pSourceData source_data, pObjSession session) *** @param cluster_data The pClusterData whose attributes should be computed. *** @param node_data The current pNodeData, used to get vectors to cluster. *** @returns 0 if successful, or - *** -1 other value on failure. + *** -1 on failure. ***/ static int cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) @@ -3362,7 +3362,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) *** @param search_data The pSearchData whose attributes should be computed. *** @param node_data The current pNodeData, used to get vectors to cluster. *** @returns 0 if successful, or - *** -1 other value on failure. + *** -1 on failure. ***/ static int cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) @@ -3524,7 +3524,7 @@ cluster_i_computeSearchData(pSearchData search_data, pNodeData node_data) { mssError(1, "Cluster", "Failed to add new pair to pairs XArray."); - /** Free renamining items. **/ + /** Free remaining items. **/ for (unsigned int j = i; j < cluster_pairs->nItems; j++) nmFree(cluster_pairs->Items[j], sizeof(Pair)); warnFail(xaFree(cluster_pairs)); diff --git a/centrallix/tests/test_expfn_metaphone_00.to b/centrallix/tests/test_expfn_metaphone_00.to index de1897c3e..c99cec5b4 100644 --- a/centrallix/tests/test_expfn_metaphone_00.to +++ b/centrallix/tests/test_expfn_metaphone_00.to @@ -13,7 +13,7 @@ # # *Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt # **GPT-5 mini (Preview) was run in GitHub Copilot to suggest the words -# for some tests after analizing a generated coverage report. I (Israel) +# for some tests after analyzing a generated coverage report. I (Israel) # used the suggestions to write some "AI generated" test cases. # # For more information, see the manual test suite implementation at the diff --git a/centrallix/tests/test_expfn_trim_00.cmp b/centrallix/tests/test_expfn_trim_00.cmp index 5515d9aa1..e80833013 100644 --- a/centrallix/tests/test_expfn_trim_00.cmp +++ b/centrallix/tests/test_expfn_trim_00.cmp @@ -1,4 +1,4 @@ -Attribute [trim("White space on the left o]: string "No white space on the left or right side." +Attribute [trim("No white space on the lef]: string "No white space on the left or right side." Attribute [trim(" White space on the l]: string "White space on the left side." Attribute [trim("White space on the right ]: string "White space on the right side." Attribute [trim(" White space on the r]: string "White space on the right and the left side." diff --git a/centrallix/tests/test_expfn_trim_00.to b/centrallix/tests/test_expfn_trim_00.to index 746de6ebc..86da8529a 100644 --- a/centrallix/tests/test_expfn_trim_00.to +++ b/centrallix/tests/test_expfn_trim_00.to @@ -1,6 +1,6 @@ ##NAME trim() function -query select 'trim("White space on the left or right side.")' = trim("No white space on the left or right side.") +query select 'trim("No white space on the left or right side.")' = trim("No white space on the left or right side.") query select 'trim(" White space on the left side.")' = trim(" White space on the left side.") From e0fa24fba35ed3a4dcb2f62c28f73329ff3b9b2c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 16:59:59 -0600 Subject: [PATCH 310/361] Add tests for the snprintBytes() bug that was recently fixed. --- centrallix-lib/tests/test_util_02.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index 8f616491d..1bb0158f2 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -85,6 +85,15 @@ static bool doTest(void) success &= TEST_SNPRINT_BYTES(buf, buf_size, INT_MAX, (cs) ? "2 GiB" : "2.15 GB"); success &= TEST_SNPRINT_BYTES(buf, buf_size, UINT_MAX, (cs) ? "4 GiB" : "4.29 GB"); + /** Rounding boundaries: a value that rounds up to the next unit prints in that unit. **/ + success &= TEST_SNPRINT_BYTES(buf, buf_size, 1048524, (cs) ? "1023.9 KiB" : "1.05 MB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, 1048525, (cs) ? "1 MiB" : "1.05 MB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, 1048575, (cs) ? "1 MiB" : "1.05 MB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, 1073741823, (cs) ? "1 GiB" : "1.07 GB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, 10239, (cs) ? "10 KiB" : "10.24 KB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, 102399, (cs) ? "100 KiB" : "102.4 KB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, ULONG_MAX, (cs) ? "16 EiB" : "18.45 EB"); + /** Truncation: the full length is returned, but only part is written. **/ success &= TEST_SNPRINT_BYTES(buf, 0, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); success &= TEST_SNPRINT_BYTES(buf, 1, pow(1024, 1), (cs) ? "1 KiB" : "1.02 KB"); @@ -150,5 +159,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "util-02 Printing"; - return loopTest(doTest) * (22 + 48); + return loopTest(doTest) * (29 + 48); } From ecabce3be2976f51e73003537a2b59ed768c4c24 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 17:05:22 -0600 Subject: [PATCH 311/361] Fix obvious mistakes and typos. --- centrallix-lib/tests/t_driver.c | 2 +- centrallix-lib/tests/test_newmalloc_00.c | 4 ++-- centrallix-lib/tests/test_newmalloc_01.c | 2 +- centrallix-lib/tests/test_range_01.c | 2 +- centrallix-lib/tests/test_timer_00.c | 2 +- centrallix-lib/tests/test_util_02.c | 2 +- centrallix-lib/tests/test_utils.h | 11 +++++------ centrallix-lib/tests/test_xarray_00.c | 4 ++-- centrallix-lib/tests/test_xhash_00.c | 2 +- centrallix-lib/tests/test_xhash_05.c | 2 +- 10 files changed, 16 insertions(+), 17 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 5878b90ea..b0247255f 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -8,7 +8,7 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: test_00baseline.c */ +/* Module: t_driver.c */ /* Author: Greg Beeley (GRB) */ /* Creation: March 11th, 2005 */ /* Description: Test suite driver for centrallix-lib tests. */ diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 98f2ad8eb..16b7f3aa2 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -11,8 +11,8 @@ /* Module: test_newmalloc_00.c */ /* Author: Israel Fuller */ /* Creation: November 25th, 2025 */ -/* Description: Test the nmSysMalloc(), nmSysFree(), nmSysRealloc(), */ -/* and nmSysStrDup functions from the NewMalloc library. */ +/* Description: Test nmSysMalloc(), nmSysFree(), nmSysRealloc(), */ +/* and nmSysStrdup() from the NewMalloc library. */ /************************************************************************/ #include diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 29377f2eb..7c3978810 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -129,7 +129,7 @@ static bool doTest(void) if (*((unsigned char*)large_buf + i) != (unsigned char)(i % 255lu)) mismatches++; success &= ASSERT_EQL(mismatches, 0lu, "%zu"); - /** Dup string data is unharmed. **/ + /** Basic string data is unharmed. **/ success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); diff --git a/centrallix-lib/tests/test_range_01.c b/centrallix-lib/tests/test_range_01.c index 1b027d363..70d4c6acf 100644 --- a/centrallix-lib/tests/test_range_01.c +++ b/centrallix-lib/tests/test_range_01.c @@ -54,7 +54,7 @@ static bool doTest(void) success &= ASSERT_EQL(roundTo(123.4, -1), 120.0, "%g"); success &= ASSERT_EQL(roundTo(586241.7, -4), 590000.0, "%g"); - /** Rounding infinity. */ + /** Rounding infinity. **/ success &= ASSERT_EQL(roundTo( INFINITY, 0), INFINITY, "%g"); success &= ASSERT_EQL(roundTo(-INFINITY, 0), -INFINITY, "%g"); success &= ASSERT_EQL(roundTo( INFINITY, 16), INFINITY, "%g"); diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c index 96fb48121..17e4b72f1 100644 --- a/centrallix-lib/tests/test_timer_00.c +++ b/centrallix-lib/tests/test_timer_00.c @@ -11,7 +11,7 @@ /* Module: test_timer_00.c */ /* Author: Israel Fuller */ /* Creation: November 24th, 2025 */ -/* Description: Test the util.h timer1 functionality. */ +/* Description: Test the timer.h functionality. */ /************************************************************************/ #include diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index 1bb0158f2..ca143a076 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -30,7 +30,7 @@ /*** Call an snprint-style function and check both what it wrote and what it *** returned. *** `expect` is the untruncated result so that we can expect the return to be - *** the correct length while requring the function to write exactly as much as + *** the correct length while requiring the function to write exactly as much as *** `buf_size` allows. Nothing is written when `buf_size` is zero. ***/ #define TEST_SNPRINT(fn, buf, buf_size, value, expect) \ diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index ac5955d38..8b1913e6c 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -46,8 +46,7 @@ /*** Assert a value to be true. *** - *** @param v1 The value. - *** @param v2 The second value. + *** @param v The value. *** @returns true if successful, false otherwise. ***/ #define ASSERT_TRUE(v) \ @@ -66,7 +65,7 @@ *** @param v2 The second value. *** @param sp The specifier to print the values if there is an error. This *** MUST be a string known to the compiler at compile time, such as a - *** sting literal or a macro that expands to one. + *** string literal or a macro that expands to one. *** @returns true if successful, false otherwise. ***/ #define ASSERT_EQL(v1, v2, sp) \ @@ -117,11 +116,11 @@ { \ const size_t buf_len = max(7, _len + 1); \ char _tmp1[buf_len]; \ - if (str1 == NULL) strcpy(_tmp1, "(null)"); \ + if (_str1 == NULL) strcpy(_tmp1, "(null)"); \ else STR_COPY_REPLACE_NULLS(_tmp1, _str1, _len); \ \ char _tmp2[buf_len]; \ - if (str2 == NULL) strcpy(_tmp2, "(null)"); \ + if (_str2 == NULL) strcpy(_tmp2, "(null)"); \ else STR_COPY_REPLACE_NULLS(_tmp2, _str2, _len); \ \ fprintf(stderr, \ @@ -160,7 +159,7 @@ *** @param max_v The maximum acceptable value. *** @param sp The specifier to print the values if there is an error. This *** MUST be a string known to the compiler at compile time, such as a - *** sting literal or a macro that expands to one. + *** string literal or a macro that expands to one. *** @returns true if successful, false otherwise. ***/ #define ASSERT_RANGE(v, min_v, max_v, sp) \ diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 1becd94b0..06241f21a 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -100,8 +100,8 @@ static bool doTest(void) *v_not = value_not; success &= ASSERT_EQL(xaFindItem(xa, NULL), -1, "%d"); success &= ASSERT_EQL(xaFindItemR(xa, NULL), -1, "%d"); - success &= ASSERT_EQL(xaFindItem(xa, &v_not), -1, "%d"); - success &= ASSERT_EQL(xaFindItemR(xa, &v_not), -1, "%d"); + success &= ASSERT_EQL(xaFindItem(xa, v_not), -1, "%d"); + success &= ASSERT_EQL(xaFindItemR(xa, v_not), -1, "%d"); nmFree(v_not, sizeof(int)); v_not = NULL; /** Insert before index 1. **/ diff --git a/centrallix-lib/tests/test_xhash_00.c b/centrallix-lib/tests/test_xhash_00.c index 4218b8e3f..288ac6755 100644 --- a/centrallix-lib/tests/test_xhash_00.c +++ b/centrallix-lib/tests/test_xhash_00.c @@ -16,7 +16,7 @@ /* */ /* Unsupported cases skipped by the test suite: */ /* - a table with zero rows: divides by zero. */ -/* - a key shorter nonzero key length: reads past the end. */ +/* - a key shorter than a nonzero key length: over reads. */ /* - xhDeInit() on a table with entries: leaks them. */ /* - xhAdd() allocation failure: needs an inject to test. */ /************************************************************************/ diff --git a/centrallix-lib/tests/test_xhash_05.c b/centrallix-lib/tests/test_xhash_05.c index 775f3d7ce..cc9a7ad35 100644 --- a/centrallix-lib/tests/test_xhash_05.c +++ b/centrallix-lib/tests/test_xhash_05.c @@ -24,7 +24,7 @@ /** Tested module. **/ #include "xhash.h" -/*** These are internal functions, so we use forward forward declarations to +/*** These are internal functions, so we use forward declarations to *** make them available for testing. ***/ int xhInitialize(); From 3930af06626ce6294477b3b0a0a2339570f414ff Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Sep 2026 17:09:18 -0600 Subject: [PATCH 312/361] Document the possible memory leak if excessive recursion occurs. --- centrallix/osdrivers/objdrv_cluster.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index f9f74664a..a44830bfc 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -2358,6 +2358,8 @@ cluster_i_freeSourceData(pSourceData source_data) // LINK #functions /*** Free pClusterData struct with an option to recursively free subclusters. + *** The recursive version may leak some memory if the stack recursion limit + *** is reached. *** *** @param cluster_data The cluster data struct to free. *** @param recursive Whether to recursively free subclusters. From d303e4286482881b8b0a5668a8d0c6c7bf38cc92 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 09:56:57 -0600 Subject: [PATCH 313/361] Revert changes that are not in scope for this work. --- centrallix-doc/Widgets/onepage.html | 2 +- centrallix-doc/Widgets/table.html | 2 +- centrallix-doc/Widgets/widgets.xml | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/centrallix-doc/Widgets/onepage.html b/centrallix-doc/Widgets/onepage.html index d2e733884..9a449b6a5 100644 --- a/centrallix-doc/Widgets/onepage.html +++ b/centrallix-doc/Widgets/onepage.html @@ -5976,7 +5976,7 @@

Child Properties:

type string -The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. +The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. width diff --git a/centrallix-doc/Widgets/table.html b/centrallix-doc/Widgets/table.html index a3361828d..d0538544b 100644 --- a/centrallix-doc/Widgets/table.html +++ b/centrallix-doc/Widgets/table.html @@ -329,7 +329,7 @@

Child Properties:

type string -The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. +The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. width diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index c2c2e5eb5..b6b50afde 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -3613,8 +3613,6 @@ myTabControl "widget/tab" Whether to permit the user to select rows in the table. Default "yes". - Whether clicking a column header sorts the table by that column. Acts as the default for all columns; individual columns may override via their own "allow_sorting" property. Default "yes". - A background image for the table. This "shows through" between table cells. A color, RGB or named, to be used between table cells and rows.If neither bgcolor nor background are specified, the table is transparent. @@ -3723,8 +3721,6 @@ myTabControl "widget/tab" The alignment of the column: "left" or "right". - Whether clicking this column's header sorts the table by this column. Defaults to the table's "allow_sorting" value (which defaults to "yes"). - It is a pseudonym for the fieldname. The color of the caption_fieldname. @@ -3735,7 +3731,7 @@ myTabControl "widget/tab" The title of the column to be displayed in the header row. - The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. + The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. width of the column. From ec6b61d1df770ead58665bf880c7d4d3bd57a8d0 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 09:59:19 -0600 Subject: [PATCH 314/361] Fix more typos and mistakes. --- centrallix-lib/include/magic.h | 2 +- centrallix-lib/src/clusters.c | 25 +++++++++--------- centrallix-lib/tests/test_clusters_00.c | 2 +- centrallix-lib/tests/test_clusters_03.c | 2 +- centrallix-lib/tests/test_clusters_05.c | 4 +-- centrallix-os/datasets/README.md | 2 +- centrallix-sysdoc/Libraries/mtsession.md | 2 +- centrallix-sysdoc/Libraries/newmalloc.md | 2 +- centrallix-sysdoc/Libraries/xarray.md | 4 +-- centrallix-sysdoc/Libraries/xhash.md | 2 +- centrallix-sysdoc/Libraries/xstring.md | 2 +- centrallix-sysdoc/OSDriver_Authoring.md | 16 ++++++------ centrallix-sysdoc/string_similarity.md | 4 +-- centrallix/expression/exp_functions.c | 2 +- centrallix/objectsystem/obj_datatypes.c | 2 +- centrallix/osdrivers/objdrv_cluster.c | 28 ++++++++++----------- centrallix/tests/test_cos_compare_00.to | 2 +- centrallix/tests/test_expfn_log_00.cmp | 8 +++--- centrallix/tests/test_expfn_metaphone_00.to | 2 +- centrallix/tests/test_expfn_trim_00.to | 4 +-- centrallix/tests/test_lev_compare_00.to | 2 +- centrallix/utility/double_metaphone.c | 20 +++++++-------- centrallix/utility/stparse.c | 4 +-- 23 files changed, 72 insertions(+), 71 deletions(-) diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index 5c80d3cb1..a10cd47c3 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -84,7 +84,7 @@ typedef struct Magic_t Magic; } MagicHdr, *pMagicHdr; - + #define MGK_FILE 0x12340001 /* mtask.h::File */ #define MGK_OBJECT 0x12340102 /* obj.h::Object */ #define MGK_OBJQUERY 0x1234015a /* obj.h::ObjQuery */ diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 2f2153947..c092c5b51 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -76,7 +76,7 @@ ca_i_hashCharPair(const unsigned char c1, const unsigned char c2) *** @param c2 The second character in the character pair. *** @param hash The hash for the two characters, calculated by calling the *** ca_i_hashCharPair() function (above). - **/ + ***/ typedef struct { unsigned char c1, c2; @@ -177,7 +177,7 @@ caBuildVector(const char* str) unsigned char c = (unsigned char)maybe_char; /** Ignore insignificant characters. **/ - /** isspace(): space, \n, \v, \f, \r **/ + /** isspace(): space, \t, \n, \v, \f, \r **/ /** ispunct(): !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ **/ if (c != CA_BOUNDARY_CHAR && (isspace(c) || ispunct(c))) continue; @@ -543,9 +543,9 @@ ca_i_sparseSimilarityToCentroid(const pVector v1, const pCentroid c1) *** @param str1 The first string. *** @param str2 The second string. *** @param str1_length The length of the first string, or 0 to detect from - *** the length from the null-terminator. + *** the null-terminator. *** @param str2_length The length of the second string, or 0 to detect from - *** the length from the null-terminator. + *** the null-terminator. *** @returns The edit distance between the two strings, or a negative value on error. ***/ int @@ -555,8 +555,9 @@ caEditDist(const char* str1, const char* str2, const size_t str1_length, const s unsigned int** lev_matrix = NULL; /*** lev_matrix: - *** For all i and j, d[i][j] will hold the Levenshtein distance between - *** the first i characters of s and the first j characters of t. + *** For all i and j, lev_matrix[i][j] will hold the Levenshtein distance + *** between the first i characters of str1 and the first j characters of + *** str2. *** *** As they say, no dynamic programming algorithm is complete without a *** matrix that you fill out and it has the answer in the final location. @@ -597,7 +598,7 @@ caEditDist(const char* str1, const char* str2, const size_t str1_length, const s lev_matrix[i][0] = i; /*** Base case #2: - *** Any target prefixes can be transformed into an empty string by + *** Any target prefix can be built from an empty string by *** inserting each character. ***/ for (unsigned int j = 1u; j <= str2_len; j++) @@ -729,7 +730,7 @@ caCosCompare(void* v1, void* v2) *** *** @attention - This function takes `void*` instead of `char*` to match *** the `pSimilarityFn` signature expected by search functions like - *** `caCompleteSearch()`. However, behavior is undefined if `v1` or `v2` + *** `caCompleteSearch()`. However, behavior is undefined if `s1` or `s2` *** are not `char*`s. *** *** @param s1 A `char*` to the first string to compare. @@ -756,12 +757,12 @@ caLevCompare(void* s1, void* s2) if (len1 == 0lu && len2 != 0lu) return 0.0; /*** It's difficult to know if we are the first to detect an error - *** later in this function, so clear the error stack before hand + *** later in this function, so clear the error stack beforehand *** to avoid stale error data. ***/ mssClearError(); - /** Compute levenshtein edit distance. **/ + /** Compute Levenshtein edit distance. **/ const int edit_dist = caEditDist((const char*)str1, (const char*)str2, len1, len2); if (UNLIKELY(edit_dist < 0)) goto err; @@ -916,7 +917,7 @@ caKmeans( || isnan(min_improvement) )) goto end; - + /** Initialize labels. **/ memset(labels, 0u, num_vectors * sizeof(unsigned int)); @@ -1027,7 +1028,7 @@ caKmeans( } /** Is there enough improvement? **/ - if (min_improvement <= -1.0) continue; /** Skip check if it will never end the loop. **/ + if (min_improvement <= -1.0) continue; /* Skip check if it will never end the loop. */ const double average_cluster_size = ca_i_getClusterSize(vectors, num_vectors, labels, centroids, num_clusters); if (UNLIKELY(isnan(average_cluster_size))) goto end; const double improvement = old_average_cluster_size - average_cluster_size; diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c index 86d84f3e9..8922baeb3 100644 --- a/centrallix-lib/tests/test_clusters_00.c +++ b/centrallix-lib/tests/test_clusters_00.c @@ -43,7 +43,7 @@ static bool doTest(void) success &= ASSERT_EQL(caEditDist("kitten", "kittne", 0, 0), 1, "%d"); /* 1 transpose (end). */ /** Alternate words. **/ - success &= ASSERT_EQL(caEditDist("lawn", "flown", 0, 0), 2, "%d"); /* 1 insert and one replace. */ + success &= ASSERT_EQL(caEditDist("lawn", "flown", 0, 0), 2, "%d"); /* 1 insert and 1 replace. */ success &= ASSERT_EQL(caEditDist("hello", "hello!", 0, 0), 1, "%d"); /* 1 insert (end). */ success &= ASSERT_EQL(caEditDist("zert", "zerf", 0, 0), 1, "%d"); /* 1 replace (end). */ success &= ASSERT_EQL(caEditDist("llearr", "lear", 0, 0), 2, "%d"); /* 2 deletes (start & end). */ diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c index ef98d0552..2d942171d 100644 --- a/centrallix-lib/tests/test_clusters_03.c +++ b/centrallix-lib/tests/test_clusters_03.c @@ -51,7 +51,7 @@ static bool doTest(void) success &= ASSERT_RANGE(cos_cmp("hello", "hello world"), 0.6, 0.7, "%g"); success &= ASSERT_RANGE(cos_cmp("hello there", "hellow there"), 0.9, 1.0, "%g"); - /** Tests on fabricated contact information. */ + /** Tests on fabricated contact information. **/ /*** All email addresses and phone numbers are imaginary and were *** fabricated for the purposes of this test. ***/ diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index bb761ee9f..f8080dbb1 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -81,7 +81,7 @@ static bool doTest(void) success &= ASSERT_STR_EQL(caMostSimilar("kitten", (void*[]){"str_abc", "str1"}, 2, caLevCompare, 0.0), "str1"); success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, caLevCompare, 0.0), "str2"); - /** Many, identically similar options. */ + /** Many, identically similar options. **/ success &= ASSERT_STR_EQL(caMostSimilar("kitten", (void*[]){"skitten", "itten", "mitten", "iktten", "kittens", "kitte", "kittem", "kittne"}, 8, caLevCompare, 0.0), "skitten"); @@ -168,7 +168,7 @@ static bool doTest(void) success &= ASSERT_STR_EQL(caMostSimilar("str1", (void*[]){"str2", "str", "eight"}, 3, getSimMock, 0.9), NULL); success &= ASSERT_EQL(xhClear(&sim_table, do_nothing, NULL), 0, "%d"); - /** Nans are skipped. **/ + /** NaNs are skipped. **/ double val_nan = 0.8, val_vals = NAN, val_val = 0.2; success &= ASSERT_EQL(xhAdd(&sim_table, "val|nan", (void*)&val_nan), 0, "%d"); success &= ASSERT_EQL(xhAdd(&sim_table, "val|vals", (void*)&val_vals), 0, "%d"); diff --git a/centrallix-os/datasets/README.md b/centrallix-os/datasets/README.md index 055770a55..c78e3a0f6 100644 --- a/centrallix-os/datasets/README.md +++ b/centrallix-os/datasets/README.md @@ -1,6 +1,6 @@ # Datasets Folder -The datasets folder is intended for storing datasets used in testing that must not be committed to GitHub. Datasets in this folder may contain sensitive data, including production data or patterns from production data. This data _must_ be treated with care, even if it is obfuscated. Thus, this directory should be "git-ignored". If you ever see files in this directory listed as changes when you run `git status` or use similar commands or tools, immediately remove these changes and investigate. Data in this directory must _never_ be committed or pushed! +The datasets folder is intended for storing datasets used in testing that must not be committed to GitHub. Datasets in this folder may contain sensitive data, including production data or patterns from production data. This data _must_ be treated with care, even if it is obfuscated. Thus, this directory should be "git-ignored". If you ever see files in this directory listed as changes when you run `git status` or use similar commands or tools, immediately remove these changes and investigate. Data in this directory must _never_ be committed or pushed! ## Modifying this README.md If you need to modify this readme file, you may need to pass `-f` when adding it because the directory is part of the `.gitignore` (e.g. `git add -f README.md`). Obviously, be careful _not to `git add` any other files_ in this directory. diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md index e6ec53cf7..6685abac5 100644 --- a/centrallix-sysdoc/Libraries/mtsession.md +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -120,4 +120,4 @@ The `mssError()` function is not required to be called at every function nesting ```c void mssErrorErrno(int clr, char* module, char* message, ...); ``` -This function works the same way as [`mssError`](#msserror), except checks the current value of `errno` and postpends that error message to the end of the provided error. This is useful if a system call or other library function is responsible for this error. +This function works the same way as [`mssError`](#msserror), except that it checks the current value of `errno` and post-pends that error message to the end of the provided error. This is useful if a system call or other library function is responsible for this error. diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index 0ccab1603..8ea6d99a8 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -61,7 +61,7 @@ Centrallix has its own memory management wrapper that caches deallocated blocks In addition, the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. `malloc()`, and on how many blocks of each size/type are `malloc()`ed and cached. This information can be helpful for tracking down memory leaks. Empirical testing has shown an increase of performance of around 50% or more in programs that use newmalloc. -One caveat is that this memory manager does not provide `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or [`nmSysMalloc()`](#nmsysmalloc), [`nmSysFree()`](#nmsysfree), and [`nmSysRealloc()`](#nmsysrealloc) should be used for blocks of memory that might vary in size. +One caveat is that this memory manager does not provide an `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or [`nmSysMalloc()`](#nmsysmalloc), [`nmSysFree()`](#nmsysfree), and [`nmSysRealloc()`](#nmsysrealloc) should be used for blocks of memory that might vary in size. The newmalloc module can be accessed by adding `#include "cxlib/newmalloc.h"` to the include section of a .c file in centrallix, or `#include "newmalloc.h"` in centrallix-lib. diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 3ccf4760a..851295d11 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -150,7 +150,7 @@ This function returns an item given a specific index into the XArray, or `NULL` ```c int xaFindItem(pXArray this, void* item); ``` -This function returns array index for the provided item in the array, or -1 if the item could not be found. Requires an exact match, so two `void*` pointing to different memory with identical contents are not considered equal by this function. If the data is actually another datatype typecasted as a `void*`, all 8 bytes must be identical for a match. +This function returns the array index for the provided item in the array, or -1 if the item could not be found. Requires an exact match, so two `void*` pointing to different memory with identical contents are not considered equal by this function. If the data is actually another datatype typecasted as a `void*`, all 8 bytes must be identical for a match. For example: ```c @@ -181,7 +181,7 @@ This function works the same as [`xaFindItem()`](#xafinditem), however it iterat ```c int xaRemoveItem(pXArray this, int index); ``` -This function removes an item from the XArray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. +This function removes an item from the XArray at the given index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. ## xaClear() diff --git a/centrallix-sysdoc/Libraries/xhash.md b/centrallix-sysdoc/Libraries/xhash.md index 7124f31be..fce58b136 100644 --- a/centrallix-sysdoc/Libraries/xhash.md +++ b/centrallix-sysdoc/Libraries/xhash.md @@ -112,7 +112,7 @@ Clears all items from a hash table. If a `free_fn()` is provided, it will be in ```c int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, va_list), ...); ``` -This function executes an operation on each entry of the hash table entry. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `va_list` of any additional arguments passed to `xhForEach()`. Each invocation receives the full list, so the callback may consume the arguments freely. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. +This function executes an operation on each entry of the hash table. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `va_list` of any additional arguments passed to `xhForEach()`. Each invocation receives the full list, so the callback may consume the arguments freely. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. This function returns 0 if the function executes successfully, 1 if the callback function is `NULL`, or n (where n != 0) if the callback function returns n. It does not return any error code other than 1 or any error codes returned by `callback_fn()`. diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index cea36a3e9..96b58a63b 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -172,7 +172,7 @@ This function copies the string `text` into the XString, overwriting any previou ```c char* xsStringEnd(pXString this); ``` -This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some string may contain nulls, using `strlen()` may produce an incorrect result. It is equivalent to `this->String + xsLength()`, but it is shorter to type and keeps the internal workings of the xstring abstracted away from the caller. +This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some strings may contain nulls, using `strlen()` may produce an incorrect result. It is equivalent to `this->String + xsLength()`, but it is shorter to type and keeps the internal workings of the xstring abstracted away from the caller. ## xsConcatPrintf() diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 3ab2b9c9f..d28e563d2 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -156,12 +156,12 @@ A driver can be opened multiple times, leading one driver to have multiple "node An instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. -The structure of the subtree beneath the node object is entirely up to the drivers' author to determine; the OSML does not impose any structural restrictions on such subtrees. Each object within this structure (e.g. `/example.qy`) can have three types of readable data: +The structure of the subtree beneath the node object is entirely up to the driver's author to determine; the OSML does not impose any structural restrictions on such subtrees. Each object within this structure (e.g. `/example.qy`) can have three types of readable data: - Child objects (e.g. `/rows`) which can have their own data. - Content, which can be read similar to reading a file. - Query data, allowing the object to be queried for information. -Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradigm, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traverse its child objects, although queries are not required to be implemented this way. +Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradigm, however, it is common for web servers, where opening a directory often returns the `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traverse its child objects, although queries are not required to be implemented this way. Below is an example of the MySQL driver's node object and its subtrees of child objects (defined in `objdrv_mysql.c`): @@ -679,7 +679,7 @@ The value pointer points to a union struct which can hold one of several types o \*_See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more info about this datatype._ -In this way, `int`s and `double`s can be returned by value while other types are returned by reference. Items returned by reference must be guaranteed to be valid until either the object is closed, or another `GetAttrValue()` or `SetAttrValue()` call is made to any driver (which ever happens first). +In this way, `int`s and `double`s can be returned by value while other types are returned by reference. Items returned by reference must be guaranteed to be valid until either the object is closed, or another `GetAttrValue()` or `SetAttrValue()` call is made to any driver (whichever happens first). This function should return 0 on success, 1 if the value is `NULL` or undefined / unset, or -1 on a non-existent attribute or other error. @@ -787,8 +787,8 @@ The return value, `hints : ObjPresentationHints`, contains the following useful - `hints->MinValue : void*`: An expression defining the minimum valid value. - `hints->MaxValue : void*`: An expression defining the maximum valid value. - `hints->EnumList : XArray`: If the attribute is a string enum, this XArray lists the valid string values. -- `hints->EnumQuery : char*`: A query string which enumerates the valid values a string enum attribute. -- `hints->Format : char*`: A presentation format for datetime or money types, such as `"dd MMM yyyy HH:mm"` or `"$0.00"`. See `obj_datatypes.c` (near line 100) for more information creating a presentation format. +- `hints->EnumQuery : char*`: A query string which enumerates the valid values of a string enum attribute. +- `hints->Format : char*`: A presentation format for datetime or money types, such as `"dd MMM yyyy HH:mm"` or `"$0.00"`. See `obj_datatypes.c` (near line 100) for more information on creating a presentation format. - `hints->AllowChars : char*`: An array of all valid characters for a string attribute, NULL to allow all characters. - `hints->BadChars : char*`: An array of all invalid characters for a string attribute. If a character appears in both `hints->BadChars` and `hints->AllowChars`, the character should be rejected. - `hints->Length : int`: The maximum length of data that can be included in a string attribute. @@ -813,7 +813,7 @@ The following macros are provided for setting style flags: - `OBJ_PH_STYLE_BUTTONS`: Radio buttons or check boxes should be used for the presentation of enum attribute values. - `OBJ_PH_STYLE_NOTNULL`: The attribute does not allow `NULL` values. - `OBJ_PH_STYLE_STRNULL`: An empty string (`""`) should be treated as a `NULL` value. -- `OBJ_PH_STYLE_GROUPED`: The GroupID should be checked and so that fields can be grouped together. +- `OBJ_PH_STYLE_GROUPED`: The GroupID should be checked so that fields can be grouped together. - `OBJ_PH_STYLE_READONLY`: The user is not allowed to modify this attribute. - `OBJ_PH_STYLE_HIDDEN`: This attribute should be hidden and not presented to the user. - `OBJ_PH_STYLE_PASSWORD`: Values in this attribute should be hidden, such as for passwords. @@ -890,7 +890,7 @@ This function is only intended to be used by the MultiQuery module. Any other d ## III Reading the Node Object -A driver will commonly configure itself by reading text content from its node object file, at the root of its object subtree. This content may define what resource(s) a driver should provide, how it should access or compute them, and other similar information. Most drivers use the structure file format for their node objects because SN module makes parsing, reading, and writing these files easier. It also performs caching automatically to improve performance. +A driver will commonly configure itself by reading text content from its node object file, at the root of its object subtree. This content may define what resource(s) a driver should provide, how it should access or compute them, and other similar information. Most drivers use the structure file format for their node objects because the SN module makes parsing, reading, and writing these files easier. It also performs caching automatically to improve performance. - 📖 **Note**: The node object will **already be open** as an object in the ObjectSystem: The OSML does this for each driver. If a driver does not use the SN/ST modules, then it should read and write the node object directly with `objRead()` and `objWrite()`. A driver should **NEVER** `objClose()` the node object! The OSML handles that. @@ -1544,7 +1544,7 @@ The term "**MAY**" refers to optional, but permissible, behavior. ### C. Querying Subobjects -1. If an object cannot support queries for subobjects, `xxxOpenQuery()` call SHOULD fail. +1. If an object cannot support queries for subobjects, the `xxxOpenQuery()` call SHOULD fail. 2. If an object can support the existence of subobjects, but has no subobjects, the `xxxOpenQuery()` should succeed, but calls to `xxxQueryFetch()` MUST return `NULL`. diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index c55efaed8..bc94d521f 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -89,10 +89,10 @@ Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296 ### String Vectors Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). -Remember that the first and last characters form a pairs with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real use cases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string (this allows us to get large performance and memory improvements with [Sparse Vectors](#sparse-vectors)). +Remember that the first and last characters form pairs with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real use cases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string (this allows us to get large performance and memory improvements with [Sparse Vectors](#sparse-vectors)). ### Sparse Vectors -As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. +As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of `-n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. **Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 9ef9e5114..7be79712f 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -4762,7 +4762,7 @@ exp_fn_compare(pExpression tree) return 0; } - err: + err: mssError(0, "EXP", "%s(): Failed to compute the similarity.", tree->Name); return -1; } diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index e63cfbdf5..fbb66d66c 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -183,7 +183,7 @@ objTypeFromStr(const char* str) case 'U': case 'u': if (strcmp(str+1, "Unknown"+1) == 0) return DATA_T_UNAVAILABLE; - if (strcmp(str+1, "Unavailable"+1) == 0) return DATA_T_UNAVAILABLE; + if (strcmp(str+1, "Unavailable"+1) == 0) return DATA_T_UNAVAILABLE; break; } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index a44830bfc..a64032e4c 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -528,10 +528,10 @@ typedef struct _SEARCH *** @param Params A pParam array storing the params in the .cluster file. *** @param nParams The number of specified params. *** @param ParamList A "scope" for resolving parameter values during parsing. - *** @param ClusterDatas A pCluster array for the clusters in the .cluster file, + *** @param ClusterDatas A pClusterData array for the clusters in the .cluster file, *** NULL if `nClusterDatas == 0`. *** @param nClusterDatas The number of specified clusters. - *** @param SearchDatas A SearchData array for the searches in the .cluster file. + *** @param SearchDatas A pSearchData array for the searches in the .cluster file. *** @param nSearchDatas The number of specified searches. *** @param Parent The parent object used to open this NodeData instance. *** @param OpenCount The number of open driver instances that are using the @@ -717,7 +717,7 @@ static void cluster_i_cacheFreeCluster(pXHashEntry entry, void* path); static void cluster_i_cacheFreeSearch(pXHashEntry entry, void* path); int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); -/** Unimplemented DriverFunctions. **/ +/** Unimplemented Driver Functions. **/ // LINK #unimplemented int clusterCreate(pObject obj, int mask, pContentType systype, char* usrtype, pObjTrxTree* oxt); int clusterDelete(pObject obj, pObjTrxTree* oxt); @@ -811,7 +811,7 @@ cluster_i_unknownAttribute(char* attr_name, const TargetType target_type) // LINK #functions -/*** Parses a ClusteringAlgorithm from the algorithm attribute in the pStructInf. +/*** Parses a ClusterAlgorithm from the algorithm attribute in the pStructInf. *** *** @attention - Promises that a failure invokes mssError() at least once. *** @@ -1936,7 +1936,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) || xaInit(&cluster_infs, CI_INITIAL_INFS) != 0 || xaInit(&search_infs, CI_INITIAL_INFS) != 0 ) { - mssError(1, "Cluster", "Failed to initialize an XArray of size %u.", CI_INITIAL_INFS); + mssError(1, "Cluster", "Failed to initialize an XArray of size %d.", CI_INITIAL_INFS); goto err_free; } for (unsigned int i = 0u; i < inf->nSubInf; i++) @@ -2527,7 +2527,7 @@ cluster_i_freeNodeData(pNodeData node_data) nmSysFree(node_data->SearchDatas); node_data->SearchDatas = NULL; } - + /** Free data source, if one exists. **/ /*** Note: SourceData is freed last since other free functions may need to *** access information from this structure when freeing data. @@ -3317,7 +3317,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) return 0; err_free: - if (cluster_data->Sims != NULL) + if (cluster_data->Sims != NULL) { nmSysFree(cluster_data->Sims); cluster_data->Sims = NULL; @@ -3646,11 +3646,11 @@ cluster_i_getParamType(void* inf_v, const char* attr_name) *** @param inf_v Node data containing the list of parameters. *** @param attr_name The name of the requested parameter. *** @param datatype The expected datatype of the parameter value. - *** See datatypes.h for a list of valid datatypes. + *** See datatypes.h for a list of valid datatypes. *** @param val A pointer to a location where a pointer to the requested *** data should be stored. Typically, the caller creates a local variable *** to store this pointer, then passes a pointer to that local variable - *** so that they will have a pointer to the data. + *** so that they will have a pointer to the data. *** This buffer will not be modified unless the data is successfully *** found. If a value other than 0 is returned, the buffer is not updated. *** @returns 0 if successful, @@ -4368,7 +4368,7 @@ clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) *** @param val A pointer to a location where a pointer to the requested *** data should be stored. Typically, the caller creates a local variable *** to store this pointer, then passes a pointer to that local variable - *** so that they will have a pointer to the data. + *** so that they will have a pointer to the data. *** This buffer will not be modified unless the data is successfully *** found. If a value other than 0 is returned, the buffer is not updated. *** @param oxt The transaction tree (for the incomplete transaction system). @@ -5402,9 +5402,9 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) // LINK #functions /*** Prints a hash table entry that is assumed to be from one of the caches. - *** + *** *** @attention - Intended for use in `xhForEach()`. - *** + *** *** @param entry The hash table entry to print. *** @param args Several arguments that control the printing: *** - The type of driver struct data being printed: either source data, @@ -5689,7 +5689,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree snprintBytes(buf, sizeof(buf), search_bytes); printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, buf); snprintBytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes); - printf("%-8s %-4d %-12s\n\n", "Total", total_caches, buf); + printf("%-8s %-4u %-12s\n\n", "Total", total_caches, buf); /** Print skip stats (if anything was skipped.) **/ if (num_uncomputed_skipped > 0) @@ -5795,7 +5795,7 @@ int clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt) { mssError(1, "Cluster", "clusterRead() not implemented."); - fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); + fprintf(stderr, "HINT: Use queries instead (e.g. clusterOpenQuery()).\n"); return -1; } diff --git a/centrallix/tests/test_cos_compare_00.to b/centrallix/tests/test_cos_compare_00.to index f6a635389..deed11716 100644 --- a/centrallix/tests/test_cos_compare_00.to +++ b/centrallix/tests/test_cos_compare_00.to @@ -4,7 +4,7 @@ query select case1 = condition((cos_compare('hello', 'hello') >= 0.999) and (cos_compare('hello', 'hello') <= 1.0), "pass", "fail") query select case2 = condition((cos_compare('hello', 'zephora') <= 0.001) and (cos_compare('hello', 'zephora') >= 0.0), "pass", "fail") query select case3 = condition((cos_compare('hello', 'hello world') <= 0.7) and (cos_compare('hello', 'hello world') >= 0.6), "pass", "fail") -query select case4 = condition((cos_compare('hello there', 'hellow there') >= 0.9) and (cos_compare('hello', 'hellow') <= 1.0), "pass", "fail") +query select case4 = condition((cos_compare('hello there', 'hellow there') >= 0.9) and (cos_compare('hello there', 'hellow there') <= 1.0), "pass", "fail") # Tests on fabricated contact information. diff --git a/centrallix/tests/test_expfn_log_00.cmp b/centrallix/tests/test_expfn_log_00.cmp index 130056813..6e12ebf95 100644 --- a/centrallix/tests/test_expfn_log_00.cmp +++ b/centrallix/tests/test_expfn_log_00.cmp @@ -37,7 +37,7 @@ Attribute [log(1e10)+]: integer 1 Attribute [log(1e10)-]: integer 1 Attribute [ln(2.718281828)]: double 1.0 Attribute [log10(3.14159)]: double 0.49714951 -Attribute [log(10, 1.1)]: double 0.04139269 -Attribute [log(1.1, 10)]: double 24.15885793 -Attribute [log(10, 0.001)]: double -3.0 -Attribute [log(0.1, 1000)]: double -3.0 +Attribute [log(1.1, 10)]: double 0.04139269 +Attribute [log(10, 1.1)]: double 24.15885793 +Attribute [log(0.001, 10)]: double -3.0 +Attribute [log(1000, 0.1)]: double -3.0 diff --git a/centrallix/tests/test_expfn_metaphone_00.to b/centrallix/tests/test_expfn_metaphone_00.to index c99cec5b4..74b266303 100644 --- a/centrallix/tests/test_expfn_metaphone_00.to +++ b/centrallix/tests/test_expfn_metaphone_00.to @@ -15,7 +15,7 @@ # **GPT-5 mini (Preview) was run in GitHub Copilot to suggest the words # for some tests after analyzing a generated coverage report. I (Israel) # used the suggestions to write some "AI generated" test cases. -# +# # For more information, see the manual test suite implementation at the # end of the exp_double_metaphone.c file. diff --git a/centrallix/tests/test_expfn_trim_00.to b/centrallix/tests/test_expfn_trim_00.to index 86da8529a..74fece7f9 100644 --- a/centrallix/tests/test_expfn_trim_00.to +++ b/centrallix/tests/test_expfn_trim_00.to @@ -1,4 +1,4 @@ -##NAME trim() function +##NAME trim() function query select 'trim("No white space on the left or right side.")' = trim("No white space on the left or right side.") @@ -12,7 +12,7 @@ query select 'trim("With tab character\t")' = trim("With tab character\t") query select 'trim("With newline character\n")' = trim("With newline character\n") -# query select 'trim("\r With carriage return character.")' = trim("\r With carriage return character") +# query select 'trim("\r With carriage return character.")' = trim("\r With carriage return character.") query select 'trim("")' = trim("") diff --git a/centrallix/tests/test_lev_compare_00.to b/centrallix/tests/test_lev_compare_00.to index 5d9cec0f7..74423613e 100644 --- a/centrallix/tests/test_lev_compare_00.to +++ b/centrallix/tests/test_lev_compare_00.to @@ -20,7 +20,7 @@ query select case14 = condition(lev_compare('kitten', 'kitten') >= 0.99 and lev query select case15 = condition(lev_compare('kitten', 'skitten') >= 0.8 and lev_compare('kitten', 'skitten') <= 0.9, 'pass', 'fail') -- 1 insert query select case16 = condition(lev_compare('kitten', 'itten') >= 0.8 and lev_compare('kitten', 'itten') <= 0.9, 'pass', 'fail') -- 1 delete query select case17 = condition(lev_compare('kitten', 'mitten') >= 0.8 and lev_compare('kitten', 'mitten') <= 0.9, 'pass', 'fail') -- 1 replace -query select case18 = condition(lev_compare('kitten', 'smitten') >= 0.7 and lev_compare('kitten', 'smitten') <= 0.8, 'pass', 'fail') -- 1 insert and one replace +query select case18 = condition(lev_compare('kitten', 'smitten') >= 0.7 and lev_compare('kitten', 'smitten') <= 0.8, 'pass', 'fail') -- 1 insert and 1 replace query select case19 = condition(lev_compare('kitten', 'iktten') >= 0.8 and lev_compare('kitten', 'iktten') <= 0.9, 'pass', 'fail') -- 1 transpose query select case20 = condition(lev_compare('kitten', 'kittens') >= 0.8 and lev_compare('kitten', 'kittens') <= 0.9, 'pass', 'fail') -- 1 insert (end) query select case21 = condition(lev_compare('kitten', 'kitte') >= 0.8 and lev_compare('kitten', 'kitte') <= 0.9, 'pass', 'fail') -- 1 delete (end) diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 26c686cf6..a26b4f500 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -80,7 +80,7 @@ *** This file was copied from a GitHub Repo with licensing (see above). *** *** As for this code, I've modified it to use styling, memory management, and - *** libraries used to be consistent Centrallix. I also wrote comments and + *** libraries used to be consistent with Centrallix. I also wrote comments and *** tests based on my own understanding, so they might not accurately reflect *** the original author's intent. *** @@ -166,7 +166,7 @@ meta_i_isVowel(pXString s, unsigned int pos) *** string is Slavo Germanic. *** *** @param s The XString to be searched. - *** @returns 1 if the XString is Slavo Germanic, or 0 otherwise. + *** @returns 1 if the XString is Slavo Germanic, or 0 otherwise. ***/ static bool meta_i_isSlavoGermanic(pXString s) @@ -334,12 +334,12 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (xsConcatenateLiteral(&secondary, "A") < 0) goto end_free; } current += 1; - break; + break; } case 'B': { - /** "-mb", e.g", "dumb", already skipped over... **/ + /** "-mb", e.g. "dumb", already skipped over... **/ if (xsConcatenateLiteral(&primary, "P") < 0) goto end_free; if (xsConcatenateLiteral(&secondary, "P") < 0) goto end_free; @@ -412,7 +412,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) /** Germanic, greek, or otherwise 'ch' for 'kh' sound. **/ if ( meta_i_isStrAt(&original, 0, "SCH", "VAN ", "VON ", "") - /** 'architect but not 'arch', 'orchestra', 'orchid' **/ + /** 'architect' but not 'arch', 'orchestra', 'orchid' **/ || meta_i_isStrAt(&original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") || meta_i_isStrAt(&original, (current + 2), "T", "S", "") || ( @@ -451,7 +451,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) break; } - /** e.g, 'czerny' **/ + /** e.g., 'czerny' **/ if (meta_i_isStrAt(&original, current, "CZ", "") && !meta_i_isStrAt(&original, (current - 2), "WICZ", "")) { @@ -538,7 +538,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; if (xsConcatenateLiteral(&secondary, "K") < 0) goto end_free; - /** Name sent in 'mac caffrey', 'mac gregor **/ + /** Name sent in 'mac caffrey', 'mac gregor' **/ if (meta_i_isStrAt(&original, (current + 1), " C", " Q", " G", "")) current += 3; else if (meta_i_isStrAt(&original, (current + 1), "C", "K", "Q", "") @@ -720,7 +720,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) break; } - /** -ger-, -gy- **/ + /** -ger-, -gy- **/ if ( (next_char == 'Y' || meta_i_isStrAt(&original, (current + 1), "ER", "")) /** Exceptions. **/ @@ -734,7 +734,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) break; } - /** Italian e.g, 'biaggi' **/ + /** Italian e.g., 'biaggi' **/ if ( meta_i_isStrAt(&original, (current + 1), "E", "I", "Y", "") || meta_i_isStrAt(&original, (current - 1), "AGGI", "OGGI", "") @@ -1077,7 +1077,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) || meta_i_isStrAt(&original, 0, "SCH", "VAN ", "VON ", "") ) ? "T" : "0"; /* Zero, not O. */ - if (xsConcatenate(&primary, primary_char, -1) < 0) goto end_free; + if (xsConcatenate(&primary, primary_char, -1) < 0) goto end_free; if (xsConcatenateLiteral(&secondary, "T") < 0) goto end_free; current += 2; break; diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index 08e427014..129e2d8da 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -664,10 +664,10 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio rval = expExpressionToPod(find_exp, find_exp->DataType, pod); end: - if (UNLIKELY(rval < 0)) + if (UNLIKELY(rval < 0)) { mssError(1, "ST", - "Failed to parse attribute \"%s\" from group \"%s\"", + "Failed to parse attribute \"%s\" from group \"%s\".", this->Name, this->Parent->Name ); } From 8c1b59f444495851e7cfa817a860b5be0a4dfa31 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 10:20:09 -0600 Subject: [PATCH 315/361] Improve readability. --- centrallix-lib/tests/test_newmalloc_00.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 16b7f3aa2..92c843d3f 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -86,7 +86,8 @@ static bool doTest(void) srand(seed_counter++); /** Initialize the mock error function. **/ - err_buf = malloc(err_buf_size = 256); + err_buf_size = 256; + err_buf = malloc(err_buf_size); if (!ASSERT_NOT_NULL(err_buf)) return false; err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); From be3fae9df3c1756d3c0d2985f9a34162111cb7ce Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 10:22:55 -0600 Subject: [PATCH 316/361] Remove an assert function that should have stayed on the dups branch. --- centrallix-lib/tests/test_utils.h | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 8b1913e6c..6931f2998 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -131,27 +131,6 @@ success; \ }) -/*** Assert two cosine vectors from `cluster.c` to be equal. - *** - *** @param v1 The first vector. - *** @param v2 The second vector. - *** @returns true if successful, false otherwise. - ***/ -#define ASSERT_VEC_EQL(v1, v2) \ - ({ \ - pVector _v1 = (v1); \ - pVector _v2 = (v2); \ - int success = ca_eql(_v1, _v2); \ - if (!success) \ - { \ - printf(" > Expected %s (1.) to equal %s (2.) at %s:%d, but got:\n", #v1, #v2, __FILE__, __LINE__); \ - printf(" > 1. (%p) ", _v1); ca_print_vector(_v1); printf("\n"); \ - printf(" > 2. (%p) ", _v2); ca_print_vector(_v2); printf("\n"); \ - fflush(stdout); \ - } \ - success; \ - }) - /*** Assert a value to fall within a range. *** *** @param v The value. From 4833d29a0af487c03d612db4eb08f2382a16ae7a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 10:37:31 -0600 Subject: [PATCH 317/361] Update test asserts to use bools. --- centrallix-lib/tests/test_utils.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 6931f2998..d694a4635 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -18,6 +18,7 @@ /* testing centrallix-lib. */ /************************************************************************/ +#include #include #include @@ -51,7 +52,7 @@ ***/ #define ASSERT_TRUE(v) \ ({ \ - const int success = !!(v); \ + const bool success = !!(v); \ if (!success) fprintf(stderr, \ " > Expected %s to be true at %s:%d\n", \ #v, __FILE__, __LINE__ \ @@ -72,7 +73,7 @@ ({ \ __typeof__ (v1) _v1 = (v1); \ __typeof__ (v2) _v2 = (v2); \ - int success = (_v1 == _v2); \ + const bool success = (_v1 == _v2); \ if (!success) fprintf(stderr, \ " > Expected %s ("sp") to equal %s ("sp") at %s:%d\n", \ #v1, _v1, #v2, _v2, __FILE__, __LINE__ \ @@ -90,7 +91,7 @@ ({ \ char* _str1 = (str1); \ char* _str2 = (str2); \ - int success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ + const bool success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ if (!success) fprintf(stderr, \ " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", \ #str1, _str1, #str2, _str2, __FILE__, __LINE__ \ @@ -111,7 +112,7 @@ const char* _str1 = (str1); \ const char* _str2 = (str2); \ const size_t _len = (size_t)(len); \ - const int success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ + const bool success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ if (!success)\ { \ const size_t buf_len = max(7, _len + 1); \ @@ -146,7 +147,7 @@ __typeof__ (v) _v = (v); \ __typeof__ (min_v) _min = (min_v); \ __typeof__ (max_v) _max = (max_v); \ - int success = (_min <= _v && _v <= _max); \ + const bool success = (_min <= _v && _v <= _max); \ if (!success) fprintf(stderr, \ " > Expected %s ("sp") to be in the range %s ("sp") - %s ("sp") at %s:%d\n", \ #v, _v, #min_v, _min, #max_v, _max, __FILE__, __LINE__ \ @@ -163,7 +164,7 @@ #define ASSERT_NOT_NULL(ptr) \ ({ \ __typeof__ (ptr) _ptr = (ptr); \ - int success = (_ptr != NULL); \ + const bool success = (_ptr != NULL); \ if (!success) fprintf(stderr, \ " > Expected %s (%p) to be non null at %s:%d\n", \ #ptr, _ptr, __FILE__, __LINE__ \ From 51aff7df44f381560f7bb968daeee61dde355bd2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 10:38:12 -0600 Subject: [PATCH 318/361] Improve performance of testing functions. --- centrallix-lib/tests/test_utils.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index d694a4635..55178de14 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -22,8 +22,9 @@ #include #include -#include "timer.h" +#include "expect.h" #include "range.h" +#include "timer.h" /*** Define lockup times. Valgrind instruments every memory access, so tests *** may need longer to finish when running under valgrind. @@ -53,7 +54,7 @@ #define ASSERT_TRUE(v) \ ({ \ const bool success = !!(v); \ - if (!success) fprintf(stderr, \ + if (UNLIKELY(!success)) fprintf(stderr, \ " > Expected %s to be true at %s:%d\n", \ #v, __FILE__, __LINE__ \ ); \ @@ -74,7 +75,7 @@ __typeof__ (v1) _v1 = (v1); \ __typeof__ (v2) _v2 = (v2); \ const bool success = (_v1 == _v2); \ - if (!success) fprintf(stderr, \ + if (UNLIKELY(!success)) fprintf(stderr, \ " > Expected %s ("sp") to equal %s ("sp") at %s:%d\n", \ #v1, _v1, #v2, _v2, __FILE__, __LINE__ \ ); \ @@ -92,7 +93,7 @@ char* _str1 = (str1); \ char* _str2 = (str2); \ const bool success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ - if (!success) fprintf(stderr, \ + if (UNLIKELY(!success)) fprintf(stderr, \ " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", \ #str1, _str1, #str2, _str2, __FILE__, __LINE__ \ ); \ @@ -113,7 +114,7 @@ const char* _str2 = (str2); \ const size_t _len = (size_t)(len); \ const bool success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ - if (!success)\ + if (UNLIKELY(!success))\ { \ const size_t buf_len = max(7, _len + 1); \ char _tmp1[buf_len]; \ @@ -148,7 +149,7 @@ __typeof__ (min_v) _min = (min_v); \ __typeof__ (max_v) _max = (max_v); \ const bool success = (_min <= _v && _v <= _max); \ - if (!success) fprintf(stderr, \ + if (UNLIKELY(!success)) fprintf(stderr, \ " > Expected %s ("sp") to be in the range %s ("sp") - %s ("sp") at %s:%d\n", \ #v, _v, #min_v, _min, #max_v, _max, __FILE__, __LINE__ \ ); \ @@ -165,7 +166,7 @@ ({ \ __typeof__ (ptr) _ptr = (ptr); \ const bool success = (_ptr != NULL); \ - if (!success) fprintf(stderr, \ + if (UNLIKELY(!success)) fprintf(stderr, \ " > Expected %s (%p) to be non null at %s:%d\n", \ #ptr, _ptr, __FILE__, __LINE__ \ ); \ From f7f3b48dc1606f61a5f2ac802441cb41747a6681 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 10:42:27 -0600 Subject: [PATCH 319/361] Fix ASSERT_STR_EQL() and ASSERT_STR_EQL_N() comparing heap and stack pointers, which confused the compiler. --- centrallix-lib/tests/test_utils.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 55178de14..840edad3f 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -92,7 +92,9 @@ ({ \ char* _str1 = (str1); \ char* _str2 = (str2); \ - const bool success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ + const bool both_null = (_str1 == NULL && _str2 == NULL); \ + const bool both_eql = (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ + const bool success = (both_null || both_eql); \ if (UNLIKELY(!success)) fprintf(stderr, \ " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", \ #str1, _str1, #str2, _str2, __FILE__, __LINE__ \ @@ -113,7 +115,9 @@ const char* _str1 = (str1); \ const char* _str2 = (str2); \ const size_t _len = (size_t)(len); \ - const bool success = (_str1 == _str2) || (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ + const bool both_null = (_str1 == NULL && _str2 == NULL); \ + const bool both_eql = (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ + const bool success = (both_null || both_eql); \ if (UNLIKELY(!success))\ { \ const size_t buf_len = max(7, _len + 1); \ From 2452ff9d08da6f3fb9372c649246462e3a59a4f1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 11:01:38 -0600 Subject: [PATCH 320/361] Fix mistakes and typos. --- centrallix-lib/tests/test_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 840edad3f..66aa01104 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -181,7 +181,7 @@ *** replaced with the '_' character. *** *** @param dest The destination string buffer (must be at least `n+1` bytes). - *** @param src The source string (must be at least `n+1` bytes), which can + *** @param src The source string (must be at least `n` bytes), which can *** include null-terminator characters ('\0'). *** @param n The number of characters to copy. ***/ From fef1f58846b8172c28064779ae25b50269e3ad8a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 11:09:57 -0600 Subject: [PATCH 321/361] Fix mistakes and typos. --- centrallix-lib/tests/test_utils.h | 2 +- centrallix-os/cluster-schema.cluster | 2 +- centrallix-sysdoc/GCC_Dependencies.md | 2 +- centrallix-sysdoc/OSDriver_Authoring.md | 2 +- centrallix/expression/exp_evaluate.c | 4 ++-- centrallix/expression/exp_functions.c | 1 + centrallix/osdrivers/objdrv_cluster.c | 15 ++++++++------- centrallix/tests/test_expfn_log_00.to | 2 +- centrallix/tests/test_expfn_metaphone_00.to | 5 +---- centrallix/utility/stparse.c | 2 ++ 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 58a294b1a..74159c0d7 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -137,7 +137,7 @@ success; \ }) -/*** Assert two cosine vectors from `cluster.c` to be equal. +/*** Assert two cosine vectors from `clusters.c` to be equal. *** *** @param v1 The first vector. *** @param v2 The second vector. diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster index 019a6823d..84c40a9cf 100644 --- a/centrallix-os/cluster-schema.cluster +++ b/centrallix-os/cluster-schema.cluster @@ -62,5 +62,5 @@ file_name "system/cluster" - /{query} - /key1 : string // The key of the first data point. - /key2 : string // The key of the second data point. - - /sim : double && 0.0 < x <= threshold // The similarity of the two data points. + - /sim : double && threshold <= x <= 1.0 // The similarity of the two data points. ... diff --git a/centrallix-sysdoc/GCC_Dependencies.md b/centrallix-sysdoc/GCC_Dependencies.md index f4a26f7fb..9e933ac9c 100644 --- a/centrallix-sysdoc/GCC_Dependencies.md +++ b/centrallix-sysdoc/GCC_Dependencies.md @@ -15,7 +15,7 @@ Date: December 4, 2025 This document tracks dependencies on the GCC toolchain in the centrallix codebase. As code is added which relies on GCC specific behavior, such additions should be noted here to make possible use of a different toolchain (e.g. LLVM) in the future less painful. ## List of Dependencies -- `util.h` & `magic.h`: Use `__typeof__` and `({ ... })` in macros to avoid double-computation. +- `range.h` & `magic.h`: Use `__typeof__` and `({ ... })` in macros to avoid double-computation. ## Notes `__FILE__` and `__LINE__` are not dependencies as they were added in C90. See [this page](https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html) for information about predefined macros. diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index d28e563d2..088bf1694 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -1295,7 +1295,7 @@ This function copies a pathname structure from the `src` to the `dest`, returnin ### obj_internal_FreePathStruct() ```c -void obj_internal_FreePathStruct(pPathname path); +int obj_internal_FreePathStruct(pPathname path); ``` This function frees a pathname structure. diff --git a/centrallix/expression/exp_evaluate.c b/centrallix/expression/exp_evaluate.c index 2fba38b1a..e71edfe66 100644 --- a/centrallix/expression/exp_evaluate.c +++ b/centrallix/expression/exp_evaluate.c @@ -1470,8 +1470,8 @@ expEvalProperty(pExpression tree, pParamObjects objlist) break; /*** FIX: Shallow copy violates contract, but objdrv_cluster.c is - *** the only one location that StringVec is used and I think it - *** won't cause issues. + *** the only location that StringVec is used and I think it won't + *** cause issues. ***/ case DATA_T_STRINGVEC: v = getfn(obj, tree->Name, DATA_T_STRINGVEC, &vptr); diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 7be79712f..d781882e9 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1740,6 +1740,7 @@ exp_fn_rtrim(pExpression tree) /** Shortcut for trimming nothing. **/ if (n == len) { + exp_fn_i_freeResultString(tree); tree->String = str; goto end; } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index a64032e4c..368406903 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -476,7 +476,7 @@ typedef struct _CD *** @skip --> Attribute Data. *** @param Name The search name, specified in the .cluster file. *** @param CacheKey The key associated with this object in the SearchDataCache. - *** @param Source The cluster from which this search is to be derived. + *** @param SourceCluster The cluster from which this search is to be derived. *** @param SimilarityMeasure The similarity measure used to compare items. *** @param Threshold The minimum similarity threshold for elements to be *** included in the results of the search. @@ -1802,6 +1802,7 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) search_data->Threshold, search_data->SimilarityMeasure ); + search_data->CacheKey = cache_key; pXHashTable search_cache = &ClusterDriverCaches.SearchDataCache; /** Check for a cached version. **/ @@ -2107,8 +2108,8 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) " > Param #%u: %s\n" " > Provided Param #%u: %s\n" " > Error code: %d", - i, param->Name, - j, provided_param->Name, + i + 1, param->Name, + j + 1, provided_param->Name, ret ); goto err_free; @@ -3345,7 +3346,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) cluster->Indexes = NULL; } } - nmFree(cluster_data->Clusters, clusters_size); + nmSysFree(cluster_data->Clusters); cluster_data->Clusters = NULL; } @@ -5642,7 +5643,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree &ClusterDriverCaches.SourceDataCache, cluster_i_printEntry, (int)CI_SOURCE_DATA, &source_bytes, num_uncomputed_skipped_ptr, path - )) != 0) + ) != 0)) { mssError(0, "Cluster", "Failed to print source data cache."); ret = -1; @@ -5653,7 +5654,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree &ClusterDriverCaches.ClusterDataCache, cluster_i_printEntry, (int)CI_CLUSTER_DATA, &cluster_bytes, num_uncomputed_skipped_ptr, path - )) != 0) + ) != 0)) { mssError(0, "Cluster", "Failed to print cluster data cache."); ret = -1; @@ -5664,7 +5665,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree &ClusterDriverCaches.SearchDataCache, cluster_i_printEntry, (int)CI_SEARCH_DATA, &search_bytes, num_uncomputed_skipped_ptr, path - )) != 0) + ) != 0)) { mssError(0, "Cluster", "Failed to print search data cache."); ret = -1; diff --git a/centrallix/tests/test_expfn_log_00.to b/centrallix/tests/test_expfn_log_00.to index 993064a87..4754084e4 100644 --- a/centrallix/tests/test_expfn_log_00.to +++ b/centrallix/tests/test_expfn_log_00.to @@ -3,7 +3,7 @@ # Natural Log: ln(x) query select 'ln(1)' = ln(1) -- Expect 0. query select 'ln(e)' = ln(2.718281828459045) -- Expect 1. -query select 'ln(0)' = ln(0) -- Expect -inf (log approaches infinity). +query select 'ln(0)' = ln(0) -- Expect -inf (log approaches negative infinity). query select 'ln(-1)' = ln(-1) -- Expect NaN (log undefined for negative). query select 'ln(10)' = round(ln(10), 8) -- Expect ~2.30258509. query select 'ln(1.5)' = round(ln(1.5), 8) -- Expect ~0.40546511. diff --git a/centrallix/tests/test_expfn_metaphone_00.to b/centrallix/tests/test_expfn_metaphone_00.to index 74b266303..7564ae380 100644 --- a/centrallix/tests/test_expfn_metaphone_00.to +++ b/centrallix/tests/test_expfn_metaphone_00.to @@ -6,7 +6,7 @@ # 3: https://en.toolpage.org/tool/metaphone # These tests were collected from the following sources: -# - Example comments in the source code of exp_double_metaphone.c +# - Example comments in the source code of double_metaphone.c # - Maurice Aubrey's Tests* # - Tests manually written by Israel Fuller # - Tests written by prompting ChatGPT-5 (preview)** @@ -15,9 +15,6 @@ # **GPT-5 mini (Preview) was run in GitHub Copilot to suggest the words # for some tests after analyzing a generated coverage report. I (Israel) # used the suggestions to write some "AI generated" test cases. -# -# For more information, see the manual test suite implementation at the -# end of the exp_double_metaphone.c file. query select result = metaphone("Test") query select result = metaphone("Basic") diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index 129e2d8da..46d4ae95b 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -641,6 +641,8 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio if (expEvalTree(find_exp, my_objlist) < 0) { mssError(0, "ST", "Failed to evaluate expression tree."); + if (objlist == NULL) + warnFail(expFreeParamList(my_objlist)); goto end; } if (objlist == NULL) From 670e2af74a600eecae4ed4c7214c60cb1f4760b6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 11:28:37 -0600 Subject: [PATCH 322/361] Fix mistakes and typos. --- centrallix-lib/src/clusters.c | 11 +++++++++-- centrallix-lib/tests/test_clusters_02.c | 2 +- centrallix-lib/tests/test_clusters_03.c | 2 +- centrallix-lib/tests/test_clusters_05.c | 2 +- centrallix-sysdoc/Libraries/newmalloc.md | 2 +- centrallix-sysdoc/Libraries/xstring.md | 8 ++++---- centrallix-sysdoc/OSDriver_Authoring.md | 8 ++++---- centrallix-sysdoc/string_similarity.md | 2 +- centrallix/include/double_metaphone.h | 2 +- centrallix/osdrivers/objdrv_cluster.c | 25 ++++++++++++------------ centrallix/tests/test_expfn_log_00.to | 4 ++-- centrallix/utility/double_metaphone.c | 10 +++++----- centrallix/utility/stparse.c | 2 +- 13 files changed, 43 insertions(+), 37 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index c092c5b51..552c8f933 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -207,7 +207,9 @@ caBuildVector(const char* str) char_pairs[i].c2 = chars[i + 1]; /** Hash the character pair into an index (dimension). **/ - /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ + /*** Note: The passed value should always be between + *** 96 (boundary) and 132 ('9'). + ***/ char_pairs[i].hash = ca_i_hashCharPair(chars[i], chars[i + 1]); } @@ -1209,7 +1211,12 @@ caSlidingSearch( pair->i = i; pair->j = j; pair->similarity = sim; - warnNeg(xaAddItem(pairs, (void*)pair)); + if (UNLIKELY(xaAddItem(pairs, (void*)pair) < 0)) + { + mssError(1, "CA", "xaAddItem() failed."); + nmFree(pair, pair_size); + goto err_free; + } } } } diff --git a/centrallix-lib/tests/test_clusters_02.c b/centrallix-lib/tests/test_clusters_02.c index 9b3363561..3b638cb8f 100644 --- a/centrallix-lib/tests/test_clusters_02.c +++ b/centrallix-lib/tests/test_clusters_02.c @@ -60,7 +60,7 @@ static bool doTest(void) if (index >= max_index) { printf(" > MEMORY ERROR!!\n"); - printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index + 1, max_index); + printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index, max_index); printf(" > Increase the size of the free list (aka. max_index) to %u or more.\n", index + 1); return false; } diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c index 2d942171d..4f3b59cc2 100644 --- a/centrallix-lib/tests/test_clusters_03.c +++ b/centrallix-lib/tests/test_clusters_03.c @@ -71,7 +71,7 @@ static bool doTest(void) if (index >= max_index) { printf(" > MEMORY ERROR!!\n"); - printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index + 1, max_index); + printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index, max_index); printf(" > Increase the size of the free list (aka. max_index) to %u or more.\n", index + 1); return false; } diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index f8080dbb1..a224aea1d 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -142,7 +142,7 @@ static bool doTest(void) if (index >= max_index) { printf(" > MEMORY ERROR!!\n"); - printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index + 1, max_index); + printf(" > Allocated %u vectors, overflowing the free list of size %u.\n", index, max_index); printf(" > Increase the size of the free list (aka. max_index) to %u or more.\n", index + 1); return false; } diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md index 8ea6d99a8..684b26151 100644 --- a/centrallix-sysdoc/Libraries/newmalloc.md +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -111,7 +111,7 @@ NewMalloc subsystem statistics: ```c void nmRegister(int size, char* name); ``` -Registers an intelligent name for block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production use cases, but using it can make tracking down memory leaks easier. +Registers an intelligent name for a block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production use cases, but using it can make tracking down memory leaks easier. This function is usually called in a module's `Initialize()` function on each of the structures the module uses internally. diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md index 96b58a63b..4d4f36d88 100644 --- a/centrallix-sysdoc/Libraries/xstring.md +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -172,7 +172,7 @@ This function copies the string `text` into the XString, overwriting any previou ```c char* xsStringEnd(pXString this); ``` -This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some strings may contain nulls, using `strlen()` may produce an incorrect result. It is equivalent to `this->String + xsLength()`, but it is shorter to type and keeps the internal workings of the xstring abstracted away from the caller. +This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some strings may contain nulls, using `strlen()` may produce an incorrect result. It is equivalent to `this->String + xsLength(this)`, but it is shorter to type and keeps the internal workings of the xstring abstracted away from the caller. ## xsConcatPrintf() @@ -208,21 +208,21 @@ This function writes data into the xstring, similar to using the standard fdWrit ```c int xsRTrim(pXString this); ``` -This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the right side of the xstring. This function returns 0 if successful, or -1 if an error occurs. +This function trims whitespace characters (spaces, tabs, newlines, and carriage returns) from the right side of the xstring. This function returns 0 if successful, or -1 if an error occurs. ## xsLTrim() ```c int xsLTrim(pXString this); ``` -This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the left side of the xstring. This function returns 0 if successful, or -1 if an error occurs. +This function trims whitespace characters (spaces, tabs, newlines, and carriage returns) from the left side of the xstring. This function returns 0 if successful, or -1 if an error occurs. ## xsTrim() ```c int xsTrim(pXString this); ``` -This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from both sides of the xstring. This function returns 0 if successful, or -1 if an error occurs. +This function trims whitespace characters (spaces, tabs, newlines, and carriage returns) from both sides of the xstring. This function returns 0 if successful, or -1 if an error occurs. ## xsFind() diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 088bf1694..93dd4d7e1 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -462,7 +462,7 @@ The `Delete()` function is used to delete an object, which often means removing | Param | Type | Description | |-------|--------------|---------------------------------------------------------------------------| -| obj | pObject | The Object structure pointer, used in the same way as in Open and Delete. | +| obj | pObject | The Object structure pointer, used in the same way as in Open and Create. | | oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | `Delete()` should return 0 on success and -1 on failure. @@ -1226,7 +1226,7 @@ The functions use the following parameters: - `v : void*` is the object provided in `expAddParamToList()` (or a similar function). - `attr_name : char*` is the string name for the requested attribute. - `datatype : int` is the data type for the requested attribute. -- `val : pObjData` is either a buffer in which to store the requested data (`cluster_i_getParamValue()`) or a buffer containing data that will be copied to the parameter `cluster_i_setParamValue()`. +- `val : pObjData` is either a buffer in which to store the requested data (`cluster_i_getParamValue()`) or a buffer containing data that will be copied to the parameter (`cluster_i_setParamValue()`). The functions return the following values: - The `cluster_i_getParamType()` function returns the datatype on success (e.g. `DATA_T_INTEGER`), or -1 if an error occurs. @@ -1246,12 +1246,12 @@ top-level tree node. The following properties are useful: | Property | Description | ------------------ | ------------ -| tree->DataType | The type of the final value, see 'Managing Object Attributes' above for types. +| tree->DataType | The type of the final value, see [Object Attributes](#object-attributes) above for types. | tree->Flags | Contains the bit EXPR_F_NULL if the expression evaluated to NULL. | tree->Integer | If DATA_T_INTEGER, this is the integer value. | tree->String | If DATA_T_STRING, this is the string value. | tree->Types.Double | If DATA_T_DOUBLE, this is the double value. -| tree->Types.Date | If DATA_T_DATETIME, this is the date/time value +| tree->Types.Date | If DATA_T_DATETIME, this is the date/time value. | tree->Types.Money | If DATA_T_MONEY, this is the money value. There are several other EXP functions used to deal with aggregates and a few other obscure features as well. Aggregates are mostly handled internally by Centrallix so further explanation should not be necessary here. diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index bc94d521f..58db46ada 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -160,5 +160,5 @@ When a new record is entered, a quick scan is run to check if it might be a dupl ### Fix Known Issues - The cluster driver often fails to open the structure file if it was modified since the last time the path was opened. Opening a different path (including the root path, even though it does not support queries) fixes this issue. This is either a bug in the st_node caching or in the cluster driver's usage of stparse. -- The cluster does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to manually invalidate caches when data is changed, or use a fresh object system instance. +- The cluster driver does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to manually invalidate caches when data is changed, or use a fresh object system instance. - It is not possible to open cluster or search entries, they can only be retrieved by querying. diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h index 78eef04f1..de969ff6b 100644 --- a/centrallix/include/double_metaphone.h +++ b/centrallix/include/double_metaphone.h @@ -3,7 +3,7 @@ /************************************************************************/ /* Text-DoubleMetaphone */ -/* Centrallix Base Library */ +/* Centrallix Core */ /* */ /* Copyright 2000, Maurice Aubrey . */ /* All rights reserved. */ diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 368406903..003de42e9 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -485,7 +485,7 @@ typedef struct _CD *** @param Pairs An array holding the pairs found by the search, or NULL if *** the search has not been computed yet. The indexes stored in these *** pairs are indexes into the SourceData data arrays (access with - *** `Source->SourceData->[DATA_ARRAY]`). + *** `SourceCluster->SourceData->[DATA_ARRAY]`). *** @param nPairs The number of pairs found, or 0 if the search has not been *** computed yet. *** @@ -2090,7 +2090,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) if (UNLIKELY(provided_param == NULL)) { mssError(1, "Cluster", - "Failed to get param #%u/%u.", + "Failed to get param #%u/%d.", j + 1, num_provided_params ); goto err_free; @@ -2142,7 +2142,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) if (UNLIKELY(provided_param == NULL)) { mssError(1, "Cluster", - "Failed to get provided param #%u/%u.", + "Failed to get provided param #%u/%d.", i + 1, num_provided_params ); goto err_free; @@ -2151,7 +2151,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) if (UNLIKELY(provided_name == NULL)) { mssError(1, "Cluster", - "Failed to get provided param name from param #%u/%u.", + "Failed to get provided param name from param #%u/%d.", i + 1, num_provided_params ); goto err_free; @@ -2537,9 +2537,8 @@ cluster_i_freeNodeData(pNodeData node_data) ***/ if (node_data->SourceData != NULL) { - /*** This data is cached, so we should NOT free it! The caching system - *** is responsible for the memory. We only need to free the array - *** holding our pointers to said cached memory. + /*** This data is cached, so we should NOT free it! The cache owns + *** this memory so we only need to drop our pointer. ***/ node_data->SourceData = NULL; } @@ -4821,7 +4820,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO err:; char* name; clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); - mssError(1, "Cluster", + mssError(0, "Cluster", "Failed to get attribute for cluster object %s (target type: %u, \"%s\").", driver_data->NodeData->SourceData->Name, target_type, name ); @@ -5692,7 +5691,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree snprintBytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes); printf("%-8s %-4u %-12s\n\n", "Total", total_caches, buf); - /** Print skip stats (if anything was skipped.) **/ + /** Print skip stats (if anything was skipped). **/ if (num_uncomputed_skipped > 0) printf("Skipped %llu uncomputed caches.\n\n", num_uncomputed_skipped); @@ -5747,7 +5746,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree return 0; } - /** Unknown parameter. **/ + /** Unknown method. **/ mssError(1, "Cluster", "Unknown command: \"%s\"", method_name); /** Attempt to give hint. **/ @@ -5886,7 +5885,7 @@ clusterInitialize(void) goto err_free; } - /** Setup the structure. **/ + /** Set up the structure. **/ if (strtcpy(drv->Name, "cluster - Clustering Driver", sizeof(drv->Name)) < 0) { mssError(1, "Cluster", "Failed to write driver name."); @@ -5905,7 +5904,7 @@ clusterInitialize(void) drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ - /** Setup the function references. **/ + /** Set up the function references. **/ drv->Open = clusterOpen; drv->OpenChild = NULL; drv->Close = clusterClose; @@ -5965,7 +5964,7 @@ clusterInitialize(void) nmFree(drv, sizeof(ObjDriver)); } - mssError(1, "Cluster", "Failed to initialize cluster driver."); + mssError(0, "Cluster", "Failed to initialize cluster driver."); return -1; } diff --git a/centrallix/tests/test_expfn_log_00.to b/centrallix/tests/test_expfn_log_00.to index 4754084e4..009302bc1 100644 --- a/centrallix/tests/test_expfn_log_00.to +++ b/centrallix/tests/test_expfn_log_00.to @@ -7,7 +7,7 @@ query select 'ln(0)' = ln(0) -- Expect -inf (log a query select 'ln(-1)' = ln(-1) -- Expect NaN (log undefined for negative). query select 'ln(10)' = round(ln(10), 8) -- Expect ~2.30258509. query select 'ln(1.5)' = round(ln(1.5), 8) -- Expect ~0.40546511. -query select 'ln(1e-10)' = ln(0.0000000001) < 0.0000000001 -- Expect true (value is very small). +query select 'ln(1e-10)' = ln(0.0000000001) < 0.0000000001 -- Expect true (value is ~-23.02585). query select 'ln(1e10)+' = ln(10000000000.0) > 23.0 -- Expect true (value is ~23.02585). query select 'ln(1e10)-' = ln(10000000000.0) < 23.1 -- Expect true (value is ~23.02585). @@ -46,7 +46,7 @@ query select 'log(1e-10)' = (log(0.0000000001) == ln(0.0000000001)) query select 'log(1e10)+' = (log(10000000000.0) == ln(10000000000.0)) query select 'log(1e10)-' = (log(10000000000.0) == ln(10000000000.0)) --- Additional double/int mixed cases +# Additional double/int mixed cases query select 'ln(2.718281828)' = round(ln(2.718281828), 8) -- Expect ~1 (close to e). query select 'log10(3.14159)' = round(log10(3.14159), 8) -- Expect ~0.49715. query select 'log(1.1, 10)' = round(log(1.1, 10), 8) -- Expect 0.04139269. diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index a26b4f500..8864e832e 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -1,6 +1,6 @@ /************************************************************************/ /* Text-DoubleMetaphone */ -/* Centrallix Base Library */ +/* Centrallix Core */ /* */ /* Copyright 2000, Maurice Aubrey . */ /* All rights reserved. */ @@ -447,8 +447,8 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (xsConcatenateLiteral(&secondary, "X") < 0) goto end_free; } } - current += 2; - break; + current += 2; + break; } /** e.g., 'czerny' **/ @@ -763,7 +763,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) } current += 2; break; - } + } current += (next_char == 'G') ? 2 : 1; if (xsConcatenateLiteral(&primary, "K") < 0) goto end_free; @@ -958,7 +958,7 @@ metaDoubleMetaphone(const char* str, char** primary_code, char** secondary_code) if (xsConcatenateLiteral(&secondary, "R") < 0) goto end_free; current += (next_char == 'R') ? 2 : 1; break; - } + } case 'S': { diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index 46d4ae95b..b7f54005b 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -19,7 +19,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ From 24ca1e0f791b97cfe7451ba097bc9aab99353a2e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 24 Sep 2026 11:37:50 -0600 Subject: [PATCH 323/361] Fix mistakes and typos. Wow, how many more of these are there!? --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/src/clusters.c | 4 ++-- centrallix-sysdoc/Libraries/xarray.md | 2 +- centrallix-sysdoc/string_similarity.md | 4 ++-- centrallix/osdrivers/objdrv_cluster.c | 19 ++++++++++--------- centrallix/utility/double_metaphone.c | 2 +- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index f355c46af..6630ea2e4 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -58,7 +58,7 @@ *** cause an int underflow with the current encoding scheme. *** *** Unfortunately, we can't use a number this large yet because - *** kmeans algorithm creates densely allocated centroids with + *** the kmeans algorithm creates densely allocated centroids with *** `CA_NUM_DIMS` dimensions, so a large number causes it to fail. *** Thus, we use 251 as the largest prime number less than 256, *** giving us a decent balance between collision reduction and diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 552c8f933..3988481d2 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -54,7 +54,7 @@ /*** Gets the hash for a pair of ASCII characters, represented by unsigned ints. - *** Thank you professor John Delano for this hashing algorithm. + *** Thank you Professor John Delano for this hashing algorithm. *** *** @param c1 The first character in the pair. *** @param c2 The second character in the pair. @@ -1138,7 +1138,7 @@ caMostSimilar( /*** Runs a sliding search over the provided data, comparing each element to - *** the next `window_size` elements. If any comparison yields a similarity + *** the next `window_size - 1` elements. If any comparison yields a similarity *** greater than the threshold, it is stored in the returned xArray. *** *** @param data The data to be searched. diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 851295d11..1922c7fb8 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -202,7 +202,7 @@ This function works the same as [`xaClear()`](#xaclear), except that it is sligh ```c int xaCount(pXArray this); ``` -This function returns the number of items in the XArray, or -1 on error. It is equivalent to accessing `XArray->nItems` (although the latter expression will not return an error). +This function returns the number of items in the XArray, or -1 on error. It is equivalent to accessing `XArray->nItems`. ## xaInsertBefore() diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 58db46ada..0629c4d83 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -53,7 +53,7 @@ The following sections discuss the approaches to calculating similarity between - [K-means Clustering](#k-means-clustering) - [K-means++ Clustering](#k-means-clustering-1) - [K-medoids Clustering](#k-medoids-clustering) - - [DBScan Clustering](#db-scan) + - [DBScan Clustering](#db-scan-clustering) - [Sliding Clusters](#sliding-clusters) - [Future Implementation](#future-implementation) - [K-means Fuzzy Clustering](#k-means-fuzzy-clustering) @@ -130,7 +130,7 @@ This method is largely identical to k-means, except that [k-means++](https://en. **Not yet implemented** This method is also very similar to k-means, except that [k-medoids](https://en.wikipedia.org/wiki/K-medoids) places an additional requirement that all centroids be points in the data. This would theoretically allow for other similarity measures (such as Levenshtein edit distance) to be used for clustering instead of only cosine compare. -### DB-Scan +### DB-Scan Clustering **Proposed, not yet implemented or documented** ### Sliding Clusters diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 003de42e9..cc45fb70e 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -332,7 +332,7 @@ char* METHOD_NAMES[] = *** *** Memory Stats: *** - Padding: 0 bytes - *** - Total size: 80 bytes + *** - Total size: 88 bytes *** *** @skip --> Attribute Data. *** @param Name The source name, specified in the .cluster file. @@ -1367,7 +1367,7 @@ cluster_i_parseClusterData(pStructInf inf, pParamObjects param_list, pSourceData ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; if (UNLIKELY(name == NULL)) - goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->name. */ + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->Name. */ /** Handle various struct types. **/ const int struct_type = stStructType(sub_inf); @@ -1722,7 +1722,7 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; if (UNLIKELY(name == NULL)) - goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->name. */ + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->Name. */ /** Handle various struct types. **/ const int struct_type = stStructType(sub_inf); @@ -1951,7 +1951,7 @@ cluster_i_parseNodeData(pStructInf inf, pObject parent) ASSERTMAGIC(sub_inf, MGK_STRUCTINF); char* name = sub_inf->Name; if (UNLIKELY(name == NULL)) - goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->name. */ + goto err_free; /* Skip in-loop error handler, which expects a valid sub_inf->Name. */ /** Handle various struct types. **/ const int struct_type = stStructType(sub_inf); @@ -4565,7 +4565,7 @@ clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pO goto err; } - /** Handle date_computed. **/ + /** Handle last_modification and date_computed. **/ if (strcmp(attr_name, "last_modification") == 0 || strcmp(attr_name, "date_computed") == 0) { @@ -5413,6 +5413,7 @@ clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) *** used by the entry printed. *** - A pointer to an unsigned long long that counts how many uncomputed *** caches have been skipped. Specify NULL to include uncomputed caches. + *** - A key prefix to filter entries by, or NULL to print all entries. *** @returns 0 if successful, *** -1 if an error occurs. ***/ @@ -5524,7 +5525,7 @@ cluster_i_printEntry(pXHashEntry entry, va_list args) static void cluster_i_cacheFreeSourceData(pXHashEntry entry, void* unused) { - /** Free the data, the key, and the entry itself. **/ + /** Free the data and the key. **/ cluster_i_freeSourceData((pSourceData)entry->Data); nmSysFree(entry->Key); @@ -5544,7 +5545,7 @@ cluster_i_cacheFreeSourceData(pXHashEntry entry, void* unused) static void cluster_i_cacheFreeCluster(pXHashEntry entry, void* unused) { - /** Free the data, the key, and the entry itself. **/ + /** Free the data and the key. **/ cluster_i_freeClusterData((pClusterData)entry->Data, false); nmSysFree(entry->Key); @@ -5564,7 +5565,7 @@ cluster_i_cacheFreeCluster(pXHashEntry entry, void* unused) static void cluster_i_cacheFreeSearch(pXHashEntry entry, void* unused) { - /** Free the data, the key, and the entry itself. **/ + /** Free the data and the key. **/ cluster_i_freeSearchData((pSearchData)entry->Data); nmSysFree(entry->Key); @@ -5606,7 +5607,7 @@ clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree goto err; } - /** 'show' and 'show_all'. **/ + /** 'show', 'show_less', and 'show_all'. **/ bool show = false, skip_uncomputed = false; if (strcmp(param->String, "show_less") == 0) { diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c index 8864e832e..06a94ff93 100644 --- a/centrallix/utility/double_metaphone.c +++ b/centrallix/utility/double_metaphone.c @@ -80,7 +80,7 @@ *** This file was copied from a GitHub Repo with licensing (see above). *** *** As for this code, I've modified it to use styling, memory management, and - *** libraries used to be consistent with Centrallix. I also wrote comments and + *** libraries to be consistent with Centrallix. I also wrote comments and *** tests based on my own understanding, so they might not accurately reflect *** the original author's intent. *** From 8945cbbc4d9ecffbc401f801b804e0bafdaa137d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 25 Sep 2026 15:36:36 -0600 Subject: [PATCH 324/361] Move objTypeFromStr() and objTypeToStr() to the add-driver-functions branch. --- centrallix/objectsystem/obj_datatypes.c | 87 ------------------------- 1 file changed, 87 deletions(-) diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index fbb66d66c..74749fc0e 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -128,93 +128,6 @@ char* obj_default_money_fmt = "$0.00"; char* obj_default_null_fmt = "NULL"; -/** Should maybe replace current type parsing in the presentation hints. **/ -/*** Parse the given string into a datatype. The case of the first character - *** is ignored, but all other characters must be capitalized correctly. - *** - *** @attention - This function is optimized to prevent performance hits in - *** situations where it may need to be called many thousands of times. - *** - *** @param str The string to be parsed to a datatype. - *** @returns The datatype. - *** - *** LINK ../../centrallix-lib/include/datatypes.h:72 - ***/ -int -objTypeFromStr(const char* str) - { - /** All valid types are non-null strings, at least 2 characters long. **/ - if (str == NULL || str[0] == '\0' || str[1] == '\0') return -1; - - /** Check type. **/ - switch (str[0]) - { - case 'A': case 'a': - if (strcmp(str+1, "Array"+1) == 0) return DATA_T_ARRAY; - if (strcmp(str+1, "Any"+1) == 0) return DATA_T_ANY; - break; - - case 'B': case 'b': - if (strcmp(str+1, "Binary"+1) == 0) return DATA_T_BINARY; - break; - - case 'C': case 'c': - if (strcmp(str+1, "Code"+1) == 0) return DATA_T_CODE; - break; - - case 'D': case 'd': - if (strcmp(str+1, "Double"+1) == 0) return DATA_T_DOUBLE; - if (strcmp(str+1, "DateTime"+1) == 0) return DATA_T_DATETIME; - break; - - case 'I': case 'i': - if (strcmp(str+1, "Integer"+1) == 0) return DATA_T_INTEGER; - if (strcmp(str+1, "IntVector"+1) == 0) return DATA_T_INTVEC; - break; - - case 'M': case 'm': - if (strcmp(str+1, "Money"+1) == 0) return DATA_T_MONEY; - break; - - case 'S': case 's': - if (strcmp(str+1, "String"+1) == 0) return DATA_T_STRING; - if (strcmp(str+1, "StringVector"+1) == 0) return DATA_T_STRINGVEC; - break; - - case 'U': case 'u': - if (strcmp(str+1, "Unknown"+1) == 0) return DATA_T_UNAVAILABLE; - if (strcmp(str+1, "Unavailable"+1) == 0) return DATA_T_UNAVAILABLE; - break; - } - - /** Invalid type. **/ - return -1; - } - - -/*** Convert a type to its string name. - *** - *** @param type The type to be converted. - *** @returns A char* to the type name, or - *** "(unknown)" if the type is unknown, or - *** "invalid" if the type number cannot even be a valid type. - ***/ -char* -objTypeToStr(const int type) - { - /** Guard out of bounds reads. **/ - if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) - { - /** Invalid type. **/ - mssError(1, "OBJ", "Invalid type %d.", type); - - return "invalid"; /* Shall not parse to a valid type in objTypeFromStr(). */ - } - - return obj_type_names[type]; - } - - /*** obj_internal_ParseDateLang - looks up a list of language internationalization *** strings inside the date format. WARNING - modifies the "srcptr" data in *** place. From 5d49f166dbed4d51e92e049685168a92e7c97bec Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 25 Sep 2026 15:40:57 -0600 Subject: [PATCH 325/361] Move objFileName(), objFilePath(), and DRIVER_ATTRIBUTE_NAMES to the add-driver-functions branch. --- centrallix/include/obj.h | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index ebe16f523..7bae2f881 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -632,34 +632,6 @@ typedef struct #define OBJ_MQ_F_NOUPDATE (1<<1) /* disallow any updates in this query */ #define OBJ_MQ_F_ONEROW (1<<2) /* only need first row from results */ -/** @returns The file name for the provided object. **/ -#define objFileName(obj) \ - ({ \ - __typeof__ (obj) _obj = (obj); \ - obj_internal_PathPart(_obj->Pathname, _obj->SubPtr - 1, 1); \ - }) -/** @returns The file path to the provided object. **/ -#define objFilePath(obj) \ - ({ \ - __typeof__ (obj) _obj = (obj); \ - obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ - }) - -/*** An array of the names of the general attributes that must be implemented - *** for every object system driver. See `OSDriver_Authoring.md` for more - *** information. - ***/ -#define DRIVER_ATTRIBUTE_NAMES \ - ((char*[]){ \ - "name", \ - "annotation", \ - "content_type", \ - "inner_type", \ - "outer_type", \ - "last_modification", \ - }) -#define N_DRIVER_ATTRIBUTE_NAMES ((unsigned int)(sizeof(DRIVER_ATTRIBUTE_NAMES) / sizeof(DRIVER_ATTRIBUTE_NAMES[0]))) - /** objectsystem main functions **/ int objInitialize(); From 1efe38a9dc52177593086b5315f89615f7321d28 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 25 Sep 2026 17:05:10 -0600 Subject: [PATCH 326/361] Fix an error that misused obj_internal_PathPart(). --- centrallix/osdrivers/objdrv_cluster.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index cc45fb70e..2019d5174 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -3927,10 +3927,7 @@ clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pOb if (node_data != NULL) cluster_i_freeNodeData(node_data); if (driver_data != NULL) nmFree(driver_data, sizeof(DriverData)); - mssError(0, "Cluster", - "Failed to open cluster file \"%s\" at: %s", - objFileName(parent), objFilePath(parent) - ); + mssError(0, "Cluster", "Failed to open cluster file: %s", objFilePath(parent)); return NULL; } From 944ba53abb1e734fef3d84261d3c55bcead4c387 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 2 Oct 2026 17:26:21 -0600 Subject: [PATCH 327/361] Fix mistakes in comments and spacing. --- centrallix-lib/tests/test_mtlexer_15.c | 2 +- centrallix-lib/tests/test_mtlexer_16.c | 2 +- centrallix-lib/tests/test_util_01.c | 2 +- centrallix-lib/tests/test_util_02.c | 2 +- centrallix-lib/tests/test_xarray_00.c | 2 +- centrallix-lib/tests/test_xhash_00.c | 2 +- centrallix-lib/tests/test_xhash_05.c | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/centrallix-lib/tests/test_mtlexer_15.c b/centrallix-lib/tests/test_mtlexer_15.c index b630b4cc6..8ef0d8814 100644 --- a/centrallix-lib/tests/test_mtlexer_15.c +++ b/centrallix-lib/tests/test_mtlexer_15.c @@ -10,7 +10,7 @@ #include #include "test_utils.h" -/** Integers in the data file, and so the ops performed by one pass. **/ +/** Integers in the data file. **/ #define N_INTS 12 static int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; diff --git a/centrallix-lib/tests/test_mtlexer_16.c b/centrallix-lib/tests/test_mtlexer_16.c index eaa627ec2..19e2fa352 100644 --- a/centrallix-lib/tests/test_mtlexer_16.c +++ b/centrallix-lib/tests/test_mtlexer_16.c @@ -10,7 +10,7 @@ #include #include "test_utils.h" -/** Integers in the data file, and so the ops performed by one pass. **/ +/** Integers in the data file. **/ #define N_INTS 12 static int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; diff --git a/centrallix-lib/tests/test_util_01.c b/centrallix-lib/tests/test_util_01.c index 11b2e51d7..bd9863b63 100644 --- a/centrallix-lib/tests/test_util_01.c +++ b/centrallix-lib/tests/test_util_01.c @@ -11,7 +11,7 @@ /* Module: test_util_01.c */ /* Author: Micah Shennum */ /* Creation: May 26th, 2011 */ -/* Description: Test strtoui */ +/* Description: Test strtoui */ /************************************************************************/ #include diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index ca143a076..456f080a6 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -72,7 +72,7 @@ static bool doTest(void) success &= TEST_SNPRINT_BYTES(buf, buf_size, 100, "100 bytes"); success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 1) - pow(1000, 0), (cs) ? "999 bytes" : "999 bytes"); success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 1), (cs) ? "1000 bytes" : "1 KB"); - success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 2) - pow(1000, 1), (cs) ? "975.59 KiB" : "999 KB"); + success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 2) - pow(1000, 1), (cs) ? "975.59 KiB" : "999 KB"); success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 2), (cs) ? "976.56 KiB" : "1 MB"); success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 3) - pow(1000, 2), (cs) ? "952.72 MiB" : "999 MB"); success &= TEST_SNPRINT_BYTES(buf, buf_size, pow(1000, 3), (cs) ? "953.67 MiB" : "1 GB"); diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 06241f21a..b1fd28fb7 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -130,7 +130,7 @@ static bool doTest(void) success &= ASSERT_EQL(xaGetItem(xa, 4), NULL, "%p"); /* Check null gap. */ /** Remove an item and ensure that it is gone. **/ - success &= ASSERT_EQL(xaRemoveItem(xa, 2), 0, "%d"); /* Remove original index 2. */ + success &= ASSERT_EQL(xaRemoveItem(xa, 2), 0, "%d"); /* Remove v2. */ success &= ASSERT_EQL(xaCount(xa), 5, "%d"); nmFree(v2, sizeof(int)); v2 = NULL; /* v2 is removed so it isn't freed with xaClear(). */ diff --git a/centrallix-lib/tests/test_xhash_00.c b/centrallix-lib/tests/test_xhash_00.c index 288ac6755..4ec71fad4 100644 --- a/centrallix-lib/tests/test_xhash_00.c +++ b/centrallix-lib/tests/test_xhash_00.c @@ -16,7 +16,7 @@ /* */ /* Unsupported cases skipped by the test suite: */ /* - a table with zero rows: divides by zero. */ -/* - a key shorter than a nonzero key length: over reads. */ +/* - a key shorter than a nonzero key length: over reads. */ /* - xhDeInit() on a table with entries: leaks them. */ /* - xhAdd() allocation failure: needs an inject to test. */ /************************************************************************/ diff --git a/centrallix-lib/tests/test_xhash_05.c b/centrallix-lib/tests/test_xhash_05.c index cc9a7ad35..2f1fbd242 100644 --- a/centrallix-lib/tests/test_xhash_05.c +++ b/centrallix-lib/tests/test_xhash_05.c @@ -47,8 +47,8 @@ static bool doTest(void) { bool success = true; - /*** The hash never leaves the rows it was given, and survives - *** xhInitialize(), which once rebuilt the table the hash reads from. + /*** The hash never leaves the rows it was given, and xhInitialize() + *** does not change it. ***/ for (int r = 0; r < ROW_COUNT; r++) { From 5158f3fa5a073f0dd5c70fc4f53dc5656ae3c603 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 09:42:40 -0600 Subject: [PATCH 328/361] Update tests from master that weren't reformatted yet. --- centrallix-lib/tests/test_mtlexer_22.c | 108 +++++++++++++----------- centrallix-lib/tests/test_smmalloc_09.c | 105 ++++++++++++----------- 2 files changed, 115 insertions(+), 98 deletions(-) diff --git a/centrallix-lib/tests/test_mtlexer_22.c b/centrallix-lib/tests/test_mtlexer_22.c index 0dd070056..10d0d3ec8 100644 --- a/centrallix-lib/tests/test_mtlexer_22.c +++ b/centrallix-lib/tests/test_mtlexer_22.c @@ -1,6 +1,9 @@ #include +#include #include +#include "test_utils.h" + #include "mtsession.h" #include "newmalloc.h" @@ -43,66 +46,69 @@ static Case cases[] = { "/;;;", { T_FILE, T_SEMI, T_SEMI, T_SEMI, T_EOF }, { "/" } }, }; -long long -test(char** tname) +/** Number of cases run per call to doTest(), counting the long path. **/ +#define NCASES ((int)(sizeof(cases) / sizeof(Case)) + 1) + +/** Path longer than the token buffer, followed by a semicolon. **/ +static char long_path[MLX_STRVAL * 2 + 2]; + +/*** This test verifies that a semicolon ends an unquoted path, both when the + *** path fits in the token buffer and when it is read in pieces, and that a + *** quoted path can still contain one. + ***/ +static bool +doTest(void) { - int i, c, j; - int iter; - int ncases = sizeof(cases) / sizeof(Case); + int c, j; int alloc; pLxSession lxs; char* str; - char longpath[MLX_STRVAL * 2 + 2]; - - /*** This test verifies that a semicolon ends an unquoted path, both - *** when the path fits in the token buffer and when it is read in - *** pieces, and that a quoted path can still contain one. - ***/ - *tname = "mtlexer-22 semicolon ends a filename"; - - mssInitialize("system", "", "", 0, "test"); - - /** Path longer than the token buffer, followed by a semicolon. **/ - longpath[0] = '/'; - memset(longpath + 1, 'a', sizeof(longpath) - 3); - longpath[sizeof(longpath) - 2] = ';'; - longpath[sizeof(longpath) - 1] = '\0'; - - iter = 20000; - for(i=0;i #include #include +#include +#include "test_utils.h" -long long -test(char** tname) +/** Blocks handed from the parent to the child in each pass. **/ +#define N_BLOCKS 2000 + +/** Region and message queue shared by every pass; created and destroyed by test(). **/ +static pSmRegion region = NULL; +static int msg_id = -1; + +static bool +doTest(void) { int i; - pSmRegion r; - int iter; char* ptr; - int pipefd[2]; int childpid; - int msg_id; struct my_msgbuf { long mtype; char mtext[sizeof(char*)]; } buf; int status; - smInitialize(); - - *tname = "smmalloc-09 2 process, A:malloc -> B:free"; - srand(time(NULL)); - iter = 20000; - r = smCreate(1024*1024); - - pipe(pipefd); - msg_id = msgget(IPC_PRIVATE, IPC_CREAT | IPC_EXCL | 0600); - childpid = fork(); - if (childpid < 0) - { - smDestroy(r); - return -1; - } + if (childpid < 0) return false; if (childpid == 0) { /** in child **/ - close(pipefd[1]); - for(i=0;i 0) rval *= N_BLOCKS; msgctl(msg_id, IPC_RMID, NULL); + msg_id = -1; + smDestroy(region); + region = NULL; - return iter; + return rval; } From 227cf87bba2d9a8bd564928ad293b8fcb1630feb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 09:45:57 -0600 Subject: [PATCH 329/361] Fix mistakes, typos, and memory leaks. --- centrallix-lib/tests/t_driver.c | 2 +- centrallix-lib/tests/test_mtlexer_09.c | 2 +- centrallix-lib/tests/test_timer_00.c | 2 ++ centrallix-lib/tests/test_xarray_00.c | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index b0247255f..be4da2362 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -58,7 +58,7 @@ start(void* v) clock_t start,end; long long rval; - /** Setup handlers for signals that may occur during a test. **/ + /** Set up handlers for signals that may occur during a test. **/ signal(SIGSEGV, segv_handler); signal(SIGABRT, abort_handler); signal(SIGALRM, alarm_handler); diff --git a/centrallix-lib/tests/test_mtlexer_09.c b/centrallix-lib/tests/test_mtlexer_09.c index 988d31a1d..0a6e0f500 100644 --- a/centrallix-lib/tests/test_mtlexer_09.c +++ b/centrallix-lib/tests/test_mtlexer_09.c @@ -70,7 +70,7 @@ doTest(void) } mlxCloseSession(lxs); - /** Put the line breaks back, ready for the next pass. **/ + /** Remove the line breaks to clean up for the next pass. **/ str[i+1] = 'a'; str[i+2] = 'a'; tokstr[0][i] = 'a'; diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c index 17e4b72f1..f4ad62d29 100644 --- a/centrallix-lib/tests/test_timer_00.c +++ b/centrallix-lib/tests/test_timer_00.c @@ -116,5 +116,7 @@ long long test(char** tname) /** Return failure. **/ fail: + timerDeInit(timer1); + timerFree(timer2); return -1; } diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index b1fd28fb7..6cbae4fdc 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -13,8 +13,8 @@ /* Creation: November 25th, 2025 */ /* Description: Test all the functions in the xarray library, except */ /* xaInit() and xaDeInit() because testing xaNew() and */ -/* xaFree() should cover them, and xaAddItemSorted() and */ -/* xaAddItemSortedInt32(). */ +/* xaFree() should cover them, and xaAddItemSorted(), */ +/* xaAddItemSortedInt32(), and xaClearR(). */ /************************************************************************/ #include @@ -29,7 +29,7 @@ /** Tested module. **/ #include "xarray.h" -/** Helper used by xaClear/xaClearR to free allocated items and count them. **/ +/** Helper used by xaClear() to free allocated items and count them. **/ static int test_free(void* p, void* arg) { if (p == NULL) return 0; From 81a9f0791207702e752c2bf4bf0e917ca7464d7d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 09:49:01 -0600 Subject: [PATCH 330/361] Fix test operation counting. --- centrallix-lib/tests/test_mtlexer_03.c | 3 ++- centrallix-lib/tests/test_mtlexer_09.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_mtlexer_03.c b/centrallix-lib/tests/test_mtlexer_03.c index fe997c303..ff3f0edbc 100644 --- a/centrallix-lib/tests/test_mtlexer_03.c +++ b/centrallix-lib/tests/test_mtlexer_03.c @@ -93,7 +93,8 @@ test(char** tname) tokstr[1] = "nextline"; tokstr[2] = "thirdline"; - rval = loopTest(doTest); + /** Each pass lexes MAX_LEN / N_PASS of the line lengths. **/ + rval = loopTest(doTest) * MAX_LEN / N_PASS; nmSysFree(tokstr[0]); tokstr[0] = NULL; diff --git a/centrallix-lib/tests/test_mtlexer_09.c b/centrallix-lib/tests/test_mtlexer_09.c index 0a6e0f500..c115fcbfe 100644 --- a/centrallix-lib/tests/test_mtlexer_09.c +++ b/centrallix-lib/tests/test_mtlexer_09.c @@ -100,7 +100,8 @@ test(char** tname) memset(tokstr[0], 'a', MAX_LEN+1); tokstr[0][MAX_LEN+1] = '\0'; - rval = loopTest(doTest); + /** Each pass lexes about (MAX_LEN-20) / N_PASS of the strings. **/ + rval = loopTest(doTest) * (MAX_LEN-20) / N_PASS; nmSysFree(tokstr[0]); tokstr[0] = NULL; From 1d4c9bdf0b093d1f9afa85453da29201c281bbe2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 09:50:44 -0600 Subject: [PATCH 331/361] Fix a type mistakes. --- centrallix-lib/tests/test_xarray_00.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 6cbae4fdc..7b0067fbe 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -136,14 +136,15 @@ static bool doTest(void) /** Count non-NULL items prior to clearing. **/ int count_before = xaCount(xa); - int nonnull = 0; - for (unsigned int i = 0; i < count_before; i++) + success &= ASSERT_RANGE(count_before, 0u, (unsigned int)__INT_MAX__, "%u"); + unsigned int nonnull = 0; + for (unsigned int i = 0; i < (unsigned int)count_before; i++) if (xaGetItem(xa, i) != NULL) nonnull++; /** Clear and verify free was called for each non-NULL item. **/ unsigned int freed_count = 0; success &= ASSERT_EQL(xaClear(xa, test_free, &freed_count), 0, "%d"); - success &= ASSERT_EQL(freed_count, nonnull, "%d"); + success &= ASSERT_EQL(freed_count, nonnull, "%u"); success &= ASSERT_EQL(xaCount(xa), 0, "%d"); /** Clean up. **/ From 6096a22bae9ab9d9e0f2ff6bca7e8a70e5bd2f1d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 09:55:10 -0600 Subject: [PATCH 332/361] Fix memory leaks in failing valgrind tests. --- centrallix-lib/tests/test_newmalloc_00.c | 90 ++++++++++++++++-------- centrallix-lib/tests/test_newmalloc_01.c | 72 ++++++++++++------- 2 files changed, 109 insertions(+), 53 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 92c843d3f..8070a402a 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -53,9 +53,10 @@ static int mockErrorFn(char* error_msg) /** Ensure enough space to store the error. **/ while (len > err_buf_size - err_buf_i) { + char* new_buf = realloc(err_buf, err_buf_size * 2); + if (!ASSERT_NOT_NULL(new_buf)) return -1; + err_buf = new_buf; err_buf_size *= 2; - err_buf = realloc(err_buf, err_buf_size); - if (!ASSERT_NOT_NULL(err_buf)) return -1; } err_buf_i += snprintf( @@ -78,9 +79,29 @@ static void* randomInit(void* ptr, size_t size) return ptr; } +/** Free the bulk data arrays and the entries allocated so far. **/ +static void freeBulk(void** data, void** test) + { + for (size_t i = 1lu; i < TEST_LIMIT; i++) + { + if (data != NULL) free(data[i]); + if (test != NULL && test[i] != NULL) nmSysFree(test[i]); + } + free(data); + free(test); + } + static bool doTest(void) { bool success = true; + bool finished = false; + char* str1 = NULL; + char* str2 = NULL; + void** data = NULL; + void** test = NULL; + char* str_dup1 = NULL; + char* str_dup2 = NULL; + void* large_buf = NULL; /** Set a consistent, distinct seed for each test iteration. **/ srand(seed_counter++); @@ -88,29 +109,25 @@ static bool doTest(void) /** Initialize the mock error function. **/ err_buf_size = 256; err_buf = malloc(err_buf_size); - if (!ASSERT_NOT_NULL(err_buf)) return false; + if (!ASSERT_NOT_NULL(err_buf)) goto end; err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); /** Basic string data. **/ - char* str1; - if (!ASSERT_NOT_NULL(str1 = nmSysMalloc(16))) return false; + if (!ASSERT_NOT_NULL(str1 = nmSysMalloc(16))) goto end; snprintf(str1, 16, "ThisIsSomeData!"); - char* str2; - if (!ASSERT_NOT_NULL(str2 = nmSysMalloc(32))) return false; + if (!ASSERT_NOT_NULL(str2 = nmSysMalloc(32))) goto end; snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ - void** data = malloc(TEST_LIMIT * sizeof(void*)); - void** test = malloc(TEST_LIMIT * sizeof(void*)); - if (!ASSERT_NOT_NULL(data)) return false; - if (!ASSERT_NOT_NULL(test)) return false; + if (!ASSERT_NOT_NULL(data = calloc(TEST_LIMIT, sizeof(void*)))) goto end; + if (!ASSERT_NOT_NULL(test = calloc(TEST_LIMIT, sizeof(void*)))) goto end; for (size_t i = 1lu; i < TEST_LIMIT; i++) { - if (!ASSERT_NOT_NULL(test[i] = nmSysMalloc(i))) return false; - if (!ASSERT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; + if (!ASSERT_NOT_NULL(test[i] = nmSysMalloc(i))) goto end; + if (!ASSERT_NOT_NULL(data[i] = randomInit(malloc(i), i))) goto end; memcpy(test[i], data[i], i); /* Write test data into test memory. */ } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) @@ -122,7 +139,11 @@ static bool doTest(void) /** Reallocate all variably sized memory to a different size. **/ for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) - if (!ASSERT_NOT_NULL(test[i] = nmSysRealloc(test[i], TEST_LIMIT - i))) return false; + { + void* new_ptr = nmSysRealloc(test[i], TEST_LIMIT - i); + if (!ASSERT_NOT_NULL(new_ptr)) goto end; + test[i] = new_ptr; + } for (size_t i = 1lu; i < TEST_LIMIT; i++) success &= ASSERT_EQL(memcmp(data[i], test[i], min(i, TEST_LIMIT - i)), 0, "%d"); @@ -131,10 +152,8 @@ static bool doTest(void) success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Testing strdup. **/ - char* str_dup1; - char* str_dup2; - if (!ASSERT_NOT_NULL(str_dup1 = nmSysStrdup(str1))) return false; - if (!ASSERT_NOT_NULL(str_dup2 = nmSysStrdup(str2))) return false; + if (!ASSERT_NOT_NULL(str_dup1 = nmSysStrdup(str1))) goto end; + if (!ASSERT_NOT_NULL(str_dup2 = nmSysStrdup(str2))) goto end; success &= ASSERT_STR_EQL(str_dup1, "ThisIsSomeData!"); success &= ASSERT_STR_EQL(str_dup2, "ThisDataIsDifferentStringData.\n"); str_dup1[12] = '\0'; @@ -148,13 +167,9 @@ static bool doTest(void) success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ - for (size_t i = 1lu; i < TEST_LIMIT; i++) - { - free(data[i]); - nmSysFree(test[i]); - } - free(data); - free(test); + freeBulk(data, test); + data = NULL; + test = NULL; /** Basic string data is unharmed. **/ success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); @@ -162,15 +177,16 @@ static bool doTest(void) /** Free data. **/ nmSysFree(str1); + str1 = NULL; nmSysFree(str2); + str2 = NULL; /** Dup string data is unharmed. **/ success &= ASSERT_STR_EQL(str_dup1, "ThisIsSomeDa"); success &= ASSERT_STR_EQL(str_dup2, "ThatDataIsDifferentStringData.\n"); /** Large singular allocation. **/ - void* large_buf; - if (!ASSERT_NOT_NULL(large_buf = nmSysMalloc(LARGE_BUF_SIZE))) return false; + if (!ASSERT_NOT_NULL(large_buf = nmSysMalloc(LARGE_BUF_SIZE))) goto end; for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; @@ -185,18 +201,34 @@ static bool doTest(void) /** Free dups. **/ nmSysFree(str_dup1); + str_dup1 = NULL; nmSysFree(str_dup2); + str_dup2 = NULL; /** Free large allocation. **/ nmSysFree(large_buf); + large_buf = NULL; /** Expect no captured errors. **/ success &= ASSERT_STR_EQL(err_buf, ""); - /** Clean up. **/ + finished = true; + + end: + /** Clean up whatever is still allocated. **/ + if (large_buf != NULL) nmSysFree(large_buf); + if (str_dup2 != NULL) nmSysFree(str_dup2); + if (str_dup1 != NULL) nmSysFree(str_dup1); + freeBulk(data, test); + if (str2 != NULL) nmSysFree(str2); + if (str1 != NULL) nmSysFree(str1); + + /** Unregister the mock error function before freeing its buffer. **/ + nmSetErrFunction(NULL); free(err_buf); + err_buf = NULL; - return success; + return finished && success; } long long test(char** tname) diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 7c3978810..1df7a7be4 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -52,9 +52,10 @@ static int mockErrorFn(char* error_msg) /** Ensure enough space to store the error. **/ while (len > err_buf_size - err_buf_i) { + char* new_buf = realloc(err_buf, err_buf_size * 2); + if (!ASSERT_NOT_NULL(new_buf)) return -1; + err_buf = new_buf; err_buf_size *= 2; - err_buf = realloc(err_buf, err_buf_size); - if (!ASSERT_NOT_NULL(err_buf)) return -1; } err_buf_i += snprintf( @@ -77,38 +78,52 @@ static void* randomInit(void* ptr, size_t size) return ptr; } +/** Free the bulk data arrays and the entries allocated so far. **/ +static void freeBulk(void** data, void** test) + { + for (size_t i = 1lu; i < TEST_LIMIT; i++) + { + if (data != NULL) free(data[i]); + if (test != NULL && test[i] != NULL) nmFree(test[i], i); + } + free(data); + free(test); + } + static bool doTest(void) { bool success = true; + bool finished = false; + char* str1 = NULL; + char* str2 = NULL; + void** data = NULL; + void** test = NULL; + void* large_buf = NULL; /** Set a consistent, distinct seed for each test iteration. **/ srand(seed_counter++); /** Initialize the mock error function. **/ err_buf = malloc(err_buf_size = 256); - if (!ASSERT_NOT_NULL(err_buf)) return false; + if (!ASSERT_NOT_NULL(err_buf)) goto end; err_buf_i = snprintf(err_buf, err_buf_size, "%s", ""); nmSetErrFunction(mockErrorFn); /** Basic string data. **/ - char* str1; - if (!ASSERT_NOT_NULL(str1 = nmMalloc(16))) return false; + if (!ASSERT_NOT_NULL(str1 = nmMalloc(16))) goto end; snprintf(str1, 16, "ThisIsSomeData!"); - char* str2; - if (!ASSERT_NOT_NULL(str2 = nmMalloc(32))) return false; + if (!ASSERT_NOT_NULL(str2 = nmMalloc(32))) goto end; snprintf(str2, 32, "ThisDataIsDifferentStringData.\n"); success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Random data, varying sizes. **/ - void** data = malloc(TEST_LIMIT * sizeof(void*)); - void** test = malloc(TEST_LIMIT * sizeof(void*)); - if (!ASSERT_NOT_NULL(data)) return false; - if (!ASSERT_NOT_NULL(test)) return false; + if (!ASSERT_NOT_NULL(data = calloc(TEST_LIMIT, sizeof(void*)))) goto end; + if (!ASSERT_NOT_NULL(test = calloc(TEST_LIMIT, sizeof(void*)))) goto end; for (size_t i = 1lu; i < TEST_LIMIT; i++) { - if (!ASSERT_NOT_NULL(test[i] = nmMalloc(i))) return false; - if (!ASSERT_NOT_NULL(data[i] = randomInit(malloc(i), i))) return false; + if (!ASSERT_NOT_NULL(test[i] = nmMalloc(i))) goto end; + if (!ASSERT_NOT_NULL(data[i] = randomInit(malloc(i), i))) goto end; memcpy(test[i], data[i], i); } for (size_t i = TEST_LIMIT - 1lu; i > 0lu; i--) @@ -119,8 +134,7 @@ static bool doTest(void) success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Large singular allocation. **/ - void* large_buf; - if (!ASSERT_NOT_NULL(large_buf = nmMalloc(LARGE_BUF_SIZE))) return false; + if (!ASSERT_NOT_NULL(large_buf = nmMalloc(LARGE_BUF_SIZE))) goto end; for (size_t i = LARGE_BUF_SIZE - 1lu; i > 0lu; i--) *((unsigned char*)large_buf + i) = (unsigned char)(i % 255lu); *(unsigned char*)large_buf = 0u; @@ -134,13 +148,9 @@ static bool doTest(void) success &= ASSERT_STR_EQL(str2, "ThisDataIsDifferentStringData.\n"); /** Free random data, varying sizes. **/ - for (size_t i = 1lu; i < TEST_LIMIT; i++) - { - free(data[i]); - nmFree(test[i], i); - } - free(data); - free(test); + freeBulk(data, test); + data = NULL; + test = NULL; /** Basic string data is unharmed. **/ success &= ASSERT_STR_EQL(str1, "ThisIsSomeData!"); @@ -148,10 +158,13 @@ static bool doTest(void) /** Free data. **/ nmFree(str1, 16); + str1 = NULL; nmFree(str2, 32); + str2 = NULL; /** Free large allocation. **/ nmFree(large_buf, LARGE_BUF_SIZE); + large_buf = NULL; /** Clear cache. **/ nmClear(); @@ -159,10 +172,21 @@ static bool doTest(void) /** Expect no captured errors. **/ success &= ASSERT_STR_EQL(err_buf, ""); - /** Clean up. **/ + finished = true; + + end: + /** Clean up whatever is still allocated. **/ + if (large_buf != NULL) nmFree(large_buf, LARGE_BUF_SIZE); + freeBulk(data, test); + if (str2 != NULL) nmFree(str2, 32); + if (str1 != NULL) nmFree(str1, 16); + + /** Unregister the mock error function before freeing its buffer. **/ + nmSetErrFunction(NULL); free(err_buf); + err_buf = NULL; - return success; + return finished && success; } long long test(char** tname) From 63eedfcae3b581a67ccbb48f086e82c34bf0386a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:00:39 -0600 Subject: [PATCH 333/361] Fix the new malloc test's mockErrorFn() function allocating an error buffer that was too small. --- centrallix-lib/tests/test_newmalloc_00.c | 2 +- centrallix-lib/tests/test_newmalloc_01.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 8070a402a..f7a098554 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -48,7 +48,7 @@ static unsigned int err_buf_size; static int mockErrorFn(char* error_msg) { - const size_t len = strlen(error_msg) + 1lu; + const size_t len = strlen(error_msg) + 4lu; /* "> " + message + "\n" + NUL */ /** Ensure enough space to store the error. **/ while (len > err_buf_size - err_buf_i) diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 1df7a7be4..c1586dfe6 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -47,7 +47,7 @@ static unsigned int err_buf_size; static int mockErrorFn(char* error_msg) { - const size_t len = strlen(error_msg) + 1lu; + const size_t len = strlen(error_msg) + 4lu; /* "> " + message + "\n" + NUL */ /** Ensure enough space to store the error. **/ while (len > err_buf_size - err_buf_i) From 1c459eb3aec61af4e5332f57093c8fdab1d043f1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:12:48 -0600 Subject: [PATCH 334/361] Remove partial sweeping in some tests to increase coverage. --- centrallix-lib/tests/test_util_00.c | 16 ++-------------- centrallix-lib/tests/test_util_01.c | 16 ++-------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/centrallix-lib/tests/test_util_00.c b/centrallix-lib/tests/test_util_00.c index 403c30378..752f41dff 100644 --- a/centrallix-lib/tests/test_util_00.c +++ b/centrallix-lib/tests/test_util_00.c @@ -28,29 +28,17 @@ #define TXT_SIZE 1024 #define RANGE 500000 -/** Values from the sweep converted per call to doTest(). **/ -#define CHUNK 10000 - -/** Next value in the sweep, advanced by CHUNK and wrapped at the end. **/ -static int sweep = -RANGE; - static bool doTest(void) { int i; - int end; char text[TXT_SIZE]=""; - if (sweep >= RANGE) sweep = -RANGE; - end = sweep + CHUNK; - if (end > RANGE) end = RANGE; - - for(i=sweep; i= RANGE) sweep = -RANGE; - end = sweep + CHUNK; - if (end > RANGE) end = RANGE; - - for(i=sweep; i Date: Thu, 8 Oct 2026 10:27:26 -0600 Subject: [PATCH 335/361] Fix the Makefile not rebuilding when test_utils.h was updated. --- centrallix-lib/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 726ce9114..9c655d471 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -170,7 +170,7 @@ install: all @echo "***************************************************************" @echo "" -%.bin: %.c tests/t_driver.c libCentrallix.a +%.bin: %.c tests/t_driver.c tests/test_utils.h libCentrallix.a $(CC) $(TCFLAGS) tests/t_driver.c $< libCentrallix.a -o $@ ${LIBS} ## Runs the selected tests $(TESTJOBS) at a time. Output is captured per test From d2486c4a1dffce5f205e1e08cd6ebddcaf188832 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:27:52 -0600 Subject: [PATCH 336/361] Fix optimization optimizing out the entire baseline pass test. --- centrallix-lib/tests/test_00baseline.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_00baseline.c b/centrallix-lib/tests/test_00baseline.c index 72773400d..d9fddec10 100644 --- a/centrallix-lib/tests/test_00baseline.c +++ b/centrallix-lib/tests/test_00baseline.c @@ -13,7 +13,7 @@ static bool doTest(void) { int i; - int array[2] = {0}; + volatile int array[2] = {0}; for(i=0;i Date: Thu, 8 Oct 2026 10:38:35 -0600 Subject: [PATCH 337/361] Shorten the names of tests (test_newmalloc_00's name was too long to print). --- centrallix-lib/tests/test_newmalloc_00.c | 2 +- centrallix-lib/tests/test_newmalloc_01.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index f7a098554..2d4c1e17f 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -233,7 +233,7 @@ static bool doTest(void) long long test(char** tname) { - *tname = "newmalloc-00 nmSysMalloc(), nmSysFree(), nmSysRealloc(), & nmSysStrdup()"; + *tname = "newmalloc-00 nmSysMalloc/Free/Realloc/Strdup()"; return loopTest(doTest) * ((long long)TEST_LIMIT + 3ll); } diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index c1586dfe6..87e4f016b 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -191,7 +191,7 @@ static bool doTest(void) long long test(char** tname) { - *tname = "newmalloc-01 nmMalloc(), nmFree(), & nmClear()"; + *tname = "newmalloc-01 nmMalloc/Free/Clear()"; return loopTest(doTest) * ((long long)TEST_LIMIT + 3ll); } From c0dd2dc972e010044f9af55b58f0b16ef71363ca Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:39:23 -0600 Subject: [PATCH 338/361] Improve coverage of nmClear() when caching is enabled.. --- centrallix-lib/tests/test_newmalloc_01.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 87e4f016b..b4e2fadb1 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -161,6 +161,9 @@ static bool doTest(void) str1 = NULL; nmFree(str2, 32); str2 = NULL; +#ifndef NO_BLK_CACHE + success &= ASSERT_TRUE(lists[32] != NULL); +#endif /** Free large allocation. **/ nmFree(large_buf, LARGE_BUF_SIZE); @@ -168,6 +171,10 @@ static bool doTest(void) /** Clear cache. **/ nmClear(); + size_t cached = 0lu; + for (size_t i = MIN_SIZE; i <= MAX_SIZE; i++) + if (lists[i] != NULL) cached++; + success &= ASSERT_EQL(cached, 0lu, "%zu"); /** Expect no captured errors. **/ success &= ASSERT_STR_EQL(err_buf, ""); From 395337a75d90898b01f69db7bcf1a2b426339587 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:43:10 -0600 Subject: [PATCH 339/361] Drop checkChain() in an xhash test because it asserted internals not promised by the library. --- centrallix-lib/tests/test_xhash_02.c | 48 ++-------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/centrallix-lib/tests/test_xhash_02.c b/centrallix-lib/tests/test_xhash_02.c index cd24a65e2..d6fe04fad 100644 --- a/centrallix-lib/tests/test_xhash_02.c +++ b/centrallix-lib/tests/test_xhash_02.c @@ -41,37 +41,10 @@ static bool isRemoved(int i) return (i == 0 || i == CHAIN_COUNT / 2 || i == CHAIN_COUNT - 1); } -/*** Walk the chain of a row, checking that it holds the expected keys in - *** order. xhAdd() appends to the end of the chain, so the keys come back - *** in the order they were added. - *** - *** @param hash The hash table, which must have exactly one row. - *** @param expect_keys The keys expected in the chain, in order. - *** @param expect_count The number of expected keys. - *** @returns true if the chain matched, false otherwise. - ***/ -static bool checkChain(pXHashTable hash, char** expect_keys, int expect_count) - { - bool success = true; - pXHashEntry entry = XHE(hash->Rows.Items[0]); - - for (int i = 0; i < expect_count; i++) - { - if (!ASSERT_NOT_NULL(entry)) return false; - success &= ASSERT_EQL(entry->Key, expect_keys[i], "%p"); - entry = entry->Next; - } - success &= ASSERT_EQL(entry, NULL, "%p"); - - return success; - } - static bool doTest(void) { bool success = true; XHashTable hash; - char* remaining[CHAIN_COUNT + 1]; - int remaining_count = 0; /** One row means every key collides. **/ success &= ASSERT_EQL(xhInit(&hash, 1, 0), 0, "%d"); @@ -83,7 +56,6 @@ static bool doTest(void) success &= ASSERT_EQL(xhAdd(&hash, keys[i], data[i]), 0, "%d"); success &= ASSERT_EQL(hash.nItems, i + 1, "%d"); } - success &= checkChain(&hash, keys, CHAIN_COUNT); /** Every key in the chain is reachable. **/ for (int i = 0; i < CHAIN_COUNT; i++) @@ -101,32 +73,23 @@ static bool doTest(void) /** Unlink the head, the middle, and the tail of the chain. **/ int removed_count = 0; for (int i = 0; i < CHAIN_COUNT; i++) - { if (isRemoved(i)) { success &= ASSERT_EQL(xhRemove(&hash, keys[i]), 0, "%d"); removed_count++; } - else - remaining[remaining_count++] = keys[i]; - } success &= ASSERT_EQL(removed_count, REMOVED_COUNT, "%d"); - /*** The chain kept the other entries, in order, and lost the removed - *** ones. - ***/ + /** The other entries survive and the removed ones are gone. **/ for (int i = 0; i < CHAIN_COUNT; i++) success &= ASSERT_EQL(xhLookup(&hash, keys[i]), isRemoved(i) ? NULL : data[i], "%p"); success &= ASSERT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT, "%d"); - success &= checkChain(&hash, remaining, remaining_count); - /** A removed key can be added back, landing at the end of the chain. **/ + /** A removed key can be added back. **/ success &= ASSERT_EQL(xhAdd(&hash, keys[0], data[0]), 0, "%d"); success &= ASSERT_EQL(xhLookup(&hash, keys[0]), data[0], "%p"); success &= ASSERT_EQL(hash.nItems, CHAIN_COUNT - REMOVED_COUNT + 1, "%d"); - remaining[remaining_count++] = keys[0]; - success &= checkChain(&hash, remaining, remaining_count); /** Clean up. **/ success &= ASSERT_EQL(xhClear(&hash, NULL, NULL), 0, "%d"); @@ -140,12 +103,7 @@ static bool doTest(void) long long test(char** tname) { *tname = "xhash-02 Collisions"; - - /*** The three chain walks check two things per entry plus the end of the - *** chain, over chains of CHAIN_COUNT, CHAIN_COUNT - REMOVED_COUNT, and - *** one more than that. - ***/ - return loopTest(doTest) * (21ll - 3ll * REMOVED_COUNT + 10ll * CHAIN_COUNT); + return loopTest(doTest) * (16ll + REMOVED_COUNT + 4ll * CHAIN_COUNT); } /** Scope cleanup. **/ From f35aa5fb3d2c23fcadbdac2d4b09473d0df52383 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:46:11 -0600 Subject: [PATCH 340/361] Update op counts. --- centrallix-lib/tests/test_memstr_00.c | 2 +- centrallix-lib/tests/test_mtlexer_15.c | 2 +- centrallix-lib/tests/test_mtlexer_16.c | 2 +- centrallix-lib/tests/test_newmalloc_00.c | 2 +- centrallix-lib/tests/test_newmalloc_01.c | 2 +- centrallix-lib/tests/test_qprintf_62.c | 2 +- centrallix-lib/tests/test_xarray_00.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/tests/test_memstr_00.c b/centrallix-lib/tests/test_memstr_00.c index f161832c8..0311b7beb 100755 --- a/centrallix-lib/tests/test_memstr_00.c +++ b/centrallix-lib/tests/test_memstr_00.c @@ -57,5 +57,5 @@ long long test(char** tname) { *tname = "memstr-00 correct null ptr"; - return loopTest(doTest) * 5; + return loopTest(doTest) * 6; } diff --git a/centrallix-lib/tests/test_mtlexer_15.c b/centrallix-lib/tests/test_mtlexer_15.c index 8ef0d8814..a792ff5cb 100644 --- a/centrallix-lib/tests/test_mtlexer_15.c +++ b/centrallix-lib/tests/test_mtlexer_15.c @@ -56,5 +56,5 @@ test(char** tname) { *tname = "mtlexer-15 comments // # ; -- /**/ long and short"; mssInitialize("system", "", "", 0, "test"); - return loopTest(doTest) * 10; + return loopTest(doTest) * N_INTS; } diff --git a/centrallix-lib/tests/test_mtlexer_16.c b/centrallix-lib/tests/test_mtlexer_16.c index 19e2fa352..93d8c7453 100644 --- a/centrallix-lib/tests/test_mtlexer_16.c +++ b/centrallix-lib/tests/test_mtlexer_16.c @@ -56,5 +56,5 @@ test(char** tname) { *tname = "mtlexer-16 comments // # ; -- /**/ short only"; mssInitialize("system", "", "none", 0, "test"); - return loopTest(doTest) * 10; + return loopTest(doTest) * N_INTS; } diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 2d4c1e17f..3c7e6190b 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -234,7 +234,7 @@ static bool doTest(void) long long test(char** tname) { *tname = "newmalloc-00 nmSysMalloc/Free/Realloc/Strdup()"; - return loopTest(doTest) * ((long long)TEST_LIMIT + 3ll); + return loopTest(doTest) * (2ll * TEST_LIMIT + 3ll); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index b4e2fadb1..4f2d25ec6 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -199,7 +199,7 @@ static bool doTest(void) long long test(char** tname) { *tname = "newmalloc-01 nmMalloc/Free/Clear()"; - return loopTest(doTest) * ((long long)TEST_LIMIT + 3ll); + return loopTest(doTest) * ((long long)TEST_LIMIT + 2ll); } /** Scope cleanup. **/ diff --git a/centrallix-lib/tests/test_qprintf_62.c b/centrallix-lib/tests/test_qprintf_62.c index aeecde0c7..035c82dad 100644 --- a/centrallix-lib/tests/test_qprintf_62.c +++ b/centrallix-lib/tests/test_qprintf_62.c @@ -49,5 +49,5 @@ long long test(char** tname) { *tname = "qprintf-62 %STR&DB64 integrity test"; - return loopTest(doTest) * 6; + return loopTest(doTest) * 5; } diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 7b0067fbe..7abb3ed1e 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -156,5 +156,5 @@ static bool doTest(void) long long test(char** tname) { *tname = "xarray-00 Full Test"; - return loopTest(doTest) * 34; + return loopTest(doTest) * 37; } From ff2a6d7cea3016e9aae5503fa7b3c4ff092569da Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:48:35 -0600 Subject: [PATCH 341/361] Handle ctrl+c during tests. --- centrallix-lib/Makefile.in | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 9c655d471..173427ae3 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -184,9 +184,26 @@ install: all ## tests/t_driver.c), so a nonzero status means the test did not complete ## normally and must fail the target; only valgrind's --error-exitcode is ## dropped. A test whose output never appears is reported and also fails. +## +## On SIGINT or SIGTERM, the shell stops the tests, removes the captured +## output without printing it, and then dies from the same signal. xargs +## runs in its own process group so that the whole group can be killed: +## a background job ignores SIGINT, so Ctrl+C never reaches the tests. RUN_TESTS = \ RESULTS=$$(mktemp -d) || exit 1; \ - trap 'rm -rf "$$RESULTS"' EXIT INT TERM; \ + XARGS=; \ + stop() { \ + trap - EXIT INT TERM; \ + if [ -n "$$XARGS" ]; then \ + kill -s TERM -- -"$$XARGS" 2>/dev/null; \ + wait "$$XARGS" 2>/dev/null; \ + fi; \ + rm -rf "$$RESULTS"; \ + kill -s "$$1" $$$$; \ + }; \ + trap 'rm -rf "$$RESULTS"' EXIT; \ + trap 'stop INT' INT; \ + trap 'stop TERM' TERM; \ N=0; \ for PROG in $$( ls $(TESTPROGS) ); do \ case "$$PROG" in tests/test_$$TONLY*) \ @@ -195,7 +212,7 @@ RUN_TESTS = \ esac; \ done > "$$RESULTS/list"; \ if [ "$$N" -eq 0 ]; then exit 0; fi; \ - xargs -r -n 2 -P $(TESTJOBS) sh -c "$$TESTCMD" sh \ + setsid xargs -r -n 2 -P $(TESTJOBS) sh -c "$$TESTCMD" sh \ < "$$RESULTS/list" 2> "$$RESULTS/stderr" & \ XARGS=$$!; \ RC=0; \ From 98976bcf50a7dfa30094cd07f0cf577d02d80074 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 10:58:53 -0600 Subject: [PATCH 342/361] Fix an overflow in test_mtlexer_13. --- centrallix-lib/tests/test_mtlexer_13.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_mtlexer_13.c b/centrallix-lib/tests/test_mtlexer_13.c index c768a8abf..133d7f41e 100644 --- a/centrallix-lib/tests/test_mtlexer_13.c +++ b/centrallix-lib/tests/test_mtlexer_13.c @@ -39,11 +39,11 @@ doTest(void) str[i+1] = ' '; str[i+2] = '2'; str[i+3] = '\0'; - iv = iv*10 + 1; lxs = mlxStringSession(str, 0); assert(lxs != NULL); if ((i+1) <= 10) { + iv = iv*10 + 1; assert(mlxNextToken(lxs) == MLX_TOK_INTEGER); n = mlxIntVal(lxs); assert(n == iv); From 3885ca4ccc71c4b5bf1e0ff5091ed574e20b04a6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 11:07:02 -0600 Subject: [PATCH 343/361] Fix flakiness in xhash_05. --- centrallix-lib/tests/test_xhash_05.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/centrallix-lib/tests/test_xhash_05.c b/centrallix-lib/tests/test_xhash_05.c index 2f1fbd242..14830ef08 100644 --- a/centrallix-lib/tests/test_xhash_05.c +++ b/centrallix-lib/tests/test_xhash_05.c @@ -62,11 +62,10 @@ static bool doTest(void) } /*** The hash is in memory only, so it may be changed, but only on - *** purpose: update this value when the hash or the key above changes. + *** purpose: update these values when the hash or the key above changes. ***/ success &= ASSERT_EQL(xh_internal_ComputeHash(key, KEY_LEN, WIDE_ROWS), 609235, "%d"); - - /** Where a byte sits matters, not just which bytes are present. **/ + /* Where a byte sits matters, not just which bytes are present. */ success &= ASSERT_EQL(xh_internal_ComputeHash("ab", 2, WIDE_ROWS) != xh_internal_ComputeHash("ba", 2, WIDE_ROWS), true, "%d"); @@ -78,16 +77,22 @@ static bool doTest(void) xh_internal_ComputeHash(altered, KEY_LEN - 1, WIDE_ROWS), xh_internal_ComputeHash(key, KEY_LEN - 1, WIDE_ROWS), "%d"); - /** Every byte of the key changes the hash. **/ + /*** Every byte of the key affects the hash. One change can collide by + *** chance, so a byte fails only when several changes don't move the hash. + ***/ + static const unsigned char flips[] = {0x01, 0x5A, 0xFF}; const int base = xh_internal_ComputeHash(key, KEY_LEN, WIDE_ROWS); for (int i = 0; i < KEY_LEN; i++) { - memcpy(altered, key, KEY_LEN); - altered[i] ^= 0x5A; - bool differs = ASSERT_EQL( - xh_internal_ComputeHash(altered, KEY_LEN, WIDE_ROWS) != base, true, "%d"); + bool differs = false; + for (size_t f = 0; f < sizeof(flips) && !differs; f++) + { + memcpy(altered, key, KEY_LEN); + altered[i] ^= flips[f]; + differs = (xh_internal_ComputeHash(altered, KEY_LEN, WIDE_ROWS) != base); + } if (!differs) fprintf(stderr, " > Byte %d of the key did not affect the hash.\n", i); - success &= differs; + success &= ASSERT_TRUE(differs); } return success; From 33de0fe574f0e422dcfa9242344d518d7ed7ee1c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 8 Oct 2026 11:07:12 -0600 Subject: [PATCH 344/361] Clean up includes. --- centrallix-lib/tests/test_00baseline.c | 3 --- centrallix-lib/tests/test_newmalloc_01.c | 1 - 2 files changed, 4 deletions(-) diff --git a/centrallix-lib/tests/test_00baseline.c b/centrallix-lib/tests/test_00baseline.c index d9fddec10..73022b302 100644 --- a/centrallix-lib/tests/test_00baseline.c +++ b/centrallix-lib/tests/test_00baseline.c @@ -1,8 +1,5 @@ -#include #include #include -#include -#include #include #include "test_utils.h" diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index 4f2d25ec6..a6faaaa9b 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -15,7 +15,6 @@ /* from the NewMalloc library. */ /************************************************************************/ -#include #include #include #include From 106da3c7e041b9acc051dd8a37417d4443e9f158 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 9 Oct 2026 10:05:53 -0600 Subject: [PATCH 345/361] Add the ability for tests to skip themselves. --- centrallix-lib/tests/t_driver.c | 2 ++ centrallix-lib/tests/test_05baseline.c | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 centrallix-lib/tests/test_05baseline.c diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index be4da2362..cba013133 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -74,6 +74,8 @@ start(void* v) /** Print test results. **/ if (rval < 0) printf("%-62.62s FAIL\n", tname); + else if (rval == 0) + printf("%-62.62s SKIP\n", tname); else { long long duration = end - start; diff --git a/centrallix-lib/tests/test_05baseline.c b/centrallix-lib/tests/test_05baseline.c new file mode 100644 index 000000000..2bd2a522b --- /dev/null +++ b/centrallix-lib/tests/test_05baseline.c @@ -0,0 +1,25 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_05baseline.c */ +/* Author: Israel Fuller */ +/* Creation: October 9th, 2026 */ +/* Description: Baseline test that runs for a while, then reports no */ +/* operations, so the driver should mark it skipped. */ +/************************************************************************/ + +#include + +long long +test(char** tname) + { + *tname = "BASELINE no operations - should skip"; + return 0; + } From 07833f0afed3350ad1a72eaed195e14330b2afe1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 9 Oct 2026 10:06:21 -0600 Subject: [PATCH 346/361] Modify test_newmalloc_02.c to skip itself when nmStats() is disabled, giving it nothing to test. --- centrallix-lib/tests/test_newmalloc_02.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/centrallix-lib/tests/test_newmalloc_02.c b/centrallix-lib/tests/test_newmalloc_02.c index 7fa604fb0..f58e1cea9 100644 --- a/centrallix-lib/tests/test_newmalloc_02.c +++ b/centrallix-lib/tests/test_newmalloc_02.c @@ -28,6 +28,7 @@ #define BLOCK_COUNT 64lu #define BLOCK_SIZE 128 +#ifdef GLOBAL_BLK_COUNTING static bool doTest(void) { bool success = true; @@ -75,11 +76,20 @@ static bool doTest(void) return success; } +#endif long long test(char** tname) { *tname = "newmalloc-02 nmStats()"; +#ifdef GLOBAL_BLK_COUNTING return loopTest(doTest); +#else + /*** No stats are being collected and nmStats() is disabled, so we can't + *** test it. To test it, please rebuild with --enable-debugging. + ***/ + return 0; +#endif + } /** Scope cleanup. **/ From 749dddfef8de54c92d5a63cdc31bf44122e0dca2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 14 Oct 2026 17:06:05 -0600 Subject: [PATCH 347/361] Add tests for test_utils.h. --- centrallix-lib/tests/test_testutils_00.c | 176 +++++++++++++++++++++++ centrallix-lib/tests/test_testutils_01.c | 64 +++++++++ 2 files changed, 240 insertions(+) create mode 100644 centrallix-lib/tests/test_testutils_00.c create mode 100644 centrallix-lib/tests/test_testutils_01.c diff --git a/centrallix-lib/tests/test_testutils_00.c b/centrallix-lib/tests/test_testutils_00.c new file mode 100644 index 000000000..13f2f37a7 --- /dev/null +++ b/centrallix-lib/tests/test_testutils_00.c @@ -0,0 +1,176 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_testutils_00.c */ +/* Author: Israel Fuller */ +/* Creation: October 14th, 2026 */ +/* Description: Test the assertion macros in test_utils.h. These tests */ +/* are a little fragile, but I don't expect error messages */ +/* to change often and devs running tests should probably */ +/* know if they do. */ +/************************************************************************/ + +#include +#include +#include + +/** Tested module. **/ +#include "test_utils.h" + +#define MSG_SIZE 512 +#define COPY_SIZE 8 + +/** What the last captured assertion printed, and the line it ran on. **/ +static char msg[MSG_SIZE]; +static int msg_line; + +/*** Run an assertion with stderr redirected into msg. Keep each use on one + *** line so that msg_line matches the line the assertion reports. + *** + *** @param result Set to what the assertion returned. + *** @param assertion The assertion to run. + ***/ +#define CAPTURE(result, assertion) \ + { \ + FILE* _saved = stderr; \ + msg[0] = '\0'; \ + FILE* _stream = fmemopen(msg, sizeof(msg), "w"); \ + if (!ASSERT_NOT_NULL(_stream)) return false; \ + stderr = _stream; \ + result = (assertion); \ + fclose(stderr); \ + stderr = _saved; \ + msg_line = __LINE__; \ + } + +/*** Check the last captured message. + *** + *** @param body The message between "Expected " and " at file:line". + *** @returns true if the message matches, false otherwise. + ***/ +static bool expectMsg(const char* body) + { + char expected[MSG_SIZE]; + + snprintf(expected, sizeof(expected), " > Expected %s at %s:%d\n", body, __FILE__, msg_line); + + return ASSERT_STR_EQL(msg, expected); + } + +static bool doTest(void) + { + bool success = true; + bool result; + int zero = 0, one = 1, two = 2, three = 3; + char* null_str = NULL; + char* ab = "ab"; + char ab_copy[] = "ab"; + char* ac = "ac"; + char ab_c[] = {'a', 'b', '\0', 'c'}; + char ab_d[] = {'a', 'b', '\0', 'd'}; + char copy[COPY_SIZE]; + + /*** ASSERT_TRUE() can't vouch for itself, so its results are + *** checked directly. The checks below rely on it. + ***/ + CAPTURE(result, ASSERT_TRUE(one)); + success &= (result == true); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_TRUE(zero)); + success &= (result == false); + success &= expectMsg("zero to be true"); + + /** ASSERT_EQL() **/ + CAPTURE(result, ASSERT_EQL(two, two, "%d")); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_EQL(two, three, "%d")); + success &= ASSERT_TRUE(!result); + success &= expectMsg("two (2) to equal three (3)"); + + /** ASSERT_STR_EQL() compares contents, not pointers. **/ + CAPTURE(result, ASSERT_STR_EQL(ab, ab_copy)); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_STR_EQL(null_str, null_str)); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_STR_EQL(ab, ac)); + success &= ASSERT_TRUE(!result); + success &= expectMsg("ab (\"ab\") to equal ac (\"ac\")"); + + /** ASSERT_STR_EQL() fails when only one string is null. **/ + CAPTURE(result, ASSERT_STR_EQL(null_str, ab)); + success &= ASSERT_TRUE(!result); + success &= ASSERT_TRUE(msg[0] != '\0'); + CAPTURE(result, ASSERT_STR_EQL(ab, null_str)); + success &= ASSERT_TRUE(!result); + success &= ASSERT_TRUE(msg[0] != '\0'); + + /** ASSERT_STR_EQL_N() compares exactly len bytes, nulls included. **/ + CAPTURE(result, ASSERT_STR_EQL_N(ab_c, ab_d, 3)); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_STR_EQL_N(null_str, null_str, 4)); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_STR_EQL_N(ab_c, ab_d, 4)); + success &= ASSERT_TRUE(!result); + success &= expectMsg("ab_c (\"ab_c\") to equal ab_d (\"ab_d\") where '_' is a null-terminator,"); + CAPTURE(result, ASSERT_STR_EQL_N(null_str, ab, 2)); + success &= ASSERT_TRUE(!result); + success &= expectMsg("null_str (\"(null)\") to equal ab (\"ab\") where '_' is a null-terminator,"); + CAPTURE(result, ASSERT_STR_EQL_N(ab, null_str, 2)); + success &= ASSERT_TRUE(!result); + success &= expectMsg("ab (\"ab\") to equal null_str (\"(null)\") where '_' is a null-terminator,"); + + /** ASSERT_RANGE() includes both ends. **/ + CAPTURE(result, ASSERT_RANGE(one, one, three, "%d")); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_RANGE(three, one, three, "%d")); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_RANGE(zero, one, three, "%d")); + success &= ASSERT_TRUE(!result); + success &= expectMsg("zero (0) to be in the range one (1) - three (3)"); + CAPTURE(result, ASSERT_RANGE(three, zero, two, "%d")); + success &= ASSERT_TRUE(!result); + success &= expectMsg("three (3) to be in the range zero (0) - two (2)"); + + /** ASSERT_NOT_NULL() **/ + CAPTURE(result, ASSERT_NOT_NULL(ab)); + success &= ASSERT_TRUE(result); + success &= ASSERT_STR_EQL(msg, ""); + CAPTURE(result, ASSERT_NOT_NULL(null_str)); + success &= ASSERT_TRUE(!result); + success &= ASSERT_TRUE(msg[0] != '\0'); + + /** STR_COPY_REPLACE_NULLS() writes n bytes and a terminator. **/ + memset(copy, 'x', sizeof(copy)); + STR_COPY_REPLACE_NULLS(copy, ab_c, 4); + success &= ASSERT_STR_EQL_N(copy, "ab_c\0xxx", COPY_SIZE); + memset(copy, 'x', sizeof(copy)); + STR_COPY_REPLACE_NULLS(copy, ab_c, 0); + success &= ASSERT_STR_EQL_N(copy, "\0xxxxxxx", COPY_SIZE); + + return success; + } + +long long test(char** tname) + { + *tname = "testutils-00 Assertions"; + return loopTest(doTest) * 22ll; + } + +/** Scope cleanup. **/ +#undef MSG_SIZE +#undef COPY_SIZE +#undef CAPTURE diff --git a/centrallix-lib/tests/test_testutils_01.c b/centrallix-lib/tests/test_testutils_01.c new file mode 100644 index 000000000..c47db89ad --- /dev/null +++ b/centrallix-lib/tests/test_testutils_01.c @@ -0,0 +1,64 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: test_testutils_01.c */ +/* Author: Israel Fuller */ +/* Creation: October 14th, 2026 */ +/* Description: Test loopTest() from test_utils.h. */ +/************************************************************************/ + +#include + +/** Test dependencies. **/ +#include "timer.h" + +/** Tested module. **/ +#include "test_utils.h" + +/** Calls to countCalls(), and the call that fails (0 for none). **/ +static long long calls; +static long long fail_at; + +static bool countCalls(void) + { + calls++; + return (calls != fail_at); + } + +long long test(char** tname) + { + bool success = true; + long long iterations; + Timer timer_buf, *timer; + + *tname = "testutils-01 loopTest()"; + + /** A passing test repeats until the time window has passed. **/ + calls = 0ll; + fail_at = 0ll; + timer = timerStart(timerInit(&timer_buf)); + iterations = loopTest(countCalls); + success &= ASSERT_TRUE(timerGet(timer) >= MIN_TEST_SECONDS); + timerDeInit(timer); + success &= ASSERT_TRUE(iterations > 0ll); + success &= ASSERT_EQL(iterations, calls, "%lld"); + + /** A failing test stops at the first failure. **/ + calls = 0ll; + fail_at = 1ll; + success &= ASSERT_EQL(loopTest(countCalls), -1ll, "%lld"); + success &= ASSERT_EQL(calls, 1ll, "%lld"); + calls = 0ll; + fail_at = 3ll; + success &= ASSERT_EQL(loopTest(countCalls), -1ll, "%lld"); + success &= ASSERT_EQL(calls, 3ll, "%lld"); + + return (success) ? iterations : -1ll; + } From 45db80a681b33ecd180afdf75ed98aac59fe6ec7 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 14 Oct 2026 17:12:01 -0600 Subject: [PATCH 348/361] Rename test_utils.h to t_utils.h. --- centrallix-lib/Makefile.in | 2 +- centrallix-lib/tests/t_driver.c | 4 ++-- centrallix-lib/tests/{test_utils.h => t_utils.h} | 2 +- centrallix-lib/tests/test_00baseline.c | 2 +- centrallix-lib/tests/test_memstr_00.c | 2 +- centrallix-lib/tests/test_mtlexer_00.c | 2 +- centrallix-lib/tests/test_mtlexer_01.c | 2 +- centrallix-lib/tests/test_mtlexer_02.c | 2 +- centrallix-lib/tests/test_mtlexer_03.c | 2 +- centrallix-lib/tests/test_mtlexer_04.c | 2 +- centrallix-lib/tests/test_mtlexer_05.c | 2 +- centrallix-lib/tests/test_mtlexer_06.c | 2 +- centrallix-lib/tests/test_mtlexer_07.c | 2 +- centrallix-lib/tests/test_mtlexer_08.c | 2 +- centrallix-lib/tests/test_mtlexer_09.c | 2 +- centrallix-lib/tests/test_mtlexer_10.c | 2 +- centrallix-lib/tests/test_mtlexer_11.c | 2 +- centrallix-lib/tests/test_mtlexer_12.c | 2 +- centrallix-lib/tests/test_mtlexer_13.c | 2 +- centrallix-lib/tests/test_mtlexer_14.c | 2 +- centrallix-lib/tests/test_mtlexer_15.c | 2 +- centrallix-lib/tests/test_mtlexer_16.c | 2 +- centrallix-lib/tests/test_mtlexer_17.c | 2 +- centrallix-lib/tests/test_mtlexer_18.c | 2 +- centrallix-lib/tests/test_mtlexer_19.c | 2 +- centrallix-lib/tests/test_mtlexer_20.c | 2 +- centrallix-lib/tests/test_mtlexer_21.c | 2 +- centrallix-lib/tests/test_mtlexer_22.c | 2 +- centrallix-lib/tests/test_newmalloc_00.c | 2 +- centrallix-lib/tests/test_newmalloc_01.c | 2 +- centrallix-lib/tests/test_newmalloc_02.c | 2 +- centrallix-lib/tests/test_qprintf_00.c | 2 +- centrallix-lib/tests/test_qprintf_01.c | 2 +- centrallix-lib/tests/test_qprintf_02.c | 2 +- centrallix-lib/tests/test_qprintf_03.c | 2 +- centrallix-lib/tests/test_qprintf_04.c | 2 +- centrallix-lib/tests/test_qprintf_05.c | 2 +- centrallix-lib/tests/test_qprintf_06.c | 2 +- centrallix-lib/tests/test_qprintf_07.c | 2 +- centrallix-lib/tests/test_qprintf_08.c | 2 +- centrallix-lib/tests/test_qprintf_09.c | 2 +- centrallix-lib/tests/test_qprintf_10.c | 2 +- centrallix-lib/tests/test_qprintf_11.c | 2 +- centrallix-lib/tests/test_qprintf_12.c | 2 +- centrallix-lib/tests/test_qprintf_13.c | 2 +- centrallix-lib/tests/test_qprintf_14.c | 2 +- centrallix-lib/tests/test_qprintf_15.c | 2 +- centrallix-lib/tests/test_qprintf_16.c | 2 +- centrallix-lib/tests/test_qprintf_17.c | 2 +- centrallix-lib/tests/test_qprintf_18.c | 2 +- centrallix-lib/tests/test_qprintf_19.c | 2 +- centrallix-lib/tests/test_qprintf_20.c | 2 +- centrallix-lib/tests/test_qprintf_21.c | 2 +- centrallix-lib/tests/test_qprintf_22.c | 2 +- centrallix-lib/tests/test_qprintf_23.c | 2 +- centrallix-lib/tests/test_qprintf_24.c | 2 +- centrallix-lib/tests/test_qprintf_25.c | 2 +- centrallix-lib/tests/test_qprintf_26.c | 2 +- centrallix-lib/tests/test_qprintf_27.c | 2 +- centrallix-lib/tests/test_qprintf_28.c | 2 +- centrallix-lib/tests/test_qprintf_29.c | 2 +- centrallix-lib/tests/test_qprintf_30.c | 2 +- centrallix-lib/tests/test_qprintf_31.c | 2 +- centrallix-lib/tests/test_qprintf_32.c | 2 +- centrallix-lib/tests/test_qprintf_33.c | 2 +- centrallix-lib/tests/test_qprintf_34.c | 2 +- centrallix-lib/tests/test_qprintf_35.c | 2 +- centrallix-lib/tests/test_qprintf_36.c | 2 +- centrallix-lib/tests/test_qprintf_37.c | 2 +- centrallix-lib/tests/test_qprintf_38.c | 2 +- centrallix-lib/tests/test_qprintf_39.c | 2 +- centrallix-lib/tests/test_qprintf_40.c | 2 +- centrallix-lib/tests/test_qprintf_41.c | 2 +- centrallix-lib/tests/test_qprintf_42.c | 2 +- centrallix-lib/tests/test_qprintf_43.c | 2 +- centrallix-lib/tests/test_qprintf_44.c | 2 +- centrallix-lib/tests/test_qprintf_45.c | 2 +- centrallix-lib/tests/test_qprintf_46.c | 2 +- centrallix-lib/tests/test_qprintf_47.c | 2 +- centrallix-lib/tests/test_qprintf_48.c | 2 +- centrallix-lib/tests/test_qprintf_49.c | 2 +- centrallix-lib/tests/test_qprintf_50.c | 2 +- centrallix-lib/tests/test_qprintf_51.c | 2 +- centrallix-lib/tests/test_qprintf_52.c | 2 +- centrallix-lib/tests/test_qprintf_53.c | 2 +- centrallix-lib/tests/test_qprintf_54.c | 2 +- centrallix-lib/tests/test_qprintf_55.c | 2 +- centrallix-lib/tests/test_qprintf_56.c | 2 +- centrallix-lib/tests/test_qprintf_57.c | 2 +- centrallix-lib/tests/test_qprintf_58.c | 2 +- centrallix-lib/tests/test_qprintf_59.c | 2 +- centrallix-lib/tests/test_qprintf_60.c | 2 +- centrallix-lib/tests/test_qprintf_61.c | 2 +- centrallix-lib/tests/test_qprintf_62.c | 2 +- centrallix-lib/tests/test_qprintf_63.c | 2 +- centrallix-lib/tests/test_qprintf_64.c | 2 +- centrallix-lib/tests/test_qprintf_65.c | 2 +- centrallix-lib/tests/test_qprintf_66.c | 2 +- centrallix-lib/tests/test_qprintf_67.c | 2 +- centrallix-lib/tests/test_qprintf_68.c | 2 +- centrallix-lib/tests/test_range_00.c | 2 +- centrallix-lib/tests/test_range_01.c | 2 +- centrallix-lib/tests/test_smmalloc_01.c | 2 +- centrallix-lib/tests/test_smmalloc_02.c | 2 +- centrallix-lib/tests/test_smmalloc_03.c | 2 +- centrallix-lib/tests/test_smmalloc_04.c | 2 +- centrallix-lib/tests/test_smmalloc_05.c | 2 +- centrallix-lib/tests/test_smmalloc_06.c | 2 +- centrallix-lib/tests/test_smmalloc_07.c | 2 +- centrallix-lib/tests/test_smmalloc_08.c | 2 +- centrallix-lib/tests/test_smmalloc_09.c | 2 +- centrallix-lib/tests/test_strtcpy_00.c | 2 +- centrallix-lib/tests/test_strtcpy_01.c | 2 +- centrallix-lib/tests/test_strtcpy_02.c | 2 +- centrallix-lib/tests/test_strtcpy_03.c | 2 +- centrallix-lib/tests/test_strtcpy_04.c | 2 +- centrallix-lib/tests/test_strtcpy_05.c | 2 +- centrallix-lib/tests/test_strtcpy_06.c | 2 +- centrallix-lib/tests/test_strtcpy_07.c | 2 +- centrallix-lib/tests/test_strtcpy_08.c | 2 +- centrallix-lib/tests/test_strtcpy_09.c | 2 +- centrallix-lib/tests/test_strtcpy_10.c | 2 +- centrallix-lib/tests/test_strtcpy_11.c | 2 +- centrallix-lib/tests/test_strtcpy_12.c | 2 +- centrallix-lib/tests/test_strtcpy_13.c | 2 +- .../tests/{test_testutils_00.c => test_t_utils_00.c} | 8 ++++---- .../tests/{test_testutils_01.c => test_t_utils_01.c} | 8 ++++---- centrallix-lib/tests/test_timer_00.c | 2 +- centrallix-lib/tests/test_util_00.c | 2 +- centrallix-lib/tests/test_util_01.c | 2 +- centrallix-lib/tests/test_util_02.c | 2 +- centrallix-lib/tests/test_xarray_00.c | 2 +- centrallix-lib/tests/test_xhash_00.c | 2 +- centrallix-lib/tests/test_xhash_01.c | 2 +- centrallix-lib/tests/test_xhash_02.c | 2 +- centrallix-lib/tests/test_xhash_03.c | 2 +- centrallix-lib/tests/test_xhash_04.c | 2 +- centrallix-lib/tests/test_xhash_05.c | 2 +- 138 files changed, 145 insertions(+), 145 deletions(-) rename centrallix-lib/tests/{test_utils.h => t_utils.h} (99%) rename centrallix-lib/tests/{test_testutils_00.c => test_t_utils_00.c} (97%) rename centrallix-lib/tests/{test_testutils_01.c => test_t_utils_01.c} (91%) diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 173427ae3..1977ab5f4 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -170,7 +170,7 @@ install: all @echo "***************************************************************" @echo "" -%.bin: %.c tests/t_driver.c tests/test_utils.h libCentrallix.a +%.bin: %.c tests/t_driver.c tests/t_utils.h libCentrallix.a $(CC) $(TCFLAGS) tests/t_driver.c $< libCentrallix.a -o $@ ${LIBS} ## Runs the selected tests $(TESTJOBS) at a time. Output is captured per test diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index cba013133..1c969cbbb 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -22,7 +22,7 @@ #include "cxlibconfig-internal.h" #include "mtask.h" -#include "test_utils.h" +#include "t_utils.h" #include "util.h" @@ -82,7 +82,7 @@ start(void* v) if (duration == 0) { printf("%-62.62s PASS ???\n", tname); - printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loopTest() from test_utils.h.\n"); + printf("Warning: Test ran too fast! Ops/sec could not be measured. Please run tests in a loop or use loopTest() from t_utils.h.\n"); return; } double ops_per_second = rval * (100.0 / duration); diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/t_utils.h similarity index 99% rename from centrallix-lib/tests/test_utils.h rename to centrallix-lib/tests/t_utils.h index 66aa01104..ed171f8eb 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/t_utils.h @@ -11,7 +11,7 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: test_utils.h */ +/* Module: t_utils.h */ /* Author: Israel Fuller */ /* Creation: November 24th, 2025 */ /* Description: Useful utils to improve the developer experience when */ diff --git a/centrallix-lib/tests/test_00baseline.c b/centrallix-lib/tests/test_00baseline.c index 73022b302..b90467545 100644 --- a/centrallix-lib/tests/test_00baseline.c +++ b/centrallix-lib/tests/test_00baseline.c @@ -1,7 +1,7 @@ #include #include #include -#include "test_utils.h" +#include "t_utils.h" /** No-op operations per pass, enough that the loop dominates the timing. **/ #define OPS_PER_PASS (1000*1000) diff --git a/centrallix-lib/tests/test_memstr_00.c b/centrallix-lib/tests/test_memstr_00.c index 0311b7beb..a18017d95 100755 --- a/centrallix-lib/tests/test_memstr_00.c +++ b/centrallix-lib/tests/test_memstr_00.c @@ -22,7 +22,7 @@ #include "memstr.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_mtlexer_00.c b/centrallix-lib/tests/test_mtlexer_00.c index 1aadd6bc4..343e86db9 100644 --- a/centrallix-lib/tests/test_mtlexer_00.c +++ b/centrallix-lib/tests/test_mtlexer_00.c @@ -7,7 +7,7 @@ #include "mtsession.h" #include "mtlexer.h" #include -#include "test_utils.h" +#include "t_utils.h" #define N_FLAGTYPE 4 #define N_TOK 3 diff --git a/centrallix-lib/tests/test_mtlexer_01.c b/centrallix-lib/tests/test_mtlexer_01.c index a37ab9dbd..e1406a909 100644 --- a/centrallix-lib/tests/test_mtlexer_01.c +++ b/centrallix-lib/tests/test_mtlexer_01.c @@ -7,7 +7,7 @@ #include "mtsession.h" #include "mtlexer.h" #include -#include "test_utils.h" +#include "t_utils.h" #define N_FLAGTYPE 4 #define N_TOK 4 diff --git a/centrallix-lib/tests/test_mtlexer_02.c b/centrallix-lib/tests/test_mtlexer_02.c index 3d1b83854..42a4a0860 100644 --- a/centrallix-lib/tests/test_mtlexer_02.c +++ b/centrallix-lib/tests/test_mtlexer_02.c @@ -7,7 +7,7 @@ #include "mtsession.h" #include "mtlexer.h" #include -#include "test_utils.h" +#include "t_utils.h" #define N_FLAGTYPE 4 #define N_TOK 9 diff --git a/centrallix-lib/tests/test_mtlexer_03.c b/centrallix-lib/tests/test_mtlexer_03.c index ff3f0edbc..b15b7c887 100644 --- a/centrallix-lib/tests/test_mtlexer_03.c +++ b/centrallix-lib/tests/test_mtlexer_03.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Longest line the sweep builds. **/ #define MAX_LEN 6000 diff --git a/centrallix-lib/tests/test_mtlexer_04.c b/centrallix-lib/tests/test_mtlexer_04.c index e647f0364..52deb854c 100644 --- a/centrallix-lib/tests/test_mtlexer_04.c +++ b/centrallix-lib/tests/test_mtlexer_04.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static char str[65536] = ""; diff --git a/centrallix-lib/tests/test_mtlexer_05.c b/centrallix-lib/tests/test_mtlexer_05.c index 1b956573e..f1710cbe1 100644 --- a/centrallix-lib/tests/test_mtlexer_05.c +++ b/centrallix-lib/tests/test_mtlexer_05.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Integers in the data file, and so the ops performed by one pass. **/ #define N_INTS 60000 diff --git a/centrallix-lib/tests/test_mtlexer_06.c b/centrallix-lib/tests/test_mtlexer_06.c index b81a907e1..e7db20607 100644 --- a/centrallix-lib/tests/test_mtlexer_06.c +++ b/centrallix-lib/tests/test_mtlexer_06.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Integers in the data file, and so the ops performed by one pass. **/ #define N_INTS 60000 diff --git a/centrallix-lib/tests/test_mtlexer_07.c b/centrallix-lib/tests/test_mtlexer_07.c index 3b4fcf477..8e87c1c4d 100644 --- a/centrallix-lib/tests/test_mtlexer_07.c +++ b/centrallix-lib/tests/test_mtlexer_07.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static char* reswds[] = { "reserved", NULL }; diff --git a/centrallix-lib/tests/test_mtlexer_08.c b/centrallix-lib/tests/test_mtlexer_08.c index d00f34a83..0f4769e6f 100644 --- a/centrallix-lib/tests/test_mtlexer_08.c +++ b/centrallix-lib/tests/test_mtlexer_08.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static char* teststr = "'string' 'test string' \"string\" \"test string\" 'string\\'s' \"\\\"string\\\"\" 'string\\\\string' 'string\"string' \"string'string\""; static char* strs[] = {"string", "test string", "string", "test string", "string's", "\"string\"", "string\\string", "string\"string", "string'string", NULL}; diff --git a/centrallix-lib/tests/test_mtlexer_09.c b/centrallix-lib/tests/test_mtlexer_09.c index c115fcbfe..95f6f4145 100644 --- a/centrallix-lib/tests/test_mtlexer_09.c +++ b/centrallix-lib/tests/test_mtlexer_09.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Longest string the sweep builds. **/ #define MAX_LEN 6000 diff --git a/centrallix-lib/tests/test_mtlexer_10.c b/centrallix-lib/tests/test_mtlexer_10.c index f2fb82509..da651cecd 100644 --- a/centrallix-lib/tests/test_mtlexer_10.c +++ b/centrallix-lib/tests/test_mtlexer_10.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static char* teststr = "0 -1 1 -255 -256 255 256 -127 -128 127 128 32767 32768 -32767 -32768 65535 65536 -65535 -65536 16777216 -16777216 2147483647 -2147483648\n" ".0 .1 .10 .01 0.0 0.1 0.10 0.01 1.0 1.1 1.10 1.01 10.0 10.1 10.10 10.01 -.0 -.1 -.10 -.01 -0.0 -0.1 -0.10 -0.01 -1.0 -1.1 -1.10 -1.01 -10.0 -10.1 -10.10 -10.01"; diff --git a/centrallix-lib/tests/test_mtlexer_11.c b/centrallix-lib/tests/test_mtlexer_11.c index c7e6876f1..20cbe7181 100644 --- a/centrallix-lib/tests/test_mtlexer_11.c +++ b/centrallix-lib/tests/test_mtlexer_11.c @@ -7,7 +7,7 @@ #include "mtsession.h" #include "mtlexer.h" #include -#include "test_utils.h" +#include "t_utils.h" #define N_FLAGTYPE 4 #define N_TOK 9 diff --git a/centrallix-lib/tests/test_mtlexer_12.c b/centrallix-lib/tests/test_mtlexer_12.c index 6861146c4..2482ddabe 100644 --- a/centrallix-lib/tests/test_mtlexer_12.c +++ b/centrallix-lib/tests/test_mtlexer_12.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Longest keyword the sweep builds. **/ #define MAX_LEN 20000 diff --git a/centrallix-lib/tests/test_mtlexer_13.c b/centrallix-lib/tests/test_mtlexer_13.c index 133d7f41e..2f41c61f4 100644 --- a/centrallix-lib/tests/test_mtlexer_13.c +++ b/centrallix-lib/tests/test_mtlexer_13.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Longest integer the sweep builds. **/ #define MAX_LEN 20000 diff --git a/centrallix-lib/tests/test_mtlexer_14.c b/centrallix-lib/tests/test_mtlexer_14.c index 56665bed2..1ce420860 100644 --- a/centrallix-lib/tests/test_mtlexer_14.c +++ b/centrallix-lib/tests/test_mtlexer_14.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Longest number the sweep builds. **/ #define MAX_LEN 20000 diff --git a/centrallix-lib/tests/test_mtlexer_15.c b/centrallix-lib/tests/test_mtlexer_15.c index a792ff5cb..96eacf318 100644 --- a/centrallix-lib/tests/test_mtlexer_15.c +++ b/centrallix-lib/tests/test_mtlexer_15.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Integers in the data file. **/ #define N_INTS 12 diff --git a/centrallix-lib/tests/test_mtlexer_16.c b/centrallix-lib/tests/test_mtlexer_16.c index 93d8c7453..981e469b9 100644 --- a/centrallix-lib/tests/test_mtlexer_16.c +++ b/centrallix-lib/tests/test_mtlexer_16.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /** Integers in the data file. **/ #define N_INTS 12 diff --git a/centrallix-lib/tests/test_mtlexer_17.c b/centrallix-lib/tests/test_mtlexer_17.c index 0979953f4..1cfa50637 100644 --- a/centrallix-lib/tests/test_mtlexer_17.c +++ b/centrallix-lib/tests/test_mtlexer_17.c @@ -7,7 +7,7 @@ #include "mtsession.h" #include "mtlexer.h" #include -#include "test_utils.h" +#include "t_utils.h" #define N_FLAGTYPE 4 #define N_TOK 15 diff --git a/centrallix-lib/tests/test_mtlexer_18.c b/centrallix-lib/tests/test_mtlexer_18.c index 420440b1b..859ba9865 100644 --- a/centrallix-lib/tests/test_mtlexer_18.c +++ b/centrallix-lib/tests/test_mtlexer_18.c @@ -7,7 +7,7 @@ #include "mtsession.h" #include "mtlexer.h" #include -#include "test_utils.h" +#include "t_utils.h" #define N_FLAGTYPE 4 #define N_TOK 15 diff --git a/centrallix-lib/tests/test_mtlexer_19.c b/centrallix-lib/tests/test_mtlexer_19.c index 9854c38f9..60a337a61 100644 --- a/centrallix-lib/tests/test_mtlexer_19.c +++ b/centrallix-lib/tests/test_mtlexer_19.c @@ -7,7 +7,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" #define N_TOK 16 diff --git a/centrallix-lib/tests/test_mtlexer_20.c b/centrallix-lib/tests/test_mtlexer_20.c index 9071701b3..f8470b83a 100644 --- a/centrallix-lib/tests/test_mtlexer_20.c +++ b/centrallix-lib/tests/test_mtlexer_20.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_mtlexer_21.c b/centrallix-lib/tests/test_mtlexer_21.c index ea34cad95..c9d7e4da6 100644 --- a/centrallix-lib/tests/test_mtlexer_21.c +++ b/centrallix-lib/tests/test_mtlexer_21.c @@ -8,7 +8,7 @@ #include "mtlexer.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_mtlexer_22.c b/centrallix-lib/tests/test_mtlexer_22.c index 10d0d3ec8..f6e2ebc6e 100644 --- a/centrallix-lib/tests/test_mtlexer_22.c +++ b/centrallix-lib/tests/test_mtlexer_22.c @@ -2,7 +2,7 @@ #include #include -#include "test_utils.h" +#include "t_utils.h" #include "mtsession.h" #include "newmalloc.h" diff --git a/centrallix-lib/tests/test_newmalloc_00.c b/centrallix-lib/tests/test_newmalloc_00.c index 3c7e6190b..1eee6af08 100644 --- a/centrallix-lib/tests/test_newmalloc_00.c +++ b/centrallix-lib/tests/test_newmalloc_00.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" #include "range.h" /** Tested module. **/ diff --git a/centrallix-lib/tests/test_newmalloc_01.c b/centrallix-lib/tests/test_newmalloc_01.c index a6faaaa9b..a28e96eb8 100644 --- a/centrallix-lib/tests/test_newmalloc_01.c +++ b/centrallix-lib/tests/test_newmalloc_01.c @@ -20,7 +20,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "newmalloc.h" diff --git a/centrallix-lib/tests/test_newmalloc_02.c b/centrallix-lib/tests/test_newmalloc_02.c index f58e1cea9..28db864d7 100644 --- a/centrallix-lib/tests/test_newmalloc_02.c +++ b/centrallix-lib/tests/test_newmalloc_02.c @@ -20,7 +20,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "newmalloc.h" diff --git a/centrallix-lib/tests/test_qprintf_00.c b/centrallix-lib/tests/test_qprintf_00.c index ceac9ef72..d7a96f3da 100644 --- a/centrallix-lib/tests/test_qprintf_00.c +++ b/centrallix-lib/tests/test_qprintf_00.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_01.c b/centrallix-lib/tests/test_qprintf_01.c index f252e51d6..e44d35f8b 100644 --- a/centrallix-lib/tests/test_qprintf_01.c +++ b/centrallix-lib/tests/test_qprintf_01.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_02.c b/centrallix-lib/tests/test_qprintf_02.c index 2adaabee9..f0efa3fad 100644 --- a/centrallix-lib/tests/test_qprintf_02.c +++ b/centrallix-lib/tests/test_qprintf_02.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_03.c b/centrallix-lib/tests/test_qprintf_03.c index b495c0b43..976749f91 100644 --- a/centrallix-lib/tests/test_qprintf_03.c +++ b/centrallix-lib/tests/test_qprintf_03.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_04.c b/centrallix-lib/tests/test_qprintf_04.c index 7d424a1ae..868722340 100644 --- a/centrallix-lib/tests/test_qprintf_04.c +++ b/centrallix-lib/tests/test_qprintf_04.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_05.c b/centrallix-lib/tests/test_qprintf_05.c index 83a6da21a..735b7eaa5 100644 --- a/centrallix-lib/tests/test_qprintf_05.c +++ b/centrallix-lib/tests/test_qprintf_05.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_06.c b/centrallix-lib/tests/test_qprintf_06.c index 811621b3b..e177bd6e0 100644 --- a/centrallix-lib/tests/test_qprintf_06.c +++ b/centrallix-lib/tests/test_qprintf_06.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_07.c b/centrallix-lib/tests/test_qprintf_07.c index c6fa656c3..759e40f1c 100644 --- a/centrallix-lib/tests/test_qprintf_07.c +++ b/centrallix-lib/tests/test_qprintf_07.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_08.c b/centrallix-lib/tests/test_qprintf_08.c index 9dbf37f98..5682d533a 100644 --- a/centrallix-lib/tests/test_qprintf_08.c +++ b/centrallix-lib/tests/test_qprintf_08.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_09.c b/centrallix-lib/tests/test_qprintf_09.c index 11bcc5178..cd2cfd548 100644 --- a/centrallix-lib/tests/test_qprintf_09.c +++ b/centrallix-lib/tests/test_qprintf_09.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_10.c b/centrallix-lib/tests/test_qprintf_10.c index 07d57d337..36786003c 100644 --- a/centrallix-lib/tests/test_qprintf_10.c +++ b/centrallix-lib/tests/test_qprintf_10.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_11.c b/centrallix-lib/tests/test_qprintf_11.c index b96f5715b..b0ed4d611 100644 --- a/centrallix-lib/tests/test_qprintf_11.c +++ b/centrallix-lib/tests/test_qprintf_11.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_12.c b/centrallix-lib/tests/test_qprintf_12.c index d8b663c2c..642b2322a 100644 --- a/centrallix-lib/tests/test_qprintf_12.c +++ b/centrallix-lib/tests/test_qprintf_12.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_13.c b/centrallix-lib/tests/test_qprintf_13.c index ea1e05c2f..f09e6f399 100644 --- a/centrallix-lib/tests/test_qprintf_13.c +++ b/centrallix-lib/tests/test_qprintf_13.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_14.c b/centrallix-lib/tests/test_qprintf_14.c index 6e4fa8a72..b0358e0f7 100644 --- a/centrallix-lib/tests/test_qprintf_14.c +++ b/centrallix-lib/tests/test_qprintf_14.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_15.c b/centrallix-lib/tests/test_qprintf_15.c index 75b2666ca..7d94ef0ff 100644 --- a/centrallix-lib/tests/test_qprintf_15.c +++ b/centrallix-lib/tests/test_qprintf_15.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_16.c b/centrallix-lib/tests/test_qprintf_16.c index 0bef635b2..267917006 100644 --- a/centrallix-lib/tests/test_qprintf_16.c +++ b/centrallix-lib/tests/test_qprintf_16.c @@ -7,7 +7,7 @@ #include #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_17.c b/centrallix-lib/tests/test_qprintf_17.c index 922164029..1f1446a89 100644 --- a/centrallix-lib/tests/test_qprintf_17.c +++ b/centrallix-lib/tests/test_qprintf_17.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_18.c b/centrallix-lib/tests/test_qprintf_18.c index fd1534457..bec380b8e 100644 --- a/centrallix-lib/tests/test_qprintf_18.c +++ b/centrallix-lib/tests/test_qprintf_18.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_19.c b/centrallix-lib/tests/test_qprintf_19.c index 6713c152d..616a362bd 100644 --- a/centrallix-lib/tests/test_qprintf_19.c +++ b/centrallix-lib/tests/test_qprintf_19.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_20.c b/centrallix-lib/tests/test_qprintf_20.c index 323c4ef09..076cbd092 100644 --- a/centrallix-lib/tests/test_qprintf_20.c +++ b/centrallix-lib/tests/test_qprintf_20.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_21.c b/centrallix-lib/tests/test_qprintf_21.c index f13c1e3bd..52fec9822 100644 --- a/centrallix-lib/tests/test_qprintf_21.c +++ b/centrallix-lib/tests/test_qprintf_21.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_22.c b/centrallix-lib/tests/test_qprintf_22.c index dcbbd4003..c50313883 100644 --- a/centrallix-lib/tests/test_qprintf_22.c +++ b/centrallix-lib/tests/test_qprintf_22.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_23.c b/centrallix-lib/tests/test_qprintf_23.c index c0f8d5dab..6ffd98543 100644 --- a/centrallix-lib/tests/test_qprintf_23.c +++ b/centrallix-lib/tests/test_qprintf_23.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_24.c b/centrallix-lib/tests/test_qprintf_24.c index dde0bf616..ecfc5651c 100644 --- a/centrallix-lib/tests/test_qprintf_24.c +++ b/centrallix-lib/tests/test_qprintf_24.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_25.c b/centrallix-lib/tests/test_qprintf_25.c index f03d70b84..b7dd1362f 100644 --- a/centrallix-lib/tests/test_qprintf_25.c +++ b/centrallix-lib/tests/test_qprintf_25.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_26.c b/centrallix-lib/tests/test_qprintf_26.c index ccd20d355..267f25fd7 100644 --- a/centrallix-lib/tests/test_qprintf_26.c +++ b/centrallix-lib/tests/test_qprintf_26.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_27.c b/centrallix-lib/tests/test_qprintf_27.c index 3856095ed..3cfde2d2c 100644 --- a/centrallix-lib/tests/test_qprintf_27.c +++ b/centrallix-lib/tests/test_qprintf_27.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_28.c b/centrallix-lib/tests/test_qprintf_28.c index 4e0bc7694..c773c9815 100644 --- a/centrallix-lib/tests/test_qprintf_28.c +++ b/centrallix-lib/tests/test_qprintf_28.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_29.c b/centrallix-lib/tests/test_qprintf_29.c index 2985d1c1f..ec078e0ab 100644 --- a/centrallix-lib/tests/test_qprintf_29.c +++ b/centrallix-lib/tests/test_qprintf_29.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_30.c b/centrallix-lib/tests/test_qprintf_30.c index e7db817a8..8fc120e5d 100644 --- a/centrallix-lib/tests/test_qprintf_30.c +++ b/centrallix-lib/tests/test_qprintf_30.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_31.c b/centrallix-lib/tests/test_qprintf_31.c index d1ddddfff..3f13c1171 100644 --- a/centrallix-lib/tests/test_qprintf_31.c +++ b/centrallix-lib/tests/test_qprintf_31.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_32.c b/centrallix-lib/tests/test_qprintf_32.c index 4d5283521..4409b6a64 100644 --- a/centrallix-lib/tests/test_qprintf_32.c +++ b/centrallix-lib/tests/test_qprintf_32.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_33.c b/centrallix-lib/tests/test_qprintf_33.c index 1927f764a..c76f7f638 100644 --- a/centrallix-lib/tests/test_qprintf_33.c +++ b/centrallix-lib/tests/test_qprintf_33.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_34.c b/centrallix-lib/tests/test_qprintf_34.c index bf0ab5b56..a1ff89a26 100644 --- a/centrallix-lib/tests/test_qprintf_34.c +++ b/centrallix-lib/tests/test_qprintf_34.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_35.c b/centrallix-lib/tests/test_qprintf_35.c index 3442eaffc..4cbeb145b 100644 --- a/centrallix-lib/tests/test_qprintf_35.c +++ b/centrallix-lib/tests/test_qprintf_35.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_36.c b/centrallix-lib/tests/test_qprintf_36.c index cd5fb6f4b..409c54a77 100644 --- a/centrallix-lib/tests/test_qprintf_36.c +++ b/centrallix-lib/tests/test_qprintf_36.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_37.c b/centrallix-lib/tests/test_qprintf_37.c index de779340a..e74de947e 100644 --- a/centrallix-lib/tests/test_qprintf_37.c +++ b/centrallix-lib/tests/test_qprintf_37.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_38.c b/centrallix-lib/tests/test_qprintf_38.c index f65a89df9..f82f225e7 100644 --- a/centrallix-lib/tests/test_qprintf_38.c +++ b/centrallix-lib/tests/test_qprintf_38.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_39.c b/centrallix-lib/tests/test_qprintf_39.c index 199b7739f..24907dea1 100644 --- a/centrallix-lib/tests/test_qprintf_39.c +++ b/centrallix-lib/tests/test_qprintf_39.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_40.c b/centrallix-lib/tests/test_qprintf_40.c index 9535925c5..b1baa20e4 100644 --- a/centrallix-lib/tests/test_qprintf_40.c +++ b/centrallix-lib/tests/test_qprintf_40.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_41.c b/centrallix-lib/tests/test_qprintf_41.c index 72eb100cd..7316ff28d 100644 --- a/centrallix-lib/tests/test_qprintf_41.c +++ b/centrallix-lib/tests/test_qprintf_41.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_42.c b/centrallix-lib/tests/test_qprintf_42.c index b17a80e72..d76997eea 100644 --- a/centrallix-lib/tests/test_qprintf_42.c +++ b/centrallix-lib/tests/test_qprintf_42.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_43.c b/centrallix-lib/tests/test_qprintf_43.c index 9076b55d1..0bdbf83b3 100644 --- a/centrallix-lib/tests/test_qprintf_43.c +++ b/centrallix-lib/tests/test_qprintf_43.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_44.c b/centrallix-lib/tests/test_qprintf_44.c index eed57c627..ce4bad464 100644 --- a/centrallix-lib/tests/test_qprintf_44.c +++ b/centrallix-lib/tests/test_qprintf_44.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_45.c b/centrallix-lib/tests/test_qprintf_45.c index ed2815dc1..e8b72c6e6 100644 --- a/centrallix-lib/tests/test_qprintf_45.c +++ b/centrallix-lib/tests/test_qprintf_45.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_46.c b/centrallix-lib/tests/test_qprintf_46.c index 79a2f48cb..e8fc30225 100644 --- a/centrallix-lib/tests/test_qprintf_46.c +++ b/centrallix-lib/tests/test_qprintf_46.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_47.c b/centrallix-lib/tests/test_qprintf_47.c index 2e5d73163..1add7ed3c 100644 --- a/centrallix-lib/tests/test_qprintf_47.c +++ b/centrallix-lib/tests/test_qprintf_47.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_48.c b/centrallix-lib/tests/test_qprintf_48.c index 99d25930b..0b1922433 100644 --- a/centrallix-lib/tests/test_qprintf_48.c +++ b/centrallix-lib/tests/test_qprintf_48.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_49.c b/centrallix-lib/tests/test_qprintf_49.c index 93cc4cf98..76e441b8b 100644 --- a/centrallix-lib/tests/test_qprintf_49.c +++ b/centrallix-lib/tests/test_qprintf_49.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_50.c b/centrallix-lib/tests/test_qprintf_50.c index 44448d093..90f6734eb 100644 --- a/centrallix-lib/tests/test_qprintf_50.c +++ b/centrallix-lib/tests/test_qprintf_50.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_51.c b/centrallix-lib/tests/test_qprintf_51.c index 3daefae69..f11b34f23 100644 --- a/centrallix-lib/tests/test_qprintf_51.c +++ b/centrallix-lib/tests/test_qprintf_51.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_52.c b/centrallix-lib/tests/test_qprintf_52.c index f9b03f70b..02b73e1a1 100644 --- a/centrallix-lib/tests/test_qprintf_52.c +++ b/centrallix-lib/tests/test_qprintf_52.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_53.c b/centrallix-lib/tests/test_qprintf_53.c index d2a25f6ed..426e556e5 100644 --- a/centrallix-lib/tests/test_qprintf_53.c +++ b/centrallix-lib/tests/test_qprintf_53.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_54.c b/centrallix-lib/tests/test_qprintf_54.c index 757ccc8cd..3aec645b8 100644 --- a/centrallix-lib/tests/test_qprintf_54.c +++ b/centrallix-lib/tests/test_qprintf_54.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_55.c b/centrallix-lib/tests/test_qprintf_55.c index 2af189785..1381d51a7 100644 --- a/centrallix-lib/tests/test_qprintf_55.c +++ b/centrallix-lib/tests/test_qprintf_55.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_56.c b/centrallix-lib/tests/test_qprintf_56.c index 9667f0ab9..366eb9bfa 100644 --- a/centrallix-lib/tests/test_qprintf_56.c +++ b/centrallix-lib/tests/test_qprintf_56.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_57.c b/centrallix-lib/tests/test_qprintf_57.c index 9d8146376..f7244189c 100644 --- a/centrallix-lib/tests/test_qprintf_57.c +++ b/centrallix-lib/tests/test_qprintf_57.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_58.c b/centrallix-lib/tests/test_qprintf_58.c index 6a0bec542..52bca55bd 100644 --- a/centrallix-lib/tests/test_qprintf_58.c +++ b/centrallix-lib/tests/test_qprintf_58.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_59.c b/centrallix-lib/tests/test_qprintf_59.c index 42ec7a392..cddcfcb88 100644 --- a/centrallix-lib/tests/test_qprintf_59.c +++ b/centrallix-lib/tests/test_qprintf_59.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_60.c b/centrallix-lib/tests/test_qprintf_60.c index 6fe6d86ca..025c645fa 100644 --- a/centrallix-lib/tests/test_qprintf_60.c +++ b/centrallix-lib/tests/test_qprintf_60.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_61.c b/centrallix-lib/tests/test_qprintf_61.c index f2e1f64d4..ce674cc2c 100644 --- a/centrallix-lib/tests/test_qprintf_61.c +++ b/centrallix-lib/tests/test_qprintf_61.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_62.c b/centrallix-lib/tests/test_qprintf_62.c index 035c82dad..4fc430a43 100644 --- a/centrallix-lib/tests/test_qprintf_62.c +++ b/centrallix-lib/tests/test_qprintf_62.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_63.c b/centrallix-lib/tests/test_qprintf_63.c index 63cc9bb0d..ef9c9fa5f 100644 --- a/centrallix-lib/tests/test_qprintf_63.c +++ b/centrallix-lib/tests/test_qprintf_63.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_64.c b/centrallix-lib/tests/test_qprintf_64.c index 00202033c..130540a27 100644 --- a/centrallix-lib/tests/test_qprintf_64.c +++ b/centrallix-lib/tests/test_qprintf_64.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_65.c b/centrallix-lib/tests/test_qprintf_65.c index 88f72ef12..40073dcb2 100644 --- a/centrallix-lib/tests/test_qprintf_65.c +++ b/centrallix-lib/tests/test_qprintf_65.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_66.c b/centrallix-lib/tests/test_qprintf_66.c index e48d7d1dc..553d27b91 100644 --- a/centrallix-lib/tests/test_qprintf_66.c +++ b/centrallix-lib/tests/test_qprintf_66.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_67.c b/centrallix-lib/tests/test_qprintf_67.c index 13dcd8b4e..ec7c46b89 100644 --- a/centrallix-lib/tests/test_qprintf_67.c +++ b/centrallix-lib/tests/test_qprintf_67.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_qprintf_68.c b/centrallix-lib/tests/test_qprintf_68.c index 68c0ca3d6..c6ec1b4bb 100644 --- a/centrallix-lib/tests/test_qprintf_68.c +++ b/centrallix-lib/tests/test_qprintf_68.c @@ -6,7 +6,7 @@ #include "qprintf.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_range_00.c b/centrallix-lib/tests/test_range_00.c index 2a16d2917..c94379e69 100644 --- a/centrallix-lib/tests/test_range_00.c +++ b/centrallix-lib/tests/test_range_00.c @@ -22,7 +22,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "range.h" diff --git a/centrallix-lib/tests/test_range_01.c b/centrallix-lib/tests/test_range_01.c index 70d4c6acf..87e27f98f 100644 --- a/centrallix-lib/tests/test_range_01.c +++ b/centrallix-lib/tests/test_range_01.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "range.h" diff --git a/centrallix-lib/tests/test_smmalloc_01.c b/centrallix-lib/tests/test_smmalloc_01.c index f5bf47a63..7107bf16f 100644 --- a/centrallix-lib/tests/test_smmalloc_01.c +++ b/centrallix-lib/tests/test_smmalloc_01.c @@ -5,7 +5,7 @@ #include #include "smmalloc.h" #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_smmalloc_02.c b/centrallix-lib/tests/test_smmalloc_02.c index 9f7ec72ed..1f9e5cb06 100644 --- a/centrallix-lib/tests/test_smmalloc_02.c +++ b/centrallix-lib/tests/test_smmalloc_02.c @@ -5,7 +5,7 @@ #include #include "smmalloc.h" #include -#include "test_utils.h" +#include "t_utils.h" /** Region shared by every pass; created and destroyed by test(). **/ static pSmRegion region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_03.c b/centrallix-lib/tests/test_smmalloc_03.c index f3c45e572..00904a462 100644 --- a/centrallix-lib/tests/test_smmalloc_03.c +++ b/centrallix-lib/tests/test_smmalloc_03.c @@ -6,7 +6,7 @@ #include "smmalloc.h" #include "smmalloc_private.h" #include -#include "test_utils.h" +#include "t_utils.h" /** Region shared by every pass; created and destroyed by test(). **/ static pSmRegion region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_04.c b/centrallix-lib/tests/test_smmalloc_04.c index bfd1665a8..153a74698 100644 --- a/centrallix-lib/tests/test_smmalloc_04.c +++ b/centrallix-lib/tests/test_smmalloc_04.c @@ -6,7 +6,7 @@ #include "smmalloc.h" #include "smmalloc_private.h" #include -#include "test_utils.h" +#include "t_utils.h" /** Region shared by every pass; created and destroyed by test(). **/ static pSmRegion region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_05.c b/centrallix-lib/tests/test_smmalloc_05.c index ec6e2ef19..f8cc9552f 100644 --- a/centrallix-lib/tests/test_smmalloc_05.c +++ b/centrallix-lib/tests/test_smmalloc_05.c @@ -7,7 +7,7 @@ #include "smmalloc.h" #include "smmalloc_private.h" #include -#include "test_utils.h" +#include "t_utils.h" /** Region shared by every pass; created and destroyed by test(). **/ static pSmRegion region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_06.c b/centrallix-lib/tests/test_smmalloc_06.c index 8b3aec81a..51a47dfd5 100644 --- a/centrallix-lib/tests/test_smmalloc_06.c +++ b/centrallix-lib/tests/test_smmalloc_06.c @@ -6,7 +6,7 @@ #include #include "smmalloc.h" #include -#include "test_utils.h" +#include "t_utils.h" /** Region shared by every pass; created and destroyed by test(). **/ static pSmRegion region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_07.c b/centrallix-lib/tests/test_smmalloc_07.c index 7c330b65c..f546a450e 100644 --- a/centrallix-lib/tests/test_smmalloc_07.c +++ b/centrallix-lib/tests/test_smmalloc_07.c @@ -6,7 +6,7 @@ #include #include "smmalloc.h" #include -#include "test_utils.h" +#include "t_utils.h" /** Region shared by every pass; created and destroyed by test(). **/ static pSmRegion region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_08.c b/centrallix-lib/tests/test_smmalloc_08.c index 6cb52626f..67a10f9b2 100644 --- a/centrallix-lib/tests/test_smmalloc_08.c +++ b/centrallix-lib/tests/test_smmalloc_08.c @@ -6,7 +6,7 @@ #include #include "smmalloc.h" #include -#include "test_utils.h" +#include "t_utils.h" /** Region shared by every pass; created and destroyed by test(). **/ static pSmRegion region = NULL; diff --git a/centrallix-lib/tests/test_smmalloc_09.c b/centrallix-lib/tests/test_smmalloc_09.c index 2cdfdabec..988881367 100644 --- a/centrallix-lib/tests/test_smmalloc_09.c +++ b/centrallix-lib/tests/test_smmalloc_09.c @@ -10,7 +10,7 @@ #include #include #include -#include "test_utils.h" +#include "t_utils.h" /** Blocks handed from the parent to the child in each pass. **/ #define N_BLOCKS 2000 diff --git a/centrallix-lib/tests/test_strtcpy_00.c b/centrallix-lib/tests/test_strtcpy_00.c index 1ee0f1748..b31ee2139 100644 --- a/centrallix-lib/tests/test_strtcpy_00.c +++ b/centrallix-lib/tests/test_strtcpy_00.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_01.c b/centrallix-lib/tests/test_strtcpy_01.c index 3a8694f82..c7eaead13 100644 --- a/centrallix-lib/tests/test_strtcpy_01.c +++ b/centrallix-lib/tests/test_strtcpy_01.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_02.c b/centrallix-lib/tests/test_strtcpy_02.c index be6bf4a99..e32763fe4 100644 --- a/centrallix-lib/tests/test_strtcpy_02.c +++ b/centrallix-lib/tests/test_strtcpy_02.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_03.c b/centrallix-lib/tests/test_strtcpy_03.c index 7bcb95bbb..5e2fe343c 100644 --- a/centrallix-lib/tests/test_strtcpy_03.c +++ b/centrallix-lib/tests/test_strtcpy_03.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_04.c b/centrallix-lib/tests/test_strtcpy_04.c index 470979bb0..cd49731c7 100644 --- a/centrallix-lib/tests/test_strtcpy_04.c +++ b/centrallix-lib/tests/test_strtcpy_04.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_05.c b/centrallix-lib/tests/test_strtcpy_05.c index 9bc26b1fd..f39748a06 100644 --- a/centrallix-lib/tests/test_strtcpy_05.c +++ b/centrallix-lib/tests/test_strtcpy_05.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_06.c b/centrallix-lib/tests/test_strtcpy_06.c index d5d81e6f2..02f3d1e45 100644 --- a/centrallix-lib/tests/test_strtcpy_06.c +++ b/centrallix-lib/tests/test_strtcpy_06.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_07.c b/centrallix-lib/tests/test_strtcpy_07.c index d99cc0483..369f29ff2 100644 --- a/centrallix-lib/tests/test_strtcpy_07.c +++ b/centrallix-lib/tests/test_strtcpy_07.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_08.c b/centrallix-lib/tests/test_strtcpy_08.c index ea7304c6b..20f21c01a 100644 --- a/centrallix-lib/tests/test_strtcpy_08.c +++ b/centrallix-lib/tests/test_strtcpy_08.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" /*** Main reason to have both a 1 char and 2 char test is that the compiler's *** optimization seems to partly unroll the loop into a copy-first-char and diff --git a/centrallix-lib/tests/test_strtcpy_09.c b/centrallix-lib/tests/test_strtcpy_09.c index 883beea73..efa86b018 100644 --- a/centrallix-lib/tests/test_strtcpy_09.c +++ b/centrallix-lib/tests/test_strtcpy_09.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_10.c b/centrallix-lib/tests/test_strtcpy_10.c index 4d07bb2cd..b072917cc 100644 --- a/centrallix-lib/tests/test_strtcpy_10.c +++ b/centrallix-lib/tests/test_strtcpy_10.c @@ -6,7 +6,7 @@ #include "strtcpy.h" #include #include -#include "test_utils.h" +#include "t_utils.h" static bool doTest(void) diff --git a/centrallix-lib/tests/test_strtcpy_11.c b/centrallix-lib/tests/test_strtcpy_11.c index a007d440f..55b86db01 100644 --- a/centrallix-lib/tests/test_strtcpy_11.c +++ b/centrallix-lib/tests/test_strtcpy_11.c @@ -4,7 +4,7 @@ #include #include -#include "test_utils.h" +#include "t_utils.h" #include "strtcpy.h" diff --git a/centrallix-lib/tests/test_strtcpy_12.c b/centrallix-lib/tests/test_strtcpy_12.c index 5b6d22588..22ff33fa5 100644 --- a/centrallix-lib/tests/test_strtcpy_12.c +++ b/centrallix-lib/tests/test_strtcpy_12.c @@ -5,7 +5,7 @@ #include #include -#include "test_utils.h" +#include "t_utils.h" #include "strtcpy.h" diff --git a/centrallix-lib/tests/test_strtcpy_13.c b/centrallix-lib/tests/test_strtcpy_13.c index 2d9772cdb..836a21a58 100644 --- a/centrallix-lib/tests/test_strtcpy_13.c +++ b/centrallix-lib/tests/test_strtcpy_13.c @@ -6,7 +6,7 @@ #include #include -#include "test_utils.h" +#include "t_utils.h" #include "strtcpy.h" diff --git a/centrallix-lib/tests/test_testutils_00.c b/centrallix-lib/tests/test_t_utils_00.c similarity index 97% rename from centrallix-lib/tests/test_testutils_00.c rename to centrallix-lib/tests/test_t_utils_00.c index 13f2f37a7..26b584e6f 100644 --- a/centrallix-lib/tests/test_testutils_00.c +++ b/centrallix-lib/tests/test_t_utils_00.c @@ -8,10 +8,10 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: test_testutils_00.c */ +/* Module: test_t_utils_00.c */ /* Author: Israel Fuller */ /* Creation: October 14th, 2026 */ -/* Description: Test the assertion macros in test_utils.h. These tests */ +/* Description: Test the assertion macros in t_utils.h. These tests */ /* are a little fragile, but I don't expect error messages */ /* to change often and devs running tests should probably */ /* know if they do. */ @@ -22,7 +22,7 @@ #include /** Tested module. **/ -#include "test_utils.h" +#include "t_utils.h" #define MSG_SIZE 512 #define COPY_SIZE 8 @@ -166,7 +166,7 @@ static bool doTest(void) long long test(char** tname) { - *tname = "testutils-00 Assertions"; + *tname = "t_utils-00 Assertions"; return loopTest(doTest) * 22ll; } diff --git a/centrallix-lib/tests/test_testutils_01.c b/centrallix-lib/tests/test_t_utils_01.c similarity index 91% rename from centrallix-lib/tests/test_testutils_01.c rename to centrallix-lib/tests/test_t_utils_01.c index c47db89ad..c4a463620 100644 --- a/centrallix-lib/tests/test_testutils_01.c +++ b/centrallix-lib/tests/test_t_utils_01.c @@ -8,10 +8,10 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: test_testutils_01.c */ +/* Module: test_t_utils_01.c */ /* Author: Israel Fuller */ /* Creation: October 14th, 2026 */ -/* Description: Test loopTest() from test_utils.h. */ +/* Description: Test loopTest() from t_utils.h. */ /************************************************************************/ #include @@ -20,7 +20,7 @@ #include "timer.h" /** Tested module. **/ -#include "test_utils.h" +#include "t_utils.h" /** Calls to countCalls(), and the call that fails (0 for none). **/ static long long calls; @@ -38,7 +38,7 @@ long long test(char** tname) long long iterations; Timer timer_buf, *timer; - *tname = "testutils-01 loopTest()"; + *tname = "t_utils-01 loopTest()"; /** A passing test repeats until the time window has passed. **/ calls = 0ll; diff --git a/centrallix-lib/tests/test_timer_00.c b/centrallix-lib/tests/test_timer_00.c index f4ad62d29..7c86bec50 100644 --- a/centrallix-lib/tests/test_timer_00.c +++ b/centrallix-lib/tests/test_timer_00.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "timer.h" diff --git a/centrallix-lib/tests/test_util_00.c b/centrallix-lib/tests/test_util_00.c index 752f41dff..337f28b15 100644 --- a/centrallix-lib/tests/test_util_00.c +++ b/centrallix-lib/tests/test_util_00.c @@ -21,7 +21,7 @@ #include #include -#include "test_utils.h" +#include "t_utils.h" #include "util.h" diff --git a/centrallix-lib/tests/test_util_01.c b/centrallix-lib/tests/test_util_01.c index 092cb54da..a81a01e8e 100644 --- a/centrallix-lib/tests/test_util_01.c +++ b/centrallix-lib/tests/test_util_01.c @@ -21,7 +21,7 @@ #include #include -#include "test_utils.h" +#include "t_utils.h" #include "util.h" diff --git a/centrallix-lib/tests/test_util_02.c b/centrallix-lib/tests/test_util_02.c index 456f080a6..7fa1b272c 100644 --- a/centrallix-lib/tests/test_util_02.c +++ b/centrallix-lib/tests/test_util_02.c @@ -22,7 +22,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "util.h" diff --git a/centrallix-lib/tests/test_xarray_00.c b/centrallix-lib/tests/test_xarray_00.c index 7abb3ed1e..fbb119554 100644 --- a/centrallix-lib/tests/test_xarray_00.c +++ b/centrallix-lib/tests/test_xarray_00.c @@ -23,7 +23,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" #include "newmalloc.h" /** Tested module. **/ diff --git a/centrallix-lib/tests/test_xhash_00.c b/centrallix-lib/tests/test_xhash_00.c index 4ec71fad4..f1aa8f2c1 100644 --- a/centrallix-lib/tests/test_xhash_00.c +++ b/centrallix-lib/tests/test_xhash_00.c @@ -26,7 +26,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "xhash.h" diff --git a/centrallix-lib/tests/test_xhash_01.c b/centrallix-lib/tests/test_xhash_01.c index 14e961c85..8a865a90d 100644 --- a/centrallix-lib/tests/test_xhash_01.c +++ b/centrallix-lib/tests/test_xhash_01.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "xhash.h" diff --git a/centrallix-lib/tests/test_xhash_02.c b/centrallix-lib/tests/test_xhash_02.c index d6fe04fad..ab211aea3 100644 --- a/centrallix-lib/tests/test_xhash_02.c +++ b/centrallix-lib/tests/test_xhash_02.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "xhash.h" diff --git a/centrallix-lib/tests/test_xhash_03.c b/centrallix-lib/tests/test_xhash_03.c index eaa6f607c..5ec8a8afe 100644 --- a/centrallix-lib/tests/test_xhash_03.c +++ b/centrallix-lib/tests/test_xhash_03.c @@ -22,7 +22,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" #include "newmalloc.h" /** Tested module. **/ diff --git a/centrallix-lib/tests/test_xhash_04.c b/centrallix-lib/tests/test_xhash_04.c index e7f40b835..1432822c3 100644 --- a/centrallix-lib/tests/test_xhash_04.c +++ b/centrallix-lib/tests/test_xhash_04.c @@ -19,7 +19,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "xhash.h" diff --git a/centrallix-lib/tests/test_xhash_05.c b/centrallix-lib/tests/test_xhash_05.c index 14830ef08..f66da04de 100644 --- a/centrallix-lib/tests/test_xhash_05.c +++ b/centrallix-lib/tests/test_xhash_05.c @@ -19,7 +19,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "xhash.h" From 0062ae8eef429b266d76d5ac76ba3b8594c1ffed Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 16 Oct 2026 17:05:55 -0600 Subject: [PATCH 349/361] Add comments to reduce confusion. --- centrallix-lib/tests/t_driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/tests/t_driver.c b/centrallix-lib/tests/t_driver.c index 1c969cbbb..8d748d90d 100644 --- a/centrallix-lib/tests/t_driver.c +++ b/centrallix-lib/tests/t_driver.c @@ -91,7 +91,7 @@ start(void* v) int precision = 3; unsigned long long factor = 1; double scaled = ops_per_second; - while (scaled >= 10.0) + while (scaled >= 10.0) /* Scale large numbers down. */ { scaled /= 10.0; if (precision > 0) @@ -99,7 +99,7 @@ start(void* v) else factor *= 10; } - while (scaled > 0.0 && scaled < 1.0) + while (scaled > 0.0 && scaled < 1.0) /* Scale small numbers up. */ { scaled *= 10.0; precision++; From fb302b0d89d49a8762eff4eff2990c28a2dbfc11 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 16 Oct 2026 17:06:01 -0600 Subject: [PATCH 350/361] Improve spacing. --- centrallix-lib/tests/t_utils.h | 45 +++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/centrallix-lib/tests/t_utils.h b/centrallix-lib/tests/t_utils.h index ed171f8eb..bf76ab511 100644 --- a/centrallix-lib/tests/t_utils.h +++ b/centrallix-lib/tests/t_utils.h @@ -54,10 +54,11 @@ #define ASSERT_TRUE(v) \ ({ \ const bool success = !!(v); \ - if (UNLIKELY(!success)) fprintf(stderr, \ - " > Expected %s to be true at %s:%d\n", \ - #v, __FILE__, __LINE__ \ - ); \ + if (UNLIKELY(!success)) \ + fprintf(stderr, \ + " > Expected %s to be true at %s:%d\n", \ + #v, __FILE__, __LINE__ \ + ); \ success; \ }) @@ -75,10 +76,11 @@ __typeof__ (v1) _v1 = (v1); \ __typeof__ (v2) _v2 = (v2); \ const bool success = (_v1 == _v2); \ - if (UNLIKELY(!success)) fprintf(stderr, \ - " > Expected %s ("sp") to equal %s ("sp") at %s:%d\n", \ - #v1, _v1, #v2, _v2, __FILE__, __LINE__ \ - ); \ + if (UNLIKELY(!success)) \ + fprintf(stderr, \ + " > Expected %s ("sp") to equal %s ("sp") at %s:%d\n", \ + #v1, _v1, #v2, _v2, __FILE__, __LINE__ \ + ); \ success; \ }) @@ -95,10 +97,11 @@ const bool both_null = (_str1 == NULL && _str2 == NULL); \ const bool both_eql = (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ const bool success = (both_null || both_eql); \ - if (UNLIKELY(!success)) fprintf(stderr, \ - " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", \ - #str1, _str1, #str2, _str2, __FILE__, __LINE__ \ - ); \ + if (UNLIKELY(!success)) \ + fprintf(stderr, \ + " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", \ + #str1, _str1, #str2, _str2, __FILE__, __LINE__ \ + ); \ success; \ }) @@ -153,10 +156,11 @@ __typeof__ (min_v) _min = (min_v); \ __typeof__ (max_v) _max = (max_v); \ const bool success = (_min <= _v && _v <= _max); \ - if (UNLIKELY(!success)) fprintf(stderr, \ - " > Expected %s ("sp") to be in the range %s ("sp") - %s ("sp") at %s:%d\n", \ - #v, _v, #min_v, _min, #max_v, _max, __FILE__, __LINE__ \ - ); \ + if (UNLIKELY(!success)) \ + fprintf(stderr, \ + " > Expected %s ("sp") to be in the range %s ("sp") - %s ("sp") at %s:%d\n", \ + #v, _v, #min_v, _min, #max_v, _max, __FILE__, __LINE__ \ + ); \ success; \ }) @@ -170,10 +174,11 @@ ({ \ __typeof__ (ptr) _ptr = (ptr); \ const bool success = (_ptr != NULL); \ - if (UNLIKELY(!success)) fprintf(stderr, \ - " > Expected %s (%p) to be non null at %s:%d\n", \ - #ptr, _ptr, __FILE__, __LINE__ \ - ); \ + if (UNLIKELY(!success)) \ + fprintf(stderr, \ + " > Expected %s (%p) to be non null at %s:%d\n", \ + #ptr, _ptr, __FILE__, __LINE__ \ + ); \ success; \ }) From ff98431bbea88829764ae5c8ceb5fb5a84642110 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 16 Oct 2026 17:27:15 -0600 Subject: [PATCH 351/361] Refactor t_utils.h macros into functions with t_utils.c for readability. --- centrallix-lib/Makefile.in | 4 +- centrallix-lib/tests/t_utils.c | 184 +++++++++++++++++++++++++ centrallix-lib/tests/t_utils.h | 121 ++++------------ centrallix-lib/tests/test_t_utils_00.c | 6 +- 4 files changed, 212 insertions(+), 103 deletions(-) create mode 100644 centrallix-lib/tests/t_utils.c diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index 1977ab5f4..105ff0c91 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -170,8 +170,8 @@ install: all @echo "***************************************************************" @echo "" -%.bin: %.c tests/t_driver.c tests/t_utils.h libCentrallix.a - $(CC) $(TCFLAGS) tests/t_driver.c $< libCentrallix.a -o $@ ${LIBS} +%.bin: %.c tests/t_driver.c tests/t_utils.c tests/t_utils.h libCentrallix.a + $(CC) $(TCFLAGS) tests/t_driver.c tests/t_utils.c $< libCentrallix.a -o $@ ${LIBS} ## Runs the selected tests $(TESTJOBS) at a time. Output is captured per test ## rather than written straight to the terminal, then printed in listing order diff --git a/centrallix-lib/tests/t_utils.c b/centrallix-lib/tests/t_utils.c new file mode 100644 index 000000000..6f58a4eab --- /dev/null +++ b/centrallix-lib/tests/t_utils.c @@ -0,0 +1,184 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Base Library */ +/* */ +/* Copyright (C) 2026 LightSys Technology Services, Inc. */ +/* */ +/* You may use these files and this library under the terms of the */ +/* GNU Lesser General Public License, Version 2.1, contained in the */ +/* included file "COPYING". */ +/* */ +/* Module: t_utils.c */ +/* Author: Israel Fuller */ +/* Creation: October 16th, 2026 */ +/* Description: Implementations of the functions in t_utils.h. */ +/************************************************************************/ + +#include +#include +#include +#include + +#include "expect.h" +#include "range.h" +#include "t_utils.h" + +/*** Implement ASSERT_TRUE(). + *** + *** @param success Whether the value was true. + *** @param expr The source text of the value. + *** @param file The file of the assertion. + *** @param line The line of the assertion. + *** @returns success. + ***/ +bool +t_i_assertTrue(bool success, const char* expr, const char* file, int line) + { + if (UNLIKELY(!success)) + fprintf(stderr, + " > Expected %s to be true at %s:%d\n", + expr, file, line + ); + + return success; + } + +/*** Implement ASSERT_EQL() and ASSERT_RANGE(), which compare in the macro + *** to keep the types of their values. + *** + *** @param success Whether the comparison passed. + *** @param fmt The message to print on failure. + *** @param ... The arguments for fmt. + *** @returns success. + ***/ +bool +t_i_assertFmt(bool success, const char* fmt, ...) + { + va_list args; + + if (UNLIKELY(!success)) + { + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + } + + return success; + } + +/*** Implement ASSERT_STR_EQL(). + *** + *** @param str1 The first string. + *** @param str2 The second string. + *** @param expr1 The source text of the first string. + *** @param expr2 The source text of the second string. + *** @param file The file of the assertion. + *** @param line The line of the assertion. + *** @returns true if successful, false otherwise. + ***/ +bool +t_i_assertStrEql(const char* str1, const char* str2, const char* expr1, const char* expr2, const char* file, int line) + { + const bool both_null = (str1 == NULL && str2 == NULL); + const bool both_eql = (str1 != NULL && str2 != NULL && strcmp(str1, str2) == 0); + const bool success = (both_null || both_eql); + + if (UNLIKELY(!success)) + fprintf(stderr, + " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", + expr1, str1, expr2, str2, file, line + ); + + return success; + } + +/*** Implement ASSERT_STR_EQL_N(). + *** + *** @param str1 The first string. + *** @param str2 The second string. + *** @param len The length of strings to check (aka. N). + *** @param expr1 The source text of the first string. + *** @param expr2 The source text of the second string. + *** @param file The file of the assertion. + *** @param line The line of the assertion. + *** @returns true if successful, false otherwise. + ***/ +bool +t_i_assertStrEqlN(const char* str1, const char* str2, size_t len, const char* expr1, const char* expr2, const char* file, int line) + { + const bool both_null = (str1 == NULL && str2 == NULL); + const bool both_eql = (str1 != NULL && str2 != NULL && memcmp(str1, str2, len) == 0); + const bool success = (both_null || both_eql); + + if (UNLIKELY(!success)) + { + const size_t buf_len = max(7, len + 1); + char tmp1[buf_len]; + if (str1 == NULL) strcpy(tmp1, "(null)"); + else copyReplaceNulls(tmp1, str1, len); + + char tmp2[buf_len]; + if (str2 == NULL) strcpy(tmp2, "(null)"); + else copyReplaceNulls(tmp2, str2, len); + + fprintf(stderr, + " > Expected %s (\"%s\") to equal %s (\"%s\") where '_' is a null-terminator, at %s:%d\n", + expr1, tmp1, expr2, tmp2, file, line + ); + } + + return success; + } + +/*** Implement ASSERT_NOT_NULL(). + *** + *** @param ptr The pointer. + *** @param expr The source text of the pointer. + *** @param file The file of the assertion. + *** @param line The line of the assertion. + *** @returns true if successful, false otherwise. + ***/ +bool +t_i_assertNotNull(const void* ptr, const char* expr, const char* file, int line) + { + const bool success = (ptr != NULL); + + if (UNLIKELY(!success)) + fprintf(stderr, + " > Expected %s (%p) to be non null at %s:%d\n", + expr, ptr, file, line + ); + + return success; + } + +void +copyReplaceNulls(char* dest, const char* src, size_t n) + { + for (size_t i = 0lu; i < n; i++) + dest[i] = (src[i] == '\0') ? '_' : src[i]; + dest[n] = '\0'; + + return; + } + +long long +loopTest(bool (*test_fn)(void)) + { + long long result = 0ll; + Timer iter_timer_buf; + Timer *iter_timer = timerStart(timerInit(&iter_timer_buf)); + + while (timerGet(iter_timer) < MIN_TEST_SECONDS) + { + result++; + if (!test_fn()) + { + result = -1; + break; + } + } + timerDeInit(iter_timer); + + return result; + } diff --git a/centrallix-lib/tests/t_utils.h b/centrallix-lib/tests/t_utils.h index bf76ab511..d9064b8ca 100644 --- a/centrallix-lib/tests/t_utils.h +++ b/centrallix-lib/tests/t_utils.h @@ -46,21 +46,20 @@ ***/ #define MIN_TEST_SECONDS 0.2 +/** Assertion functions, defined in t_utils.c. **/ +bool t_i_assertTrue(bool success, const char* expr, const char* file, int line); +bool t_i_assertFmt(bool success, const char* fmt, ...) __attribute__ ((format(printf, 2, 3))); +bool t_i_assertStrEql(const char* str1, const char* str2, const char* expr1, const char* expr2, const char* file, int line); +bool t_i_assertStrEqlN(const char* str1, const char* str2, size_t len, const char* expr1, const char* expr2, const char* file, int line); +bool t_i_assertNotNull(const void* ptr, const char* expr, const char* file, int line); + /*** Assert a value to be true. *** *** @param v The value. *** @returns true if successful, false otherwise. ***/ #define ASSERT_TRUE(v) \ - ({ \ - const bool success = !!(v); \ - if (UNLIKELY(!success)) \ - fprintf(stderr, \ - " > Expected %s to be true at %s:%d\n", \ - #v, __FILE__, __LINE__ \ - ); \ - success; \ - }) + t_i_assertTrue(!!(v), #v, __FILE__, __LINE__) /*** Assert two values to be equal. *** @@ -75,13 +74,10 @@ ({ \ __typeof__ (v1) _v1 = (v1); \ __typeof__ (v2) _v2 = (v2); \ - const bool success = (_v1 == _v2); \ - if (UNLIKELY(!success)) \ - fprintf(stderr, \ - " > Expected %s ("sp") to equal %s ("sp") at %s:%d\n", \ - #v1, _v1, #v2, _v2, __FILE__, __LINE__ \ - ); \ - success; \ + t_i_assertFmt((_v1 == _v2), \ + " > Expected %s ("sp") to equal %s ("sp") at %s:%d\n", \ + #v1, _v1, #v2, _v2, __FILE__, __LINE__ \ + ); \ }) /*** Assert two strings to be equal. @@ -91,19 +87,7 @@ *** @returns true if successful, false otherwise. ***/ #define ASSERT_STR_EQL(str1, str2) \ - ({ \ - char* _str1 = (str1); \ - char* _str2 = (str2); \ - const bool both_null = (_str1 == NULL && _str2 == NULL); \ - const bool both_eql = (_str1 != NULL && _str2 != NULL && strcmp(_str1, _str2) == 0); \ - const bool success = (both_null || both_eql); \ - if (UNLIKELY(!success)) \ - fprintf(stderr, \ - " > Expected %s (\"%s\") to equal %s (\"%s\") at %s:%d\n", \ - #str1, _str1, #str2, _str2, __FILE__, __LINE__ \ - ); \ - success; \ - }) + t_i_assertStrEql((str1), (str2), #str1, #str2, __FILE__, __LINE__) /*** Assert two strings to be equal, including null characters. *** Warning: Can read off the end of strings. @@ -114,31 +98,7 @@ *** @returns true if successful, false otherwise. ***/ #define ASSERT_STR_EQL_N(str1, str2, len) \ - ({ \ - const char* _str1 = (str1); \ - const char* _str2 = (str2); \ - const size_t _len = (size_t)(len); \ - const bool both_null = (_str1 == NULL && _str2 == NULL); \ - const bool both_eql = (_str1 != NULL && _str2 != NULL && memcmp(_str1, _str2, _len) == 0); \ - const bool success = (both_null || both_eql); \ - if (UNLIKELY(!success))\ - { \ - const size_t buf_len = max(7, _len + 1); \ - char _tmp1[buf_len]; \ - if (_str1 == NULL) strcpy(_tmp1, "(null)"); \ - else STR_COPY_REPLACE_NULLS(_tmp1, _str1, _len); \ - \ - char _tmp2[buf_len]; \ - if (_str2 == NULL) strcpy(_tmp2, "(null)"); \ - else STR_COPY_REPLACE_NULLS(_tmp2, _str2, _len); \ - \ - fprintf(stderr, \ - " > Expected %s (\"%s\") to equal %s (\"%s\") where '_' is a null-terminator, at %s:%d\n", \ - #str1, _tmp1, #str2, _tmp2, __FILE__, __LINE__ \ - ); \ - } \ - success; \ - }) + t_i_assertStrEqlN((str1), (str2), (size_t)(len), #str1, #str2, __FILE__, __LINE__) /*** Assert a value to fall within a range. *** @@ -155,32 +115,20 @@ __typeof__ (v) _v = (v); \ __typeof__ (min_v) _min = (min_v); \ __typeof__ (max_v) _max = (max_v); \ - const bool success = (_min <= _v && _v <= _max); \ - if (UNLIKELY(!success)) \ - fprintf(stderr, \ - " > Expected %s ("sp") to be in the range %s ("sp") - %s ("sp") at %s:%d\n", \ - #v, _v, #min_v, _min, #max_v, _max, __FILE__, __LINE__ \ - ); \ - success; \ + t_i_assertFmt((_min <= _v && _v <= _max), \ + " > Expected %s ("sp") to be in the range %s ("sp") - %s ("sp") at %s:%d\n", \ + #v, _v, #min_v, _min, #max_v, _max, __FILE__, __LINE__ \ + ); \ }) -/*** Syntactic sugar to assert a pointer to be non null in a clearer, more - *** concise way. +/*** Assert that a value is NULL. While ASSERT_EQL(ptr, NULL) also does this, + *** ASSERT_NOT_NULL() is clearer to read and gives a distinct error message. *** *** @param ptr The pointer. *** @returns true if successful, false otherwise. ***/ #define ASSERT_NOT_NULL(ptr) \ - ({ \ - __typeof__ (ptr) _ptr = (ptr); \ - const bool success = (_ptr != NULL); \ - if (UNLIKELY(!success)) \ - fprintf(stderr, \ - " > Expected %s (%p) to be non null at %s:%d\n", \ - #ptr, _ptr, __FILE__, __LINE__ \ - ); \ - success; \ - }) + t_i_assertNotNull((ptr), #ptr, __FILE__, __LINE__) /*** Copy n characters from src to dest, including null-terminators, which are *** replaced with the '_' character. @@ -190,32 +138,9 @@ *** include null-terminator characters ('\0'). *** @param n The number of characters to copy. ***/ -#define STR_COPY_REPLACE_NULLS(dest, src, n) \ - { \ - char* _dest = (dest); \ - const char* _src = (src); \ - const size_t _n = (size_t)(n); \ - for (size_t i = 0lu; i < _n; i++) \ - _dest[i] = (_src[i] == '\0') ? '_' : _src[i]; \ - _dest[_n] = '\0'; \ - } +void copyReplaceNulls(char* dest, const char* src, size_t n); /** Repeat the test as many times as possible within a set time window. **/ -#define loopTest(test_fn) \ - ({ \ - long long result = 0ll; \ - Timer iter_timer_buf, *iter_timer = timerStart(timerInit(&iter_timer_buf)); \ - while (timerGet(iter_timer) < MIN_TEST_SECONDS) \ - { \ - result++; \ - if (!test_fn()) \ - { \ - result = -1; \ - break; \ - } \ - } \ - timerDeInit(iter_timer); \ - result; \ - }) +long long loopTest(bool (*test_fn)(void)); #endif diff --git a/centrallix-lib/tests/test_t_utils_00.c b/centrallix-lib/tests/test_t_utils_00.c index 26b584e6f..ef10e5602 100644 --- a/centrallix-lib/tests/test_t_utils_00.c +++ b/centrallix-lib/tests/test_t_utils_00.c @@ -153,12 +153,12 @@ static bool doTest(void) success &= ASSERT_TRUE(!result); success &= ASSERT_TRUE(msg[0] != '\0'); - /** STR_COPY_REPLACE_NULLS() writes n bytes and a terminator. **/ + /** copyReplaceNulls() writes n bytes and a terminator. **/ memset(copy, 'x', sizeof(copy)); - STR_COPY_REPLACE_NULLS(copy, ab_c, 4); + copyReplaceNulls(copy, ab_c, 4); success &= ASSERT_STR_EQL_N(copy, "ab_c\0xxx", COPY_SIZE); memset(copy, 'x', sizeof(copy)); - STR_COPY_REPLACE_NULLS(copy, ab_c, 0); + copyReplaceNulls(copy, ab_c, 0); success &= ASSERT_STR_EQL_N(copy, "\0xxxxxxx", COPY_SIZE); return success; From 5d36a425cf32e7678721d74f4fb716d7bd385db8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 16 Oct 2026 17:28:14 -0600 Subject: [PATCH 352/361] Reorder ASSERT_NOT_NULL() higher up in the file. --- centrallix-lib/tests/t_utils.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/centrallix-lib/tests/t_utils.h b/centrallix-lib/tests/t_utils.h index d9064b8ca..9eabc2a4f 100644 --- a/centrallix-lib/tests/t_utils.h +++ b/centrallix-lib/tests/t_utils.h @@ -61,6 +61,15 @@ bool t_i_assertNotNull(const void* ptr, const char* expr, const char* file, int #define ASSERT_TRUE(v) \ t_i_assertTrue(!!(v), #v, __FILE__, __LINE__) +/*** Assert that a value is NULL. While ASSERT_EQL(ptr, NULL) also does this, + *** ASSERT_NOT_NULL() is clearer to read and gives a distinct error message. + *** + *** @param ptr The pointer. + *** @returns true if successful, false otherwise. + ***/ +#define ASSERT_NOT_NULL(ptr) \ + t_i_assertNotNull((ptr), #ptr, __FILE__, __LINE__) + /*** Assert two values to be equal. *** *** @param v1 The first value. @@ -121,15 +130,6 @@ bool t_i_assertNotNull(const void* ptr, const char* expr, const char* file, int ); \ }) -/*** Assert that a value is NULL. While ASSERT_EQL(ptr, NULL) also does this, - *** ASSERT_NOT_NULL() is clearer to read and gives a distinct error message. - *** - *** @param ptr The pointer. - *** @returns true if successful, false otherwise. - ***/ -#define ASSERT_NOT_NULL(ptr) \ - t_i_assertNotNull((ptr), #ptr, __FILE__, __LINE__) - /*** Copy n characters from src to dest, including null-terminators, which are *** replaced with the '_' character. *** From c7043416e8ae84feabcbd57e5d7331e3144a05e6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 12:59:12 -0600 Subject: [PATCH 353/361] Fix typos in comments and docs. --- centrallix-lib/include/clusters.h | 2 +- centrallix-lib/src/clusters.c | 4 ++-- centrallix-sysdoc/Libraries/mtask.md | 2 +- centrallix-sysdoc/Libraries/xarray.md | 4 ++-- centrallix-sysdoc/OSDriver_Authoring.md | 22 +++++++++++----------- centrallix-sysdoc/string_similarity.md | 6 +++--- centrallix/osdrivers/objdrv_cluster.c | 12 ++++-------- 7 files changed, 24 insertions(+), 28 deletions(-) diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h index 6630ea2e4..000b22ce8 100644 --- a/centrallix-lib/include/clusters.h +++ b/centrallix-lib/include/clusters.h @@ -82,7 +82,7 @@ typedef double* pCentroid; /* Dense centroid. */ *** @param i The index into the provided data for the first element of the pair. *** @param j The index into the provided data for the second element of the pair. *** @param similarity A number from 0 to 1, from a similarity function, showing - *** how similar the pairs are. + *** how similar the pair is. ***/ typedef struct { diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index 3988481d2..fe282124c 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -53,7 +53,7 @@ /** This file has additional documentation in string_similarity.md. **/ -/*** Gets the hash for a pair of ASCII characters, represented by unsigned ints. +/*** Gets the hash for a pair of ASCII characters, represented by unsigned chars. *** Thank you Professor John Delano for this hashing algorithm. *** *** @param c1 The first character in the pair. @@ -206,7 +206,7 @@ caBuildVector(const char* str) char_pairs[i].c1 = chars[i]; char_pairs[i].c2 = chars[i + 1]; - /** Hash the character pair into an index (dimension). **/ + /** Hash the character pair into an index (dimension). **/ /*** Note: The passed value should always be between *** 96 (boundary) and 132 ('9'). ***/ diff --git a/centrallix-sysdoc/Libraries/mtask.md b/centrallix-sysdoc/Libraries/mtask.md index 1fc2595a2..00951cd07 100644 --- a/centrallix-sysdoc/Libraries/mtask.md +++ b/centrallix-sysdoc/Libraries/mtask.md @@ -60,7 +60,7 @@ The `MTASK` module provides simple and easy TCP/IP connectivity. It includes ma pFile netConnectTCP(const char* host_name, const char* service_name, int flags); ``` This function creates a client socket and connects it to a server on a given TCP service/port and host name. It takes the following three parameters: -- `host_name`: The host name or ascii string for the host's ip address. +- `host_name`: The host name or ASCII string for the host's IP address. - `service_name`: The name of the service (from `/etc/services`) or its numeric representation as a string. - `flags`: Normally left 0. diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md index 1922c7fb8..2473ee065 100644 --- a/centrallix-sysdoc/Libraries/xarray.md +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -120,7 +120,7 @@ int xaAddItem(pXArray this, void* item); ``` This function adds an item to the end of the XArray. The item is assumed to be a `void*`, but this function will _not_ follow pointers stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). -This function returns 0 on success, or -1 if an error occurs. +This function returns the index of the added item on success, or -1 if an error occurs. ## xaAddItemSorted() @@ -202,7 +202,7 @@ This function works the same as [`xaClear()`](#xaclear), except that it is sligh ```c int xaCount(pXArray this); ``` -This function returns the number of items in the XArray, or -1 on error. It is equivalent to accessing `XArray->nItems`. +This function returns the number of items in the XArray. It is equivalent to accessing `XArray->nItems`. ## xaInsertBefore() diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index 93dd4d7e1..4bfe62170 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -276,7 +276,7 @@ int xxxInitialize(void) - ⚠️ **Warning**: For compiled drivers, the success/failure of this function is ignored by the caller. However, for drivers loaded as modules, the return value is checked in order to determine whether to keep the module loaded. In either case, `mssError()` should be called for any failure (other than memory allocation failures). - 📖 **Note**: Unlike other functions defined in the driver, each driver author must manually add this call to the start up code, found in the `cxDriverInit()` function in `centrallix.c`. -The initialization function is called when the Centrallix starts up, and should register the driver with the OSML and initialize necessary global variables. It is recommended to place global variables in a single global 'struct' that is named with the driver's prefix in all uppercase. Global variables should **NOT** be accessed from outside the driver. Instead, the driver should define functions to access them, allowing it to abstract details away from other drivers. +The initialization function is called when Centrallix starts up, and should register the driver with the OSML and initialize necessary global variables. It is recommended to place global variables in a single global 'struct' that is named with the driver's prefix in all uppercase. Global variables should **NOT** be accessed from outside the driver. Instead, the driver should define functions to access them, allowing it to abstract details away from other drivers. To register itself with the OSML, the driver should first allocate an ObjDriver structure and initialize its contents: @@ -294,7 +294,7 @@ To initialize this struct, the driver must: - Provide function pointers to implemented functions (see [II Interface](#ii-interface) for a list). #### Name -The `name` field is a 64 character buffer (allowing names up to 63 characters, with a null terminator). It usually follows the format of the driver abbreviation prefix (in all uppercase), followed by a dash, followed by a descriptive name for the driver. +The `Name` field is a 64 character buffer (allowing names up to 63 characters, with a null terminator). It usually follows the format of the driver abbreviation prefix (in all uppercase), followed by a dash, followed by a descriptive name for the driver. For example: ```c @@ -390,7 +390,7 @@ While driver instance structures may vary, some fields are common in most driver | Field | Type | Description | |-----------|---------|-------------------------------------------------| -| inf->Obj | pObject | A copy of the `obj` pointer passed to `Open()`. | +| inf->Obj | pObject | A copy of the `parent` pointer passed to `Open()`. | | inf->Mask | int | The `mask` argument passed to `Open()`. | | inf->Node | pSnNode | A pointer to the node object. | @@ -449,7 +449,7 @@ The `Close()` function should return 0 on success or -1 on failure. ```c int xxxCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); ``` -The `Create()` function is used to create a new object, and uses the same parameters and return value as `Open()` (documented in detail above). This often means adding a new file to the file system to represent the object. Many drivers do not implement this and recommend that driver end-users create files using a standard text editor or programmatically using more general means, such as general structure file generation. If implemented, this function frequently requires very similar path parsing functionality to `Open()`. +The `Create()` function is used to create a new object, and uses the same parameters as `Open()` (documented in detail above), returning 0 on success or -1 on failure. This often means adding a new file to the file system to represent the object. Many drivers do not implement this and recommend that driver end-users create files using a standard text editor or programmatically using more general means, such as general structure file generation. If implemented, this function frequently requires very similar path parsing functionality to `Open()`. - 📖 **Note**: For many drivers, the `Create()` function calls the driver's `Open()` function with `O_CREAT`, then calls its `Close()` function, although some drivers may manage this differently. @@ -543,7 +543,7 @@ The `OpenQuery()` function returns a `void*` for the query instance struct, whic ```c int xxxQueryDelete(void* qy_v, pObjTrxTree* oxt); ``` -Deletes results in the query result set, optionally matching a certain criteria. `QueryDelete()` is passed two parameters: +Deletes results in the query result set, optionally matching certain criteria. `QueryDelete()` is passed two parameters: | Parameter | Type | Description | --------- | ------------- | ------------ @@ -635,7 +635,7 @@ The `last_modification : DATA_T_DATETIME` attribute is a sixth, optional attribu ```c int xxxGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); ``` -The `GetAttrType()` function returns DATA_T_xxx value for the datatype of the requested attribute. It takes three parameters: +The `GetAttrType()` function returns the DATA_T_xxx value for the datatype of the requested attribute. It takes three parameters: | Parameter | Type | Description | --------- | ------------- | ------------ @@ -898,7 +898,7 @@ Although using the structure file format may be complex, it allows significant f Structure files are accessed via the st_node (SN) and stparse (SP) modules. The st_node module loads and saves the structure file hierarchies as a whole. It also manages caching to reduce disk activity and eliminate repeated parsing of the same file. The stparse module provides access to the individual attributes and groups of attributes within a node structure file. -For example, if two sessions open two files, `/test1.rpt` and `/test2.rpt` the st_node module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be reparsed. The file will be reparsed if its timestamp changes. +For example, if two sessions open two files, `/test1.rpt` and `/test2.rpt`, the st_node module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be reparsed. The file will be reparsed if its timestamp changes. If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then st_node prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the st_node module to re-read the structure file defining the node object. Otherwise, the st_node module keeps track of the timestamp, and if it changes, the node object is re-read and reparsed. @@ -1143,7 +1143,7 @@ pExpression exp = expPodToExpression(POD(&value), DATA_T_INTEGER, NULL); This function returns a pointer to the expression if successful, or `NULL` if an error occurs. -- 📖 **Note**: There is also a `expPtodToExpression()` function for working with the `Ptod` (pointer to object data) struct. +- 📖 **Note**: There is also an `expPtodToExpression()` function for working with the `Ptod` (pointer to object data) struct. ### expExpressionToPod() ```c @@ -1151,9 +1151,9 @@ int expExpressionToPod(pExpression this, int type, pObjData pod); ``` This function reverses the functionality of [`expPodToExpression()`](#exppodtoexpression) to instead read data from an evaluated expression. Be careful, this does not evaluate the expression if it is not already evaluated. This function returns 0 if successful, 1 if the expression is NULL, or -1 if an error occurs. -- 📖 **Note**: The source code for this function can be a useful reference when interacting with expression structures, such as when implementing the c code for an exp_function. +- 📖 **Note**: The source code for this function can be a useful reference when interacting with expression structures, such as when implementing the C code for an exp_function. -- 📖 **Note**: There is also a `expExpressionToPtod()` function for working with the `Ptod` (pointer to object data) struct. +- 📖 **Note**: There is also an `expExpressionToPtod()` function for working with the `Ptod` (pointer to object data) struct. ### expDuplicateExpression() ```c @@ -1208,7 +1208,7 @@ int expRemoveParamFromList(pParamObjects this, char* name); ``` This function removes a parameter object from the list, returning 0 if successful and -1 if an error occurs. -- 📖 **Note**: There is also a `expRemoveParamFromListById()` function. +- 📖 **Note**: There is also an `expRemoveParamFromListById()` function. ### expSetParamFunctions() ```c diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md index 0629c4d83..ccb471f98 100644 --- a/centrallix-sysdoc/string_similarity.md +++ b/centrallix-sysdoc/string_similarity.md @@ -96,7 +96,7 @@ As noted above, the vast majority of elements in a vector generated by a typical **Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. -**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `caSparseLen()` function can be used to do this. Also, the `caBuildVector()` and `caFreeVector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. +**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `caSparseLen()` function can be used to do this. Also, the `caBuildVector()` and `caFreeVector()` functions use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. ### Computing Similarity Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their corresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `caCosCompare()` function. @@ -153,7 +153,7 @@ Running the entire search multiple times may allow some of these to be found bec If the clustering could be expanded with an additional step that makes clusters larger, adding elements from other clusters to them, this might effectively mitigate the issue. It may also allow developers to use larger numbers of clusters, improving performance as well as accuracy. Further research is needed to verify the effectiveness of this approach before an implementation is written. ### Implement Missing Algorithms -Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-medoids-clustering), and [DBScan](#db-scan)) above are proposed but lack an implementation. They may be effective and useful, however, to reduce development time, they have not yet been implemented. +Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-medoids-clustering), and [DBScan](#db-scan-clustering)) above are proposed but lack an implementation. They may be effective and useful; however, to reduce development time, they have not yet been implemented. ### Upgrade Other Duplicate Detection Systems When a new record is entered, a quick scan is run to check if it might be a duplicate. There is also a button in the UI for a record that lets you run a duplicate check. These systems could also be upgraded using the new algorithms and strategies developed for general duplicate detection. @@ -161,4 +161,4 @@ When a new record is entered, a quick scan is run to check if it might be a dupl ### Fix Known Issues - The cluster driver often fails to open the structure file if it was modified since the last time the path was opened. Opening a different path (including the root path, even though it does not support queries) fixes this issue. This is either a bug in the st_node caching or in the cluster driver's usage of stparse. - The cluster driver does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to manually invalidate caches when data is changed, or use a fresh object system instance. -- It is not possible to open cluster or search entries, they can only be retrieved by querying. +- It is not possible to open cluster or search entries; they can only be retrieved by querying. diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 2019d5174..424dc584f 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -959,7 +959,8 @@ cluster_i_parseSourceData(pStructInf inf, pParamObjects param_list, char* path) /*** Knowing that an error has started and the error stack must be *** cleared is sometimes not possible in this function, so clear it - *** while we know no errors are happening. **/ + *** while we know no errors are happening. + ***/ mssClearError(); /** Allocate SourceData. **/ @@ -2529,12 +2530,7 @@ cluster_i_freeNodeData(pNodeData node_data) node_data->SearchDatas = NULL; } - /** Free data source, if one exists. **/ - /*** Note: SourceData is freed last since other free functions may need to - *** access information from this structure when freeing data. - *** (For example, nVector which is used to determine the size of the - *** label struct in each cluster.) - ***/ + /** Release data source, if one exists. **/ if (node_data->SourceData != NULL) { /*** This data is cached, so we should NOT free it! The cache owns @@ -3362,7 +3358,7 @@ cluster_i_computeClusterData(pClusterData cluster_data, pNodeData node_data) *** @attention - Promises that mssError() will be invoked on failure. *** *** @param search_data The pSearchData whose attributes should be computed. - *** @param node_data The current pNodeData, used to get vectors to cluster. + *** @param node_data The current pNodeData, used to compute the source cluster. *** @returns 0 if successful, or *** -1 on failure. ***/ From e7197642fadb41d35b9701a2d400827ee8c86457 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 12:59:32 -0600 Subject: [PATCH 354/361] Fix issues caused by merging master. --- centrallix-doc/Widgets/widgets.xml | 4 ++++ centrallix/include/obj.h | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index b6b50afde..6d283f5f2 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -3613,6 +3613,8 @@ myTabControl "widget/tab" Whether to permit the user to select rows in the table. Default "yes". + Whether clicking a column header sorts the table by that column. Acts as the default for all columns; individual columns may override via their own "allow_sorting" property. Default "yes". + A background image for the table. This "shows through" between table cells. A color, RGB or named, to be used between table cells and rows.If neither bgcolor nor background are specified, the table is transparent. @@ -3721,6 +3723,8 @@ myTabControl "widget/tab" The alignment of the column: "left" or "right". + Whether clicking this column's header sorts the table by this column. Defaults to the table's "allow_sorting" value (which defaults to "yes"). + It is a pseudonym for the fieldname. The color of the caption_fieldname. diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index 4b01cc862..93bc89d21 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -756,8 +756,6 @@ void obj_internal_OpenCtlToString(pPathname pathinfo, int pathstart, int pathend int obj_internal_PathToText(pPathname pathinfo, int pathend, pXString str); /** objectsystem datatype functions **/ -int objTypeFromStr(const char* str); -char* objTypeToStr(const int type); int objDataToString(pXString dest, int data_type, void* data_ptr, int flags); double objDataToDouble(int data_type, void* data_ptr); int objDataToInteger(int data_type, void* data_ptr, char* format); From ee0a7910e72a39a1ea0f1b84b67a77b925df6c81 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 13:00:07 -0600 Subject: [PATCH 355/361] Fix memory correctness issues. --- centrallix-lib/src/clusters.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c index fe282124c..b3b333829 100644 --- a/centrallix-lib/src/clusters.c +++ b/centrallix-lib/src/clusters.c @@ -927,14 +927,14 @@ caKmeans( /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ const size_t centroids_size = num_clusters * sizeof(pCentroid); centroids = nmMalloc(centroids_size); + if (centroids != NULL) memset(centroids, 0, centroids_size); new_centroids = nmMalloc(centroids_size); + if (new_centroids != NULL) memset(new_centroids, 0, centroids_size); if (centroids == NULL || new_centroids == NULL) { mssError(1, "CA", "nmMalloc(%zu) failed.", centroids_size); goto end; } - memset(centroids, 0, centroids_size); - memset(new_centroids, 0, centroids_size); for (unsigned int i = 0u; i < num_clusters; i++) { centroids[i] = nmMalloc(CENTROID_SIZE); From 69ad2d08cb6418151dc1f13ebb35269d663fbff9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 13:00:36 -0600 Subject: [PATCH 356/361] Fix the log expression function's handling of null parameters. --- centrallix/expression/exp_functions.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index d781882e9..cd71ebb8b 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -3900,7 +3900,19 @@ exp_fn_log(pExpression tree) mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); return -1; } - + + /** Null checks. **/ + for (int i = 0; i < tree->Children.nItems; i++) + { + pExpression arg = tree->Children.Items[i]; + if (arg->Flags & EXPR_F_NULL) + { + tree->DataType = DATA_T_DOUBLE; + tree->Flags |= EXPR_F_NULL; + return 0; + } + } + /** Extract args. **/ double number, base; if (UNLIKELY(exp_fn_i_getNumber(tree->Children.Items[0], &number) != 0)) From 8f11bb265c2ba1d76be3120c034618c31d51bcdf Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 13:02:04 -0600 Subject: [PATCH 357/361] Fix nan being treated as a valid positive or negative double. --- centrallix/expression/exp_functions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index cd71ebb8b..91cd41859 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -245,7 +245,7 @@ exp_fn_i_verifyArg(const char* fn_name, pExpression arg, const ArgExpect* arg_ex case DATA_T_DOUBLE: { double value = arg->Types.Double; - if (arg_expect->Flags & EXP_ARG_NON_NAN && isnan(value)) + if (arg_expect->Flags & (EXP_ARG_NON_NAN | EXP_ARG_POSITIVE | EXP_ARG_NEGATIVE) && isnan(value)) { mssError(1, "EXP", "%s(...): Expects non-nan double but got %g.", From ae6e684075450fe13084b5953aabdc6e3486434d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 13:02:31 -0600 Subject: [PATCH 358/361] Fix and improve error messages. --- centrallix/expression/exp_functions.c | 4 ++-- centrallix/osdrivers/objdrv_cluster.c | 2 +- centrallix/utility/stparse.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 91cd41859..9fbb5b2ec 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -383,7 +383,7 @@ exp_fn_i_verifySchema(const ArgExpect* arg_expects, pExpression tree) else if (UNLIKELY(opt_args > 0)) { /** Required argument follows optional argument (not allowed). **/ - mssError(1, "EXP", "%s(?): Invalid Schema! Required argument #%u after optional argument.", tree->Name, i); + mssError(1, "EXP", "%s(?): Invalid Schema! Required argument #%u after optional argument.", tree->Name, i + 1); return -1; } else @@ -3857,7 +3857,7 @@ exp_fn_i_doMath(pExpression tree, double (*math)(), int arg_num) { if (exp_fn_i_getNumber(tree->Children.Items[i], &(n[i])) != 0) { - mssError(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); + mssError(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i + 1); return -1; } } diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c index 424dc584f..a92a8f6ca 100644 --- a/centrallix/osdrivers/objdrv_cluster.c +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -1665,7 +1665,7 @@ cluster_i_parseSearchData(pStructInf inf, pNodeData node_data) { /** Print error. **/ mssError(1, "Cluster", - "Could not find cluster \"%s\" for search \"%s\".", + "Failed to find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name ); diff --git a/centrallix/utility/stparse.c b/centrallix/utility/stparse.c index b7f54005b..81df8a99a 100644 --- a/centrallix/utility/stparse.c +++ b/centrallix/utility/stparse.c @@ -668,9 +668,9 @@ stGetAttrValueOSML(pStructInf this, int type, pObjData pod, int nval, pObjSessio end: if (UNLIKELY(rval < 0)) { - mssError(1, "ST", + mssError(0, "ST", "Failed to parse attribute \"%s\" from group \"%s\".", - this->Name, this->Parent->Name + this->Name, (this->Parent != NULL) ? this->Parent->Name : "(none)" ); } From d53bf73bc12a8041c81826f3f3b6d7d006a26049 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 13:02:41 -0600 Subject: [PATCH 359/361] Fix a type in test_utils.h. --- centrallix-lib/tests/test_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-lib/tests/test_utils.h b/centrallix-lib/tests/test_utils.h index 74159c0d7..0528494f7 100644 --- a/centrallix-lib/tests/test_utils.h +++ b/centrallix-lib/tests/test_utils.h @@ -147,7 +147,7 @@ ({ \ pVector _v1 = (v1); \ pVector _v2 = (v2); \ - int success = caEql(_v1, _v2); \ + const bool success = caEql(_v1, _v2); \ if (!success) \ { \ printf(" > Expected %s (1.) to equal %s (2.) at %s:%d, but got:\n", #v1, #v2, __FILE__, __LINE__); \ From 73e7b53ee0f48250aee6f45ec4d3435354a03ed3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 23 Oct 2026 13:05:26 -0600 Subject: [PATCH 360/361] Update lib tests because of changes on add-lib-tests. --- centrallix-lib/tests/test_clusters_00.c | 2 +- centrallix-lib/tests/test_clusters_01.c | 2 +- centrallix-lib/tests/test_clusters_02.c | 2 +- centrallix-lib/tests/test_clusters_03.c | 2 +- centrallix-lib/tests/test_clusters_04.c | 2 +- centrallix-lib/tests/test_clusters_05.c | 2 +- centrallix-lib/tests/test_clusters_06.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/centrallix-lib/tests/test_clusters_00.c b/centrallix-lib/tests/test_clusters_00.c index 8922baeb3..c5a5b38ac 100644 --- a/centrallix-lib/tests/test_clusters_00.c +++ b/centrallix-lib/tests/test_clusters_00.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "clusters.h" diff --git a/centrallix-lib/tests/test_clusters_01.c b/centrallix-lib/tests/test_clusters_01.c index 2acee0142..c73d9bad6 100644 --- a/centrallix-lib/tests/test_clusters_01.c +++ b/centrallix-lib/tests/test_clusters_01.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "clusters.h" diff --git a/centrallix-lib/tests/test_clusters_02.c b/centrallix-lib/tests/test_clusters_02.c index 3b638cb8f..56228d638 100644 --- a/centrallix-lib/tests/test_clusters_02.c +++ b/centrallix-lib/tests/test_clusters_02.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" /** Tested module. **/ #include "clusters.h" diff --git a/centrallix-lib/tests/test_clusters_03.c b/centrallix-lib/tests/test_clusters_03.c index 4f3b59cc2..dd9f8fd17 100644 --- a/centrallix-lib/tests/test_clusters_03.c +++ b/centrallix-lib/tests/test_clusters_03.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" #include "warn.h" /** Tested module. **/ diff --git a/centrallix-lib/tests/test_clusters_04.c b/centrallix-lib/tests/test_clusters_04.c index 6ea63b5c3..16ba41164 100644 --- a/centrallix-lib/tests/test_clusters_04.c +++ b/centrallix-lib/tests/test_clusters_04.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" #include "warn.h" /** Tested module. **/ diff --git a/centrallix-lib/tests/test_clusters_05.c b/centrallix-lib/tests/test_clusters_05.c index a224aea1d..dc1dfc7e9 100644 --- a/centrallix-lib/tests/test_clusters_05.c +++ b/centrallix-lib/tests/test_clusters_05.c @@ -21,7 +21,7 @@ #include /** Test dependencies. **/ -#include "test_utils.h" +#include "t_utils.h" #include "warn.h" #include "xhash.h" diff --git a/centrallix-lib/tests/test_clusters_06.c b/centrallix-lib/tests/test_clusters_06.c index 2a2df3f3d..55e799b3c 100644 --- a/centrallix-lib/tests/test_clusters_06.c +++ b/centrallix-lib/tests/test_clusters_06.c @@ -22,7 +22,7 @@ /** Test dependencies. **/ #include "newmalloc.h" -#include "test_utils.h" +#include "t_utils.h" #include "warn.h" /** Tested module. **/ From ef0eecb5fc0eac037a6e374aba513a12923dcaea Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 2 Nov 2026 10:50:42 -0700 Subject: [PATCH 361/361] Clarify a detail about Upserts that caused bugs in my code. --- centrallix-sysdoc/Upsert.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/centrallix-sysdoc/Upsert.md b/centrallix-sysdoc/Upsert.md index d53674fb4..66b5dd8b7 100644 --- a/centrallix-sysdoc/Upsert.md +++ b/centrallix-sysdoc/Upsert.md @@ -25,6 +25,10 @@ The ON DUPLICATE expressions may take on the form of SELECT items; that is they In the above instance, key_id refers to the attribute in the data source being inserted into. The values unique_code and last_name refer to attributes from the SELECT statement. +In the UPDATE SET expressions, an unqualified attribute refers to the existing object being updated. To use a value from the SELECT's sources, qualify it with the source's name, as in: + + UPDATE SET :amount = :new_data:amount + ## Algorithm - A. For each ON DUPLICATE expression, 1. Compile the expression into an expression tree.