/* * Data structures -- TODO.md section 3.6. * * Not libc wrappers. These are the parts of the list and tree API that were * visibly missing, plus the two structures the first real consumer had to write * for itself: a string-keyed hash table (akbasic's src/symtab.c, ~130 lines, * needed three times over for variables, functions and labels) and a growable * string buffer, without which the bounded formatting wrappers are unpleasant to * use for anything of unknown length. * * Two things hold throughout. * * Nothing here allocates unless its name says so. The list and tree functions * relink nodes the caller already owns; aksl_list_free_all and * aksl_tree_free_all take the free function to use, so a caller drawing from a * fixed pool can hand over its own. Only aksl_strbuf_* owns memory, and it says * so in the type name. * * The hash map is fixed-capacity and refuses rather than resizes when full, * which is the same decision akbasic made and for the same reason: a table that * silently reallocates is a table whose pointers move underneath you, and one * that cannot grow is one whose worst case you can state. */ #include #include #include #include #include #include #include "aksl_internal.h" /* ====================================================================== */ /* Linked list */ /* ====================================================================== */ /* * Walk to the tail, refusing a cyclic list. Shared by the functions below that * need the end; the bound is Floyd's, exactly as aksl_list_append does it, so * every whole-list walk in this library fails the same way on the same input. */ static akerr_ErrorContext AKERR_NOIGNORE *list_tail(aksl_ListNode *head, aksl_ListNode **dest) { aksl_ListNode *slow = head; aksl_ListNode *fast = head; aksl_ListNode *tail = head; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, head, AKERR_NULLPOINTER, "head"); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest"); *dest = NULL; while ( fast != NULL && fast->next != NULL ) { slow = slow->next; fast = fast->next->next; if ( fast == slow ) { FAIL_RETURN(e, AKERR_CIRCULAR_REFERENCE, "%p", (void *)head); } } while ( tail->next != NULL ) { tail = tail->next; } *dest = tail; SUCCEED_RETURN(e); } /* * obj becomes the new head. Takes the head by reference for the same reason * aksl_list_pop does: the caller's own pointer has to move, and there is no way * to do that from a node pointer alone. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_prepend(aksl_ListNode **head, aksl_ListNode *obj) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, head, AKERR_NULLPOINTER, "head=%p, obj=%p", (void *)head, (void *)obj); FAIL_ZERO_RETURN(e, obj, AKERR_NULLPOINTER, "head=%p, obj=%p", (void *)head, (void *)obj); FAIL_NONZERO_RETURN(e, (*head == obj), AKERR_VALUE, "obj %p is already the head of this list", (void *)obj); obj->prev = NULL; obj->next = *head; if ( *head != NULL ) { (*head)->prev = obj; } *head = obj; SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_list_insert_after(aksl_ListNode *node, aksl_ListNode *obj) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node=%p, obj=%p", (void *)node, (void *)obj); FAIL_ZERO_RETURN(e, obj, AKERR_NULLPOINTER, "node=%p, obj=%p", (void *)node, (void *)obj); FAIL_NONZERO_RETURN(e, (node == obj), AKERR_VALUE, "cannot insert node %p after itself", (void *)obj); obj->prev = node; obj->next = node->next; if ( node->next != NULL ) { node->next->prev = obj; } node->next = obj; SUCCEED_RETURN(e); } /* * head is required because inserting before the current head moves it, which is * the case a caller is most likely to get wrong by hand. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_insert_before(aksl_ListNode **head, aksl_ListNode *node, aksl_ListNode *obj) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, head, AKERR_NULLPOINTER, "head=%p, node=%p, obj=%p", (void *)head, (void *)node, (void *)obj); FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "head=%p, node=%p, obj=%p", (void *)head, (void *)node, (void *)obj); FAIL_ZERO_RETURN(e, obj, AKERR_NULLPOINTER, "head=%p, node=%p, obj=%p", (void *)head, (void *)node, (void *)obj); FAIL_NONZERO_RETURN(e, (node == obj), AKERR_VALUE, "cannot insert node %p before itself", (void *)obj); obj->next = node; obj->prev = node->prev; if ( node->prev != NULL ) { node->prev->next = obj; } node->prev = obj; if ( *head == node ) { *head = obj; } SUCCEED_RETURN(e); } /* A NULL head is an empty list of length 0, not an error. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_length(aksl_ListNode *head, size_t *dest) { aksl_ListNode *slow = head; aksl_ListNode *fast = head; aksl_ListNode *walk = head; size_t n = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest"); *dest = 0; while ( fast != NULL && fast->next != NULL ) { slow = slow->next; fast = fast->next->next; if ( fast == slow ) { FAIL_RETURN(e, AKERR_CIRCULAR_REFERENCE, "%p", (void *)head); } } while ( walk != NULL ) { n++; walk = walk->next; } *dest = n; SUCCEED_RETURN(e); } /* * The first node the predicate accepts, or NULL if none does -- absent is an * answer, as everywhere else in this library. The predicate reports through an * out-param and may raise, in which case the search stops and the error * propagates with its message intact. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_find(aksl_ListNode *head, aksl_ListNodePredicate pred, void *data, aksl_ListNode **dest) { aksl_ListNode *walk = head; aksl_ListNode *tail = NULL; int matched = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, pred, AKERR_NULLPOINTER, "pred=%p, dest=%p", (void *)pred, (void *)dest); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "pred=%p, dest=%p", (void *)pred, (void *)dest); *dest = NULL; /* Bound the walk before making it, so a cyclic list cannot spin forever. */ if ( head != NULL ) { PASS(e, list_tail(head, &tail)); } while ( walk != NULL ) { PASS(e, pred(walk, data, &matched)); if ( matched != 0 ) { *dest = walk; break; } walk = walk->next; } SUCCEED_RETURN(e); } /* Reverses in place, swapping every node's links and moving the caller's head. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_reverse(aksl_ListNode **head) { aksl_ListNode *walk = NULL; aksl_ListNode *prev = NULL; aksl_ListNode *next = NULL; aksl_ListNode *tail = NULL; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, head, AKERR_NULLPOINTER, "head"); if ( *head == NULL ) { SUCCEED_RETURN(e); } PASS(e, list_tail(*head, &tail)); walk = *head; while ( walk != NULL ) { next = walk->next; walk->next = prev; walk->prev = next; prev = walk; walk = next; } *head = prev; SUCCEED_RETURN(e); } /* * Links `other` onto the end of `head`. Both must be acyclic and disjoint -- * concatenating a list with itself, or with something already inside it, would * make a cycle, so the tail walk that finds the join point also refuses it. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_concat(aksl_ListNode *head, aksl_ListNode *other) { aksl_ListNode *tail = NULL; aksl_ListNode *walk = NULL; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, head, AKERR_NULLPOINTER, "head=%p, other=%p", (void *)head, (void *)other); if ( other == NULL ) { SUCCEED_RETURN(e); } FAIL_NONZERO_RETURN(e, (head == other), AKERR_VALUE, "cannot concatenate a list with itself"); PASS(e, list_tail(head, &tail)); for ( walk = head; walk != NULL; walk = walk->next ) { FAIL_NONZERO_RETURN(e, (walk == other), AKERR_VALUE, "node %p is already in the destination list", (void *)other); } tail->next = other; other->prev = tail; SUCCEED_RETURN(e); } /* * Releases every node through lfree, or through aksl_free when it is NULL, and * clears the caller's head. The next pointer is read before the node goes, which * is the whole trick; freeing forwards without that reads freed memory on the * very next iteration. * * A failure part-way through does not abandon the rest of the list: the first * error is kept and returned once the walk is finished, so a bad free cannot * turn into a leak of everything after it. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_free_all(aksl_ListNode **head, aksl_FreeFunc lfree) { aksl_ListNode *walk = NULL; aksl_ListNode *next = NULL; aksl_ListNode *tail = NULL; akerr_ErrorContext *first = NULL; akerr_ErrorContext *raised = NULL; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, head, AKERR_NULLPOINTER, "head"); if ( lfree == NULL ) { lfree = &aksl_free; } if ( *head == NULL ) { SUCCEED_RETURN(e); } PASS(e, list_tail(*head, &tail)); walk = *head; *head = NULL; while ( walk != NULL ) { next = walk->next; raised = lfree(walk); if ( raised != NULL && first == NULL ) { first = raised; } else if ( raised != NULL ) { raised = akerr_release_error(raised); } walk = next; } if ( first != NULL ) { return first; } SUCCEED_RETURN(e); } /* * Iterates from `tail` back towards the head. Given a tail rather than a head * because that is what the caller has after a walk forwards, and because a * doubly-linked list that cannot be read backwards is just a linked list. * AKERR_ITERATOR_BREAK stops it, as it does going the other way. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate_reverse(aksl_ListNode *tail, aksl_ListNodeIterator iter, void *data) { aksl_ListNode *slow = tail; aksl_ListNode *fast = tail; aksl_ListNode *node = tail; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, tail, AKERR_NULLPOINTER, "tail=%p, iter=%p", (void *)tail, (void *)iter); FAIL_ZERO_RETURN(e, iter, AKERR_NULLPOINTER, "tail=%p, iter=%p", (void *)tail, (void *)iter); while ( fast != NULL && fast->prev != NULL ) { slow = slow->prev; fast = fast->prev->prev; if ( fast == slow ) { FAIL_RETURN(e, AKERR_CIRCULAR_REFERENCE, "%p", (void *)tail); } } while ( node != NULL ) { ATTEMPT { CATCH(e, iter(node, data)); node = node->prev; } CLEANUP { } PROCESS(e) { } HANDLE(e, AKERR_ITERATOR_BREAK) { // Not an error condition; it is telling us to stop early SUCCEED_RETURN(e); } FINISH(e, true); } SUCCEED_RETURN(e); } /* ---------------------------------------------------------------------- */ /* The tracked list container */ /* ---------------------------------------------------------------------- */ /* * aksl_list_append has to walk the whole list to find the tail, so building a * list of n nodes with it is O(n^2). That is fine for the handful of nodes the * bare-node API was written for and wrong for anything larger, which is what * TODO.md 3.6 means by "a head/tail-tracking container type so append is O(1)". * * The container holds the length as well, so aksl_list_length stops being a * walk. It owns no memory -- the nodes are still the caller's -- so there is no * aksl_list_destroy; use aksl_list_clear if the nodes need releasing. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_init(aksl_List *list) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, list, AKERR_NULLPOINTER, "list"); list->head = NULL; list->tail = NULL; list->length = 0; SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_list_push(aksl_List *list, aksl_ListNode *obj) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, list, AKERR_NULLPOINTER, "list=%p, obj=%p", (void *)list, (void *)obj); FAIL_ZERO_RETURN(e, obj, AKERR_NULLPOINTER, "list=%p, obj=%p", (void *)list, (void *)obj); obj->next = NULL; obj->prev = list->tail; if ( list->tail != NULL ) { list->tail->next = obj; } else { list->head = obj; } list->tail = obj; list->length += 1; SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_list_unshift(aksl_List *list, aksl_ListNode *obj) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, list, AKERR_NULLPOINTER, "list=%p, obj=%p", (void *)list, (void *)obj); FAIL_ZERO_RETURN(e, obj, AKERR_NULLPOINTER, "list=%p, obj=%p", (void *)list, (void *)obj); obj->prev = NULL; obj->next = list->head; if ( list->head != NULL ) { list->head->prev = obj; } else { list->tail = obj; } list->head = obj; list->length += 1; SUCCEED_RETURN(e); } /* * Unlinks a node the caller already holds. It must be in this list -- the * container's length and endpoints would otherwise silently stop describing it * -- so removing a node that is not is AKERR_VALUE rather than quiet corruption. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_remove(aksl_List *list, aksl_ListNode *node) { aksl_ListNode *walk = NULL; int found = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, list, AKERR_NULLPOINTER, "list=%p, node=%p", (void *)list, (void *)node); FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "list=%p, node=%p", (void *)list, (void *)node); for ( walk = list->head; walk != NULL; walk = walk->next ) { if ( walk == node ) { found = 1; break; } } FAIL_ZERO_RETURN(e, found, AKERR_VALUE, "node %p is not in this list", (void *)node); if ( node->prev != NULL ) { node->prev->next = node->next; } else { list->head = node->next; } if ( node->next != NULL ) { node->next->prev = node->prev; } else { list->tail = node->prev; } node->next = NULL; node->prev = NULL; list->length -= 1; SUCCEED_RETURN(e); } /* Empties the container, releasing every node through lfree (aksl_free if NULL). */ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_clear(aksl_List *list, aksl_FreeFunc lfree) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, list, AKERR_NULLPOINTER, "list"); ATTEMPT { CATCH(e, aksl_list_free_all(&list->head, lfree)); } CLEANUP { /* The container is emptied whether or not every node released cleanly; * leaving it describing nodes that are half gone would be worse. */ list->head = NULL; list->tail = NULL; list->length = 0; } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } /* ====================================================================== */ /* Binary search tree */ /* ====================================================================== */ /* * An ordered tree needs an ordering, so these take a comparator. It reports * through an out-param and may raise, like every other callback here. * * These are the functions that set and read aksl_TreeNode.parent, which was * declared and then never touched by anything in the library (TODO.md 2.2.15). * aksl_tree_remove needs it: relinking a node's replacement means telling that * node's parent about it, and finding the parent by walking from the root again * would turn a removal into a second search. * * This is a plain unbalanced BST. Inserting already-sorted data gives a * degenerate chain, which aksl_tree_iterate then refuses past * AKSL_TREE_MAX_DEPTH -- so it is bounded rather than dangerous, but it is not * a balanced tree and does not pretend to be. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_insert(aksl_TreeNode **root, aksl_TreeNode *node, aksl_TreeCompareFunc cmp) { aksl_TreeNode *walk = NULL; int order = 0; int depth = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "root=%p, node=%p, cmp=%p", (void *)root, (void *)node, (void *)cmp); FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "root=%p, node=%p, cmp=%p", (void *)root, (void *)node, (void *)cmp); FAIL_ZERO_RETURN(e, cmp, AKERR_NULLPOINTER, "root=%p, node=%p, cmp=%p", (void *)root, (void *)node, (void *)cmp); node->left = NULL; node->right = NULL; node->parent = NULL; if ( *root == NULL ) { *root = node; SUCCEED_RETURN(e); } walk = *root; while ( 1 ) { FAIL_NONZERO_RETURN(e, (depth >= AKSL_TREE_MAX_DEPTH), AKERR_OUTOFBOUNDS, "tree deeper than AKSL_TREE_MAX_DEPTH (%d)", AKSL_TREE_MAX_DEPTH); depth++; PASS(e, cmp(node->leaf, walk->leaf, &order)); if ( order < 0 ) { if ( walk->left == NULL ) { walk->left = node; node->parent = walk; break; } walk = walk->left; } else { /* * Equal keys go right, so insertion order is preserved among them * and a duplicate is stored rather than refused. A caller who wants * uniqueness checks with aksl_tree_find first. */ if ( walk->right == NULL ) { walk->right = node; node->parent = walk; break; } walk = walk->right; } } SUCCEED_RETURN(e); } /* NULL and success when the key is not present. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_find(aksl_TreeNode *root, void *leaf, aksl_TreeCompareFunc cmp, aksl_TreeNode **dest) { aksl_TreeNode *walk = root; int order = 0; int depth = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, cmp, AKERR_NULLPOINTER, "cmp=%p, dest=%p", (void *)cmp, (void *)dest); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "cmp=%p, dest=%p", (void *)cmp, (void *)dest); *dest = NULL; while ( walk != NULL ) { FAIL_NONZERO_RETURN(e, (depth >= AKSL_TREE_MAX_DEPTH), AKERR_OUTOFBOUNDS, "tree deeper than AKSL_TREE_MAX_DEPTH (%d)", AKSL_TREE_MAX_DEPTH); depth++; PASS(e, cmp(leaf, walk->leaf, &order)); if ( order == 0 ) { *dest = walk; break; } walk = (order < 0) ? walk->left : walk->right; } SUCCEED_RETURN(e); } /* Attach `child` (which may be NULL) where `node` used to hang off its parent. */ static void tree_replace(aksl_TreeNode **root, aksl_TreeNode *node, aksl_TreeNode *child) { if ( node->parent == NULL ) { *root = child; } else if ( node->parent->left == node ) { node->parent->left = child; } else { node->parent->right = child; } if ( child != NULL ) { child->parent = node->parent; } } /* * The textbook three cases: no children, one child, two children. The last is * the interesting one -- the node's in-order successor (leftmost of the right * subtree) takes its place, because that is the only value that keeps every * ordering invariant on both sides. * * The removed node's own links are cleared, so it can go straight back into a * pool or be inserted somewhere else without carrying stale pointers. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_remove(aksl_TreeNode **root, aksl_TreeNode *node) { aksl_TreeNode *successor = NULL; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "root=%p, node=%p", (void *)root, (void *)node); FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "root=%p, node=%p", (void *)root, (void *)node); FAIL_ZERO_RETURN(e, *root, AKERR_VALUE, "the tree is empty"); if ( node->left == NULL ) { tree_replace(root, node, node->right); } else if ( node->right == NULL ) { tree_replace(root, node, node->left); } else { successor = node->right; while ( successor->left != NULL ) { successor = successor->left; } if ( successor->parent != node ) { tree_replace(root, successor, successor->right); successor->right = node->right; successor->right->parent = successor; } tree_replace(root, node, successor); successor->left = node->left; successor->left->parent = successor; } node->parent = NULL; node->left = NULL; node->right = NULL; SUCCEED_RETURN(e); } /* Height in nodes: an empty tree is 0, a single node is 1. */ static akerr_ErrorContext AKERR_NOIGNORE *tree_height(aksl_TreeNode *root, int depth, int *dest) { int left = 0; int right = 0; PREPARE_ERROR(e); FAIL_NONZERO_RETURN(e, (depth >= AKSL_TREE_MAX_DEPTH), AKERR_OUTOFBOUNDS, "tree deeper than AKSL_TREE_MAX_DEPTH (%d)", AKSL_TREE_MAX_DEPTH); if ( root == NULL ) { *dest = 0; SUCCEED_RETURN(e); } PASS(e, tree_height(root->left, depth + 1, &left)); PASS(e, tree_height(root->right, depth + 1, &right)); *dest = 1 + (left > right ? left : right); SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_height(aksl_TreeNode *root, int *dest) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest"); *dest = 0; return tree_height(root, 0, dest); } static akerr_ErrorContext AKERR_NOIGNORE *tree_count(aksl_TreeNode *root, int depth, size_t *dest) { size_t left = 0; size_t right = 0; PREPARE_ERROR(e); FAIL_NONZERO_RETURN(e, (depth >= AKSL_TREE_MAX_DEPTH), AKERR_OUTOFBOUNDS, "tree deeper than AKSL_TREE_MAX_DEPTH (%d)", AKSL_TREE_MAX_DEPTH); if ( root == NULL ) { *dest = 0; SUCCEED_RETURN(e); } PASS(e, tree_count(root->left, depth + 1, &left)); PASS(e, tree_count(root->right, depth + 1, &right)); *dest = 1 + left + right; SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_count(aksl_TreeNode *root, size_t *dest) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest"); *dest = 0; return tree_count(root, 0, dest); } /* * Post-order, necessarily: a node's children have to be released before the * node that points at them, and any other order reads freed memory to find the * second subtree. The first failure is kept and returned at the end rather than * abandoning the rest of the tree, as aksl_list_free_all does. */ static akerr_ErrorContext AKERR_NOIGNORE *tree_free_all(aksl_TreeNode *root, int depth, aksl_FreeFunc lfree, akerr_ErrorContext **first) { aksl_TreeNode *left = NULL; aksl_TreeNode *right = NULL; akerr_ErrorContext *raised = NULL; PREPARE_ERROR(e); FAIL_NONZERO_RETURN(e, (depth >= AKSL_TREE_MAX_DEPTH), AKERR_OUTOFBOUNDS, "tree deeper than AKSL_TREE_MAX_DEPTH (%d)", AKSL_TREE_MAX_DEPTH); if ( root == NULL ) { SUCCEED_RETURN(e); } left = root->left; right = root->right; PASS(e, tree_free_all(left, depth + 1, lfree, first)); PASS(e, tree_free_all(right, depth + 1, lfree, first)); raised = lfree(root); if ( raised != NULL && *first == NULL ) { *first = raised; } else if ( raised != NULL ) { raised = akerr_release_error(raised); } SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_free_all(aksl_TreeNode **root, aksl_FreeFunc lfree) { akerr_ErrorContext *first = NULL; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "root"); if ( lfree == NULL ) { lfree = &aksl_free; } ATTEMPT { CATCH(e, tree_free_all(*root, 0, lfree, &first)); } CLEANUP { *root = NULL; } PROCESS(e) { } FINISH(e, true); if ( first != NULL ) { return first; } SUCCEED_RETURN(e); } /* ====================================================================== */ /* Hashes */ /* ====================================================================== */ /* * FNV-1a, the other half of TODO.md 3.6's hash request. It differs from djb2 in * XOR-then-multiply rather than multiply-then-add, which mixes the low bits * rather better -- worth having when the keys are short and share a prefix, * which is exactly what identifiers in a symbol table look like. * * Bytes are read unsigned here for the same reason they are in djb2: a * sign-extended byte would make the hash depend on whether plain char happens to * be signed on the target. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a(const char *str, size_t len, uint32_t *hashval) { const unsigned char *cursor = NULL; uint32_t h = 2166136261u; /* FNV offset basis, 32-bit */ PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, str, AKERR_NULLPOINTER, "str=%p, hashval=%p", (void *)str, (void *)hashval); FAIL_ZERO_RETURN(e, hashval, AKERR_NULLPOINTER, "str=%p, hashval=%p", (void *)str, (void *)hashval); cursor = (const unsigned char *)str; while ( len-- ) { h ^= (uint32_t)*cursor++; h *= 16777619u; /* FNV prime, 32-bit */ } *hashval = h; SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a_str(const char *str, uint32_t *hashval) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, str, AKERR_NULLPOINTER, "str=%p, hashval=%p", (void *)str, (void *)hashval); return aksl_strhash_fnv1a(str, strlen(str), hashval); } /* ====================================================================== */ /* Hash map */ /* ====================================================================== */ /* * Fixed-capacity, open-addressed, linear-probing, string-keyed. * * The shape is akbasic's src/symtab.c, which TODO.md 3.6 says is "worth lifting * more or less verbatim": the caller supplies the slot array, the map refuses * rather than resizes when full, and the keys are copied into fixed-size slots * so the map owns them and a caller cannot outlive its own key strings. * * Refusing rather than resizing is the deliberate part. A table that reallocates * is a table whose entry pointers move underneath anything holding one; a table * that cannot grow has a worst case you can state and a failure you can see. The * cost is that the caller has to size it, which is why aksl_hashmap_init takes * the slot array rather than allocating one. * * Deletion leaves a tombstone rather than an empty slot, because clearing the * slot outright would break the probe chain of anything that hashed past it. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_init(aksl_HashMap *map, aksl_HashEntry *slots, size_t capacity) { size_t i = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, map, AKERR_NULLPOINTER, "map=%p, slots=%p", (void *)map, (void *)slots); FAIL_ZERO_RETURN(e, slots, AKERR_NULLPOINTER, "map=%p, slots=%p", (void *)map, (void *)slots); FAIL_ZERO_RETURN(e, capacity, AKERR_VALUE, "capacity=0"); for ( i = 0; i < capacity; i++ ) { slots[i].state = AKSL_HASHMAP_SLOT_EMPTY; slots[i].key[0] = '\0'; slots[i].value = NULL; } map->slots = slots; map->capacity = capacity; map->count = 0; SUCCEED_RETURN(e); } /* * Find the slot a key belongs in. * * *found is the index of the key if it is present. *insert is where it would go * if it is not -- the first tombstone seen, or the empty slot the probe stopped * at -- so a put that has to insert does not have to probe twice. Either may * come back as capacity, meaning "no such thing". */ static akerr_ErrorContext AKERR_NOIGNORE *hashmap_probe(aksl_HashMap *map, const char *key, size_t *found, size_t *insert) { uint32_t h = 0; size_t idx = 0; size_t probed = 0; PREPARE_ERROR(e); *found = map->capacity; *insert = map->capacity; PASS(e, aksl_strhash_djb2_str(key, &h)); idx = (size_t)h % map->capacity; for ( probed = 0; probed < map->capacity; probed++ ) { if ( map->slots[idx].state == AKSL_HASHMAP_SLOT_EMPTY ) { if ( *insert == map->capacity ) { *insert = idx; } /* An empty slot ends the probe chain: the key is not here. */ break; } if ( map->slots[idx].state == AKSL_HASHMAP_SLOT_DELETED ) { if ( *insert == map->capacity ) { *insert = idx; } } else if ( strcmp(map->slots[idx].key, key) == 0 ) { *found = idx; break; } idx = (idx + 1) % map->capacity; } SUCCEED_RETURN(e); } /* * Inserts, or replaces the value of an existing key. A key longer than * AKSL_HASHMAP_MAX_KEY-1 is AKERR_OUTOFBOUNDS rather than a truncated key that * would collide with a different one sharing its prefix. A full map is * AKERR_OUTOFBOUNDS too, naming the capacity it hit. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_put(aksl_HashMap *map, const char *key, void *value) { size_t found = 0; size_t insert = 0; size_t keylen = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, map, AKERR_NULLPOINTER, "map=%p, key=%p", (void *)map, (void *)key); FAIL_ZERO_RETURN(e, key, AKERR_NULLPOINTER, "map=%p, key=%p", (void *)map, (void *)key); FAIL_ZERO_RETURN(e, map->slots, AKERR_NULLPOINTER, "map is not initialised"); keylen = strlen(key); FAIL_NONZERO_RETURN(e, (keylen >= AKSL_HASHMAP_MAX_KEY), AKERR_OUTOFBOUNDS, "key of %zu bytes exceeds AKSL_HASHMAP_MAX_KEY (%d)", keylen, AKSL_HASHMAP_MAX_KEY); PASS(e, hashmap_probe(map, key, &found, &insert)); if ( found != map->capacity ) { map->slots[found].value = value; SUCCEED_RETURN(e); } FAIL_NONZERO_RETURN(e, (insert == map->capacity), AKERR_OUTOFBOUNDS, "map is full at %zu entries; it does not resize", map->capacity); memcpy(map->slots[insert].key, key, keylen + 1); map->slots[insert].value = value; map->slots[insert].state = AKSL_HASHMAP_SLOT_OCCUPIED; map->count += 1; SUCCEED_RETURN(e); } /* * A key that is not present is *found = 0 and success, not an error -- looking * something up and not finding it is the ordinary case in a symbol table, and a * caller should not have to catch an error to discover it. *value is written * only when the key is there. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_get(aksl_HashMap *map, const char *key, void **value, int *found) { size_t at = 0; size_t insert = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, map, AKERR_NULLPOINTER, "map=%p, key=%p, found=%p", (void *)map, (void *)key, (void *)found); FAIL_ZERO_RETURN(e, key, AKERR_NULLPOINTER, "map=%p, key=%p, found=%p", (void *)map, (void *)key, (void *)found); FAIL_ZERO_RETURN(e, found, AKERR_NULLPOINTER, "map=%p, key=%p, found=%p", (void *)map, (void *)key, (void *)found); FAIL_ZERO_RETURN(e, map->slots, AKERR_NULLPOINTER, "map is not initialised"); *found = 0; FAIL_NONZERO_RETURN(e, (strlen(key) >= AKSL_HASHMAP_MAX_KEY), AKERR_OUTOFBOUNDS, "key exceeds AKSL_HASHMAP_MAX_KEY (%d)", AKSL_HASHMAP_MAX_KEY); PASS(e, hashmap_probe(map, key, &at, &insert)); if ( at != map->capacity ) { *found = 1; if ( value != NULL ) { *value = map->slots[at].value; } } SUCCEED_RETURN(e); } /* Removing a key that is not there is success with *removed = 0. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_remove(aksl_HashMap *map, const char *key, int *removed) { size_t at = 0; size_t insert = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, map, AKERR_NULLPOINTER, "map=%p, key=%p", (void *)map, (void *)key); FAIL_ZERO_RETURN(e, key, AKERR_NULLPOINTER, "map=%p, key=%p", (void *)map, (void *)key); FAIL_ZERO_RETURN(e, map->slots, AKERR_NULLPOINTER, "map is not initialised"); if ( removed != NULL ) { *removed = 0; } FAIL_NONZERO_RETURN(e, (strlen(key) >= AKSL_HASHMAP_MAX_KEY), AKERR_OUTOFBOUNDS, "key exceeds AKSL_HASHMAP_MAX_KEY (%d)", AKSL_HASHMAP_MAX_KEY); PASS(e, hashmap_probe(map, key, &at, &insert)); if ( at != map->capacity ) { /* * A tombstone, not an empty slot: emptying it would cut the probe chain * of every key that hashed to an earlier slot and walked past this one. */ map->slots[at].state = AKSL_HASHMAP_SLOT_DELETED; map->slots[at].key[0] = '\0'; map->slots[at].value = NULL; map->count -= 1; if ( removed != NULL ) { *removed = 1; } } SUCCEED_RETURN(e); } /* * Visits every live entry, in slot order -- which is to say in no order the * caller can predict or should rely on. AKERR_ITERATOR_BREAK stops it, as * everywhere else. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_iterate(aksl_HashMap *map, aksl_HashMapIterator iter, void *data) { size_t i = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, map, AKERR_NULLPOINTER, "map=%p, iter=%p", (void *)map, (void *)iter); FAIL_ZERO_RETURN(e, iter, AKERR_NULLPOINTER, "map=%p, iter=%p", (void *)map, (void *)iter); FAIL_ZERO_RETURN(e, map->slots, AKERR_NULLPOINTER, "map is not initialised"); for ( i = 0; i < map->capacity; i++ ) { if ( map->slots[i].state != AKSL_HASHMAP_SLOT_OCCUPIED ) { continue; } ATTEMPT { CATCH(e, iter(map->slots[i].key, map->slots[i].value, data)); } CLEANUP { } PROCESS(e) { } HANDLE(e, AKERR_ITERATOR_BREAK) { // Not an error condition; it is telling us to stop early SUCCEED_RETURN(e); } FINISH(e, true); } SUCCEED_RETURN(e); } /* ====================================================================== */ /* Growable string buffer */ /* ====================================================================== */ /* * The one thing here that owns memory. * * The bounded formatting wrappers are the right answer when the destination is * a fixed buffer, and no answer at all when the output length is not known in * advance -- which is why TODO.md 3.6 asks for this to "make the snprintf and * strcat wrappers pleasant to use". Building a diagnostic, a serialised record * or a generated line means appending to something that grows. * * Capacity doubles, so n appends cost O(n) amortised rather than O(n^2). The * buffer is always NUL-terminated, so aksl_strbuf_cstr is valid at any point * without a finalise step -- and the terminator is not counted in `length`, so * embedded NULs are possible but the C string view stops at the first one. */ #define AKSL_STRBUF_MIN_CAPACITY 32 akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_init(aksl_StrBuf *buf, size_t initial) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf"); buf->data = NULL; buf->length = 0; buf->capacity = 0; if ( initial < AKSL_STRBUF_MIN_CAPACITY ) { initial = AKSL_STRBUF_MIN_CAPACITY; } ATTEMPT { CATCH(e, aksl_malloc(initial, (void **)&buf->data)); buf->capacity = initial; buf->data[0] = '\0'; } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } /* Make room for `extra` more bytes plus the terminator, doubling as needed. */ static akerr_ErrorContext AKERR_NOIGNORE *strbuf_reserve(aksl_StrBuf *buf, size_t extra) { size_t needed = 0; size_t capacity = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf->data, AKERR_NULLPOINTER, "buffer is not initialised"); /* size_t overflow would turn a huge append into a tiny allocation. */ FAIL_NONZERO_RETURN(e, (extra > (size_t)-1 - buf->length - 1), AKERR_OUTOFBOUNDS, "appending %zu bytes to %zu overflows size_t", extra, buf->length); needed = buf->length + extra + 1; if ( needed <= buf->capacity ) { SUCCEED_RETURN(e); } capacity = buf->capacity; while ( capacity < needed ) { /* Doubling, but never past what size_t can hold. */ if ( capacity > ((size_t)-1) / 2 ) { capacity = needed; break; } capacity *= 2; } ATTEMPT { CATCH(e, aksl_realloc((void **)&buf->data, capacity)); buf->capacity = capacity; } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append(aksl_StrBuf *buf, const char *s) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf=%p, s=%p", (void *)buf, (void *)s); FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "buf=%p, s=%p", (void *)buf, (void *)s); return aksl_strbuf_append_bytes(buf, s, strlen(s)); } akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append_bytes(aksl_StrBuf *buf, const char *s, size_t n) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf=%p, s=%p", (void *)buf, (void *)s); FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "buf=%p, s=%p", (void *)buf, (void *)s); ATTEMPT { CATCH(e, strbuf_reserve(buf, n)); memcpy(buf->data + buf->length, s, n); buf->length += n; buf->data[buf->length] = '\0'; } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append_char(aksl_StrBuf *buf, char c) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf"); return aksl_strbuf_append_bytes(buf, &c, 1); } /* * Formatted append. vsnprintf is called twice on purpose: once with a zero * length to ask how much room the result needs, and again once that room * exists. The alternative -- guess, then retry on truncation -- is the same two * calls in the bad case and a wasted guess in the good one. * * The va_list is copied because a vsnprintf leaves it consumed, and using the * same one twice is undefined behaviour rather than merely wrong. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_vappendf(aksl_StrBuf *buf, const char *format, va_list args) { va_list measure; int needed = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf=%p, format=%p", (void *)buf, (void *)format); FAIL_ZERO_RETURN(e, format, AKERR_NULLPOINTER, "buf=%p, format=%p", (void *)buf, (void *)format); va_copy(measure, args); needed = vsnprintf(NULL, 0, format, measure); va_end(measure); FAIL_NONZERO_RETURN(e, (needed < 0), AKERR_IO, "could not format the arguments"); ATTEMPT { CATCH(e, strbuf_reserve(buf, (size_t)needed)); /* * The reserve above guarantees needed + 1 bytes, so this cannot * truncate -- vsnprintf's return is checked all the same, because * "cannot happen" is a claim about today's reserve. */ FAIL_NONZERO_BREAK(e, (vsnprintf(buf->data + buf->length, buf->capacity - buf->length, format, args) != needed), AKERR_IO, "formatted output changed length between passes"); buf->length += (size_t)needed; buf->data[buf->length] = '\0'; } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_appendf(aksl_StrBuf *buf, const char *format, ...) { va_list args; akerr_ErrorContext *raised = NULL; va_start(args, format); raised = aksl_strbuf_vappendf(buf, format, args); va_end(args); return raised; } /* Empties without releasing, so the capacity is reused by the next round. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_reset(aksl_StrBuf *buf) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf"); FAIL_ZERO_RETURN(e, buf->data, AKERR_NULLPOINTER, "buffer is not initialised"); buf->length = 0; buf->data[0] = '\0'; SUCCEED_RETURN(e); } /* * The contents as a C string. Points into the buffer, so it is invalidated by * the next append -- copy it with aksl_strdup if it has to outlive one. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_cstr(aksl_StrBuf *buf, const char **dest) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf=%p, dest=%p", (void *)buf, (void *)dest); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "buf=%p, dest=%p", (void *)buf, (void *)dest); FAIL_ZERO_RETURN(e, buf->data, AKERR_NULLPOINTER, "buffer is not initialised"); *dest = buf->data; SUCCEED_RETURN(e); } /* * Releases the storage and leaves the buffer in the state aksl_strbuf_init would * refuse to leave it in -- zeroed, so a second free is an error rather than a * double free, exactly as aksl_freep arranges for a bare pointer. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_free(aksl_StrBuf *buf) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf"); FAIL_ZERO_RETURN(e, buf->data, AKERR_NULLPOINTER, "buffer is already released"); ATTEMPT { CATCH(e, aksl_free(buf->data)); } CLEANUP { buf->data = NULL; buf->length = 0; buf->capacity = 0; } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); }