// // immer: immutable data structures for C++ // Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente // // This software is distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // #pragma once #include #include #include #include #include #include #include namespace immer { namespace detail { namespace rbts { template struct rbtree { using node_t = node; using edit_t = typename node_t::edit_t; using owner_t = typename MemoryPolicy::transience_t::owner; size_t size; shift_t shift; node_t* root; node_t* tail; static const rbtree& empty() { static const rbtree empty_{ 0, BL, node_t::make_inner_n(0u), node_t::make_leaf_n(0u)}; return empty_; } template static auto from_initializer_list(std::initializer_list values) { auto e = owner_t{}; auto result = rbtree{empty()}; for (auto&& v : values) result.push_back_mut(e, v); return result; } template , bool> = true> static auto from_range(Iter first, Sent last) { auto e = owner_t{}; auto result = rbtree{empty()}; for (; first != last; ++first) result.push_back_mut(e, *first); return result; } static auto from_fill(size_t n, T v) { auto e = owner_t{}; auto result = rbtree{empty()}; while (n-- > 0) result.push_back_mut(e, v); return result; } rbtree(size_t sz, shift_t sh, node_t* r, node_t* t) : size{sz} , shift{sh} , root{r} , tail{t} { assert(check_tree()); } rbtree(const rbtree& other) : rbtree{other.size, other.shift, other.root, other.tail} { inc(); } rbtree(rbtree&& other) : rbtree{empty()} { swap(*this, other); } rbtree& operator=(const rbtree& other) { auto next = other; swap(*this, next); return *this; } rbtree& operator=(rbtree&& other) { swap(*this, other); return *this; } friend void swap(rbtree& x, rbtree& y) { using std::swap; swap(x.size, y.size); swap(x.shift, y.shift); swap(x.root, y.root); swap(x.tail, y.tail); } ~rbtree() { dec(); } void inc() const { root->inc(); tail->inc(); } void dec() const { traverse(dec_visitor()); } auto tail_size() const { return size ? ((size - 1) & mask) +1 : 0; } auto tail_offset() const { return size ? (size - 1) & ~mask : 0; } template void traverse(Visitor v, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; if (tail_off) make_regular_sub_pos(root, shift, tail_off).visit(v, args...); else make_empty_regular_pos(root).visit(v, args...); make_leaf_sub_pos(tail, tail_size).visit(v, args...); } template void traverse(Visitor v, size_t first, size_t last, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; if (first < tail_off) make_regular_sub_pos(root, shift, tail_off) .visit(v, first, last < tail_off ? last : tail_off, args...); if (last > tail_off) make_leaf_sub_pos(tail, tail_size) .visit(v, first > tail_off ? first - tail_off : 0, last - tail_off, args...); } template bool traverse_p(Visitor v, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; return (tail_off ? make_regular_sub_pos(root, shift, tail_off) .visit(v, args...) : make_empty_regular_pos(root).visit(v, args...)) && make_leaf_sub_pos(tail, tail_size).visit(v, args...); } template bool traverse_p(Visitor v, size_t first, size_t last, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; return (first < tail_off ? make_regular_sub_pos(root, shift, tail_off) .visit(v, first, last < tail_off ? last : tail_off, args...) : true) && (last > tail_off ? make_leaf_sub_pos(tail, tail_size) .visit(v, first > tail_off ? first - tail_off : 0, last - tail_off, args...) : true); } template decltype(auto) descend(Visitor v, size_t idx) const { auto tail_off = tail_offset(); return idx >= tail_off ? make_leaf_descent_pos(tail).visit(v, idx) : visit_regular_descent(root, shift, v, idx); } template void for_each_chunk(Fn&& fn) const { traverse(for_each_chunk_visitor{}, std::forward(fn)); } template void for_each_chunk(size_t first, size_t last, Fn&& fn) const { traverse(for_each_chunk_i_visitor{}, first, last, std::forward(fn)); } template bool for_each_chunk_p(Fn&& fn) const { return traverse_p(for_each_chunk_p_visitor{}, std::forward(fn)); } template bool for_each_chunk_p(size_t first, size_t last, Fn&& fn) const { return traverse_p( for_each_chunk_p_i_visitor{}, first, last, std::forward(fn)); } bool equals(const rbtree& other) const { if (size != other.size) return false; if (size == 0) return true; return (size <= branches || make_regular_sub_pos(root, shift, tail_offset()) .visit(equals_visitor{}, other.root)) && make_leaf_sub_pos(tail, tail_size()) .visit(equals_visitor{}, other.tail); } void ensure_mutable_tail(edit_t e, count_t n) { if (!tail->can_mutate(e)) { auto new_tail = node_t::copy_leaf_e(e, tail, n); dec_leaf(tail, n); tail = new_tail; } } void push_back_mut(edit_t e, T value) { auto tail_off = tail_offset(); auto ts = size - tail_off; if (ts < branches) { ensure_mutable_tail(e, ts); new (&tail->leaf()[ts]) T{std::move(value)}; } else { auto new_tail = node_t::make_leaf_e(e, std::move(value)); try { if (tail_off == size_t{branches} << shift) { auto new_root = node_t::make_inner_e(e); try { auto path = node_t::make_path_e(e, shift, tail); new_root->inner()[0] = root; new_root->inner()[1] = path; root = new_root; tail = new_tail; shift += B; } catch (...) { node_t::delete_inner_e(new_root); throw; } } else if (tail_off) { auto new_root = make_regular_sub_pos(root, shift, tail_off) .visit(push_tail_mut_visitor{}, e, tail); root = new_root; tail = new_tail; } else { auto new_root = node_t::make_path_e(e, shift, tail); assert(tail_off == 0); dec_empty_regular(root); root = new_root; tail = new_tail; } } catch (...) { node_t::delete_leaf(new_tail, 1); throw; } } ++size; } rbtree push_back(T value) const { auto tail_off = tail_offset(); auto ts = size - tail_off; if (ts < branches) { auto new_tail = node_t::copy_leaf_emplace(tail, ts, std::move(value)); return {size + 1, shift, root->inc(), new_tail}; } else { auto new_tail = node_t::make_leaf_n(1, std::move(value)); try { if (tail_off == size_t{branches} << shift) { auto new_root = node_t::make_inner_n(2); try { auto path = node_t::make_path(shift, tail); new_root->inner()[0] = root; new_root->inner()[1] = path; root->inc(); tail->inc(); return {size + 1, shift + B, new_root, new_tail}; } catch (...) { node_t::delete_inner(new_root, 2); throw; } } else if (tail_off) { auto new_root = make_regular_sub_pos(root, shift, tail_off) .visit(push_tail_visitor{}, tail); tail->inc(); return {size + 1, shift, new_root, new_tail}; } else { auto new_root = node_t::make_path(shift, tail); tail->inc(); return {size + 1, shift, new_root, new_tail}; } } catch (...) { node_t::delete_leaf(new_tail, 1); throw; } } } const T* array_for(size_t index) const { return descend(array_for_visitor(), index); } T& get_mut(edit_t e, size_t idx) { auto tail_off = tail_offset(); if (idx >= tail_off) { ensure_mutable_tail(e, size - tail_off); return tail->leaf()[idx & mask]; } else { return make_regular_sub_pos(root, shift, tail_off) .visit(get_mut_visitor{}, idx, e, &root); } } const T& get(size_t index) const { return descend(get_visitor(), index); } const T& get_check(size_t index) const { if (index >= size) throw std::out_of_range{"index out of range"}; return descend(get_visitor(), index); } const T& front() const { return get(0); } const T& back() const { return tail->leaf()[(size - 1) & mask]; } template void update_mut(edit_t e, size_t idx, FnT&& fn) { auto& elem = get_mut(e, idx); elem = std::forward(fn)(std::move(elem)); } template rbtree update(size_t idx, FnT&& fn) const { auto tail_off = tail_offset(); if (idx >= tail_off) { auto tail_size = size - tail_off; auto new_tail = make_leaf_sub_pos(tail, tail_size) .visit(update_visitor{}, idx - tail_off, fn); return {size, shift, root->inc(), new_tail}; } else { auto new_root = make_regular_sub_pos(root, shift, tail_off) .visit(update_visitor{}, idx, fn); return {size, shift, new_root, tail->inc()}; } } void assoc_mut(edit_t e, size_t idx, T value) { update_mut(e, idx, [&](auto&&) { return std::move(value); }); } rbtree assoc(size_t idx, T value) const { return update(idx, [&](auto&&) { return std::move(value); }); } rbtree take(size_t new_size) const { auto tail_off = tail_offset(); if (new_size == 0) { return empty(); } else if (new_size >= size) { return *this; } else if (new_size > tail_off) { auto new_tail = node_t::copy_leaf(tail, new_size - tail_off); return {new_size, shift, root->inc(), new_tail}; } else { using std::get; auto l = new_size - 1; auto v = slice_right_visitor(); auto r = make_regular_sub_pos(root, shift, tail_off).visit(v, l); auto new_shift = get<0>(r); auto new_root = get<1>(r); auto new_tail = get<3>(r); if (new_root) { IMMER_ASSERT_TAGGED(new_root->compute_shift() == get<0>(r)); assert(new_root->check(new_shift, new_size - get<2>(r))); return {new_size, new_shift, new_root, new_tail}; } else { return {new_size, BL, empty().root->inc(), new_tail}; } } } void take_mut(edit_t e, size_t new_size) { auto tail_off = tail_offset(); if (new_size == 0) { // todo: more efficient? *this = empty(); } else if (new_size >= size) { return; } else if (new_size > tail_off) { auto ts = size - tail_off; auto newts = new_size - tail_off; if (tail->can_mutate(e)) { destroy_n(tail->leaf() + newts, ts - newts); } else { auto new_tail = node_t::copy_leaf_e(e, tail, newts); dec_leaf(tail, ts); tail = new_tail; } size = new_size; return; } else { using std::get; auto l = new_size - 1; auto v = slice_right_mut_visitor(); auto r = make_regular_sub_pos(root, shift, tail_off).visit(v, l, e); auto new_shift = get<0>(r); auto new_root = get<1>(r); auto new_tail = get<3>(r); if (new_root) { root = new_root; shift = new_shift; } else { root = empty().root->inc(); shift = BL; } dec_leaf(tail, size - tail_off); size = new_size; tail = new_tail; return; } } bool check_tree() const { #if IMMER_DEBUG_DEEP_CHECK assert(shift >= BL); assert(tail_offset() <= size); assert(check_root()); assert(check_tail()); #endif return true; } bool check_tail() const { #if IMMER_DEBUG_DEEP_CHECK if (tail_size() > 0) assert(tail->check(0, tail_size())); #endif return true; } bool check_root() const { #if IMMER_DEBUG_DEEP_CHECK if (tail_offset() > 0) assert(root->check(shift, tail_offset())); else { IMMER_ASSERT_TAGGED(root->kind() == node_t::kind_t::inner); assert(shift == BL); } #endif return true; } }; } // namespace rbts } // namespace detail } // namespace immer