src/tree.c

Fri, 23 May 2025 12:44:24 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 23 May 2025 12:44:24 +0200
changeset 1327
ed75dc1db503
parent 1319
aa1f580f8f59
permissions
-rw-r--r--

make test-compile depend on both static and shared

the shared lib is not needed for the tests,
but when run with coverage, gcov will be confused
when outdated line information is available from
a previous shared build

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "cx/tree.h"

#include "cx/array_list.h"

#include <assert.h>

#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
#define tree_parent(node) CX_TREE_PTR(node, loc_parent)
#define tree_children(node) CX_TREE_PTR(node, loc_children)
#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child)
#define tree_prev(node) CX_TREE_PTR(node, loc_prev)
#define tree_next(node) CX_TREE_PTR(node, loc_next)

#define cx_tree_ptr_locations \
    loc_parent, loc_children, loc_last_child, loc_prev, loc_next

#define cx_tree_node_layout(tree) \
    (tree)->loc_parent,\
    (tree)->loc_children,\
    (tree)->loc_last_child,\
    (tree)->loc_prev,  \
    (tree)->loc_next

static void cx_tree_zero_pointers(
        void *node,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    tree_parent(node) = NULL;
    if (loc_prev >= 0) {
        tree_prev(node) = NULL;
    }
    tree_next(node) = NULL;
    tree_children(node) = NULL;
    if (loc_last_child >= 0) {
        tree_last_child(node) = NULL;
    }
}

void cx_tree_link(
        void *parent,
        void *node,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    assert(loc_parent >= 0);
    assert(loc_children >= 0);
    assert(loc_next >= 0);

    void *current_parent = tree_parent(node);
    if (current_parent == parent) return;
    if (current_parent != NULL) {
        cx_tree_unlink(node, cx_tree_ptr_locations);
    }

    if (tree_children(parent) == NULL) {
        tree_children(parent) = node;
        if (loc_last_child >= 0) {
            tree_last_child(parent) = node;
        }
    } else {
        void *child;
        if (loc_last_child >= 0) {
            child = tree_last_child(parent);
            tree_last_child(parent) = node;
        } else {
            child = tree_children(parent);
            void *next;
            while ((next = tree_next(child)) != NULL) {
                child = next;
            }
        }
        if (loc_prev >= 0) {
            tree_prev(node) = child;
        }
        tree_next(child) = node;
    }
    tree_parent(node) = parent;
}

static void *cx_tree_node_prev(
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_next,
        const void *node
) {
    void *parent = tree_parent(node);
    void *begin = tree_children(parent);
    if (begin == node) return NULL;
    const void *cur = begin;
    const void *next;
    while (1) {
        next = tree_next(cur);
        if (next == node) return (void *) cur;
        cur = next;
    }
}

void cx_tree_unlink(
        void *node,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    if (tree_parent(node) == NULL) return;

    assert(loc_children >= 0);
    assert(loc_next >= 0);
    assert(loc_parent >= 0);
    void *left;
    if (loc_prev >= 0) {
        left = tree_prev(node);
    } else {
        left = cx_tree_node_prev(loc_parent, loc_children, loc_next, node);
    }
    void *right = tree_next(node);
    void *parent = tree_parent(node);
    assert(left == NULL || tree_children(parent) != node);
    assert(right == NULL || loc_last_child < 0 ||
           tree_last_child(parent) != node);

    if (left == NULL) {
        tree_children(parent) = right;
    } else {
        tree_next(left) = right;
    }
    if (right == NULL) {
        if (loc_last_child >= 0) {
            tree_last_child(parent) = left;
        }
    } else {
        if (loc_prev >= 0) {
            tree_prev(right) = left;
        }
    }

    tree_parent(node) = NULL;
    tree_next(node) = NULL;
    if (loc_prev >= 0) {
        tree_prev(node) = NULL;
    }
}

int cx_tree_search(
        const void *root,
        size_t depth,
        const void *node,
        cx_tree_search_func sfunc,
        void **result,
        ptrdiff_t loc_children,
        ptrdiff_t loc_next
) {
    // help avoiding bugs due to uninitialized memory
    assert(result != NULL);
    *result = NULL;

    // remember return value for best match
    int ret = sfunc(root, node);
    if (ret < 0) {
        // not contained, exit
        return -1;
    }
    *result = (void*) root;
    // if root is already exact match, exit
    if (ret == 0) {
        return 0;
    }

    // when depth is one, we are already done
    if (depth == 1) {
        return ret;
    }

    // special case: indefinite depth
    if (depth == 0) {
        depth = SIZE_MAX;
    }

    // create an iterator
    CxTreeIterator iter = cx_tree_iterator(
            (void*) root, false, loc_children, loc_next
    );

    // skip root, we already handled it
    cxIteratorNext(iter);

    // loop through the remaining tree
    cx_foreach(void *, elem, iter) {
        // investigate the current node
        int ret_elem = sfunc(elem, node);
        if (ret_elem == 0) {
            // if found, exit the search
            *result = elem;
            ret = 0;
            break;
        } else if (ret_elem > 0 && ret_elem < ret) {
            // new distance is better
            *result = elem;
            ret = ret_elem;
        } else if (ret_elem < 0 || ret_elem > ret) {
            // not contained or distance is worse, skip entire subtree
            cxTreeIteratorContinue(iter);
        }

        // when we reached the max depth, skip the subtree
        if (iter.depth == depth) {
            cxTreeIteratorContinue(iter);
        }
    }

    // dispose the iterator as we might have exited the loop early
    cxTreeIteratorDispose(&iter);

    assert(ret < 0 || *result != NULL);
    return ret;
}

int cx_tree_search_data(
        const void *root,
        size_t depth,
        const void *data,
        cx_tree_search_data_func sfunc,
        void **result,
        ptrdiff_t loc_children,
        ptrdiff_t loc_next
) {
    // it is basically the same implementation
    return cx_tree_search(
            root, depth, data,
            (cx_tree_search_func) sfunc,
            result,
            loc_children, loc_next);
}

static bool cx_tree_iter_valid(const void *it) {
    const struct cx_tree_iterator_s *iter = it;
    return iter->node != NULL;
}

static void *cx_tree_iter_current(const void *it) {
    const struct cx_tree_iterator_s *iter = it;
    return iter->node;
}

static void cx_tree_iter_next(void *it) {
    struct cx_tree_iterator_s *iter = it;
    ptrdiff_t const loc_next = iter->loc_next;
    ptrdiff_t const loc_children = iter->loc_children;
    // protect us from misuse
    if (!iter->base.valid(iter)) return;

    void *children;

    // check if we are currently exiting or entering nodes
    if (iter->exiting) {
        children = NULL;
        // skipping on exit is pointless, just clear the flag
        iter->skip = false;
    } else {
        if (iter->skip) {
            // skip flag is set, pretend that there are no children
            iter->skip = false;
            children = NULL;
        } else {
            // try to enter the children (if any)
            children = tree_children(iter->node);
        }
    }

    if (children == NULL) {
        // search for the next node
        void *next = NULL;
        cx_tree_iter_search_next:
        // check if there is a sibling, but only if we are not a (subtree-)root
        if (iter->exiting) {
            next = iter->node_next;
        } else if (iter->depth > 1) {
            next = tree_next(iter->node);
            iter->node_next = next;
        }
        if (next == NULL) {
            // no sibling, we are done with this node and exit
            if (iter->visit_on_exit && !iter->exiting) {
                // iter is supposed to visit the node again
                iter->exiting = true;
            } else {
                iter->exiting = false;
                if (iter->depth == 1) {
                    // there is no parent - we have iterated the entire tree
                    // invalidate the iterator and free the node stack
                    iter->node = iter->node_next = NULL;
                    iter->stack_capacity = iter->depth = 0;
                    cxFreeDefault(iter->stack);
                    iter->stack = NULL;
                } else {
                    // the parent node can be obtained from the top of stack
                    // this way we can avoid the loc_parent in the iterator
                    iter->depth--;
                    iter->node = iter->stack[iter->depth - 1];
                    // retry with the parent node to find a sibling
                    goto cx_tree_iter_search_next;
                }
            }
        } else {
            if (iter->visit_on_exit && !iter->exiting) {
                // iter is supposed to visit the node again
                iter->exiting = true;
            } else {
                iter->exiting = false;
                // move to the sibling
                iter->counter++;
                iter->node = next;
                // new top of stack is the sibling
                iter->stack[iter->depth - 1] = next;
            }
        }
    } else {
        // node has children, push the first child onto the stack and enter it
        cx_array_simple_add(iter->stack, children);
        iter->node = children;
        iter->counter++;
    }
}

CxTreeIterator cx_tree_iterator(
        void *root,
        bool visit_on_exit,
        ptrdiff_t loc_children,
        ptrdiff_t loc_next
) {
    CxTreeIterator iter;
    iter.loc_children = loc_children;
    iter.loc_next = loc_next;
    iter.visit_on_exit = visit_on_exit;

    // initialize members
    iter.node_next = NULL;
    iter.exiting = false;
    iter.skip = false;

    // assign base iterator functions
    iter.base.mutating = false;
    iter.base.remove = false;
    iter.base.current_impl = NULL;
    iter.base.valid = cx_tree_iter_valid;
    iter.base.next = cx_tree_iter_next;
    iter.base.current = cx_tree_iter_current;

    // visit the root node
    iter.node = root;
    if (root != NULL) {
        iter.stack_capacity = 16;
        iter.stack = cxMallocDefault(sizeof(void *) * 16);
        iter.stack[0] = root;
        iter.counter = 1;
        iter.depth = 1;
    } else {
        iter.stack_capacity = 0;
        iter.stack = NULL;
        iter.counter = 0;
        iter.depth = 0;
    }

    return iter;
}

static bool cx_tree_visitor_valid(const void *it) {
    const struct cx_tree_visitor_s *iter = it;
    return iter->node != NULL;
}

static void *cx_tree_visitor_current(const void *it) {
    const struct cx_tree_visitor_s *iter = it;
    return iter->node;
}

cx_attr_nonnull
static void cx_tree_visitor_enqueue_siblings(
        struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) {
    node = tree_next(node);
    while (node != NULL) {
        struct cx_tree_visitor_queue_s *q;
        q = cxMallocDefault(sizeof(struct cx_tree_visitor_queue_s));
        q->depth = iter->queue_last->depth;
        q->node = node;
        iter->queue_last->next = q;
        iter->queue_last = q;
        node = tree_next(node);
    }
    iter->queue_last->next = NULL;
}

static void cx_tree_visitor_next(void *it) {
    struct cx_tree_visitor_s *iter = it;
    // protect us from misuse
    if (!iter->base.valid(iter)) return;

    ptrdiff_t const loc_next = iter->loc_next;
    ptrdiff_t const loc_children = iter->loc_children;

    // add the children of the current node to the queue
    // unless the skip flag is set
    void *children;
    if (iter->skip) {
        iter->skip = false;
        children = NULL;
    } else {
        children = tree_children(iter->node);
    }
    if (children != NULL) {
        struct cx_tree_visitor_queue_s *q;
        q = cxMallocDefault(sizeof(struct cx_tree_visitor_queue_s));
        q->depth = iter->depth + 1;
        q->node = children;
        if (iter->queue_last == NULL) {
            assert(iter->queue_next == NULL);
            iter->queue_next = q;
        } else {
            iter->queue_last->next = q;
        }
        iter->queue_last = q;
        cx_tree_visitor_enqueue_siblings(iter, children, loc_next);
    }

    // check if there is a next node
    if (iter->queue_next == NULL) {
        iter->node = NULL;
        return;
    }

    // dequeue the next node
    iter->node = iter->queue_next->node;
    iter->depth = iter->queue_next->depth;
    {
        struct cx_tree_visitor_queue_s *q = iter->queue_next;
        iter->queue_next = q->next;
        if (iter->queue_next == NULL) {
            assert(iter->queue_last == q);
            iter->queue_last = NULL;
        }
        cxFreeDefault(q);
    }

    // increment the node counter
    iter->counter++;
}

CxTreeVisitor cx_tree_visitor(
        void *root,
        ptrdiff_t loc_children,
        ptrdiff_t loc_next
) {
    CxTreeVisitor iter;
    iter.loc_children = loc_children;
    iter.loc_next = loc_next;

    // initialize members
    iter.skip = false;
    iter.queue_next = NULL;
    iter.queue_last = NULL;

    // assign base iterator functions
    iter.base.mutating = false;
    iter.base.remove = false;
    iter.base.current_impl = NULL;
    iter.base.valid = cx_tree_visitor_valid;
    iter.base.next = cx_tree_visitor_next;
    iter.base.current = cx_tree_visitor_current;

    // visit the root node
    iter.node = root;
    if (root != NULL) {
        iter.counter = 1;
        iter.depth = 1;
    } else {
        iter.counter = 0;
        iter.depth = 0;
    }

    return iter;
}

static void cx_tree_add_link_duplicate(
        void *original, void *duplicate,
        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev, ptrdiff_t loc_next
) {
    void *shared_parent = tree_parent(original);
    if (shared_parent == NULL) {
        cx_tree_link(original, duplicate, cx_tree_ptr_locations);
    } else {
        cx_tree_link(shared_parent, duplicate, cx_tree_ptr_locations);
    }
}

static void cx_tree_add_link_new(
        void *parent, void *node, cx_tree_search_func sfunc,
        ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev, ptrdiff_t loc_next
) {
    // check the current children one by one,
    // if they could be children of the new node
    void *child = tree_children(parent);
    while (child != NULL) {
        void *next = tree_next(child);

        if (sfunc(node, child) > 0) {
            // the sibling could be a child -> re-link
            cx_tree_link(node, child, cx_tree_ptr_locations);
        }

        child = next;
    }

    // add new node as new child
    cx_tree_link(parent, node, cx_tree_ptr_locations);
}

int cx_tree_add(
        const void *src,
        cx_tree_search_func sfunc,
        cx_tree_node_create_func cfunc,
        void *cdata,
        void **cnode,
        void *root,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    *cnode = cfunc(src, cdata);
    if (*cnode == NULL) return 1;
    cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations);

    void *match = NULL;
    int result = cx_tree_search(
            root,
            0,
            *cnode,
            sfunc,
            &match,
            loc_children,
            loc_next
    );

    if (result < 0) {
        // node does not fit into the tree - return non-zero value
        return 1;
    } else if (result == 0) {
        // data already found in the tree, link duplicate
        cx_tree_add_link_duplicate(match, *cnode, cx_tree_ptr_locations);
    } else {
        // closest match found, add new node
        cx_tree_add_link_new(match, *cnode, sfunc, cx_tree_ptr_locations);
    }

    return 0;
}

unsigned int cx_tree_add_look_around_depth = 3;

size_t cx_tree_add_iter(
        struct cx_iterator_base_s *iter,
        size_t num,
        cx_tree_search_func sfunc,
        cx_tree_node_create_func cfunc,
        void *cdata,
        void **failed,
        void *root,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    // erase the failed pointer
    *failed = NULL;

    // iter not valid? cancel...
    if (!iter->valid(iter)) return 0;

    size_t processed = 0;
    void *current_node = root;
    const void *elem;

    for (void **eptr; processed < num &&
         iter->valid(iter) && (eptr = iter->current(iter)) != NULL;
         iter->next(iter)) {
        elem = *eptr;

        // create the new node
        void *new_node = cfunc(elem, cdata);
        if (new_node == NULL) return processed;
        cx_tree_zero_pointers(new_node, cx_tree_ptr_locations);

        // start searching from current node
        void *match;
        int result;
        unsigned int look_around_retries = cx_tree_add_look_around_depth;
        cx_tree_add_look_around_retry:
        result = cx_tree_search(
                current_node,
                0,
                new_node,
                sfunc,
                &match,
                loc_children,
                loc_next
        );

        if (result < 0) {
            // traverse upwards and try to find better parents
            void *parent = tree_parent(current_node);
            if (parent != NULL) {
                if (look_around_retries > 0) {
                    look_around_retries--;
                    current_node = parent;
                } else {
                    // look around retries exhausted, start from the root
                    current_node = root;
                }
                goto cx_tree_add_look_around_retry;
            } else {
                // no parents. so we failed
                *failed = new_node;
                return processed;
            }
        } else if (result == 0) {
            // data already found in the tree, link duplicate
            cx_tree_add_link_duplicate(match, new_node, cx_tree_ptr_locations);
            // but stick with the original match, in case we needed a new root
            current_node = match;
        } else {
            // closest match found, add new node as child
            cx_tree_add_link_new(match, new_node, sfunc,
                                 cx_tree_ptr_locations);
            current_node = match;
        }

        processed++;
    }
    return processed;
}

size_t cx_tree_add_array(
        const void *src,
        size_t num,
        size_t elem_size,
        cx_tree_search_func sfunc,
        cx_tree_node_create_func cfunc,
        void *cdata,
        void **failed,
        void *root,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    // erase failed pointer
    *failed = NULL;

    // super special case: zero elements
    if (num == 0) {
        return 0;
    }

    // special case: one element does not need an iterator
    if (num == 1) {
        void *node;
        if (0 == cx_tree_add(
                src, sfunc, cfunc, cdata, &node, root,
                loc_parent, loc_children, loc_last_child,
                loc_prev, loc_next)) {
            return 1;
        } else {
            *failed = node;
            return 0;
        }
    }

    // otherwise, create iterator and hand over to other function
    CxIterator iter = cxIterator(src, elem_size, num);
    return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc,
                            cfunc, cdata, failed, root,
                            loc_parent, loc_children, loc_last_child,
                            loc_prev, loc_next);
}

static int cx_tree_default_insert_element(
        CxTree *tree,
        const void *data
) {
    void *node;
    if (tree->root == NULL) {
        node = tree->node_create(data, tree);
        if (node == NULL) return 1;
        cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
        tree->root = node;
        tree->size = 1;
        return 0;
    }
    int result = cx_tree_add(data, tree->search, tree->node_create,
                tree, &node, tree->root, cx_tree_node_layout(tree));
    if (0 == result) {
        tree->size++;
    } else {
        cxFree(tree->allocator, node);
    }
    return result;
}

static size_t cx_tree_default_insert_many(
        CxTree *tree,
        CxIteratorBase *iter,
        size_t n
) {
    size_t ins = 0;
    if (!iter->valid(iter)) return 0;
    if (tree->root == NULL) {
        // use the first element from the iter to create the root node
        void **eptr = iter->current(iter);
        void *node = tree->node_create(*eptr, tree);
        if (node == NULL) return 0;
        cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
        tree->root = node;
        ins = 1;
        iter->next(iter);
    }
    void *failed;
    ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create,
                                  tree, &failed, tree->root, cx_tree_node_layout(tree));
    tree->size += ins;
    if (ins < n) {
        cxFree(tree->allocator, failed);
    }
    return ins;
}

static void *cx_tree_default_find(
        CxTree *tree,
        const void *subtree,
        const void *data,
        size_t depth
) {
    if (tree->root == NULL) return NULL;

    void *found;
    if (0 == cx_tree_search_data(
            subtree,
            depth,
            data,
            tree->search_data,
            &found,
            tree->loc_children,
            tree->loc_next
    )) {
        return found;
    } else {
        return NULL;
    }
}

static cx_tree_class cx_tree_default_class = {
        cx_tree_default_insert_element,
        cx_tree_default_insert_many,
        cx_tree_default_find
};

CxTree *cxTreeCreate(
        const CxAllocator *allocator,
        cx_tree_node_create_func create_func,
        cx_tree_search_func search_func,
        cx_tree_search_data_func search_data_func,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    if (allocator == NULL) {
        allocator = cxDefaultAllocator;
    }
    assert(create_func != NULL);
    assert(search_func != NULL);
    assert(search_data_func != NULL);

    CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
    if (tree == NULL) return NULL;

    tree->cl = &cx_tree_default_class;
    tree->allocator = allocator;
    tree->node_create = create_func;
    tree->search = search_func;
    tree->search_data = search_data_func;
    tree->simple_destructor = NULL;
    tree->advanced_destructor = (cx_destructor_func2) cxFree;
    tree->destructor_data = (void *) allocator;
    tree->loc_parent = loc_parent;
    tree->loc_children = loc_children;
    tree->loc_last_child = loc_last_child;
    tree->loc_prev = loc_prev;
    tree->loc_next = loc_next;
    tree->root = NULL;
    tree->size = 0;

    return tree;
}

void cxTreeFree(CxTree *tree) {
    if (tree == NULL) return;
    if (tree->root != NULL) {
        cxTreeClear(tree);
    }
    cxFree(tree->allocator, tree);
}

CxTree *cxTreeCreateWrapped(
        const CxAllocator *allocator,
        void *root,
        ptrdiff_t loc_parent,
        ptrdiff_t loc_children,
        ptrdiff_t loc_last_child,
        ptrdiff_t loc_prev,
        ptrdiff_t loc_next
) {
    if (allocator == NULL) {
        allocator = cxDefaultAllocator;
    }
    assert(root != NULL);

    CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
    if (tree == NULL) return NULL;

    tree->cl = &cx_tree_default_class;
    // set the allocator anyway, just in case...
    tree->allocator = allocator;
    tree->node_create = NULL;
    tree->search = NULL;
    tree->search_data = NULL;
    tree->simple_destructor = NULL;
    tree->advanced_destructor = NULL;
    tree->destructor_data = NULL;
    tree->loc_parent = loc_parent;
    tree->loc_children = loc_children;
    tree->loc_last_child = loc_last_child;
    tree->loc_prev = loc_prev;
    tree->loc_next = loc_next;
    tree->root = root;
    tree->size = cxTreeSubtreeSize(tree, root);
    return tree;
}

void cxTreeSetParent(
        CxTree *tree,
        void *parent,
        void *child
) {
    size_t loc_parent = tree->loc_parent;
    if (tree_parent(child) == NULL) {
        tree->size++;
    }
    cx_tree_link(parent, child, cx_tree_node_layout(tree));
}

void cxTreeAddChildNode(
        CxTree *tree,
        void *parent,
        void *child
) {
    cx_tree_link(parent, child, cx_tree_node_layout(tree));
    tree->size++;
}

int cxTreeAddChild(
        CxTree *tree,
        void *parent,
        const void *data) {
    void *node = tree->node_create(data, tree);
    if (node == NULL) return 1;
    cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
    cx_tree_link(parent, node, cx_tree_node_layout(tree));
    tree->size++;
    return 0;
}

size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) {
    CxTreeVisitor visitor = cx_tree_visitor(
            subtree_root,
            tree->loc_children,
            tree->loc_next
    );
    while (cxIteratorValid(visitor)) {
        cxIteratorNext(visitor);
    }
    return visitor.counter;
}

size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) {
    CxTreeVisitor visitor = cx_tree_visitor(
            subtree_root,
            tree->loc_children,
            tree->loc_next
    );
    while (cxIteratorValid(visitor)) {
        cxIteratorNext(visitor);
    }
    return visitor.depth;
}

size_t cxTreeDepth(CxTree *tree) {
    CxTreeVisitor visitor = cx_tree_visitor(
            tree->root, tree->loc_children, tree->loc_next
    );
    while (cxIteratorValid(visitor)) {
        cxIteratorNext(visitor);
    }
    return visitor.depth;
}

int cxTreeRemoveNode(
        CxTree *tree,
        void *node,
        cx_tree_relink_func relink_func
) {
    if (node == tree->root) return 1;

    // determine the new parent
    ptrdiff_t loc_parent = tree->loc_parent;
    void *new_parent = tree_parent(node);

    // first, unlink from the parent
    cx_tree_unlink(node, cx_tree_node_layout(tree));

    // then relink each child
    ptrdiff_t loc_children = tree->loc_children;
    ptrdiff_t loc_next = tree->loc_next;
    void *child = tree_children(node);
    while (child != NULL) {
        // forcibly set the parent to NULL - we do not use the unlink function
        // because that would unnecessarily modify the children linked list
        tree_parent(child) = NULL;

        // update contents, if required
        if (relink_func != NULL) {
            relink_func(child, node, new_parent);
        }

        // link to new parent
        cx_tree_link(new_parent, child, cx_tree_node_layout(tree));

        // proceed to next child
        child = tree_next(child);
    }

    // clear the linked list of the removed node
    tree_children(node) = NULL;
    ptrdiff_t loc_last_child = tree->loc_last_child;
    if (loc_last_child >= 0) tree_last_child(node) = NULL;

    // the tree now has one member less
    tree->size--;

    return 0;
}

void cxTreeRemoveSubtree(CxTree *tree, void *node) {
    if (node == tree->root) {
        tree->root = NULL;
        tree->size = 0;
        return;
    }
    size_t subtree_size = cxTreeSubtreeSize(tree, node);
    cx_tree_unlink(node, cx_tree_node_layout(tree));
    tree->size -= subtree_size;
}

int cxTreeDestroyNode(
        CxTree *tree,
        void *node,
        cx_tree_relink_func relink_func
) {
    int result = cxTreeRemoveNode(tree, node, relink_func);
    if (result == 0) {
        if (tree->simple_destructor) {
            tree->simple_destructor(node);
        }
        if (tree->advanced_destructor) {
            tree->advanced_destructor(tree->destructor_data, node);
        }
        return 0;
    } else {
        return result;
    }
}

void cxTreeDestroySubtree(CxTree *tree, void *node) {
    cx_tree_unlink(node, cx_tree_node_layout(tree));
    CxTreeIterator iter = cx_tree_iterator(
            node, true,
            tree->loc_children, tree->loc_next
    );
    cx_foreach(void *, child, iter) {
        if (iter.exiting) {
            if (tree->simple_destructor) {
                tree->simple_destructor(child);
            }
            if (tree->advanced_destructor) {
                tree->advanced_destructor(tree->destructor_data, child);
            }
        }
    }
    tree->size -= iter.counter;
    if (node == tree->root) {
        tree->root = NULL;
    }
}

mercurial