#if defined(__GNUC__) && !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#if defined(__linux__)
#define qsort_r_common(base, n, sz, context, compare) qsort_r((base), (n), (sz), (compare), (context))
#else
#define qsort_r_common qsort_r
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "config.h"
#include "ktable.h"
#if !defined(KTABLE_DEBUG)
#define KTABLE_DEBUG 0
#endif
#define LOG(msg) \
do { if (KTABLE_DEBUG) fprintf(stderr, "%s:%d:%s(): %s", __FILE__, \
__LINE__, __func__, msg); \
fflush(stderr); } while (0)
#define LOGf(fmt, ...) \
do { if (KTABLE_DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); \
fflush(stderr); } while (0)
Ktable *ktable_new(int initial_size, size_t initial_blocksize) {
Ktable *kt = malloc(sizeof(Ktable));
if (!kt) return NULL;
kt->size = 1 + ((initial_size > 0) ? initial_size : KTABLE_INIT_SIZE);
kt->elements = calloc(kt->size, sizeof(Ktable_element));
if (!kt->elements) goto fail_kt;
kt->next = 1;
kt->blocksize = ((initial_blocksize > 0) ? ((size_t) initial_blocksize) : ((size_t) kt->size * KTABLE_AVG_ELEMENT_LEN));
kt->block = (char *) malloc(kt->blocksize);
if (!kt->block) goto fail_kt_elements;
kt->blocknext = 0;
return kt;
fail_kt_elements:
free(kt->elements);
fail_kt:
free(kt);
return NULL;
}
void ktable_free(Ktable *kt) {
LOG("in ktable_free\n");
if (kt) {
if (kt->block) free(kt->block);
if (kt->elements) free(kt->elements);
free(kt);
}
}
int ktable_len (Ktable *kt) {
if (!kt) {
LOG("null kt\n");
return 0;
}
return kt->next - 1;
}
Ktable_element *ktable_element (Ktable *kt, int i) {
if (!kt) {
LOG("null kt\n");
return NULL;
}
return ((i < 1) || (i >= kt->next)) ? NULL : &(kt->elements[i]);
}
const char *ktable_element_name (Ktable *kt, int i, size_t *len) {
Ktable_element *el = ktable_element(kt, i);
if (!el) return NULL;
*len = (size_t) el->len;
return &(kt->block[el->start]);
}
int ktable_concat(Ktable *kt1, Ktable *kt2, int *ret_n2) {
if (!kt1 || !kt2) {
if (!kt1) LOG("null k1\n");
else {
LOG("null k2\n");
}
return KTABLE_ERR_NULL;
}
int n1 = ktable_len(kt1);
assert( n1 >= 0 );
int n2 = ktable_len(kt2);
assert( n2 >= 0 );
LOGf("kt1 len %d, kt2 len %d\n", n1, n2);
int lastidx;
size_t len = 0;
const char *ptr;
if ((n1 + n2) > KTABLE_MAX_SIZE) return KTABLE_ERR_SIZE;
if (n1 == 0) {
*ret_n2 = 0;
return KTABLE_OK;
}
for (int i = 1; i <= n1; i++) {
ptr = ktable_element_name(kt1, i, &len);
lastidx = ktable_add(kt2, ptr, len);
if (lastidx < 0) return KTABLE_ERR_MEM;
assert( lastidx == (n2 + i) );
}
assert( ktable_len(kt2) == (n1 + n2) );
assert( n2 >= 0 );
*ret_n2 = n2;
return KTABLE_OK;
}
static int extend_ktable_elements(Ktable *kt) {
if (!kt) {
LOG("null kt\n");
return KTABLE_ERR_NULL;
}
size_t newsize = 2 * sizeof(Ktable_element) * kt->size;
void *temp = realloc(kt->elements, newsize);
if (!temp) return KTABLE_ERR_MEM;
kt->elements = temp;
kt->size = 2 * kt->size;
LOGf("extending element array to %d slots (%d usable)\n", kt->size, kt->size - 1);
return KTABLE_OK;
}
static int extend_ktable_block(Ktable *kt, size_t add_at_least) {
if (!kt) {
LOG("null kt\n");
return KTABLE_ERR_NULL;
}
size_t newsize = 2 * kt->blocksize;
if (add_at_least > kt->blocksize) newsize += add_at_least;
void *temp = realloc(kt->block, newsize);
if (!temp) return KTABLE_ERR_MEM;
kt->block = temp;
kt->blocksize = newsize;
LOGf("extending block to %zu bytes\n", kt->blocksize);
return KTABLE_OK;
}
int ktable_add(Ktable *kt, const char *element, size_t len) {
if (!kt) {
LOG("null kt\n");
return KTABLE_ERR_NULL;
}
assert( element != NULL );
assert( kt->next > 0 );
if (kt->next == kt->size)
if (extend_ktable_elements(kt) < 0) return KTABLE_ERR_MEM;
if (blockspace(kt) < len)
if (extend_ktable_block(kt, len) < 0) return KTABLE_ERR_MEM;
assert( kt->next < kt->size );
assert( blockspace(kt) >= len );
memcpy(kt->block + kt->blocknext, element, len);
Ktable_element *el = &(kt->elements[kt->next]);
el->start = kt->blocknext;
el->len = (int32_t) len;
el->entrypoint = 0;
kt->blocknext += len;
kt->next++;
LOGf("new element '%.*s' added in position %d/%d\n", (int) len, element, kt->next - 1, kt->size - 1);
return kt->next - 1;
}
inline int ktable_name_cmp(const char *a, size_t alen, const char *b, size_t blen) {
int a_shorter = (alen < blen);
int res = memcmp(a, b, (a_shorter ? alen : blen));
if (res == 0) {
if (alen == blen) return 0;
else return a_shorter ? -1 : 1;
}
return res;
}
inline int ktable_entry_name_compare(Ktable *kt, const Ktable_element *k1, const Ktable_element *k2) {
const int start1 = ((const Ktable_element *) k1)->start;
const int start2 = ((const Ktable_element *) k2)->start;
const int len1 = ((const Ktable_element *) k1)->len;
const int len2 = ((const Ktable_element *) k2)->len;
return ktable_name_cmp(&(kt->block[start1]), len1, &(kt->block[start2]), len2);
}
#if defined(__linux__)
static inline int ktable_entry_cmp_internal(const void *k1, const void *k2, void *parm)
#else
static inline int ktable_entry_cmp_internal(void *parm, const void *k1, const void *k2)
#endif
{
return ktable_entry_name_compare((Ktable *) parm, (const Ktable_element *) k1, (const Ktable_element *) k2);
}
Ktable_element *ktable_sorted_index(Ktable *kt) {
size_t sz = (ktable_len(kt)+1) * sizeof(Ktable_element);
Ktable_element *elements = (Ktable_element *)malloc(sz);
memcpy(&(elements[0]), &(kt->elements[0]), sz);
qsort_r_common(&elements[1], (size_t) ktable_len(kt), sizeof(Ktable_element),
(void *)kt, &ktable_entry_cmp_internal);
#if 0#endif
return elements;
}
Ktable *ktable_compact(Ktable *orig) {
assert( orig != NULL );
int orig_len = ktable_len(orig);
Ktable_element *index = ktable_sorted_index(orig);
Ktable *new = ktable_new(0, 0);
if (orig_len == 0) goto done;
size_t prev_len = index[1].len;
const char *prev = &(orig->block[index[1].start]);
ktable_add(new, prev, prev_len);
for (int i = 2; i <= orig_len; i++) {
size_t len = index[i].len;
const char *name = &(orig->block[index[i].start]);
if (ktable_name_cmp(prev, prev_len, name, len) != 0) {
ktable_add(new, name, len);
prev = name;
prev_len = len;
}
}
done:
free(index);
return new;
}
int ktable_compact_search(Ktable *kt, const char *target, size_t target_len) {
int len = ktable_len(kt);
if (len == 0) return 0;
int i = 1;
int j = len;
int mid, test;
const char *ptr;
size_t entry_len;
while (1) {
mid = i + ((j - i) / 2);
ptr = &(kt->block[kt->elements[mid].start]);
entry_len = kt->elements[mid].len;
test = ktable_name_cmp(target, target_len, ptr, entry_len);
if (test == 0) return mid;
if (i == j) return 0;
if (test < 0) j = mid - 1;
else { i = mid + 1; }
}
}
void ktable_dump(Ktable *kt) {
size_t len = 0;
const char *ptr;
if (!kt) printf("Ktable pointer is NULL\n");
else {
printf("Ktable: size = %d (%d used), blocksize = %lu (%lu used)\n",
(kt->size - 1), (kt->next - 1), kt->blocksize, kt->blocksize - blockspace(kt));
printf("contents: ");
for (int i = 1; i < kt->next; i++) {
ptr = ktable_element_name(kt, i, &len);
printf("%d: %.*s ", i, (int) len, ptr);
}
printf("\n");
}
fflush(stdout);
}