├── .clang-format ├── .clang-tidy ├── .github ├── funding.yml └── workflows │ └── ci.yml ├── CMakeLists.txt ├── LICENSE ├── README.adoc ├── include └── dryad │ ├── _detail │ ├── assert.hpp │ ├── config.hpp │ ├── hash_table.hpp │ ├── iterator.hpp │ ├── memory_resource.hpp │ └── std.hpp │ ├── abstract_node.hpp │ ├── arena.hpp │ ├── hash_algorithm.hpp │ ├── hash_forest.hpp │ ├── node.hpp │ ├── node_map.hpp │ ├── symbol.hpp │ ├── symbol_table.hpp │ └── tree.hpp ├── src └── CMakeLists.txt └── tests ├── CMakeLists.txt ├── doctest_main.cpp └── dryad ├── CMakeLists.txt ├── abstract_node.cpp ├── arena.cpp ├── detail └── std.cpp ├── hash_forest.cpp ├── node.cpp ├── node_map.cpp ├── symbol.cpp ├── symbol_table.cpp ├── tree.cpp └── tuple_node.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -4 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveAssignments: true 4 | AlignConsecutiveDeclarations: true 5 | AlignEscapedNewlinesLeft: Right 6 | AlignOperands: true 7 | AlignTrailingComments: true 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: false 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: Empty 12 | AllowShortIfStatementsOnASingleLine: false 13 | AllowShortLoopsOnASingleLine: false 14 | AlwaysBreakAfterReturnType: None 15 | AlwaysBreakBeforeMultilineStrings: false 16 | AlwaysBreakTemplateDeclarations: true 17 | BinPackArguments: true 18 | BinPackParameters: true 19 | BreakBeforeBraces: Custom 20 | BraceWrapping: 21 | AfterClass: true 22 | AfterControlStatement: true 23 | AfterEnum: true 24 | AfterFunction: true 25 | AfterNamespace: true 26 | AfterStruct: true 27 | AfterUnion: true 28 | AfterExternBlock: true 29 | BeforeCatch: true 30 | BeforeElse: true 31 | SplitEmptyFunction: false 32 | SplitEmptyRecord: false 33 | SplitEmptyNamespace: false 34 | BreakBeforeBinaryOperators: All 35 | BreakBeforeTernaryOperators: true 36 | BreakConstructorInitializers: BeforeColon 37 | BreakStringLiterals: false 38 | ColumnLimit: 100 39 | CompactNamespaces: true 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 41 | ConstructorInitializerIndentWidth: 0 42 | ContinuationIndentWidth: 4 43 | Cpp11BracedListStyle: true 44 | DerivePointerAlignment: false 45 | FixNamespaceComments: true 46 | IncludeBlocks: Preserve 47 | IndentCaseLabels: false 48 | IndentPPDirectives: AfterHash 49 | IndentWidth: 4 50 | IndentWrappedFunctionNames: true 51 | KeepEmptyLinesAtTheStartOfBlocks: false 52 | Language: Cpp 53 | MaxEmptyLinesToKeep: 1 54 | NamespaceIndentation: Inner 55 | PenaltyBreakBeforeFirstCallParameter: 19937 56 | PenaltyReturnTypeOnItsOwnLine: 19937 57 | PointerAlignment: Left 58 | ReflowComments: true 59 | SortIncludes: true 60 | SortUsingDeclarations: true 61 | SpaceAfterCStyleCast: false 62 | SpaceAfterTemplateKeyword: true 63 | SpaceBeforeAssignmentOperators: true 64 | SpaceBeforeParens: ControlStatements 65 | SpaceInEmptyParentheses: false 66 | SpacesBeforeTrailingComments: 1 67 | SpacesInAngles: false 68 | SpacesInCStyleCastParentheses: false 69 | SpacesInParentheses: false 70 | SpacesInSquareBrackets: false 71 | Standard: Cpp11 72 | TabWidth: 4 73 | UseTab: Never 74 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | -*, 3 | bugprone-*, 4 | -bugprone-exception-escape, 5 | -bugprone-easily-swappable-parameters, 6 | clang-analyzer-*, 7 | misc-*, 8 | -misc-definitions-in-headers, 9 | -misc-non-private-member-variables-in-classes, 10 | -misc-no-recursion, 11 | -misc-static-assert, 12 | -misc-unused-alias-decls, 13 | performance-*, 14 | readability-*, 15 | -readability-braces-around-statements, 16 | -readability-convert-member-functions-to-static, 17 | -readability-else-after-return, 18 | -readability-function-cognitive-complexity, 19 | -readability-function-size, 20 | -readability-identifier-length, 21 | -readability-magic-numbers, 22 | -readability-make-member-function-const, 23 | -readability-misleading-indentation, 24 | -readability-named-parameter, 25 | -readability-qualified-auto, 26 | -readability-redundant-access-specifiers, 27 | -readability-redundant-declaration, 28 | -readability-static-accessed-through-instance, 29 | -readability-uppercase-literal-suffix, 30 | WarningsAsErrors: '*' 31 | FormatStyle: 'file' 32 | HeaderFilterRegex: 'include/dryad/' 33 | 34 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | patreon: foonathan 2 | custom: ['https://jonathanmueller.dev/support-me/'] 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | linux: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | image: 11 | - "gcc:7" 12 | - "gcc:8" 13 | - "gcc:9" 14 | - "gcc:10" 15 | - "gcc:11" 16 | - "gcc:12" 17 | - "clang:6" 18 | - "clang:7" 19 | - "clang:8" 20 | - "clang:9" 21 | - "clang:10" 22 | - "clang:11" 23 | - "clang:12" 24 | - "clang:13" 25 | build_type: [Debug, Release] 26 | 27 | runs-on: ubuntu-latest 28 | container: 29 | image: ghcr.io/foonathan/${{matrix.image}} 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | - name: Create Build Environment 34 | run: cmake -E make_directory build 35 | 36 | - name: Configure 37 | working-directory: build/ 38 | run: cmake -GNinja $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.build_type}} 39 | - name: Build 40 | working-directory: build/ 41 | run: cmake --build . 42 | - name: Test 43 | working-directory: build/ 44 | run: ctest --output-on-failure 45 | 46 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | # SPDX-License-Identifier: BSL-1.0 3 | 4 | cmake_minimum_required(VERSION 3.8) 5 | project(dryad LANGUAGES CXX) 6 | 7 | add_subdirectory(src) 8 | 9 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 10 | cmake_minimum_required(VERSION 3.18) 11 | option(DRYAD_BUILD_TESTS "whether or not tests should be built" ON) 12 | 13 | if(DRYAD_BUILD_TESTS) 14 | enable_testing() 15 | add_subdirectory(tests) 16 | endif() 17 | endif() 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = dryad 2 | 3 | ifdef::env-github[] 4 | image:https://github.com/foonathan/dryad/workflows/CI/badge.svg[Build Status] 5 | endif::[] 6 | 7 | dryad is a work-in-progress library with useful data structures for compiler ASTs. 8 | It is currently highly unstable, not ready for production use, and does not support anything other than a gcc/clang. 9 | 10 | It is developed for my clauf C compiler development: https://github.com/foonathan/clauf. 11 | 12 | == Features 13 | 14 | * Utilities for AST classes. 15 | * String interning. 16 | 17 | == Documentation 18 | 19 | Currently no documentation; you shouldn't use it yet. 20 | 21 | -------------------------------------------------------------------------------- /include/dryad/_detail/assert.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_DETAIL_ASSERT_HPP_INCLUDED 5 | #define DRYAD_DETAIL_ASSERT_HPP_INCLUDED 6 | 7 | #include 8 | 9 | #ifndef DRYAD_ENABLE_ASSERT 10 | 11 | // By default, enable assertions if NDEBUG is not defined. 12 | 13 | # if NDEBUG 14 | # define DRYAD_ENABLE_ASSERT 0 15 | # else 16 | # define DRYAD_ENABLE_ASSERT 1 17 | # endif 18 | 19 | #endif 20 | 21 | #if DRYAD_ENABLE_ASSERT 22 | 23 | // We want assertions: use assert() if that's available, otherwise abort. 24 | // We don't use assert() directly as that's not constexpr. 25 | 26 | # if NDEBUG 27 | 28 | # include 29 | # define DRYAD_PRECONDITION(Expr) ((Expr) ? void(0) : std::abort()) 30 | # define DRYAD_ASSERT(Expr, Msg) ((Expr) ? void(0) : std::abort()) 31 | 32 | # else 33 | 34 | # include 35 | 36 | # define DRYAD_PRECONDITION(Expr) ((Expr) ? void(0) : assert(Expr)) 37 | # define DRYAD_ASSERT(Expr, Msg) ((Expr) ? void(0) : assert((Expr) && (Msg))) 38 | 39 | # endif 40 | 41 | #else 42 | 43 | // We don't want assertions. 44 | 45 | # define DRYAD_PRECONDITION(Expr) static_cast(sizeof(Expr)) 46 | # define DRYAD_ASSERT(Expr, Msg) static_cast(sizeof(Expr)) 47 | 48 | #endif 49 | 50 | #endif // DRYAD_DETAIL_ASSERT_HPP_INCLUDED 51 | 52 | -------------------------------------------------------------------------------- /include/dryad/_detail/config.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_DETAIL_CONFIG_HPP_INCLUDED 5 | #define DRYAD_DETAIL_CONFIG_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | //=== utility traits===// 12 | #define DRYAD_MOV(...) static_cast&&>(__VA_ARGS__) 13 | #define DRYAD_FWD(...) static_cast(__VA_ARGS__) 14 | 15 | #define DRYAD_DECLVAL(...) dryad::_detail::declval<__VA_ARGS__>() 16 | 17 | #define DRYAD_DECAY_DECLTYPE(...) std::decay_t 18 | 19 | #define DRYAD_AS_CONST(Ptr) (const_cast*>(Ptr)) 20 | #define DRYAD_CTHIS DRYAD_AS_CONST(this) 21 | 22 | namespace dryad::_detail 23 | { 24 | template 25 | constexpr bool error = false; 26 | 27 | template 28 | std::add_rvalue_reference_t declval(); 29 | 30 | template 31 | constexpr void swap(T& lhs, T& rhs) 32 | { 33 | T tmp = DRYAD_MOV(lhs); 34 | lhs = DRYAD_MOV(rhs); 35 | rhs = DRYAD_MOV(tmp); 36 | } 37 | 38 | template 39 | constexpr bool is_decayed_same = std::is_same_v, std::decay_t>; 40 | 41 | template 42 | using type_or = std::conditional_t, Fallback, T>; 43 | 44 | template 45 | struct priority_tag; 46 | template <> 47 | struct priority_tag<0> 48 | {}; 49 | template 50 | struct priority_tag : priority_tag 51 | {}; 52 | } // namespace dryad::_detail 53 | 54 | //=== force inline ===// 55 | #ifndef DRYAD_FORCE_INLINE 56 | # if defined(__has_cpp_attribute) 57 | # if __has_cpp_attribute(gnu::always_inline) 58 | # define DRYAD_FORCE_INLINE [[gnu::always_inline]] 59 | # endif 60 | # endif 61 | # 62 | # ifndef DRYAD_FORCE_INLINE 63 | # define DRYAD_FORCE_INLINE inline 64 | # endif 65 | #endif 66 | 67 | //=== empty_member ===// 68 | #ifndef DRYAD_EMPTY_MEMBER 69 | 70 | # if defined(__has_cpp_attribute) 71 | # if __has_cpp_attribute(no_unique_address) 72 | # define DRYAD_HAS_EMPTY_MEMBER 1 73 | # endif 74 | # endif 75 | # ifndef DRYAD_HAS_EMPTY_MEMBER 76 | # define DRYAD_HAS_EMPTY_MEMBER 0 77 | # endif 78 | 79 | # if DRYAD_HAS_EMPTY_MEMBER 80 | # define DRYAD_EMPTY_MEMBER [[no_unique_address]] 81 | # else 82 | # define DRYAD_EMPTY_MEMBER 83 | # endif 84 | 85 | #endif 86 | 87 | #endif // DRYAD_DETAIL_CONFIG_HPP_INCLUDED 88 | 89 | -------------------------------------------------------------------------------- /include/dryad/_detail/hash_table.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_DETAIL_HASH_TABLE_HPP_INCLUDED 5 | #define DRYAD_DETAIL_HASH_TABLE_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #if 0 14 | struct Traits 15 | { 16 | using value_type = T; 17 | 18 | static bool is_unoccupied(const T&); 19 | static void fill_removed(T* data, size_t size); 20 | static void fill_unoccupied(T* data, size_t size); 21 | 22 | bool is_equal(const T& entry, const T& value) const; 23 | template 24 | bool is_equal(const T& entry, const Key& key) const; 25 | 26 | std::size_t hash(const T& value) const; 27 | template 28 | std::size_t hash(const Key& key) const; 29 | }; 30 | #endif 31 | 32 | namespace dryad::_detail 33 | { 34 | /// A simple hash table for trivial keys with linear probing. 35 | /// It is non-owning as it does not store the used memory resource. 36 | template 37 | class hash_table 38 | { 39 | public: 40 | using value_type = typename Traits::value_type; 41 | static_assert(std::is_trivial_v); 42 | 43 | constexpr hash_table() : _table(nullptr), _table_capacity(0), _table_size(0) {} 44 | 45 | template 46 | void free(ResourcePtr resource) 47 | { 48 | if (_table_capacity == 0) 49 | return; 50 | 51 | resource->deallocate(_table, _table_capacity * sizeof(value_type), alignof(value_type)); 52 | _table = nullptr; 53 | _table_size = 0; 54 | _table_capacity = 0; 55 | } 56 | 57 | struct entry_handle 58 | { 59 | hash_table* _self; 60 | value_type* _entry; 61 | bool _valid; 62 | 63 | explicit operator bool() const 64 | { 65 | return _valid; 66 | } 67 | 68 | std::size_t index() const 69 | { 70 | return std::size_t(_entry - _self->_table); 71 | } 72 | 73 | value_type& get() const 74 | { 75 | DRYAD_PRECONDITION(*this); 76 | return *_entry; 77 | } 78 | 79 | void create(const value_type& value) 80 | { 81 | DRYAD_PRECONDITION(!*this); 82 | *_entry = value; 83 | ++_self->_table_size; 84 | _valid = true; 85 | } 86 | 87 | void remove() 88 | { 89 | DRYAD_PRECONDITION(*this); 90 | Traits::fill_removed(_entry, 1); 91 | --_self->_table_size; 92 | _valid = false; 93 | } 94 | }; 95 | 96 | // Looks for an entry in the table, creating one if necessary. 97 | // 98 | // If it is already in the table, returns a pointer to its valid entry. 99 | // 100 | // Otherwise, locates a new entry for that value and returns a pointer to it which is currently 101 | // invalid. Invariants of map are broken until the ptr has been written to. 102 | template 103 | entry_handle lookup_entry(const Key& key, Traits traits = {}) 104 | { 105 | DRYAD_PRECONDITION(_table_size < _table_capacity); 106 | 107 | auto hash = traits.hash(key); 108 | auto table_idx = hash & (_table_capacity - 1); 109 | 110 | while (true) 111 | { 112 | auto entry = _table + table_idx; 113 | if (Traits::is_unoccupied(*entry)) 114 | // We found an empty entry, return it. 115 | return {this, entry, false}; 116 | 117 | // Check whether the entry is the same string. 118 | if (traits.is_equal(*entry, key)) 119 | // It is already in the table, return it. 120 | return {this, entry, true}; 121 | 122 | // Go to next entry. 123 | table_idx = (table_idx + 1) & (_table_capacity - 1); 124 | } 125 | } 126 | template 127 | value_type* lookup(const Key& key, Traits traits = {}) const 128 | { 129 | if (_table_size == 0) 130 | return nullptr; 131 | 132 | auto entry = const_cast(this)->lookup_entry(key, traits); 133 | return entry ? &entry.get() : nullptr; 134 | } 135 | 136 | bool should_rehash() const 137 | { 138 | return _table_size >= _table_capacity / 2; 139 | } 140 | 141 | static constexpr std::size_t to_table_capacity(unsigned long long cap) 142 | { 143 | if (cap < MinTableSize) 144 | return MinTableSize; 145 | 146 | // Round up to next power of two. 147 | return std::size_t(1) << (int(sizeof(cap) * CHAR_BIT) - __builtin_clzll(cap - 1)); 148 | } 149 | 150 | template 151 | void rehash( 152 | ResourcePtr resource, std::size_t new_capacity, Traits traits = {}, 153 | Callback entry_cb = +[](entry_handle, std::size_t) {}) 154 | { 155 | DRYAD_PRECONDITION(new_capacity == to_table_capacity(new_capacity)); 156 | if (new_capacity <= _table_capacity) 157 | return; 158 | 159 | auto old_table = _table; 160 | auto old_capacity = _table_capacity; 161 | 162 | // Allocate a bigger, currently empty table. 163 | _table = static_cast( 164 | resource->allocate(new_capacity * sizeof(value_type), alignof(value_type))); 165 | _table_capacity = new_capacity; 166 | Traits::fill_unoccupied(_table, _table_capacity); 167 | 168 | // Insert existing values into the new table. 169 | if (_table_size > 0) 170 | { 171 | _table_size = 0; 172 | 173 | for (auto entry = old_table; entry != old_table + old_capacity; ++entry) 174 | if (!Traits::is_unoccupied(*entry)) 175 | { 176 | auto new_entry = lookup_entry(*entry, traits); 177 | new_entry.create(*entry); 178 | entry_cb(new_entry, std::size_t(entry - old_table)); 179 | } 180 | } 181 | 182 | if (old_capacity > 0) 183 | resource->deallocate(old_table, old_capacity * sizeof(value_type), alignof(value_type)); 184 | } 185 | template 186 | void rehash( 187 | ResourcePtr resource, Traits traits = {}, 188 | Callback entry_cb = +[](entry_handle, std::size_t) {}) 189 | { 190 | rehash(resource, to_table_capacity(2 * _table_capacity), traits, entry_cb); 191 | } 192 | 193 | //=== access ===// 194 | std::size_t size() const 195 | { 196 | return _table_size; 197 | } 198 | std::size_t capacity() const 199 | { 200 | return _table_capacity; 201 | } 202 | 203 | struct entry_range 204 | { 205 | struct iterator : _detail::forward_iterator_base 206 | { 207 | hash_table* _self; 208 | value_type* _cur; 209 | 210 | iterator() : _self(nullptr), _cur(nullptr) {} 211 | explicit iterator(hash_table& self, value_type* cur) : _self(&self), _cur(cur) {} 212 | 213 | entry_handle deref() const 214 | { 215 | return {_self, _cur, true}; 216 | } 217 | void increment() 218 | { 219 | auto end = _self->_table + _self->_table_capacity; 220 | do 221 | { 222 | ++_cur; 223 | } while (_cur != end && Traits::is_unoccupied(*_cur)); 224 | } 225 | bool equal(iterator rhs) const 226 | { 227 | return _cur == rhs._cur; 228 | } 229 | }; 230 | 231 | iterator begin() const 232 | { 233 | if (_self->size() == 0) 234 | return {}; 235 | 236 | auto cur = _self->_table; 237 | while (Traits::is_unoccupied(*cur)) 238 | cur++; 239 | return iterator(*_self, cur); 240 | } 241 | iterator end() const 242 | { 243 | if (_self->size() == 0) 244 | return {}; 245 | 246 | return iterator(*_self, _self->_table + _self->_table_capacity); 247 | } 248 | 249 | hash_table* _self; 250 | }; 251 | 252 | /// Iterates over all occupied entries. 253 | entry_range entries() 254 | { 255 | return {this}; 256 | } 257 | 258 | private: 259 | value_type* _table; 260 | std::size_t _table_capacity; // power of two 261 | std::size_t _table_size; 262 | }; 263 | } // namespace dryad::_detail 264 | 265 | #endif // DRYAD_DETAIL_HASH_TABLE_HPP_INCLUDED 266 | 267 | -------------------------------------------------------------------------------- /include/dryad/_detail/iterator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_DETAIL_ITERATOR_HPP_INCLUDED 5 | #define DRYAD_DETAIL_ITERATOR_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | 10 | //=== facade classes ===// 11 | namespace dryad::_detail 12 | { 13 | template 14 | struct _proxy_pointer 15 | { 16 | T value; 17 | 18 | constexpr T* operator->() noexcept 19 | { 20 | return &value; 21 | } 22 | }; 23 | 24 | template 25 | struct forward_iterator_base 26 | { 27 | using value_type = std::remove_cv_t; 28 | using reference = Reference; 29 | using pointer = type_or>; 30 | using difference_type = std::ptrdiff_t; 31 | using iterator_category = std::forward_iterator_tag; 32 | 33 | constexpr reference operator*() const noexcept 34 | { 35 | return static_cast(*this).deref(); 36 | } 37 | constexpr pointer operator->() const noexcept 38 | { 39 | if constexpr (std::is_void_v) 40 | return pointer{**this}; 41 | else 42 | return &**this; 43 | } 44 | 45 | constexpr Derived& operator++() noexcept 46 | { 47 | auto& derived = static_cast(*this); 48 | derived.increment(); 49 | return derived; 50 | } 51 | constexpr Derived operator++(int) noexcept 52 | { 53 | auto& derived = static_cast(*this); 54 | auto copy = derived; 55 | derived.increment(); 56 | return copy; 57 | } 58 | 59 | friend constexpr bool operator==(const Derived& lhs, const Derived& rhs) 60 | { 61 | return lhs.equal(rhs); 62 | } 63 | friend constexpr bool operator!=(const Derived& lhs, const Derived& rhs) 64 | { 65 | return !lhs.equal(rhs); 66 | } 67 | }; 68 | 69 | template 70 | struct bidirectional_iterator_base : forward_iterator_base 71 | { 72 | using iterator_category = std::bidirectional_iterator_tag; 73 | 74 | constexpr Derived& operator--() noexcept 75 | { 76 | auto& derived = static_cast(*this); 77 | derived.decrement(); 78 | return derived; 79 | } 80 | constexpr Derived operator--(int) noexcept 81 | { 82 | auto& derived = static_cast(*this); 83 | auto copy = derived; 84 | derived.decrement(); 85 | return copy; 86 | } 87 | }; 88 | 89 | template 90 | struct sentinel_base 91 | { 92 | friend constexpr bool operator==(const Iterator& lhs, Derived) noexcept 93 | { 94 | return lhs.is_end(); 95 | } 96 | friend constexpr bool operator!=(const Iterator& lhs, Derived) noexcept 97 | { 98 | return !(lhs == Derived{}); 99 | } 100 | friend constexpr bool operator==(Derived, const Iterator& rhs) noexcept 101 | { 102 | return rhs == Derived{}; 103 | } 104 | friend constexpr bool operator!=(Derived, const Iterator& rhs) noexcept 105 | { 106 | return !(rhs == Derived{}); 107 | } 108 | }; 109 | } // namespace dryad::_detail 110 | 111 | #endif // DRYAD_DETAIL_ITERATOR_HPP_INCLUDED 112 | 113 | -------------------------------------------------------------------------------- /include/dryad/_detail/memory_resource.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_DETAIL_MEMORY_RESOURCE_HPP_INCLUDED 5 | #define DRYAD_DETAIL_MEMORY_RESOURCE_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | 10 | #if 0 11 | // Subset of the interface of std::pmr::memory_resource. 12 | class MemoryResource 13 | { 14 | public: 15 | void* allocate(std::size_t bytes, std::size_t alignment); 16 | void deallocate(void* ptr, std::size_t bytes, std::size_t alignment); 17 | 18 | friend bool operator==(const MemoryResource& lhs, const MemoryResource& rhs); 19 | }; 20 | #endif 21 | 22 | namespace dryad::_detail 23 | { 24 | class default_memory_resource 25 | { 26 | public: 27 | static void* allocate(std::size_t bytes, std::size_t alignment) 28 | { 29 | if (alignment > __STDCPP_DEFAULT_NEW_ALIGNMENT__) 30 | return ::operator new (bytes, std::align_val_t{alignment}); 31 | else 32 | return ::operator new(bytes); 33 | } 34 | 35 | static void deallocate(void* ptr, std::size_t bytes, std::size_t alignment) noexcept 36 | { 37 | #ifdef __cpp_sized_deallocation 38 | if (alignment > __STDCPP_DEFAULT_NEW_ALIGNMENT__) 39 | ::operator delete (ptr, bytes, std::align_val_t{alignment}); 40 | else 41 | ::operator delete(ptr, bytes); 42 | #else 43 | (void)bytes; 44 | 45 | if (alignment > __STDCPP_DEFAULT_NEW_ALIGNMENT__) 46 | ::operator delete (ptr, std::align_val_t{alignment}); 47 | else 48 | ::operator delete(ptr); 49 | #endif 50 | } 51 | 52 | friend constexpr bool operator==(default_memory_resource, default_memory_resource) noexcept 53 | { 54 | return true; 55 | } 56 | }; 57 | } // namespace dryad::_detail 58 | 59 | namespace dryad::_detail 60 | { 61 | template 62 | class _memory_resource_ptr_empty 63 | { 64 | public: 65 | constexpr explicit _memory_resource_ptr_empty(MemoryResource*) noexcept {} 66 | constexpr explicit _memory_resource_ptr_empty(void*) noexcept {} 67 | 68 | constexpr auto operator*() const noexcept 69 | { 70 | return MemoryResource{}; 71 | } 72 | 73 | constexpr auto operator->() const noexcept 74 | { 75 | struct proxy 76 | { 77 | MemoryResource _resource; 78 | 79 | constexpr MemoryResource* operator->() noexcept 80 | { 81 | return &_resource; 82 | } 83 | }; 84 | 85 | return proxy{}; 86 | } 87 | 88 | constexpr MemoryResource* get() const noexcept 89 | { 90 | return nullptr; 91 | } 92 | }; 93 | 94 | template 95 | class _memory_resource_ptr 96 | { 97 | public: 98 | constexpr explicit _memory_resource_ptr(MemoryResource* resource) noexcept : _resource(resource) 99 | { 100 | LEXY_PRECONDITION(resource); 101 | } 102 | 103 | constexpr MemoryResource& operator*() const noexcept 104 | { 105 | return *_resource; 106 | } 107 | 108 | constexpr MemoryResource* operator->() const noexcept 109 | { 110 | return _resource; 111 | } 112 | 113 | constexpr MemoryResource* get() const noexcept 114 | { 115 | return _resource; 116 | } 117 | 118 | private: 119 | MemoryResource* _resource; 120 | }; 121 | 122 | // clang-format off 123 | template 124 | using memory_resource_ptr 125 | = std::conditional_t, 126 | _memory_resource_ptr_empty, 127 | std::conditional_t, 128 | _memory_resource_ptr_empty, 129 | _memory_resource_ptr>>; 130 | // clang-format on 131 | 132 | template || std::is_empty_v>> 135 | constexpr MemoryResource* get_memory_resource() 136 | { 137 | return nullptr; 138 | } 139 | } // namespace dryad::_detail 140 | 141 | #endif // DRYAD_DETAIL_MEMORY_RESOURCE_HPP_INCLUDED 142 | 143 | -------------------------------------------------------------------------------- /include/dryad/_detail/std.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_DETAIL_STD_HPP_INCLUDED 5 | #define DRYAD_DETAIL_STD_HPP_INCLUDED 6 | 7 | #include 8 | 9 | #if defined(__GLIBCXX__) 10 | 11 | namespace std 12 | { 13 | _GLIBCXX_BEGIN_NAMESPACE_VERSION 14 | struct forward_iterator_tag; 15 | struct bidirectional_iterator_tag; 16 | _GLIBCXX_END_NAMESPACE_VERSION 17 | } // namespace std 18 | 19 | #elif defined(_LIBCPP_VERSION) 20 | 21 | _LIBCPP_BEGIN_NAMESPACE_STD 22 | struct forward_iterator_tag; 23 | struct bidirectional_iterator_tag; 24 | _LIBCPP_END_NAMESPACE_STD 25 | 26 | #else 27 | 28 | // Forward declaring things in std is not allowed, but I'm willing to take the risk. 29 | 30 | namespace std 31 | { 32 | struct forward_iterator_tag; 33 | struct bidirectional_iterator_tag; 34 | } // namespace std 35 | 36 | #endif 37 | 38 | #endif // DRYAD_DETAIL_STD_HPP_INCLUDED 39 | 40 | -------------------------------------------------------------------------------- /include/dryad/abstract_node.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_ABSTRACT_NODE_HPP_INCLUDED 5 | #define DRYAD_ABSTRACT_NODE_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | 10 | namespace dryad 11 | { 12 | /// Base class for the abstract base class of the specified nodes. 13 | /// 14 | /// It can be used to inject common data into the specified node kinds. 15 | /// Injection only happens when it is specified as the AbstractBase in the node definition. 16 | template 17 | class abstract_node : public AbstractBase 18 | { 19 | public: 20 | static constexpr bool type_is_abstract() 21 | { 22 | return true; 23 | } 24 | static constexpr bool type_matches_kind(typename AbstractBase::node_kind_type kind) 25 | { 26 | using traits = node_kind_traits; 27 | return ((traits::to_int(Kinds) == traits::to_int(kind)) || ...); 28 | } 29 | 30 | protected: 31 | using node_base = abstract_node; 32 | explicit abstract_node(node_ctor ctor, typename AbstractBase::node_kind_type kind) 33 | : AbstractBase(ctor, kind) 34 | {} 35 | ~abstract_node() = default; 36 | }; 37 | 38 | /// Base class for the abstract base class of the specified nodes. 39 | /// 40 | /// It can be used to inject common data into the specified node kinds. 41 | /// Injection only happens when it is specified as the AbstractBase in the node definition. 42 | template 44 | class abstract_node_range : public AbstractBase 45 | { 46 | public: 47 | static constexpr bool type_is_abstract() 48 | { 49 | return true; 50 | } 51 | static constexpr bool type_matches_kind(typename AbstractBase::node_kind_type kind) 52 | { 53 | using traits = node_kind_traits; 54 | return traits::to_int(FirstKind) <= traits::to_int(kind) 55 | && traits::to_int(kind) <= traits::to_int(LastKind); 56 | } 57 | 58 | protected: 59 | using node_base = abstract_node_range; 60 | explicit abstract_node_range(node_ctor ctor, typename AbstractBase::node_kind_type kind) 61 | : AbstractBase(ctor, kind) 62 | {} 63 | ~abstract_node_range() = default; 64 | }; 65 | 66 | /// Base class for the abstract base class of all nodes. 67 | /// 68 | /// It can be used to inject common data into all nodes. 69 | /// Injection only happens when it is specified as the AbstractBase in the node definition. 70 | template 71 | class abstract_node_all : public node 72 | { 73 | protected: 74 | using node_base = abstract_node_all; 75 | explicit abstract_node_all(node_ctor ctor, NodeKind kind) : node(ctor, kind) {} 76 | ~abstract_node_all() = default; 77 | }; 78 | 79 | #define DRYAD_ABSTRACT_NODE_CTOR(Name) \ 80 | explicit Name(::dryad::node_ctor ctor, typename Name::node_kind_type kind) \ 81 | : node_base(ctor, kind) \ 82 | {} 83 | } // namespace dryad 84 | 85 | #endif // DRYAD_ABSTRACT_NODE_HPP_INCLUDED 86 | 87 | -------------------------------------------------------------------------------- /include/dryad/arena.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_ARENA_HPP_INCLUDED 5 | #define DRYAD_ARENA_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace dryad::_detail 12 | { 13 | constexpr bool is_valid_alignment(std::size_t alignment) noexcept 14 | { 15 | return alignment != 0u && (alignment & (alignment - 1)) == 0u; 16 | } 17 | 18 | constexpr std::size_t align_offset(std::uintptr_t address, std::size_t alignment) noexcept 19 | { 20 | DRYAD_PRECONDITION(is_valid_alignment(alignment)); 21 | auto misaligned = address & (alignment - 1); 22 | return misaligned != 0 ? (alignment - misaligned) : 0; 23 | } 24 | inline std::size_t align_offset(const void* address, std::size_t alignment) noexcept 25 | { 26 | DRYAD_PRECONDITION(is_valid_alignment(alignment)); 27 | return align_offset(reinterpret_cast(address), alignment); 28 | } 29 | } // namespace dryad::_detail 30 | 31 | namespace dryad 32 | { 33 | /// A simple stack allocator. 34 | template 35 | class arena 36 | { 37 | using resource_ptr = _detail::memory_resource_ptr; 38 | 39 | static constexpr auto block_size = std::size_t(16) * 1024 - sizeof(void*); 40 | 41 | struct block 42 | { 43 | block* next; 44 | unsigned char memory[block_size]; 45 | 46 | static block* allocate(resource_ptr resource) 47 | { 48 | auto memory = resource->allocate(sizeof(block), alignof(block)); 49 | auto ptr = ::new (memory) block; // Don't initialize array! 50 | ptr->next = nullptr; 51 | return ptr; 52 | } 53 | 54 | static block* deallocate(resource_ptr resource, block* ptr) 55 | { 56 | auto next = ptr->next; 57 | resource->deallocate(ptr, sizeof(block), alignof(block)); 58 | return next; 59 | } 60 | 61 | unsigned char* end() noexcept 62 | { 63 | return &memory[block_size]; 64 | } 65 | }; 66 | 67 | public: 68 | //=== constructors/destructors/assignment ===// 69 | constexpr arena() noexcept : arena(_detail::get_memory_resource()) {} 70 | 71 | explicit constexpr arena(MemoryResource* resource) noexcept 72 | : _cur_block(nullptr), _cur_pos(nullptr), _resource(resource), _first_block(nullptr) 73 | {} 74 | 75 | arena(arena&& other) noexcept 76 | : _cur_block(other._cur_block), _cur_pos(other._cur_pos), _resource(other._resource), 77 | _first_block(other._first_block) 78 | { 79 | other._first_block = other._cur_block = nullptr; 80 | other._cur_pos = nullptr; 81 | } 82 | 83 | ~arena() noexcept 84 | { 85 | auto cur = _first_block; 86 | while (cur != nullptr) 87 | cur = block::deallocate(_resource, cur); 88 | } 89 | 90 | arena& operator=(arena&& other) noexcept 91 | { 92 | _detail::swap(_resource, other._resource); 93 | _detail::swap(_first_block, other._first_block); 94 | _detail::swap(_cur_block, other._cur_block); 95 | _detail::swap(_cur_pos, other._cur_pos); 96 | return *this; 97 | } 98 | 99 | //=== allocation ===// 100 | static constexpr auto max_allocation_size = block_size; 101 | 102 | void* allocate(std::size_t size, std::size_t alignment) 103 | { 104 | DRYAD_PRECONDITION(size <= max_allocation_size); 105 | DRYAD_PRECONDITION(alignment <= alignof(void*)); 106 | 107 | auto offset = _detail::align_offset(_cur_pos, alignment); 108 | auto remaining = _cur_block == nullptr ? 0 : std::size_t(_cur_block->end() - _cur_pos); 109 | if (offset + size > remaining) 110 | { 111 | if (_cur_block == nullptr) 112 | { 113 | DRYAD_ASSERT(_first_block == nullptr, 114 | "_cur_block is always valid once we have a block"); 115 | _first_block = block::allocate(_resource); 116 | _cur_block = _first_block; 117 | } 118 | else 119 | { 120 | if (_cur_block->next == nullptr) 121 | _cur_block->next = block::allocate(_resource); 122 | 123 | _cur_block = _cur_block->next; 124 | } 125 | 126 | _cur_pos = &_cur_block->memory[0]; 127 | offset = 0; 128 | DRYAD_ASSERT(_detail::align_offset(_cur_pos, alignment) == 0, 129 | "beginning of block should be properly aligned"); 130 | } 131 | 132 | _cur_pos += offset; 133 | auto result = _cur_pos; 134 | _cur_pos += size; 135 | return result; 136 | } 137 | 138 | template 139 | void* allocate() 140 | { 141 | static_assert(sizeof(T) <= max_allocation_size); 142 | static_assert(alignof(T) <= alignof(void*)); 143 | return allocate(sizeof(T), alignof(T)); 144 | } 145 | 146 | template 147 | T* construct(Args&&... args) 148 | { 149 | return ::new (allocate()) T(DRYAD_FWD(args)...); 150 | } 151 | 152 | resource_ptr resource() const 153 | { 154 | return _resource; 155 | } 156 | 157 | //=== unwinding ===// 158 | struct marker 159 | { 160 | block* cur_block; 161 | unsigned char* cur_pos; 162 | }; 163 | 164 | marker top() const 165 | { 166 | return {_cur_block, _cur_pos}; 167 | } 168 | 169 | void unwind(marker m) 170 | { 171 | _cur_block = m.cur_block; 172 | _cur_pos = m.cur_pos; 173 | } 174 | 175 | void clear() 176 | { 177 | if (_first_block == nullptr) 178 | // We never held data to begin with, so don't need to do anything. 179 | return; 180 | 181 | _cur_block = _first_block; 182 | _cur_pos = &_cur_block->memory[0]; 183 | } 184 | 185 | private: 186 | block* _cur_block; 187 | unsigned char* _cur_pos; 188 | 189 | DRYAD_EMPTY_MEMBER resource_ptr _resource; 190 | block* _first_block; 191 | }; 192 | } // namespace dryad 193 | 194 | #endif // DRYAD_ARENA_HPP_INCLUDED 195 | 196 | -------------------------------------------------------------------------------- /include/dryad/hash_algorithm.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_HASH_ALGORITHM_HPP_INCLUDED 5 | #define DRYAD_HASH_ALGORITHM_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | 10 | namespace dryad 11 | { 12 | /// FNV-1a 64 bit hash. 13 | class default_hash_algorithm 14 | { 15 | static constexpr std::uint64_t fnv_basis = 14695981039346656037ull; 16 | static constexpr std::uint64_t fnv_prime = 1099511628211ull; 17 | 18 | public: 19 | explicit default_hash_algorithm() : _hash(fnv_basis) {} 20 | 21 | default_hash_algorithm(default_hash_algorithm&&) = default; 22 | default_hash_algorithm& operator=(default_hash_algorithm&&) = default; 23 | 24 | ~default_hash_algorithm() = default; 25 | 26 | default_hash_algorithm&& hash_bytes(const unsigned char* ptr, std::size_t size) 27 | { 28 | for (auto i = 0u; i != size; ++i) 29 | { 30 | _hash ^= ptr[i]; 31 | _hash *= fnv_prime; 32 | } 33 | return DRYAD_MOV(*this); 34 | } 35 | 36 | template >> 37 | default_hash_algorithm&& hash_scalar(T value) 38 | { 39 | static_assert(!std::is_floating_point_v, 40 | "you shouldn't use floats as keys for a hash table"); 41 | hash_bytes(reinterpret_cast(&value), sizeof(T)); 42 | return DRYAD_MOV(*this); 43 | } 44 | 45 | template 46 | default_hash_algorithm&& hash_c_str(const CharT* str) 47 | { 48 | while (*str != '\0') 49 | { 50 | hash_scalar(*str); 51 | ++str; 52 | } 53 | return DRYAD_MOV(*this); 54 | } 55 | 56 | std::uint64_t finish() && 57 | { 58 | return _hash; 59 | } 60 | 61 | private: 62 | std::uint64_t _hash; 63 | }; 64 | } // namespace dryad 65 | 66 | #endif // DRYAD_HASH_ALGORITHM_HPP_INCLUDED 67 | 68 | -------------------------------------------------------------------------------- /include/dryad/hash_forest.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_HASH_FOREST_HPP_INCLUDED 5 | #define DRYAD_HASH_FOREST_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #if 0 16 | struct NodeHasher : ndryad::node_hasher_base 17 | { 18 | // Hashes the non-tree data of each node type. 19 | // Does not need to account for children or the node kind. 20 | template 21 | static void hash(HashAlgorithm& hasher, const NodeType1* n); 22 | template 23 | static void hash(HashAlgorithm& hasher, const NodeType2* n); 24 | ... 25 | 26 | template 27 | static void hash(HashAlgorithm& hasher, Key key); 28 | 29 | // Compares the non-tree data of each node type. 30 | // Does not need to compare children. 31 | static bool is_equal(const NodeType1* lhs, const NodeType1* rhs); 32 | static bool is_equal(const NodeType2* lhs, const NodeType2* rhs); 33 | ... 34 | 35 | static bool is_equal(const NodeTypeN* value, Key key); 36 | ... 37 | }; 38 | #endif 39 | 40 | namespace dryad 41 | { 42 | template 43 | struct node_hasher_base 44 | { 45 | template 46 | struct hasher_for 47 | { 48 | HashAlgorithm& hasher; 49 | 50 | void operator()(const NodeType* n) const 51 | { 52 | Derived::hash(hasher, n); 53 | } 54 | }; 55 | 56 | template 57 | static void hash_base(HashAlgorithm& hasher, const Node* n) 58 | { 59 | hasher.hash_scalar(n->kind()); 60 | visit_tree(n, hasher_for{hasher}...); 61 | } 62 | 63 | template 64 | struct is_equal_for 65 | { 66 | const Node* rhs; 67 | 68 | bool operator()(const NodeType* lhs_casted) const 69 | { 70 | auto rhs_casted = node_cast(rhs); 71 | return Derived::is_equal(lhs_casted, rhs_casted); 72 | } 73 | }; 74 | 75 | template 76 | static bool is_equal_base(const Node* lhs, const Node* rhs) 77 | { 78 | // Compare kind. 79 | if (lhs->kind() != rhs->kind()) 80 | return false; 81 | 82 | // Compare node data itself. 83 | if (!visit_node_all(lhs, is_equal_for{rhs}...)) 84 | return false; 85 | 86 | // Compare children. 87 | auto lhs_cur = lhs->children().begin(); 88 | auto lhs_end = lhs->children().end(); 89 | auto rhs_cur = rhs->children().begin(); 90 | auto rhs_end = rhs->children().end(); 91 | while (lhs_cur != lhs_end && rhs_cur != rhs_end) 92 | { 93 | if (!is_equal_base(*lhs_cur, *rhs_cur)) 94 | return false; 95 | 96 | ++lhs_cur; 97 | ++rhs_cur; 98 | } 99 | return lhs_cur == lhs_end && rhs_cur == rhs_end; 100 | } 101 | }; 102 | } // namespace dryad 103 | 104 | namespace dryad::_detail 105 | { 106 | template 107 | struct hash_forest_key 108 | { 109 | const Key* key; 110 | }; 111 | 112 | template 113 | struct hash_forest_traits 114 | { 115 | using value_type = RootNode*; 116 | 117 | static bool is_unoccupied(value_type ptr) 118 | { 119 | auto bits = reinterpret_cast(ptr); 120 | return bits == 0 || bits == std::uintptr_t(-1); 121 | } 122 | 123 | static void fill_removed(value_type* data, size_t size) 124 | { 125 | std::memset(data, static_cast(-1), size * sizeof(value_type)); 126 | } 127 | 128 | static void fill_unoccupied(value_type* data, size_t size) 129 | { 130 | std::memset(data, static_cast(0), size * sizeof(value_type)); 131 | } 132 | 133 | static bool is_equal(value_type entry, value_type key) 134 | { 135 | return NodeHasher::is_equal_base(entry, key); 136 | } 137 | template 138 | static bool is_equal(value_type entry, hash_forest_key key) 139 | { 140 | if (auto node = node_try_cast(entry)) 141 | return NodeHasher::is_equal(node, *key.key); 142 | else 143 | return false; 144 | } 145 | 146 | static std::size_t hash(value_type entry) 147 | { 148 | default_hash_algorithm hasher; 149 | NodeHasher::hash_base(hasher, entry); 150 | return DRYAD_MOV(hasher).finish(); 151 | } 152 | template 153 | static std::size_t hash(hash_forest_key key) 154 | { 155 | default_hash_algorithm hasher; 156 | hasher.hash_scalar(T::kind()); 157 | NodeHasher::hash(hasher, *key.key); 158 | return DRYAD_MOV(hasher).finish(); 159 | } 160 | }; 161 | } // namespace dryad::_detail 162 | 163 | namespace dryad 164 | { 165 | /// Owns multiple nodes, eventually all children of a list of root nodes. 166 | /// Identical trees are interned. 167 | template 168 | class hash_forest 169 | { 170 | using hash_table = _detail::hash_table<_detail::hash_forest_traits, 64>; 171 | 172 | public: 173 | using node_kind_type = typename RootNode::node_kind_type; 174 | 175 | //=== construction ===// 176 | hash_forest() = default; 177 | explicit hash_forest(MemoryResource* resource) : _arena(resource) {} 178 | 179 | ~hash_forest() 180 | { 181 | _roots.free(_arena.resource()); 182 | } 183 | 184 | hash_forest(hash_forest&& other) noexcept 185 | : _arena(DRYAD_MOV(other._arena)), _roots(other._roots) 186 | { 187 | other._roots = {}; 188 | } 189 | hash_forest& operator=(hash_forest&& other) noexcept 190 | { 191 | _detail::swap(_arena, other._arena); 192 | _detail::swap(_roots, other._roots); 193 | return *this; 194 | } 195 | 196 | //=== node creation ===// 197 | using node_creator = dryad::node_creator; 198 | 199 | template 200 | RootNode* build(Builder builder) 201 | { 202 | if (_roots.should_rehash()) 203 | rehash(2 * _roots.capacity()); 204 | 205 | auto marker = _arena.top(); 206 | 207 | auto node = builder(node_creator(_arena)); 208 | node->set_next_parent(node); 209 | 210 | auto entry = _roots.lookup_entry(node); 211 | if (entry) 212 | { 213 | // Node is already part of the tree, remove its memory and return existing one. 214 | _arena.unwind(marker); 215 | return entry.get(); 216 | } 217 | else 218 | { 219 | // Node is not part of the tree. 220 | entry.create(node); 221 | return node; 222 | } 223 | } 224 | 225 | template 226 | RootNode* create(Args&&... args) 227 | { 228 | return build( 229 | [&](node_creator creator) { return creator.template create(DRYAD_FWD(args)...); }); 230 | } 231 | 232 | template 233 | RootNode* lookup_or_create(Key&& key) 234 | { 235 | if (_roots.should_rehash()) 236 | rehash(2 * _roots.capacity()); 237 | 238 | auto entry = _roots.lookup_entry(_detail::hash_forest_key>{&key}); 239 | if (entry) 240 | return entry.get(); 241 | 242 | auto node = node_creator(_arena).template create(DRYAD_FWD(key)); 243 | node->set_next_parent(node); 244 | entry.create(node); 245 | return node; 246 | } 247 | 248 | void clear() 249 | { 250 | _roots.free(_arena.resource()); 251 | _arena.clear(); 252 | } 253 | 254 | //=== hash table interface ===// 255 | void rehash(std::size_t new_capacity) 256 | { 257 | new_capacity = _roots.to_table_capacity(new_capacity); 258 | auto capacity = _roots.capacity(); 259 | if (new_capacity <= capacity) 260 | return; 261 | 262 | _roots.rehash(_arena.resource(), new_capacity); 263 | } 264 | 265 | std::size_t root_count() const 266 | { 267 | return _roots.size(); 268 | } 269 | std::size_t root_capacity() const 270 | { 271 | return _roots.capacity(); 272 | } 273 | 274 | private: 275 | arena _arena; 276 | hash_table _roots; 277 | }; 278 | } // namespace dryad 279 | 280 | #endif // DRYAD_HASH_FOREST_HPP_INCLUDED 281 | 282 | -------------------------------------------------------------------------------- /include/dryad/node.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_NODE_HPP_INCLUDED 5 | #define DRYAD_NODE_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace dryad 12 | { 13 | template 14 | class node; 15 | } // namespace dryad 16 | 17 | namespace dryad 18 | { 19 | /// It is sometimes useful to give each node a color during certain tree algorithms, e.g. cycle 20 | /// detection. This enum represents a generic color that can be set/queried for every node as 21 | /// necessary. 22 | /// 23 | /// The meaning of the individual colors depends on the algorithm in question. 24 | enum class color 25 | { 26 | uncolored, 27 | black, 28 | grey, 29 | white, 30 | }; 31 | } // namespace dryad 32 | 33 | namespace dryad 34 | { 35 | template 36 | struct node_kind_traits 37 | { 38 | static constexpr std::uint16_t to_int(NodeKind kind) 39 | { 40 | if constexpr (std::is_enum_v) 41 | { 42 | auto underlying = static_cast>(kind); 43 | DRYAD_PRECONDITION(0 <= underlying && underlying <= 0x7FFF); 44 | return static_cast(underlying); 45 | } 46 | else 47 | { 48 | DRYAD_PRECONDITION(0 <= kind && kind <= 0x7FFF); 49 | return static_cast(kind); 50 | } 51 | } 52 | 53 | static constexpr NodeKind from_int(std::uint16_t v) 54 | { 55 | return static_cast(v); 56 | } 57 | }; 58 | 59 | template 60 | constexpr bool is_node = std::is_base_of_v, T>; 61 | template 62 | constexpr bool is_abstract_node = is_node&& T::type_is_abstract(); 63 | } // namespace dryad 64 | 65 | namespace dryad 66 | { 67 | template 68 | class node_creator; 69 | 70 | class node_ctor 71 | { 72 | node_ctor() = default; 73 | 74 | template 75 | friend class node_creator; 76 | }; 77 | 78 | /// Type-erased base class for all nodes in the AST. 79 | /// 80 | /// User nodes should never inherit from it directly. 81 | template 82 | class node 83 | { 84 | using _traits = node_kind_traits; 85 | 86 | public: 87 | node(const node&) = delete; 88 | node& operator=(const node&) = delete; 89 | 90 | using node_kind_type = NodeKind; 91 | 92 | static constexpr bool type_is_abstract() 93 | { 94 | return true; 95 | } 96 | static constexpr bool type_matches_kind(NodeKind) 97 | { 98 | return true; 99 | } 100 | 101 | NodeKind kind() const 102 | { 103 | return _traits::from_int(_kind); 104 | } 105 | 106 | //=== tree relationship ===// 107 | bool is_linked_in_tree() const 108 | { 109 | return next_node() != nullptr; 110 | } 111 | 112 | /// Root node returns a pointer to itself. 113 | const node* parent() const 114 | { 115 | if (!is_linked_in_tree()) 116 | return nullptr; 117 | 118 | // If we follow the sibling pointer repeatedly, we end up at the parent eventually. 119 | auto cur = this; 120 | while (!cur->next_node_is_parent()) 121 | { 122 | cur = cur->next_node(); 123 | // In a forest, root nodes are in a circular linked list. 124 | if (cur == this) 125 | return cur; 126 | } 127 | return cur->next_node(); 128 | } 129 | node* parent() 130 | { 131 | return const_cast(DRYAD_CTHIS->parent()); 132 | } 133 | 134 | template 135 | struct _sibling_range 136 | { 137 | const node* _self; 138 | 139 | struct iterator : _detail::forward_iterator_base 140 | { 141 | T* _cur = nullptr; 142 | 143 | static iterator from_ptr(T* ptr) 144 | { 145 | return {{}, ptr}; 146 | } 147 | static iterator from_ptr(iterator iter) 148 | { 149 | return iter; 150 | } 151 | 152 | operator typename _sibling_range::iterator() const 153 | { 154 | return {_cur}; 155 | } 156 | 157 | T* deref() const 158 | { 159 | return _cur; 160 | } 161 | void increment() 162 | { 163 | if (_cur->next_node_is_parent()) 164 | { 165 | // We're pointing to the parent, go to first child instead. 166 | // As it's a container, this is the ptr user data. 167 | DRYAD_ASSERT(!_cur->next_node()->children().empty(), "parent node is empty?!"); 168 | _cur = *_cur->next_node()->children().begin(); 169 | } 170 | else 171 | // We're pointing to a sibling, go there. 172 | _cur = _cur->next_node(); 173 | } 174 | bool equal(iterator rhs) const 175 | { 176 | return _cur == rhs._cur; 177 | } 178 | }; 179 | 180 | bool empty() const 181 | { 182 | return begin() == end(); 183 | } 184 | 185 | iterator begin() const 186 | { 187 | if (!_self->is_linked_in_tree() || _self->next_node() == _self) 188 | return {}; 189 | 190 | // We begin with the next node after ours. 191 | // If we don't have siblings, this is our node itself. 192 | return ++end(); 193 | } 194 | iterator end() const 195 | { 196 | if (!_self->is_linked_in_tree() || _self->next_node() == _self) 197 | return {}; 198 | 199 | // We end when we're back at the node. 200 | return {{}, (node*)_self}; 201 | } 202 | 203 | T* front() const 204 | { 205 | return *begin(); 206 | } 207 | }; 208 | 209 | _sibling_range> siblings() 210 | { 211 | return {this}; 212 | } 213 | _sibling_range> siblings() const 214 | { 215 | return {this}; 216 | } 217 | 218 | template 219 | struct _children_range 220 | { 221 | const node* _self; 222 | 223 | struct iterator : _detail::forward_iterator_base 224 | { 225 | T* _cur = nullptr; 226 | 227 | static iterator from_ptr(T* ptr) 228 | { 229 | return {{}, ptr}; 230 | } 231 | static iterator from_ptr(iterator iter) 232 | { 233 | return iter; 234 | } 235 | 236 | operator typename _children_range::iterator() const 237 | { 238 | return {_cur}; 239 | } 240 | 241 | T* deref() const 242 | { 243 | return (T*)_cur; 244 | } 245 | void increment() 246 | { 247 | _cur = _cur->next_node(); 248 | } 249 | bool equal(iterator rhs) const 250 | { 251 | return _cur == rhs._cur; 252 | } 253 | }; 254 | 255 | bool empty() const 256 | { 257 | return _self->_is_container ? _self->_user_data_ptr == nullptr : true; 258 | } 259 | 260 | iterator begin() const 261 | { 262 | if (empty()) 263 | return {{}, nullptr}; 264 | else 265 | return {{}, static_cast(_self->_user_data_ptr)}; 266 | } 267 | iterator end() const 268 | { 269 | if (empty()) 270 | // begin() == nullptr, so return that as well. 271 | return {{}, nullptr}; 272 | else 273 | // The last child has a pointer back to self. 274 | return {{}, const_cast(_self)}; 275 | } 276 | 277 | T* front() const 278 | { 279 | return *begin(); 280 | } 281 | }; 282 | 283 | _children_range children() 284 | { 285 | return {this}; 286 | } 287 | _children_range children() const 288 | { 289 | return {this}; 290 | } 291 | 292 | bool has_children() const 293 | { 294 | return !children().empty(); 295 | } 296 | 297 | //=== color ===// 298 | dryad::color color() const 299 | { 300 | return dryad::color((_ptr & 0b110) >> 1); 301 | } 302 | void set_color(dryad::color color) 303 | { 304 | _ptr &= ~std::uintptr_t(0b110); 305 | _ptr |= (unsigned(color) & 0b11) << 1; 306 | } 307 | 308 | protected: 309 | //=== user data ===// 310 | std::uint16_t& user_data16() 311 | { 312 | return _user_data16; 313 | } 314 | std::uint32_t& user_data32() 315 | { 316 | return _user_data32; 317 | } 318 | void*& user_data_ptr() 319 | { 320 | return _user_data_ptr; 321 | } 322 | 323 | std::uint16_t user_data16() const 324 | { 325 | return _user_data16; 326 | } 327 | std::uint32_t user_data32() const 328 | { 329 | return _user_data32; 330 | } 331 | void* user_data_ptr() const 332 | { 333 | return _user_data_ptr; 334 | } 335 | 336 | protected: 337 | explicit node(node_ctor, NodeKind kind) 338 | : _ptr(reinterpret_cast((node*)nullptr)), _is_container(false), 339 | _kind(_traits::to_int(kind) & 0x7FFF), _user_data16(0), _user_data32(0), 340 | _user_data_ptr(nullptr) 341 | {} 342 | ~node() = default; 343 | 344 | private: 345 | void unlink() 346 | { 347 | _ptr = reinterpret_cast((node*)nullptr); 348 | } 349 | void set_next_sibling(node* node) 350 | { 351 | _ptr = reinterpret_cast(node); 352 | DRYAD_ASSERT((_ptr & 0b111) == 0, "invalid pointer alignment"); 353 | } 354 | void set_next_parent(node* node) 355 | { 356 | _ptr = reinterpret_cast(node); 357 | DRYAD_ASSERT((_ptr & 0b111) == 0, "invalid pointer alignment"); 358 | _ptr |= 0b1; 359 | } 360 | void copy_next(node* copy_from) 361 | { 362 | _ptr = copy_from->_ptr; 363 | } 364 | 365 | node* next_node() const 366 | { 367 | return reinterpret_cast(_ptr & ~std::uintptr_t(0b111)); 368 | } 369 | bool next_node_is_parent() const 370 | { 371 | return (_ptr & 0b1) != 0; 372 | } 373 | 374 | // If part of a tree, ptr points to either sibling or parent. 375 | // For root node, it is a parent pointer to itself. 376 | // If not part of a tree, ptr is nullptr. 377 | // 378 | // 3 bits for alignment: cct 379 | // * cc: color 380 | // * t: 0 if sibling, 1 if parent 381 | std::uintptr_t _ptr; 382 | bool _is_container : 1; 383 | std::uint16_t _kind : 15; 384 | std::uint16_t _user_data16; 385 | std::uint32_t _user_data32; 386 | void* _user_data_ptr; // If it's a container, pointer to first child. 387 | 388 | template 389 | friend class unlinked_node_list; 390 | template 391 | friend class container_node; 392 | template 393 | friend class tree; 394 | template 395 | friend class forest; 396 | template 397 | friend class hash_forest; 398 | template 399 | friend class _traverse_range; 400 | template 401 | friend void leak_node(node* n); 402 | }; 403 | 404 | /// Finishes the node by turning it into a root node. 405 | /// It is part of the storage for the tree, but not accessible from the outside, 406 | /// so "leaked". 407 | template 408 | void leak_node(node* n) 409 | { 410 | n->set_next_parent(n); 411 | } 412 | } // namespace dryad 413 | 414 | namespace dryad 415 | { 416 | /// A node without any special members. 417 | template > 418 | class basic_node : public AbstractBase 419 | { 420 | static_assert(is_abstract_node); 421 | static_assert(AbstractBase::type_matches_kind(NodeKind)); 422 | 423 | public: 424 | static constexpr bool type_is_abstract() 425 | { 426 | return false; 427 | } 428 | static constexpr bool type_matches_kind(DRYAD_DECAY_DECLTYPE(NodeKind) kind) 429 | { 430 | return kind == NodeKind; 431 | } 432 | 433 | static constexpr auto kind() 434 | { 435 | return NodeKind; 436 | } 437 | 438 | protected: 439 | using node_base = basic_node; 440 | 441 | template 442 | explicit basic_node(node_ctor ctor, Args&&... args) 443 | : AbstractBase(ctor, kind(), DRYAD_FWD(args)...) 444 | {} 445 | 446 | ~basic_node() = default; 447 | }; 448 | 449 | #define DRYAD_NODE_CTOR(Name) \ 450 | explicit Name(::dryad::node_ctor ctor) : node_base(ctor) {} 451 | 452 | #define DRYAD_ATTRIBUTE_USER_DATA16(T, Name) \ 453 | static_assert(sizeof(T) <= sizeof(std::uint16_t)); \ 454 | T Name() const \ 455 | { \ 456 | return static_cast(node_base::user_data16()); \ 457 | } \ 458 | void set_##Name(T val) \ 459 | { \ 460 | node_base::user_data16() = static_cast(val); \ 461 | } \ 462 | void user_data16() = delete 463 | 464 | #define DRYAD_ATTRIBUTE_USER_DATA32(T, Name) \ 465 | static_assert(sizeof(T) <= sizeof(std::uint32_t)); \ 466 | T Name() const \ 467 | { \ 468 | return static_cast(node_base::user_data32()); \ 469 | } \ 470 | void set_##Name(T val) \ 471 | { \ 472 | node_base::user_data32() = static_cast(val); \ 473 | } \ 474 | void user_data32() = delete 475 | 476 | #define DRYAD_ATTRIBUTE_USER_DATA_PTR(T, Name) \ 477 | static_assert(sizeof(T) <= sizeof(void*)); \ 478 | T Name() const \ 479 | { \ 480 | return (T)(node_base::user_data_ptr()); \ 481 | } \ 482 | void set_##Name(T val) \ 483 | { \ 484 | node_base::user_data_ptr() = (void*)(val); \ 485 | } \ 486 | void user_data_ptr() = delete 487 | } // namespace dryad 488 | 489 | namespace dryad 490 | { 491 | /// A list of nodes that haven't been linked into a tree yet. 492 | template 493 | class unlinked_node_list 494 | { 495 | public: 496 | unlinked_node_list() : _first(nullptr), _last(nullptr) {} 497 | 498 | unlinked_node_list(T* node) : unlinked_node_list() 499 | { 500 | push_back(node); 501 | } 502 | 503 | //=== access ===// 504 | bool empty() const 505 | { 506 | return _first == nullptr; 507 | } 508 | 509 | bool has_single_element() const 510 | { 511 | return _first != nullptr && _first == _last; 512 | } 513 | 514 | struct iterator : _detail::forward_iterator_base 515 | { 516 | T* _cur = nullptr; 517 | 518 | T* deref() const 519 | { 520 | return _cur; 521 | } 522 | void increment() 523 | { 524 | _cur = static_cast(_cur->next_node()); 525 | } 526 | bool equal(iterator rhs) const 527 | { 528 | return _cur == rhs._cur; 529 | } 530 | }; 531 | 532 | iterator begin() const 533 | { 534 | return {{}, _first}; 535 | } 536 | iterator end() const 537 | { 538 | return {{}, nullptr}; 539 | } 540 | 541 | T* front() const 542 | { 543 | return _first; 544 | } 545 | T* back() const 546 | { 547 | return _last; 548 | } 549 | 550 | //=== modifiers ===// 551 | void push_front(T* node) 552 | { 553 | if (node == nullptr) 554 | return; 555 | DRYAD_PRECONDITION(!node->is_linked_in_tree()); 556 | 557 | if (_first == nullptr) 558 | { 559 | _first = node; 560 | _last = node; 561 | } 562 | else 563 | { 564 | node->set_next_sibling(_first); 565 | _first = node; 566 | } 567 | } 568 | 569 | void push_back(T* node) 570 | { 571 | if (node == nullptr) 572 | return; 573 | DRYAD_PRECONDITION(!node->is_linked_in_tree()); 574 | 575 | if (_last == nullptr) 576 | { 577 | _first = node; 578 | _last = node; 579 | } 580 | else 581 | { 582 | _last->set_next_sibling(node); 583 | _last = node; 584 | } 585 | } 586 | 587 | void append(unlinked_node_list&& list) 588 | { 589 | if (list.empty()) 590 | return; 591 | 592 | if (empty()) 593 | { 594 | *this = list; 595 | } 596 | else 597 | { 598 | _last->set_next_sibling(list._first); 599 | _last = list._last; 600 | } 601 | 602 | list._first = list._last = nullptr; 603 | } 604 | 605 | T* pop_front() 606 | { 607 | DRYAD_PRECONDITION(_first != nullptr); 608 | auto result = _first; 609 | 610 | _first = static_cast(result->next_node()); 611 | if (result == _last) 612 | { 613 | DRYAD_ASSERT(_first == nullptr, "last node shouldn't have a next node"); 614 | _last = nullptr; 615 | } 616 | 617 | result->unlink(); 618 | return result; 619 | } 620 | 621 | private: 622 | T* _first; 623 | T* _last; 624 | 625 | template 626 | friend class container_node; 627 | }; 628 | 629 | /// Base class for all nodes that own child nodes, which should be traversed. 630 | template 631 | class container_node : public AbstractBase 632 | { 633 | using _node = node; 634 | 635 | public: 636 | using node_kind_type = typename AbstractBase::node_kind_type; 637 | 638 | protected: 639 | explicit container_node(node_ctor ctor, node_kind_type kind) : AbstractBase(ctor, kind) 640 | { 641 | this->_is_container = true; 642 | } 643 | ~container_node() = default; 644 | 645 | //=== modifiers ===// 646 | void insert_child_after(_node* pos, _node* child) 647 | { 648 | DRYAD_PRECONDITION(!child->is_linked_in_tree()); 649 | 650 | if (pos == nullptr) 651 | { 652 | if (auto first = first_child()) 653 | child->set_next_sibling(first); 654 | else 655 | child->set_next_parent(this); 656 | 657 | set_first_child(child); 658 | } 659 | else 660 | { 661 | child->copy_next(pos); 662 | pos->set_next_sibling(child); 663 | } 664 | } 665 | template 666 | void insert_children_after(_node* pos, T*... children) 667 | { 668 | ((insert_child_after(pos, children), pos = children), ...); 669 | } 670 | template 671 | void insert_child_list_after(_node* pos, unlinked_node_list list) 672 | { 673 | if (list.empty()) 674 | return; 675 | 676 | if (pos == nullptr) 677 | { 678 | if (auto first = first_child()) 679 | list._last->set_next_sibling(first); 680 | else 681 | list._last->set_next_parent(this); 682 | 683 | set_first_child(list._first); 684 | } 685 | else 686 | { 687 | list._last->copy_next(pos); 688 | pos->set_next_sibling(list._first); 689 | } 690 | } 691 | 692 | /// Returns the child that was erased. 693 | _node* erase_child_after(_node* pos) 694 | { 695 | if (pos == nullptr) 696 | { 697 | DRYAD_PRECONDITION(first_child() != nullptr); 698 | auto child = first_child(); 699 | 700 | if (child->next_node_is_parent()) 701 | set_first_child(nullptr); 702 | else 703 | set_first_child(child->next_node()); 704 | 705 | child->unlink(); 706 | return child; 707 | } 708 | else 709 | { 710 | DRYAD_PRECONDITION(!pos->next_node_is_parent()); 711 | auto child = pos->next_node(); 712 | 713 | pos->copy_next(child); 714 | 715 | child->unlink(); 716 | return child; 717 | } 718 | } 719 | 720 | /// Returns the previous child. 721 | _node* replace_child_after(_node* pos, _node* new_child) 722 | { 723 | DRYAD_PRECONDITION(!new_child->is_linked_in_tree()); 724 | 725 | if (pos == nullptr) 726 | { 727 | DRYAD_PRECONDITION(first_child()); 728 | auto old_child = first_child(); 729 | 730 | new_child->copy_next(old_child); 731 | set_first_child(new_child); 732 | 733 | old_child->unlink(); 734 | return old_child; 735 | } 736 | else 737 | { 738 | DRYAD_PRECONDITION(!pos->next_node_is_parent()); 739 | auto old_child = pos->next_node(); 740 | 741 | new_child->copy_next(old_child); 742 | pos->set_next_sibling(new_child); 743 | 744 | old_child->unlink(); 745 | return old_child; 746 | } 747 | } 748 | 749 | //=== access ===// 750 | _node* first_child() 751 | { 752 | return static_cast<_node*>(this->user_data_ptr()); 753 | } 754 | const _node* first_child() const 755 | { 756 | return static_cast(this->user_data_ptr()); 757 | } 758 | 759 | _node* node_after(const _node* prev) 760 | { 761 | return prev == nullptr ? first_child() : prev->next_node(); 762 | } 763 | const _node* node_after(const _node* prev) const 764 | { 765 | return prev == nullptr ? first_child() : prev->next_node(); 766 | } 767 | 768 | private: 769 | void set_first_child(_node* new_child) 770 | { 771 | this->user_data_ptr() = new_child; 772 | } 773 | using _node::user_data_ptr; 774 | 775 | friend _node; 776 | }; 777 | 778 | #define DRYAD_CHILD_NODE_GETTER(Type, Name, PrevChild) \ 779 | Type* Name() \ 780 | { \ 781 | return ::dryad::node_cast(this->node_after(PrevChild)); \ 782 | } \ 783 | const Type* Name() const \ 784 | { \ 785 | return ::dryad::node_cast(this->node_after(PrevChild)); \ 786 | } 787 | 788 | #define DRYAD_CHILD_NODE_RANGE_GETTER(Type, Name, PrevChild, NextChild) \ 789 | auto Name() \ 790 | { \ 791 | using iterator = typename decltype(this->children())::iterator; \ 792 | return ::dryad::make_node_range(iterator::from_ptr(this->node_after(PrevChild)), \ 793 | iterator::from_ptr(NextChild)); \ 794 | } \ 795 | auto Name() const \ 796 | { \ 797 | using iterator = typename decltype(this->children())::iterator; \ 798 | return ::dryad::make_node_range(iterator::from_ptr(this->node_after(PrevChild)), \ 799 | iterator::from_ptr(NextChild)); \ 800 | } 801 | } // namespace dryad 802 | 803 | namespace dryad 804 | { 805 | template 806 | bool node_has_kind(const node* ptr) 807 | { 808 | static_assert(is_node); 809 | return T::type_matches_kind(ptr->kind()); 810 | } 811 | 812 | template 813 | T* node_cast(node* ptr) 814 | { 815 | static_assert(is_node); 816 | DRYAD_PRECONDITION(node_has_kind(ptr)); 817 | return static_cast(ptr); 818 | } 819 | template 820 | const T* node_cast(const node* ptr) 821 | { 822 | static_assert(is_node); 823 | DRYAD_PRECONDITION(node_has_kind(ptr)); 824 | return static_cast(ptr); 825 | } 826 | 827 | template 828 | T* node_try_cast(node* ptr) 829 | { 830 | static_assert(is_node); 831 | if (node_has_kind(ptr)) 832 | return static_cast(ptr); 833 | else 834 | return nullptr; 835 | } 836 | template 837 | const T* node_try_cast(const node* ptr) 838 | { 839 | static_assert(is_node); 840 | if (node_has_kind(ptr)) 841 | return static_cast(ptr); 842 | else 843 | return nullptr; 844 | } 845 | } // namespace dryad 846 | 847 | namespace dryad 848 | { 849 | template > 851 | struct node_range 852 | { 853 | NodeIterator _begin; 854 | NodeIterator _end; 855 | 856 | node_range(NodeIterator begin, NodeIterator end) : _begin(begin), _end(end) {} 857 | 858 | using _value_type = std::conditional_t< 859 | std::is_const_v>, const NodeType*, 860 | NodeType*>; 861 | 862 | struct iterator : _detail::forward_iterator_base 863 | { 864 | NodeIterator _cur; 865 | 866 | _value_type deref() const 867 | { 868 | return node_cast(*_cur); 869 | } 870 | void increment() 871 | { 872 | ++_cur; 873 | } 874 | bool equal(iterator rhs) const 875 | { 876 | return _cur == rhs._cur; 877 | } 878 | }; 879 | 880 | bool empty() const 881 | { 882 | return _begin == _end; 883 | } 884 | 885 | iterator begin() const 886 | { 887 | return {{}, _begin}; 888 | } 889 | iterator end() const 890 | { 891 | return {{}, _end}; 892 | } 893 | 894 | _value_type front() const 895 | { 896 | return *begin(); 897 | } 898 | }; 899 | 900 | template 901 | auto make_node_range(NodeIterator begin, NodeIterator end) 902 | { 903 | return node_range(begin, end); 904 | } 905 | template 906 | auto make_node_range(NodeIterator begin, NodeIterator end) 907 | { 908 | return node_range(begin, end); 909 | } 910 | } // namespace dryad 911 | 912 | namespace dryad::_detail 913 | { 914 | template 915 | struct node_type_list 916 | { 917 | template 918 | using insert = node_type_list; 919 | }; 920 | 921 | template 922 | struct _node_types_for_lambdas; 923 | template <> 924 | struct _node_types_for_lambdas<> 925 | { 926 | using type = node_type_list<>; 927 | }; 928 | template 929 | struct _node_types_for_lambdas 930 | { 931 | using tail = typename _node_types_for_lambdas::type; 932 | using type = typename tail::template insert; 933 | }; 934 | template 935 | struct _node_types_for_lambdas 936 | { 937 | using tail = typename _node_types_for_lambdas::type; 938 | using type = typename tail::template insert; 939 | }; 940 | template 942 | struct _node_types_for_lambdas 943 | { 944 | using tail = typename _node_types_for_lambdas::type; 945 | using type = typename tail::template insert; 946 | }; 947 | template 949 | struct _node_types_for_lambdas 950 | { 951 | using tail = typename _node_types_for_lambdas::type; 952 | using type = typename tail::template insert; 953 | }; 954 | 955 | template 956 | using node_types_for_lambdas = typename _node_types_for_lambdas<&Lambdas::operator()...>::type; 957 | } // namespace dryad::_detail 958 | 959 | namespace dryad 960 | { 961 | template 962 | struct _visit_node; 963 | template 964 | struct _visit_node<_detail::node_type_list, Lambdas...> 965 | { 966 | template 967 | DRYAD_FORCE_INLINE static void visit(Node* node, Lambdas&&... lambdas) 968 | { 969 | auto kind = node->kind(); 970 | [[maybe_unused]] auto found_callback 971 | = ((NodeTypes::type_matches_kind(kind) ? (lambdas(static_cast(node)), true) 972 | : false) 973 | || ...); 974 | } 975 | }; 976 | 977 | template 978 | struct _visit_node_all; 979 | template 980 | struct _visit_node_all<_detail::node_type_list, HLambda, TLambda...> 981 | { 982 | template 983 | DRYAD_FORCE_INLINE static auto visit(Node* node, HLambda&& head, TLambda&&... tail) 984 | { 985 | auto kind = node->kind(); 986 | if constexpr (sizeof...(T) == 0) 987 | { 988 | DRYAD_ASSERT(H::type_matches_kind(kind), "missing visitor"); 989 | return head(static_cast(node)); 990 | } 991 | else 992 | { 993 | return H::type_matches_kind(kind) 994 | ? head(static_cast(node)) 995 | : _visit_node_all<_detail::node_type_list, 996 | TLambda...>::visit(node, DRYAD_FWD(tail)...); 997 | } 998 | } 999 | }; 1000 | 1001 | /// Visits the node invoking the appropriate lambda for each node type. 1002 | /// 1003 | /// It will try each lambda in the order specified, NodeType can be abstract in which case it 1004 | /// swallows all. Only one lambda will be invoked. If the type of a node does not match any 1005 | /// lambda, it will not be invoked. 1006 | template 1007 | void visit_node(node* node, Lambdas&&... lambdas) 1008 | { 1009 | using node_types = _detail::node_types_for_lambdas...>; 1010 | _visit_node::visit(node, DRYAD_FWD(lambdas)...); 1011 | } 1012 | template 1013 | void visit_node(const node* node, Lambdas&&... lambdas) 1014 | { 1015 | using node_types = _detail::node_types_for_lambdas...>; 1016 | return _visit_node::visit(node, DRYAD_FWD(lambdas)...); 1017 | } 1018 | 1019 | /// Same as above, but it is an error if a node cannot be visited. 1020 | template 1021 | auto visit_node_all(node* node, Lambdas&&... lambdas) 1022 | { 1023 | using node_types = _detail::node_types_for_lambdas...>; 1024 | return _visit_node_all::template visit(node, DRYAD_FWD(lambdas)...); 1025 | } 1026 | template 1027 | auto visit_node_all(const node* node, Lambdas&&... lambdas) 1028 | { 1029 | using node_types = _detail::node_types_for_lambdas...>; 1030 | return _visit_node_all::template visit(node, DRYAD_FWD(lambdas)...); 1031 | } 1032 | } // namespace dryad 1033 | 1034 | #endif // DRYAD_NODE_HPP_INCLUDED 1035 | 1036 | -------------------------------------------------------------------------------- /include/dryad/node_map.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_NODE_MAP_HPP_INCLUDED 5 | #define DRYAD_NODE_MAP_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace dryad::_detail 13 | { 14 | template 15 | struct node_hash_traits 16 | { 17 | using value_type = NodeType*; 18 | 19 | static bool is_unoccupied(value_type ptr) 20 | { 21 | auto bits = reinterpret_cast(ptr); 22 | return bits == 0 || bits == std::uintptr_t(-1); 23 | } 24 | 25 | static void fill_removed(value_type* data, size_t size) 26 | { 27 | std::memset(data, static_cast(-1), size * sizeof(value_type)); 28 | } 29 | 30 | static void fill_unoccupied(value_type* data, size_t size) 31 | { 32 | std::memset(data, static_cast(0), size * sizeof(value_type)); 33 | } 34 | 35 | static bool is_equal(value_type entry, std::remove_const_t* value) 36 | { 37 | return entry == value; 38 | } 39 | static bool is_equal(value_type entry, const NodeType* key) 40 | { 41 | return entry == key; 42 | } 43 | 44 | static std::size_t hash(std::remove_const_t* value) 45 | { 46 | auto bits = reinterpret_cast(value); 47 | // We just remove the alignment bits (which are always zero), and use the rest as-is. 48 | // If benchmarks show bad performance, we might reconsider it. 49 | return bits >> 3; 50 | } 51 | static std::size_t hash(const NodeType* key) 52 | { 53 | return hash(const_cast*>(key)); 54 | } 55 | }; 56 | } // namespace dryad::_detail 57 | 58 | namespace dryad 59 | { 60 | struct _node_map_void 61 | {}; 62 | 63 | /// Maps pointers to `NodeType` (which may be `const`) to `Value` (which may be `void`). 64 | template 65 | class node_map 66 | { 67 | using resource_ptr = _detail::memory_resource_ptr; 68 | using traits = _detail::node_hash_traits; 69 | using hash_table = _detail::hash_table; 70 | 71 | using value_array = std::conditional_t, _node_map_void, Value*>; 72 | 73 | public: 74 | using key_type = NodeType*; 75 | using mapped_type = Value; 76 | 77 | //=== constructors ===// 78 | constexpr node_map() : _values(), _resource(_detail::get_memory_resource()) {} 79 | constexpr explicit node_map(MemoryResource* resource) : _values(), _resource(resource) {} 80 | 81 | ~node_map() 82 | { 83 | if constexpr (!std::is_void_v && !std::is_trivially_destructible_v) 84 | { 85 | for (auto entry : _table.entries()) 86 | _values[entry.index()].~Value(); 87 | } 88 | if constexpr (!std::is_void_v) 89 | _resource->deallocate(_values, _table.capacity() * sizeof(Value), alignof(Value)); 90 | 91 | _table.free(_resource); 92 | } 93 | 94 | node_map(node_map&& other) noexcept 95 | : _table(other._table), _values(other._values), _resource(other._resource) 96 | { 97 | other._table = {}; 98 | other._values = {}; 99 | } 100 | 101 | node_map& operator=(node_map&& other) noexcept 102 | { 103 | _detail::swap(_table, other._table); 104 | _detail::swap(_values, other._values); 105 | _detail::swap(_resource, other._resource); 106 | return *this; 107 | } 108 | 109 | //=== access ===// 110 | bool empty() const 111 | { 112 | return _table.size() == 0; 113 | } 114 | std::size_t size() const 115 | { 116 | return _table.size(); 117 | } 118 | std::size_t capacity() const 119 | { 120 | return _table.capacity(); 121 | } 122 | 123 | //=== entry_handle ===// 124 | class entry_handle 125 | { 126 | public: 127 | explicit operator bool() const 128 | { 129 | return static_cast(_entry); 130 | } 131 | 132 | NodeType* node() const 133 | { 134 | return _key; 135 | } 136 | 137 | decltype(auto) value() const 138 | { 139 | DRYAD_PRECONDITION(*this); 140 | return _values[_entry.index()]; 141 | } 142 | 143 | template 144 | decltype(auto) insert(Args&&... args) 145 | { 146 | DRYAD_PRECONDITION(!*this); 147 | _entry.create(_key); 148 | if constexpr (!std::is_void_v) 149 | return *::new (&_values[_entry.index()]) Value(DRYAD_FWD(args)...); 150 | } 151 | 152 | template 153 | decltype(auto) update(Args&&... args) const 154 | { 155 | DRYAD_PRECONDITION(*this); 156 | return _values[_entry.index()] = Value(DRYAD_FWD(args)...); 157 | } 158 | 159 | void remove() 160 | { 161 | DRYAD_PRECONDITION(*this); 162 | if constexpr (!std::is_void_v) 163 | _values[_entry.index()].~Value(); 164 | _entry.remove(); 165 | } 166 | 167 | typename hash_table::entry_handle _entry; 168 | DRYAD_EMPTY_MEMBER value_array _values; 169 | NodeType* _key; 170 | }; 171 | 172 | //=== modifiers ===// 173 | void rehash(std::size_t new_capacity) 174 | { 175 | new_capacity = _table.to_table_capacity(new_capacity); 176 | auto capacity = _table.capacity(); 177 | if (new_capacity <= capacity) 178 | return; 179 | 180 | if constexpr (std::is_void_v) 181 | { 182 | _table.rehash(_resource, new_capacity, traits{}); 183 | } 184 | else 185 | { 186 | auto new_values = static_cast( 187 | _resource->allocate(new_capacity * sizeof(Value), alignof(Value))); 188 | 189 | _table.rehash(_resource, new_capacity, traits{}, [&](auto new_entry, auto old_idx) { 190 | // Do a destructive move of the value to the new array. 191 | auto new_idx = new_entry.index(); 192 | ::new (&new_values[new_idx]) Value(DRYAD_MOV(_values[old_idx])); 193 | _values[old_idx].~Value(); 194 | }); 195 | 196 | if (capacity > 0) 197 | _resource->deallocate(_values, capacity * sizeof(Value), alignof(Value)); 198 | 199 | _values = new_values; 200 | } 201 | } 202 | 203 | entry_handle lookup_entry(NodeType* node) 204 | { 205 | if (_table.should_rehash()) 206 | rehash(2 * _table.capacity()); 207 | 208 | return {_table.lookup_entry(node, traits{}), _values, node}; 209 | } 210 | 211 | bool contains(const NodeType* node) 212 | { 213 | if (empty()) 214 | return false; 215 | 216 | auto entry = _table.lookup_entry(node, traits{}); 217 | return static_cast(entry); 218 | } 219 | 220 | Value* lookup(const NodeType* node) 221 | { 222 | if (empty()) 223 | return nullptr; 224 | 225 | auto entry = _table.lookup_entry(node, traits{}); 226 | return entry ? &_values[entry.index()] : nullptr; 227 | } 228 | const Value* lookup(const NodeType* node) const 229 | { 230 | return const_cast(this)->lookup(node); 231 | } 232 | 233 | template 234 | bool insert(NodeType* node, Args&&... args) 235 | { 236 | auto entry = lookup_entry(node); 237 | if (entry) 238 | return false; 239 | 240 | entry.insert(DRYAD_FWD(args)...); 241 | return true; 242 | } 243 | template 244 | bool insert_or_update(NodeType* node, Args&&... args) 245 | { 246 | auto entry = lookup_entry(node); 247 | if (entry) 248 | { 249 | entry.update(DRYAD_FWD(args)...); 250 | return false; 251 | } 252 | else 253 | { 254 | entry.insert(DRYAD_FWD(args)...); 255 | return true; 256 | } 257 | } 258 | 259 | bool remove(const NodeType* node) 260 | { 261 | auto entry = lookup_entry(const_cast(node)); 262 | if (!entry) 263 | return false; 264 | 265 | entry.remove(); 266 | return true; 267 | } 268 | 269 | private: 270 | hash_table _table; 271 | DRYAD_EMPTY_MEMBER value_array _values; // shares key index 272 | DRYAD_EMPTY_MEMBER resource_ptr _resource; 273 | }; 274 | 275 | template 276 | using node_set = node_map; 277 | } // namespace dryad 278 | 279 | #endif // DRYAD_NODE_MAP_HPP_INCLUDED 280 | 281 | -------------------------------------------------------------------------------- /include/dryad/symbol.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_SYMBOL_HPP_INCLUDED 5 | #define DRYAD_SYMBOL_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace dryad::_detail 13 | { 14 | // Contains all unique symbols, null-terminated, in memory one after the other. 15 | template 16 | class symbol_buffer 17 | { 18 | static constexpr auto min_buffer_size = 16 * 1024; 19 | 20 | public: 21 | constexpr symbol_buffer() : _data(nullptr), _size(0), _capacity(0) {} 22 | 23 | template 24 | void free(ResourcePtr resource) 25 | { 26 | if (_capacity == 0) 27 | return; 28 | 29 | resource->deallocate(_data, _capacity * sizeof(CharT), alignof(CharT)); 30 | _data = nullptr; 31 | _size = 0; 32 | _capacity = 0; 33 | } 34 | 35 | template 36 | void reserve(ResourcePtr resource, std::size_t new_capacity) 37 | { 38 | if (new_capacity <= _capacity) 39 | return; 40 | 41 | auto new_data 42 | = static_cast(resource->allocate(new_capacity * sizeof(CharT), alignof(CharT))); 43 | if (_size > 0) 44 | std::memcpy(new_data, _data, _size * sizeof(CharT)); 45 | if (_capacity > 0) 46 | resource->deallocate(_data, _capacity * sizeof(CharT), alignof(CharT)); 47 | 48 | _data = new_data; 49 | _capacity = new_capacity; 50 | } 51 | 52 | template 53 | void reserve_new_string(ResourcePtr resource, std::size_t new_string_length) 54 | { 55 | // +1 for null-terminator. 56 | auto new_size = _size + new_string_length + 1; 57 | if (new_size <= _capacity) 58 | return; 59 | 60 | auto new_capacity = new_size * 2; 61 | if (new_capacity < min_buffer_size) 62 | new_capacity = min_buffer_size; 63 | 64 | reserve(resource, new_capacity); 65 | } 66 | 67 | std::size_t insert(const CharT* str, std::size_t length) 68 | { 69 | DRYAD_PRECONDITION(_capacity - _size >= length + 1); 70 | 71 | auto index = _size; 72 | 73 | std::memcpy(_data + _size, str, length * sizeof(CharT)); 74 | _size += length; 75 | 76 | _data[_size] = CharT(0); 77 | ++_size; 78 | 79 | return index; 80 | } 81 | 82 | const CharT* c_str(std::size_t index) const 83 | { 84 | DRYAD_PRECONDITION(index < _size); 85 | return _data + index; 86 | } 87 | 88 | private: 89 | CharT* _data; 90 | std::size_t _size; 91 | std::size_t _capacity; 92 | }; 93 | 94 | template 95 | struct symbol_index_hash_traits 96 | { 97 | const symbol_buffer* buffer; 98 | 99 | using value_type = IndexType; 100 | 101 | struct string_view 102 | { 103 | const CharT* ptr; 104 | std::size_t length; 105 | }; 106 | 107 | static constexpr bool is_unoccupied(IndexType index) 108 | { 109 | return index == IndexType(-1); 110 | } 111 | static void fill_unoccupied(IndexType* data, std::size_t size) 112 | { 113 | // It has all bits set to 1, so we can do it per-byte. 114 | std::memset(data, static_cast(-1), size * sizeof(IndexType)); 115 | } 116 | 117 | static constexpr bool is_equal(IndexType entry, IndexType value) 118 | { 119 | return entry == value; 120 | } 121 | bool is_equal(IndexType entry, string_view str) const 122 | { 123 | auto existing_str = buffer->c_str(entry); 124 | return std::strncmp(existing_str, str.ptr, str.length) == 0; 125 | } 126 | 127 | std::size_t hash(IndexType entry) const 128 | { 129 | auto str = buffer->c_str(entry); 130 | return default_hash_algorithm().hash_c_str(str).finish(); 131 | } 132 | static constexpr std::size_t hash(string_view str) 133 | { 134 | return default_hash_algorithm() 135 | .hash_bytes(reinterpret_cast(str.ptr), str.length * sizeof(CharT)) 136 | .finish(); 137 | } 138 | }; 139 | } // namespace dryad::_detail 140 | 141 | namespace dryad 142 | { 143 | template 144 | class symbol; 145 | 146 | template 148 | class symbol_interner 149 | { 150 | static_assert(std::is_trivial_v); 151 | static_assert(std::is_unsigned_v); 152 | 153 | using resource_ptr = _detail::memory_resource_ptr; 154 | using traits = _detail::symbol_index_hash_traits; 155 | 156 | public: 157 | using symbol = dryad::symbol; 158 | 159 | //=== construction ===// 160 | constexpr symbol_interner() : _resource(_detail::get_memory_resource()) {} 161 | constexpr explicit symbol_interner(MemoryResource* resource) : _resource(resource) {} 162 | 163 | ~symbol_interner() noexcept 164 | { 165 | _buffer.free(_resource); 166 | _map.free(_resource); 167 | } 168 | 169 | symbol_interner(symbol_interner&& other) noexcept 170 | : _buffer(other._buffer), _map(other._map), _resource(other._resource) 171 | { 172 | other._buffer = {}; 173 | other._map = {}; 174 | } 175 | 176 | symbol_interner& operator=(symbol_interner&& other) noexcept 177 | { 178 | _detail::swap(_buffer, other._buffer); 179 | _detail::swap(_map, other._map); 180 | _detail::swap(_resource, other._resource); 181 | return *this; 182 | } 183 | 184 | //=== interning ===// 185 | void reserve(std::size_t number_of_symbols, std::size_t average_symbol_length) 186 | { 187 | _buffer.reserve(_resource, number_of_symbols * average_symbol_length); 188 | _map.rehash(_resource, _map.to_table_capacity(number_of_symbols), traits{&_buffer}); 189 | } 190 | 191 | symbol intern(const CharT* str, std::size_t length) 192 | { 193 | if (_map.should_rehash()) 194 | _map.rehash(_resource, traits{&_buffer}); 195 | 196 | auto entry = _map.lookup_entry(typename traits::string_view{str, length}, traits{&_buffer}); 197 | if (entry) 198 | // Already interned, return index. 199 | return symbol(entry.get()); 200 | 201 | // Copy string data to buffer, as we don't have it yet. 202 | _buffer.reserve_new_string(_resource, length); 203 | auto idx = _buffer.insert(str, length); 204 | DRYAD_PRECONDITION(idx == IndexType(idx)); // Overflow of index type. 205 | 206 | // Store index in map. 207 | entry.create(IndexType(idx)); 208 | 209 | // Return new symbol. 210 | return symbol(IndexType(idx)); 211 | } 212 | template 213 | symbol intern(const CharT (&literal)[N]) 214 | { 215 | DRYAD_PRECONDITION(literal[N - 1] == CharT(0)); 216 | return intern(literal, N - 1); 217 | } 218 | 219 | private: 220 | _detail::symbol_buffer _buffer; 221 | _detail::hash_table _map; 222 | DRYAD_EMPTY_MEMBER resource_ptr _resource; 223 | 224 | friend symbol; 225 | }; 226 | } // namespace dryad 227 | 228 | namespace dryad 229 | { 230 | template 231 | class symbol 232 | { 233 | static_assert(std::is_unsigned_v); 234 | 235 | public: 236 | using index_type = IndexType; 237 | 238 | constexpr symbol() : _index(IndexType(-1)) {} 239 | constexpr explicit symbol(IndexType idx) : _index(idx) {} 240 | 241 | //=== fast access ===// 242 | constexpr explicit operator bool() const 243 | { 244 | return _index != IndexType(-1); 245 | } 246 | 247 | constexpr IndexType id() const 248 | { 249 | return _index; 250 | } 251 | 252 | //=== slow access ===// 253 | template 254 | constexpr const CharT* c_str( 255 | const symbol_interner& interner) const 256 | { 257 | return interner._buffer.c_str(_index); 258 | } 259 | 260 | //=== comparison ===// 261 | friend constexpr bool operator==(symbol lhs, symbol rhs) 262 | { 263 | return lhs._index == rhs._index; 264 | } 265 | friend constexpr bool operator!=(symbol lhs, symbol rhs) 266 | { 267 | return lhs._index != rhs._index; 268 | } 269 | 270 | friend constexpr bool operator<(symbol lhs, symbol rhs) 271 | { 272 | return lhs._index < rhs._index; 273 | } 274 | friend constexpr bool operator<=(symbol lhs, symbol rhs) 275 | { 276 | return lhs._index <= rhs._index; 277 | } 278 | friend constexpr bool operator>(symbol lhs, symbol rhs) 279 | { 280 | return lhs._index > rhs._index; 281 | } 282 | friend constexpr bool operator>=(symbol lhs, symbol rhs) 283 | { 284 | return lhs._index >= rhs._index; 285 | } 286 | 287 | private: 288 | IndexType _index; 289 | 290 | template 291 | friend class symbol_interner; 292 | }; 293 | } // namespace dryad 294 | 295 | #endif // DRYAD_SYMBOL_HPP_INCLUDED 296 | 297 | -------------------------------------------------------------------------------- /include/dryad/symbol_table.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_SYMBOL_TABLE_HPP_INCLUDED 5 | #define DRYAD_SYMBOL_TABLE_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace dryad::_detail 14 | { 15 | template 16 | struct symbol_hash_traits 17 | { 18 | using value_type = IndexType; 19 | 20 | static bool is_unoccupied(IndexType idx) 21 | { 22 | return idx >= IndexType(-2); 23 | } 24 | 25 | static void fill_removed(IndexType* data, std::size_t size) 26 | { 27 | for (auto i = 0u; i < size; ++i) 28 | data[i] = IndexType(-2); 29 | } 30 | 31 | static void fill_unoccupied(IndexType* data, std::size_t size) 32 | { 33 | std::memset(data, static_cast(-1), size * sizeof(IndexType)); 34 | } 35 | 36 | static bool is_equal(IndexType entry, IndexType value) 37 | { 38 | return entry == value; 39 | } 40 | 41 | static std::size_t hash(IndexType entry) 42 | { 43 | return static_cast(entry); 44 | } 45 | }; 46 | } // namespace dryad::_detail 47 | 48 | namespace dryad 49 | { 50 | /// Maps symbols to their declarations. 51 | /// API assumes DeclRef is some sort of (smart) pointer to a declaration. 52 | template 53 | class symbol_table 54 | { 55 | static_assert(std::is_trivial_v); 56 | 57 | using resource_ptr = _detail::memory_resource_ptr; 58 | using traits = _detail::symbol_hash_traits; 59 | using hash_table = _detail::hash_table; 60 | 61 | public: 62 | using symbol = Symbol; 63 | using decl_ref = DeclRef; 64 | 65 | struct value_type 66 | { 67 | Symbol symbol; 68 | DeclRef decl; 69 | }; 70 | 71 | //=== constructors ===// 72 | constexpr symbol_table() 73 | : _decls(nullptr), _resource(_detail::get_memory_resource()) 74 | {} 75 | constexpr explicit symbol_table(MemoryResource* resource) : _decls(nullptr), _resource(resource) 76 | {} 77 | 78 | ~symbol_table() 79 | { 80 | _resource->deallocate(_decls, _table.capacity() * sizeof(DeclRef), alignof(DeclRef)); 81 | _table.free(_resource); 82 | } 83 | 84 | symbol_table(symbol_table&& other) noexcept 85 | : _table(other._table), _decls(other._decls), _resource(other._resource) 86 | { 87 | other._table = {}; 88 | other._decls = {}; 89 | } 90 | 91 | symbol_table& operator=(symbol_table&& other) noexcept 92 | { 93 | _detail::swap(_table, other._table); 94 | _detail::swap(_decls, other._decls); 95 | _detail::swap(_resource, other._resource); 96 | return *this; 97 | } 98 | 99 | //=== access ===// 100 | bool empty() const 101 | { 102 | return _table.size() == 0; 103 | } 104 | std::size_t size() const 105 | { 106 | return _table.size(); 107 | } 108 | std::size_t capacity() const 109 | { 110 | return _table.capacity(); 111 | } 112 | 113 | struct iterator : _detail::forward_iterator_base 114 | { 115 | const symbol_table* _self = nullptr; 116 | typename hash_table::entry_range::iterator _cur; 117 | 118 | value_type deref() const 119 | { 120 | return {symbol(_cur->get()), _self->_decls[_cur->index()]}; 121 | } 122 | void increment() 123 | { 124 | ++_cur; 125 | } 126 | bool equal(iterator rhs) const 127 | { 128 | return _cur == rhs._cur; 129 | } 130 | }; 131 | 132 | iterator begin() const 133 | { 134 | return {{}, this, const_cast(this)->_table.entries().begin()}; 135 | } 136 | iterator end() const 137 | { 138 | return {{}, this, const_cast(this)->_table.entries().end()}; 139 | } 140 | 141 | //=== modifiers ===// 142 | void rehash(std::size_t new_capacity) 143 | { 144 | new_capacity = _table.to_table_capacity(new_capacity); 145 | auto capacity = _table.capacity(); 146 | if (new_capacity <= capacity) 147 | return; 148 | 149 | auto new_decls = static_cast( 150 | _resource->allocate(new_capacity * sizeof(DeclRef), alignof(DeclRef))); 151 | 152 | _table.rehash(_resource, new_capacity, traits{}, [&](auto new_entry, auto old_idx) { 153 | // Copy the entries over. 154 | new_decls[new_entry.index()] = _decls[old_idx]; 155 | }); 156 | 157 | if (capacity > 0) 158 | _resource->deallocate(_decls, capacity * sizeof(DeclRef), alignof(DeclRef)); 159 | 160 | _decls = new_decls; 161 | } 162 | 163 | /// Inserts a new declaration into the symbol table. 164 | /// If there is already a declaration with that name, replaces it and returns it. 165 | /// Otherwise returns a default constructed DeclRef. 166 | DeclRef insert_or_shadow(Symbol symbol, DeclRef decl) 167 | { 168 | if (_table.should_rehash()) 169 | rehash(2 * _table.capacity()); 170 | 171 | auto entry = _table.lookup_entry(symbol.id()); 172 | if (entry) 173 | { 174 | auto shadowed = _decls[entry.index()]; 175 | _decls[entry.index()] = decl; 176 | return shadowed; 177 | } 178 | else 179 | { 180 | entry.create(symbol.id()); 181 | _decls[entry.index()] = decl; 182 | return DeclRef(); 183 | } 184 | } 185 | 186 | /// Removes a declaration with that name from the symbol table. 187 | DeclRef remove(Symbol symbol) 188 | { 189 | if (empty()) 190 | return DeclRef(); 191 | 192 | auto entry = _table.lookup_entry(symbol.id()); 193 | if (!entry) 194 | return DeclRef(); 195 | 196 | auto decl = _decls[entry.index()]; 197 | entry.remove(); 198 | return decl; 199 | } 200 | 201 | /// Searches for a declaration with that name. 202 | /// If found in the symbol table, returns it. 203 | /// Otherwise, returns a default constructed DeclRef. 204 | DeclRef lookup(Symbol symbol) const 205 | { 206 | if (empty()) 207 | return DeclRef(); 208 | 209 | auto entry = const_cast(this)->_table.lookup_entry(symbol.id()); 210 | return entry ? _decls[entry.index()] : DeclRef(); 211 | } 212 | 213 | private: 214 | hash_table _table; 215 | DeclRef* _decls; // shares key index 216 | DRYAD_EMPTY_MEMBER resource_ptr _resource; 217 | }; 218 | } // namespace dryad 219 | 220 | #endif // DRYAD_SYMBOL_TABLE_HPP_INCLUDED 221 | 222 | -------------------------------------------------------------------------------- /include/dryad/tree.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #ifndef DRYAD_TREE_HPP_INCLUDED 5 | #define DRYAD_TREE_HPP_INCLUDED 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace dryad 12 | { 13 | template 14 | class node_creator : public node_ctor 15 | { 16 | public: 17 | /// Creates a node of type T that is not yet linked into the tree. 18 | template 19 | T* create(Args&&... args) 20 | { 21 | static_assert(is_node && !is_abstract_node); 22 | static_assert(std::is_trivially_destructible_v, "nobody will call its destructor"); 23 | 24 | return _arena->template construct(*this, DRYAD_FWD(args)...); 25 | } 26 | 27 | private: 28 | node_creator(arena& arena) : _arena(&arena) {} 29 | 30 | arena* _arena; 31 | 32 | template 33 | friend class tree; 34 | template 35 | friend class forest; 36 | template 37 | friend class hash_forest; 38 | }; 39 | 40 | /// Owns multiple nodes, eventually all children of a single root node. 41 | template 42 | class tree 43 | { 44 | public: 45 | using node_kind_type = typename RootNode::node_kind_type; 46 | 47 | //=== construction ===// 48 | tree() = default; 49 | explicit tree(MemoryResource* resource) : _arena(resource) {} 50 | 51 | //=== node creation ===// 52 | using node_creator = dryad::node_creator; 53 | 54 | template 55 | T* create(Args&&... args) 56 | { 57 | return node_creator(_arena).template create(DRYAD_FWD(args)...); 58 | } 59 | 60 | void clear() 61 | { 62 | _root = nullptr; 63 | _arena.clear(); 64 | } 65 | 66 | //=== root node ===// 67 | bool has_root() const 68 | { 69 | return _root != nullptr; 70 | } 71 | 72 | RootNode* root() 73 | { 74 | return _root; 75 | } 76 | const RootNode* root() const 77 | { 78 | return _root; 79 | } 80 | 81 | void set_root(RootNode* root) 82 | { 83 | DRYAD_PRECONDITION(root != nullptr); 84 | DRYAD_PRECONDITION(!root->is_linked_in_tree()); 85 | _root = root; 86 | _root->set_next_parent(_root); 87 | } 88 | 89 | private: 90 | arena _arena; 91 | RootNode* _root = nullptr; 92 | }; 93 | 94 | /// Owns multiple nodes, eventually all children of a list of root nodes. 95 | template 96 | class forest 97 | { 98 | public: 99 | using node_kind_type = typename RootNode::node_kind_type; 100 | 101 | //=== construction ===// 102 | forest() = default; 103 | explicit forest(MemoryResource* resource) : _arena(resource) {} 104 | 105 | //=== node creation ===// 106 | using node_creator = dryad::node_creator; 107 | 108 | template 109 | T* create(Args&&... args) 110 | { 111 | return node_creator(_arena).template create(DRYAD_FWD(args)...); 112 | } 113 | 114 | void insert_root(RootNode* root) 115 | { 116 | _roots.push_back(root); 117 | _roots.back()->set_next_sibling(_roots.front()); 118 | } 119 | void insert_root_list(unlinked_node_list&& nodes) 120 | { 121 | _roots.append(DRYAD_MOV(nodes)); 122 | _roots.back()->set_next_sibling(_roots.front()); 123 | } 124 | 125 | void clear() 126 | { 127 | _roots = {}; 128 | _arena.clear(); 129 | } 130 | 131 | //=== root nodes ===// 132 | auto roots() const 133 | { 134 | return make_node_range(_roots.begin(), _roots.end()); 135 | } 136 | 137 | private: 138 | arena _arena; 139 | unlinked_node_list _roots; 140 | }; 141 | } // namespace dryad 142 | 143 | namespace dryad 144 | { 145 | enum class traverse_event 146 | { 147 | /// We're visiting a container node before all its children. 148 | enter, 149 | /// We're visiting a container node after all its children. 150 | exit, 151 | /// We're visiting a non-container. 152 | leaf, 153 | }; 154 | 155 | using traverse_event_enter = std::integral_constant; 156 | using traverse_event_exit = std::integral_constant; 157 | 158 | template 159 | class _traverse_range 160 | { 161 | public: 162 | struct _value_type 163 | { 164 | traverse_event event; 165 | T* node; 166 | }; 167 | 168 | class iterator : public _detail::forward_iterator_base 169 | { 170 | public: 171 | iterator() = default; 172 | 173 | _value_type deref() const 174 | { 175 | return {_ev, _cur}; 176 | } 177 | 178 | void increment() 179 | { 180 | if (_ev == traverse_event::enter) 181 | { 182 | if (_cur->has_children()) 183 | { 184 | // We go to the first child next. 185 | auto first_child = _cur->children().front(); 186 | if (first_child->_is_container) 187 | _ev = traverse_event::enter; 188 | else 189 | _ev = traverse_event::leaf; 190 | 191 | _cur = first_child; 192 | } 193 | else 194 | { 195 | // Don't have children, exit. 196 | _ev = traverse_event::exit; 197 | } 198 | } 199 | else if (_cur->next_node() == _cur) 200 | { 201 | // We're done. 202 | _cur = nullptr; 203 | _ev = traverse_event::leaf; 204 | } 205 | else 206 | { 207 | // We follow the next pointer. 208 | 209 | if (_cur->next_node_is_parent()) 210 | // We go back to a container for the second time. 211 | _ev = traverse_event::exit; 212 | else if (_cur->next_node()->_is_container) 213 | // We're having a container as sibling. 214 | _ev = traverse_event::enter; 215 | else 216 | // Non-container as sibling. 217 | _ev = traverse_event::leaf; 218 | 219 | _cur = _cur->next_node(); 220 | } 221 | } 222 | 223 | void skip_children() 224 | { 225 | DRYAD_PRECONDITION(_ev == traverse_event::enter); 226 | _ev = traverse_event::exit; 227 | } 228 | 229 | bool equal(iterator rhs) const 230 | { 231 | return _ev == rhs._ev && _cur == rhs._cur; 232 | } 233 | 234 | private: 235 | T* _cur = nullptr; 236 | traverse_event _ev = traverse_event::leaf; 237 | 238 | friend _traverse_range; 239 | }; 240 | 241 | bool empty() const 242 | { 243 | return _begin == _end; 244 | } 245 | 246 | iterator begin() const 247 | { 248 | return _begin; 249 | } 250 | 251 | iterator end() const 252 | { 253 | return _end; 254 | } 255 | 256 | private: 257 | _traverse_range(T* node) 258 | { 259 | if (node == nullptr) 260 | return; 261 | DRYAD_PRECONDITION(node->is_linked_in_tree()); 262 | 263 | if (node->_is_container) 264 | { 265 | _begin._cur = node; 266 | _begin._ev = traverse_event::enter; 267 | 268 | _end._cur = node; 269 | _end._ev = traverse_event::exit; 270 | ++_end; // half-open range 271 | } 272 | else 273 | { 274 | _begin._cur = node; 275 | _begin._ev = traverse_event::leaf; 276 | 277 | _end = _begin; 278 | ++_end; 279 | } 280 | } 281 | 282 | iterator _begin, _end; 283 | 284 | template 285 | friend _traverse_range> traverse(node* n); 286 | template 287 | friend _traverse_range> traverse(const node* n); 288 | }; 289 | 290 | template 291 | _traverse_range> traverse(node* n) 292 | { 293 | return _traverse_range>(n); 294 | } 295 | template 296 | _traverse_range> traverse(const node* n) 297 | { 298 | return _traverse_range>(n); 299 | } 300 | 301 | template 302 | auto traverse(tree& t) 303 | { 304 | return traverse(t.root()); 305 | } 306 | template 307 | auto traverse(const tree& t) 308 | { 309 | return traverse(t.root()); 310 | } 311 | } // namespace dryad 312 | 313 | namespace dryad 314 | { 315 | /// Visitor that can be used to visit the children of a node manually. 316 | template 317 | struct child_visitor 318 | { 319 | void* _ptr; 320 | void (*_fn)(void*, const node*); 321 | 322 | void operator()(const node* child) const 323 | { 324 | _fn(_ptr, child); 325 | } 326 | }; 327 | 328 | /// Use as lambda to indicate that you don't want to visit a node or its children. 329 | template 330 | constexpr auto ignore_node 331 | = [](child_visitor, const NodeType*) {}; 332 | 333 | template 334 | struct _visit_tree; 335 | template 336 | struct _visit_tree, Lambdas...> 337 | : std::decay_t... 338 | { 339 | using node_kind = std::decay_t; 340 | static constexpr auto is_const = std::is_const_v; 341 | 342 | DRYAD_FORCE_INLINE _visit_tree(Lambdas&&... lambdas) 343 | : std::decay_t(DRYAD_FWD(lambdas))... 344 | {} 345 | 346 | template 347 | DRYAD_FORCE_INLINE auto call(_detail::priority_tag<4>, Iter& iter, Lambda& lambda) 348 | -> decltype(lambda(child_visitor{}, DRYAD_DECLVAL(T*)), true) 349 | { 350 | auto node = node_try_cast(iter->node); 351 | if (node == nullptr) 352 | return false; 353 | 354 | if (iter->event == traverse_event::enter) 355 | { 356 | auto child_visit = [](void* _self, const dryad::node* child) { 357 | auto& self = *static_cast<_visit_tree*>(_self); 358 | 359 | // We might need to cast away the const that was added to maintain the function 360 | // pointer type. 361 | if constexpr (is_const) 362 | self.visit(child); 363 | else 364 | self.visit(const_cast*>(child)); 365 | }; 366 | lambda(child_visitor{this, child_visit}, node); 367 | iter.skip_children(); 368 | } 369 | return !is_abstract_node; 370 | } 371 | template 372 | DRYAD_FORCE_INLINE static auto call(_detail::priority_tag<3>, Iter& iter, Lambda& lambda) 373 | -> decltype(lambda(traverse_event::enter, DRYAD_DECLVAL(T*)), true) 374 | { 375 | auto node = node_try_cast(iter->node); 376 | if (node == nullptr) 377 | return false; 378 | 379 | lambda(iter->event, node); 380 | return !is_abstract_node; 381 | } 382 | template 383 | DRYAD_FORCE_INLINE static auto call(_detail::priority_tag<2>, Iter& iter, Lambda& lambda) 384 | -> decltype(lambda(traverse_event_enter{}, DRYAD_DECLVAL(T*)), true) 385 | { 386 | auto node = node_try_cast(iter->node); 387 | if (node == nullptr) 388 | return false; 389 | 390 | if (iter->event == traverse_event::enter) 391 | lambda(traverse_event_enter{}, node); 392 | return !is_abstract_node; 393 | } 394 | template 395 | DRYAD_FORCE_INLINE static auto call(_detail::priority_tag<2>, Iter& iter, Lambda& lambda) 396 | -> decltype(lambda(traverse_event_exit{}, DRYAD_DECLVAL(T*)), true) 397 | { 398 | auto node = node_try_cast(iter->node); 399 | if (node == nullptr) 400 | return false; 401 | 402 | if (iter->event == traverse_event::exit) 403 | lambda(traverse_event_exit{}, node); 404 | return !is_abstract_node; 405 | } 406 | template 407 | DRYAD_FORCE_INLINE static auto call(_detail::priority_tag<2>, Iter& iter, Lambda& lambda) 408 | -> decltype(lambda(DRYAD_DECLVAL(T*)), true) 409 | { 410 | auto node = node_try_cast(iter->node); 411 | if (node == nullptr) 412 | return false; 413 | 414 | if (iter->event != traverse_event::exit) 415 | lambda(node); 416 | return !is_abstract_node; 417 | } 418 | 419 | template 420 | DRYAD_FORCE_INLINE void visit(TreeOrNode&& tree_or_node) 421 | { 422 | auto range = dryad::traverse(tree_or_node); 423 | for (auto iter = range.begin(); iter != range.end(); ++iter) 424 | { 425 | (void)(call(_detail::priority_tag<4>{}, iter, static_cast(*this)) 426 | || ...); 427 | } 428 | } 429 | }; 430 | 431 | /// Traverses the (sub)tree invoking the appropriate lambda for each node type. 432 | /// 433 | /// * If a lambda has signature (traverse_event, NodeType), will invoke it every time NodeType is 434 | /// visited. 435 | /// * If a lambda has signature (traverse_event_enter, NodeType), will invoke it on enter events 436 | /// only. 437 | /// * If a lambda has signature (traverse_event_exit, NodeType), will invoke it on exit events only. 438 | /// * If a lambda has signature (NodeType), will invoke it on enter or leaf events only. 439 | /// * If a lambda has signature (child_visitor, NodeType), will invoke it on enter events only and 440 | /// not for any child nodes. Children have to be visited manually by passing them to the 441 | /// child_visitor. 442 | /// 443 | /// It will try each lambda in the order specified and stop once it founds a lambda taking a 444 | /// non-abstract NodeType that would have been invoked if the event matches. If the type matches but 445 | /// the event does not, it will not invoke the lambda but still stop searching for more lambdas. 446 | /// 447 | /// If a lambda takes an abstract NodeType, continues looking for more lambdas to invoke. As such 448 | /// they need to be specified first, as concrete types that come before swallow the node. 449 | template 450 | void visit_tree(node* node, Lambdas&&... lambdas) 451 | { 452 | using node_types = _detail::node_types_for_lambdas...>; 453 | _visit_tree(DRYAD_FWD(lambdas)...).visit(node); 454 | } 455 | template 456 | void visit_tree(const node* node, Lambdas&&... lambdas) 457 | { 458 | using node_types = _detail::node_types_for_lambdas...>; 459 | _visit_tree(DRYAD_FWD(lambdas)...).visit(node); 460 | } 461 | template 462 | void visit_tree(tree& tree, Lambdas&&... lambdas) 463 | { 464 | visit_tree(tree.root(), DRYAD_FWD(lambdas)...); 465 | } 466 | template 467 | void visit_tree(const tree& tree, Lambdas&&... lambdas) 468 | { 469 | visit_tree(tree.root(), DRYAD_FWD(lambdas)...); 470 | } 471 | } // namespace dryad 472 | 473 | #endif // DRYAD_TREE_HPP_INCLUDED 474 | 475 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 022 Jonathan Müller and dryad contributors 2 | # SPDX-License-Identifier: BSL-1.0 3 | 4 | get_filename_component(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/../include/dryad ABSOLUTE) 5 | set(header_files 6 | ${include_dir}/_detail/assert.hpp 7 | ${include_dir}/_detail/config.hpp 8 | ${include_dir}/_detail/iterator.hpp 9 | ${include_dir}/_detail/hash_table.hpp 10 | ${include_dir}/_detail/memory_resource.hpp 11 | ${include_dir}/_detail/std.hpp 12 | 13 | ${include_dir}/abstract_node.hpp 14 | ${include_dir}/arena.hpp 15 | ${include_dir}/hash_algorithm.hpp 16 | ${include_dir}/hash_forest.hpp 17 | ${include_dir}/node.hpp 18 | ${include_dir}/node_map.hpp 19 | ${include_dir}/symbol.hpp 20 | ${include_dir}/tree.hpp) 21 | 22 | add_library(dryad INTERFACE) 23 | add_library(foonathan::dryad ALIAS dryad) 24 | target_sources(dryad INTERFACE ${header_files}) 25 | target_compile_features(dryad INTERFACE cxx_std_17) 26 | target_include_directories(dryad SYSTEM INTERFACE 27 | $ 28 | $) 29 | 30 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | # SPDX-License-Identifier: BSL-1.0 3 | 4 | #=== Fetch doctest ===# 5 | message(STATUS "Fetching doctest") 6 | include(FetchContent) 7 | FetchContent_Declare(doctest URL https://github.com/doctest/doctest/archive/refs/tags/v2.4.8.zip) 8 | FetchContent_MakeAvailable(doctest) 9 | 10 | add_library(dryad_test_base ${CMAKE_CURRENT_SOURCE_DIR}/doctest_main.cpp) 11 | target_link_libraries(dryad_test_base PUBLIC doctest) 12 | target_include_directories(dryad_test_base PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include) 13 | target_compile_features(dryad_test_base PUBLIC cxx_std_17) 14 | 15 | if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") 16 | if("${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") 17 | target_compile_options(dryad_test_base PUBLIC /WX /W3 /D _CRT_SECURE_NO_WARNINGS) 18 | else() 19 | target_compile_options(dryad_test_base PUBLIC -pedantic-errors -Werror -Wall -Wextra -Wconversion -Wsign-conversion) 20 | endif() 21 | elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") 22 | target_compile_options(dryad_test_base PUBLIC -pedantic-errors -Werror -Wall -Wextra -Wconversion -Wsign-conversion) 23 | elseif(MSVC) 24 | target_compile_options(dryad_test_base PUBLIC /WX /W3 /D _CRT_SECURE_NO_WARNINGS) 25 | endif() 26 | 27 | #=== tests ===# 28 | add_subdirectory(dryad) 29 | 30 | # Test interface library. 31 | add_executable(dryad_interface_test) 32 | target_link_libraries(dryad_interface_test PRIVATE dryad_test_base foonathan::dryad) 33 | 34 | -------------------------------------------------------------------------------- /tests/doctest_main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 7 | #include 8 | 9 | -------------------------------------------------------------------------------- /tests/dryad/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | # SPDX-License-Identifier: BSL-1.0 3 | 4 | set(tests 5 | detail/std.cpp 6 | 7 | abstract_node.cpp 8 | arena.cpp 9 | hash_forest.cpp 10 | node.cpp 11 | node_map.cpp 12 | tree.cpp 13 | symbol.cpp 14 | symbol_table.cpp) 15 | 16 | add_executable(dryad_test ${tests}) 17 | target_link_libraries(dryad_test PRIVATE dryad_test_base) 18 | 19 | add_test(NAME dryad_test COMMAND dryad_test) 20 | 21 | -------------------------------------------------------------------------------- /tests/dryad/abstract_node.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace abstract_node 10 | { 11 | enum class node_kind 12 | { 13 | leaf1, 14 | leaf2, 15 | }; 16 | 17 | using node = dryad::node; 18 | 19 | struct base_node : dryad::abstract_node 20 | { 21 | DRYAD_ATTRIBUTE_USER_DATA16(std::uint16_t, foo); 22 | 23 | DRYAD_ABSTRACT_NODE_CTOR(base_node); 24 | }; 25 | 26 | struct leaf_node1 : dryad::basic_node 27 | { 28 | DRYAD_NODE_CTOR(leaf_node1); 29 | }; 30 | struct leaf_node2 : dryad::basic_node 31 | { 32 | DRYAD_NODE_CTOR(leaf_node2); 33 | }; 34 | } // namespace abstract_node 35 | 36 | TEST_CASE("abstract_node") 37 | { 38 | using namespace abstract_node; 39 | dryad::tree tree; 40 | 41 | auto leaf1 = tree.create(); 42 | leaf1->set_foo(11); 43 | CHECK(leaf1->foo() == 11); 44 | CHECK(dryad::node_has_kind(leaf1)); 45 | 46 | auto leaf2 = tree.create(); 47 | CHECK(!dryad::node_has_kind(leaf2)); 48 | } 49 | 50 | namespace abstract_node_range 51 | { 52 | enum class node_kind 53 | { 54 | leaf1, 55 | leaf2, 56 | }; 57 | 58 | using node = dryad::node; 59 | 60 | struct base_node : dryad::abstract_node_range 61 | { 62 | DRYAD_ATTRIBUTE_USER_DATA16(std::uint16_t, foo); 63 | 64 | DRYAD_ABSTRACT_NODE_CTOR(base_node); 65 | }; 66 | 67 | struct leaf_node1 : dryad::basic_node 68 | { 69 | DRYAD_NODE_CTOR(leaf_node1); 70 | }; 71 | struct leaf_node2 : dryad::basic_node 72 | { 73 | DRYAD_NODE_CTOR(leaf_node2); 74 | }; 75 | } // namespace abstract_node_range 76 | 77 | TEST_CASE("abstract_node_range") 78 | { 79 | using namespace abstract_node_range; 80 | dryad::tree tree; 81 | 82 | auto leaf1 = tree.create(); 83 | leaf1->set_foo(11); 84 | CHECK(leaf1->foo() == 11); 85 | CHECK(dryad::node_has_kind(leaf1)); 86 | 87 | auto leaf2 = tree.create(); 88 | CHECK(!dryad::node_has_kind(leaf2)); 89 | } 90 | 91 | namespace abstract_node_all 92 | { 93 | enum class node_kind 94 | { 95 | leaf1, 96 | leaf2, 97 | }; 98 | 99 | struct node : dryad::abstract_node_all 100 | { 101 | DRYAD_ATTRIBUTE_USER_DATA16(std::uint16_t, foo); 102 | 103 | DRYAD_ABSTRACT_NODE_CTOR(node); 104 | }; 105 | 106 | struct leaf_node1 : dryad::basic_node 107 | { 108 | DRYAD_NODE_CTOR(leaf_node1); 109 | }; 110 | struct leaf_node2 : dryad::basic_node 111 | { 112 | DRYAD_NODE_CTOR(leaf_node2); 113 | }; 114 | } // namespace abstract_node_all 115 | 116 | TEST_CASE("abstract_node_all") 117 | { 118 | using namespace abstract_node_all; 119 | dryad::tree tree; 120 | 121 | auto leaf1 = tree.create(); 122 | leaf1->set_foo(11); 123 | CHECK(leaf1->foo() == 11); 124 | CHECK(dryad::node_has_kind(leaf1)); 125 | 126 | auto leaf2 = tree.create(); 127 | leaf2->set_foo(11); 128 | CHECK(leaf2->foo() == 11); 129 | CHECK(dryad::node_has_kind(leaf2)); 130 | } 131 | 132 | -------------------------------------------------------------------------------- /tests/dryad/arena.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | TEST_CASE("arena") 10 | { 11 | dryad::arena<> arena; 12 | 13 | SUBCASE("basic") 14 | { 15 | auto i = arena.construct(42); 16 | 17 | auto fill_block = arena.allocate(10 * 1024ull, 1); 18 | std::memset(fill_block, 'a', 10 * 1024ull); 19 | 20 | auto next_block = arena.allocate(10 * 1024ull, 1); 21 | std::memset(next_block, 'b', 10 * 1024ull); 22 | 23 | REQUIRE(*i == 42); 24 | 25 | for (auto i = 0u; i != 10 * 1024ull; ++i) 26 | { 27 | REQUIRE(static_cast(fill_block)[i] == 'a'); 28 | REQUIRE(static_cast(next_block)[i] == 'b'); 29 | } 30 | } 31 | SUBCASE("clear and re-use") 32 | { 33 | auto a1 = arena.allocate(10 * 1024ull, 1); 34 | std::memset(a1, 'a', 10 * 1024ull); 35 | 36 | auto a2 = arena.allocate(10 * 1024ull, 1); 37 | std::memset(a2, 'b', 10 * 1024ull); 38 | 39 | arena.clear(); 40 | 41 | auto b1 = arena.allocate(10 * 1024ull, 1); 42 | std::memset(b1, 'A', 10 * 1024ull); 43 | 44 | auto b2 = arena.allocate(10 * 1024ull, 1); 45 | std::memset(b2, 'B', 10 * 1024ull); 46 | 47 | CHECK(a1 == b1); 48 | CHECK(a2 == b2); 49 | 50 | for (auto i = 0u; i != 10 * 1024ull; ++i) 51 | { 52 | REQUIRE(static_cast(b1)[i] == 'A'); 53 | REQUIRE(static_cast(b2)[i] == 'B'); 54 | } 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /tests/dryad/detail/std.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | TEST_CASE("std forward declarations") 11 | { 12 | // We just create them and see whether it's ambiguous. 13 | (void)std::forward_iterator_tag{}; 14 | (void)std::bidirectional_iterator_tag{}; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /tests/dryad/hash_forest.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace 10 | { 11 | enum class node_kind 12 | { 13 | leaf, 14 | container, 15 | }; 16 | 17 | using node = dryad::node; 18 | 19 | struct leaf_node : dryad::basic_node 20 | { 21 | int data; 22 | 23 | leaf_node(dryad::node_ctor ctor, int i) : node_base(ctor), data(i) {} 24 | }; 25 | 26 | struct container_node : dryad::basic_node> 27 | { 28 | template 29 | container_node(dryad::node_ctor ctor, Children... children) : node_base(ctor) 30 | { 31 | insert_children_after(nullptr, children...); 32 | } 33 | }; 34 | 35 | struct node_hasher : dryad::node_hasher_base 36 | { 37 | template 38 | static void hash(HashAlgorithm& hasher, const leaf_node* n) 39 | { 40 | hasher.hash_scalar(n->data); 41 | } 42 | template 43 | static void hash(HashAlgorithm&, const container_node*) 44 | {} 45 | template 46 | static void hash(HashAlgorithm& hasher, int data) 47 | { 48 | hasher.hash_scalar(data); 49 | } 50 | 51 | static bool is_equal(const leaf_node* lhs, const leaf_node* rhs) 52 | { 53 | return lhs->data == rhs->data; 54 | } 55 | static bool is_equal(const container_node*, const container_node*) 56 | { 57 | return true; 58 | } 59 | static bool is_equal(const leaf_node* entry, int data) 60 | { 61 | return entry->data == data; 62 | } 63 | }; 64 | } // namespace 65 | 66 | TEST_CASE("hash_forest") 67 | { 68 | dryad::hash_forest forest; 69 | 70 | auto container1 = forest.build([](decltype(forest)::node_creator creator) { 71 | auto a = creator.create(1); 72 | auto b = creator.create(2); 73 | auto c = creator.create(3); 74 | return creator.create(a, b, c); 75 | }); 76 | auto container2 = forest.build([](decltype(forest)::node_creator creator) { 77 | auto a = creator.create(1); 78 | auto b = creator.create(2); 79 | auto c = creator.create(3); 80 | return creator.create(a, b, c); 81 | }); 82 | CHECK(container1 == container2); 83 | 84 | auto container3 = forest.build([](decltype(forest)::node_creator creator) { 85 | auto a = creator.create(1); 86 | auto b = creator.create(2); 87 | return creator.create(a, b); 88 | }); 89 | CHECK(container1 != container3); 90 | 91 | auto leaf1 = forest.create(1); 92 | auto leaf2 = forest.lookup_or_create(1); 93 | CHECK(leaf1 == leaf2); 94 | auto leaf3 = forest.lookup_or_create(2); 95 | CHECK(leaf3 != leaf1); 96 | } 97 | 98 | -------------------------------------------------------------------------------- /tests/dryad/node.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace 11 | { 12 | enum class node_kind 13 | { 14 | leaf, 15 | container, 16 | }; 17 | 18 | using node = dryad::node; 19 | 20 | struct leaf_node : dryad::basic_node 21 | { 22 | DRYAD_NODE_CTOR(leaf_node); 23 | }; 24 | 25 | struct container_node : dryad::basic_node> 26 | { 27 | DRYAD_NODE_CTOR(container_node); 28 | 29 | template 30 | void insert_front(Children*... ns) 31 | { 32 | this->insert_children_after(nullptr, ns...); 33 | } 34 | void insert_front(dryad::unlinked_node_list list) 35 | { 36 | this->insert_child_list_after(nullptr, list); 37 | this->insert_child_list_after(nullptr, dryad::unlinked_node_list()); 38 | } 39 | 40 | auto children() const 41 | { 42 | auto node_children = node_base::children(); 43 | return dryad::make_node_range(node_children.begin(), node_children.end()); 44 | } 45 | 46 | DRYAD_CHILD_NODE_GETTER(leaf_node, first, nullptr) 47 | DRYAD_CHILD_NODE_GETTER(leaf_node, second, first()) 48 | DRYAD_CHILD_NODE_GETTER(leaf_node, third, second()) 49 | }; 50 | } // namespace 51 | 52 | TEST_CASE("node") 53 | { 54 | dryad::tree tree; 55 | 56 | auto a = tree.create(); 57 | auto b = tree.create(); 58 | auto c = tree.create(); 59 | 60 | auto container = tree.create(); 61 | SUBCASE("variadic insert") 62 | { 63 | container->insert_front(a, b, c); 64 | } 65 | SUBCASE("list insert") 66 | { 67 | dryad::unlinked_node_list list; 68 | CHECK(list.empty()); 69 | CHECK(list.begin() == list.end()); 70 | 71 | list.push_back(b); 72 | CHECK(!list.empty()); 73 | CHECK(list.has_single_element()); 74 | CHECK(list.front() == b); 75 | CHECK(list.back() == b); 76 | 77 | list.append(c); 78 | CHECK(!list.empty()); 79 | CHECK(!list.has_single_element()); 80 | CHECK(list.front() == b); 81 | CHECK(list.back() == c); 82 | 83 | list.push_front(a); 84 | CHECK(!list.empty()); 85 | CHECK(!list.has_single_element()); 86 | CHECK(list.front() == a); 87 | CHECK(list.back() == c); 88 | 89 | CHECK(list.pop_front() == a); 90 | CHECK(!list.empty()); 91 | CHECK(!list.has_single_element()); 92 | CHECK(list.front() == b); 93 | CHECK(list.back() == c); 94 | 95 | list.push_front(a); 96 | 97 | auto iter = list.begin(); 98 | CHECK(iter != list.end()); 99 | CHECK(*iter == a); 100 | ++iter; 101 | CHECK(iter != list.end()); 102 | CHECK(*iter == b); 103 | ++iter; 104 | CHECK(iter != list.end()); 105 | CHECK(*iter == c); 106 | ++iter; 107 | CHECK(iter == list.end()); 108 | 109 | container->insert_front(list); 110 | } 111 | tree.set_root(container); 112 | 113 | CHECK(container->first() == a); 114 | CHECK(container->second() == b); 115 | CHECK(container->third() == c); 116 | 117 | auto node = tree.root(); 118 | CHECK(node->is_linked_in_tree()); 119 | CHECK(node->parent() == node); 120 | CHECK(node->siblings().empty()); 121 | 122 | node->set_color(dryad::color::black); 123 | CHECK(node->color() == dryad::color::black); 124 | 125 | for (auto child : node->children()) 126 | { 127 | CHECK(child->parent() == node); 128 | 129 | auto siblings = child->siblings(); 130 | auto dist = std::distance(siblings.begin(), siblings.end()); 131 | CHECK(dist == 2); 132 | 133 | CHECK(child->children().empty()); 134 | } 135 | for (const leaf_node* child : dryad::node_cast(node)->children()) 136 | { 137 | CHECK(child->parent() == node); 138 | 139 | auto siblings = child->siblings(); 140 | auto dist = std::distance(siblings.begin(), siblings.end()); 141 | CHECK(dist == 2); 142 | 143 | CHECK(child->children().empty()); 144 | } 145 | } 146 | 147 | TEST_CASE("visit_node") 148 | { 149 | dryad::tree tree; 150 | 151 | auto a = tree.create(); 152 | auto b = tree.create(); 153 | auto c = tree.create(); 154 | 155 | auto container = tree.create(); 156 | container->insert_front(a, b, c); 157 | 158 | SUBCASE("basic") 159 | { 160 | auto leaf_count = 0; 161 | auto container_count = 0; 162 | 163 | auto visit = [&](auto node) { 164 | auto result = dryad::visit_node_all( 165 | node, 166 | [&](leaf_node*) { 167 | ++leaf_count; 168 | return 0; 169 | }, 170 | [&](container_node*) { 171 | ++container_count; 172 | return 0; 173 | }); 174 | CHECK(result == 0); 175 | }; 176 | visit(a); 177 | visit(b); 178 | visit(c); 179 | visit(container); 180 | 181 | CHECK(leaf_count == 3); 182 | CHECK(container_count == 1); 183 | } 184 | SUBCASE("catch all") 185 | { 186 | auto leaf_count = 0; 187 | auto node_count = 0; 188 | 189 | auto visit = [&](auto n) { 190 | auto result = dryad::visit_node_all( 191 | n, 192 | [&](node*) { 193 | ++node_count; 194 | return 0; 195 | }, 196 | [&](leaf_node*) { 197 | ++leaf_count; 198 | return 0; 199 | }); 200 | CHECK(result == 0); 201 | }; 202 | visit(a); 203 | visit(b); 204 | visit(c); 205 | visit(container); 206 | 207 | CHECK(leaf_count == 0); 208 | CHECK(node_count == 4); 209 | } 210 | SUBCASE("leaf only") 211 | { 212 | auto leaf_count = 0; 213 | 214 | auto visit = [&](auto node) { dryad::visit_node(node, [&](leaf_node*) { ++leaf_count; }); }; 215 | visit(a); 216 | visit(b); 217 | visit(c); 218 | visit(container); 219 | 220 | CHECK(leaf_count == 3); 221 | } 222 | } 223 | 224 | -------------------------------------------------------------------------------- /tests/dryad/node_map.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace 11 | { 12 | struct node : dryad::basic_node<'a'> 13 | { 14 | DRYAD_NODE_CTOR(node); 15 | }; 16 | 17 | void check_entry(dryad::node_map& map, node* key, const char* str) 18 | { 19 | REQUIRE(map.contains(key)); 20 | CHECK(*map.lookup(key) == str); 21 | 22 | auto entry = map.lookup_entry(key); 23 | CHECK(entry); 24 | CHECK(entry.node() == key); 25 | CHECK(entry.value() == str); 26 | } 27 | } // namespace 28 | 29 | TEST_CASE("node_map") 30 | { 31 | dryad::tree tree; 32 | auto a = tree.create(); 33 | auto b = tree.create(); 34 | auto c = tree.create(); 35 | auto d = tree.create(); 36 | 37 | dryad::node_map map; 38 | CHECK(map.empty()); 39 | CHECK(map.size() == 0); 40 | CHECK(map.capacity() == 0); 41 | 42 | SUBCASE("initial rehash") 43 | { 44 | map.rehash(10); 45 | CHECK(map.empty()); 46 | CHECK(map.size() == 0); 47 | CHECK(map.capacity() >= 10); 48 | } 49 | SUBCASE("move") 50 | { 51 | decltype(map) other; 52 | map = DRYAD_MOV(other); 53 | } 54 | CHECK(!map.contains(a)); 55 | CHECK(!map.contains(b)); 56 | CHECK(!map.contains(c)); 57 | 58 | CHECK(map.insert(a, "a")); 59 | CHECK(map.insert(b, "b")); 60 | CHECK(map.insert(c, "c")); 61 | check_entry(map, a, "a"); 62 | check_entry(map, b, "b"); 63 | check_entry(map, c, "c"); 64 | 65 | SUBCASE("rehash") 66 | { 67 | map.rehash(100); 68 | CHECK(!map.empty()); 69 | CHECK(map.size() == 3); 70 | CHECK(map.capacity() >= 100); 71 | } 72 | SUBCASE("move") 73 | { 74 | decltype(map) other(DRYAD_MOV(map)); 75 | map = DRYAD_MOV(other); 76 | } 77 | 78 | CHECK(map.insert_or_update(d, "d")); 79 | CHECK(!map.insert_or_update(c, "C")); 80 | check_entry(map, a, "a"); 81 | check_entry(map, b, "b"); 82 | check_entry(map, c, "C"); 83 | check_entry(map, d, "d"); 84 | 85 | SUBCASE("second rehash") 86 | { 87 | map.rehash(1000); 88 | CHECK(!map.empty()); 89 | CHECK(map.size() == 4); 90 | CHECK(map.capacity() >= 1000); 91 | } 92 | 93 | CHECK(map.remove(d)); 94 | CHECK(!map.contains(d)); 95 | } 96 | 97 | namespace 98 | { 99 | 100 | void check_entry(dryad::node_set& set, node* key) 101 | { 102 | REQUIRE(set.contains(key)); 103 | 104 | auto entry = set.lookup_entry(key); 105 | CHECK(entry); 106 | CHECK(entry.node() == key); 107 | } 108 | } // namespace 109 | 110 | TEST_CASE("node_set") 111 | { 112 | dryad::tree tree; 113 | auto a = tree.create(); 114 | auto b = tree.create(); 115 | auto c = tree.create(); 116 | 117 | dryad::node_set set; 118 | CHECK(set.empty()); 119 | CHECK(set.size() == 0); 120 | CHECK(set.capacity() == 0); 121 | 122 | SUBCASE("initial rehash") 123 | { 124 | set.rehash(10); 125 | CHECK(set.empty()); 126 | CHECK(set.size() == 0); 127 | CHECK(set.capacity() >= 10); 128 | } 129 | SUBCASE("move") 130 | { 131 | decltype(set) other; 132 | set = DRYAD_MOV(other); 133 | } 134 | 135 | CHECK(set.insert(a)); 136 | CHECK(set.insert(b)); 137 | CHECK(set.insert(c)); 138 | check_entry(set, a); 139 | check_entry(set, b); 140 | check_entry(set, c); 141 | 142 | SUBCASE("rehash") 143 | { 144 | set.rehash(100); 145 | CHECK(!set.empty()); 146 | CHECK(set.size() == 3); 147 | CHECK(set.capacity() >= 100); 148 | } 149 | SUBCASE("move") 150 | { 151 | decltype(set) other(DRYAD_MOV(set)); 152 | set = DRYAD_MOV(other); 153 | } 154 | 155 | CHECK(set.remove(c)); 156 | CHECK(!set.contains(c)); 157 | } 158 | 159 | -------------------------------------------------------------------------------- /tests/dryad/symbol.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | TEST_CASE("symbol_interner") 10 | { 11 | dryad::symbol_interner symbols; 12 | 13 | SUBCASE("reserve") 14 | { 15 | symbols.reserve(10, 3); 16 | } 17 | SUBCASE("move assign") 18 | { 19 | symbols.intern("hello"); 20 | symbols.intern("world"); 21 | 22 | dryad::symbol_interner other; 23 | other.reserve(10, 3); 24 | symbols = DRYAD_MOV(other); 25 | } 26 | 27 | auto abc1 = symbols.intern("abc"); 28 | auto abc2 = symbols.intern("abc"); 29 | CHECK(abc1 == abc2); 30 | CHECK(abc1.c_str(symbols) == doctest::String("abc")); 31 | 32 | auto abc3 = symbols.intern("abcd", 3); 33 | CHECK(abc1 == abc3); 34 | 35 | SUBCASE("move construct") 36 | { 37 | auto other = DRYAD_MOV(symbols); 38 | symbols = DRYAD_MOV(other); 39 | } 40 | 41 | auto def = symbols.intern("def"); 42 | CHECK(def != abc1); 43 | CHECK(def.c_str(symbols) == doctest::String("def")); 44 | 45 | SUBCASE("many symbols interned") 46 | { 47 | std::set ids; 48 | for (auto i = 0u; i < 10 * 1024; ++i) 49 | { 50 | auto string = doctest::toString(std::uint64_t(-1) - i); 51 | auto id = symbols.intern(string.c_str(), string.size()); 52 | CHECK(ids.insert(id).second); 53 | } 54 | 55 | // Now insert everything again. 56 | for (auto i = 0u; i < 10 * 1024; ++i) 57 | { 58 | auto string = doctest::toString(std::uint64_t(-1) - i); 59 | auto id = symbols.intern(string.c_str(), string.size()); 60 | CHECK(!ids.insert(id).second); 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /tests/dryad/symbol_table.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace 10 | { 11 | template 12 | void check_iter(const Table& table, T... elements) 13 | { 14 | std::set set; 15 | for (auto [sym, decl] : table) 16 | { 17 | set.insert(sym); 18 | (void)decl; 19 | } 20 | 21 | CHECK(set.size() == sizeof...(T)); 22 | auto has_elements = ((set.count(elements) == 1) && ...); 23 | CHECK(has_elements); 24 | } 25 | } // namespace 26 | 27 | TEST_CASE("symbol_table") 28 | { 29 | dryad::symbol_interner symbols; 30 | auto a = symbols.intern("a"); 31 | auto b = symbols.intern("b"); 32 | auto c = symbols.intern("c"); 33 | 34 | dryad::symbol_table table; 35 | CHECK(table.empty()); 36 | CHECK(table.size() == 0); 37 | CHECK(table.capacity() == 0); 38 | CHECK(table.begin() == table.end()); 39 | 40 | SUBCASE("initial rehash") 41 | { 42 | table.rehash(10); 43 | CHECK(table.empty()); 44 | CHECK(table.size() == 0); 45 | CHECK(table.capacity() >= 10); 46 | CHECK(table.begin() == table.end()); 47 | } 48 | SUBCASE("move") 49 | { 50 | decltype(table) other; 51 | table = DRYAD_MOV(other); 52 | } 53 | CHECK(!table.lookup(a)); 54 | CHECK(!table.lookup(b)); 55 | CHECK(!table.lookup(c)); 56 | 57 | CHECK(table.insert_or_shadow(a, &a) == nullptr); 58 | CHECK(table.insert_or_shadow(b, &b) == nullptr); 59 | CHECK(table.insert_or_shadow(c, &c) == nullptr); 60 | CHECK(!table.empty()); 61 | CHECK(table.size() == 3); 62 | CHECK(table.lookup(a) == &a); 63 | CHECK(table.lookup(b) == &b); 64 | CHECK(table.lookup(c) == &c); 65 | check_iter(table, a, b, c); 66 | 67 | SUBCASE("rehash") 68 | { 69 | table.rehash(100); 70 | CHECK(!table.empty()); 71 | CHECK(table.size() == 3); 72 | CHECK(table.capacity() >= 100); 73 | } 74 | SUBCASE("move") 75 | { 76 | decltype(table) other(DRYAD_MOV(table)); 77 | table = DRYAD_MOV(other); 78 | } 79 | 80 | CHECK(table.insert_or_shadow(b, &a) == &b); 81 | CHECK(!table.empty()); 82 | CHECK(table.size() == 3); 83 | CHECK(table.lookup(a) == &a); 84 | CHECK(table.lookup(b) == &a); 85 | CHECK(table.lookup(c) == &c); 86 | check_iter(table, a, b, c); 87 | 88 | SUBCASE("second rehash") 89 | { 90 | table.rehash(1000); 91 | CHECK(!table.empty()); 92 | CHECK(table.size() == 3); 93 | CHECK(table.capacity() >= 1000); 94 | } 95 | 96 | CHECK(table.remove(b) == &a); 97 | CHECK(!table.empty()); 98 | CHECK(table.size() == 2); 99 | CHECK(table.lookup(a) == &a); 100 | CHECK(table.lookup(b) == nullptr); 101 | CHECK(table.lookup(c) == &c); 102 | check_iter(table, a, c); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /tests/dryad/tree.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace 10 | { 11 | enum class node_kind 12 | { 13 | leaf, 14 | container, 15 | }; 16 | 17 | using node = dryad::node; 18 | 19 | struct leaf_node : dryad::basic_node 20 | { 21 | DRYAD_NODE_CTOR(leaf_node); 22 | }; 23 | 24 | struct container_node : dryad::basic_node> 25 | { 26 | DRYAD_NODE_CTOR(container_node); 27 | 28 | void insert_front(node* n) 29 | { 30 | this->insert_child_after(nullptr, n); 31 | } 32 | }; 33 | } // namespace 34 | 35 | TEST_CASE("tree") 36 | { 37 | dryad::tree tree; 38 | CHECK(!tree.has_root()); 39 | CHECK(tree.root() == nullptr); 40 | 41 | SUBCASE("clear") 42 | { 43 | tree.clear(); 44 | } 45 | 46 | auto a = tree.create(); 47 | auto b = tree.create(); 48 | auto c = tree.create(); 49 | 50 | auto container = tree.create(); 51 | container->insert_front(c); 52 | container->insert_front(b); 53 | container->insert_front(a); 54 | tree.set_root(container); 55 | CHECK(tree.root() == container); 56 | 57 | auto range = dryad::traverse(tree); 58 | auto iter = range.begin(); 59 | REQUIRE(iter != range.end()); 60 | CHECK(iter->event == dryad::traverse_event::enter); 61 | CHECK(iter->node == container); 62 | 63 | ++iter; 64 | REQUIRE(iter != range.end()); 65 | CHECK(iter->event == dryad::traverse_event::leaf); 66 | CHECK(iter->node == a); 67 | 68 | ++iter; 69 | REQUIRE(iter != range.end()); 70 | CHECK(iter->event == dryad::traverse_event::leaf); 71 | CHECK(iter->node == b); 72 | 73 | ++iter; 74 | REQUIRE(iter != range.end()); 75 | CHECK(iter->event == dryad::traverse_event::leaf); 76 | CHECK(iter->node == c); 77 | 78 | ++iter; 79 | REQUIRE(iter != range.end()); 80 | CHECK(iter->event == dryad::traverse_event::exit); 81 | CHECK(iter->node == container); 82 | 83 | ++iter; 84 | CHECK(iter == range.end()); 85 | } 86 | 87 | TEST_CASE("forest") 88 | { 89 | dryad::forest forest; 90 | CHECK(forest.roots().empty()); 91 | 92 | SUBCASE("clear") 93 | { 94 | forest.clear(); 95 | } 96 | 97 | auto a = forest.create(); 98 | auto b = forest.create(); 99 | auto c = forest.create(); 100 | auto d = forest.create(); 101 | 102 | auto root1 = forest.create(); 103 | root1->insert_front(c); 104 | root1->insert_front(b); 105 | root1->insert_front(a); 106 | forest.insert_root(root1); 107 | 108 | CHECK(!forest.roots().empty()); 109 | CHECK(forest.roots().front() == root1); 110 | CHECK(root1->parent() == root1); 111 | 112 | auto root2 = forest.create(); 113 | root2->insert_front(d); 114 | forest.insert_root_list(root2); 115 | 116 | CHECK(!forest.roots().empty()); 117 | CHECK(forest.roots().front() == root1); 118 | CHECK(*std::next(forest.roots().begin()) == root2); 119 | CHECK(root1->parent() == root1); 120 | CHECK(root2->parent() == root2); 121 | 122 | dryad::visit_tree(root1, [&](node* ptr) { CHECK(ptr != d); }); 123 | dryad::visit_tree(root2, [&](node* ptr) { CHECK((ptr == root2 || ptr == d)); }); 124 | } 125 | 126 | TEST_CASE("visit_tree") 127 | { 128 | dryad::tree tree; 129 | CHECK(tree.root() == nullptr); 130 | 131 | auto a = tree.create(); 132 | auto b = tree.create(); 133 | auto c = tree.create(); 134 | 135 | SUBCASE("non-empty container") 136 | { 137 | auto container = tree.create(); 138 | container->insert_front(c); 139 | container->insert_front(b); 140 | container->insert_front(a); 141 | tree.set_root(container); 142 | CHECK(tree.root() == container); 143 | 144 | SUBCASE("with traverse_event") 145 | { 146 | auto leaf_count = 0; 147 | auto container_count = 0; 148 | dryad::visit_tree( 149 | tree, [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }, 150 | [&](dryad::traverse_event, container_node*) { ++container_count; }); 151 | 152 | CHECK(leaf_count == 3); 153 | CHECK(container_count == 2); 154 | } 155 | SUBCASE("with traverse_event_enter") 156 | { 157 | auto leaf_count = 0; 158 | auto container_count = 0; 159 | dryad::visit_tree( 160 | tree, [&](dryad::traverse_event_enter, leaf_node*) { ++leaf_count; }, 161 | [&](dryad::traverse_event_enter, container_node*) { ++container_count; }); 162 | 163 | CHECK(leaf_count == 0); 164 | CHECK(container_count == 1); 165 | } 166 | SUBCASE("with traverse_event_exit") 167 | { 168 | auto leaf_count = 0; 169 | auto container_count = 0; 170 | dryad::visit_tree( 171 | tree, [&](dryad::traverse_event_exit, leaf_node*) { ++leaf_count; }, 172 | [&](dryad::traverse_event_exit, container_node*) { ++container_count; }); 173 | 174 | CHECK(leaf_count == 0); 175 | CHECK(container_count == 1); 176 | } 177 | SUBCASE("child visitor") 178 | { 179 | auto leaf_count = 0; 180 | auto container_count = 0; 181 | dryad::visit_tree( 182 | tree, [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }, 183 | [&](dryad::child_visitor visitor, container_node* node) { 184 | ++container_count; 185 | visitor(node->children().front()); 186 | }); 187 | 188 | CHECK(leaf_count == 1); 189 | CHECK(container_count == 1); 190 | } 191 | SUBCASE("no event argument") 192 | { 193 | auto leaf_count = 0; 194 | auto container_count = 0; 195 | dryad::visit_tree( 196 | tree, [&](leaf_node*) { ++leaf_count; }, 197 | [&](container_node*) { ++container_count; }); 198 | 199 | CHECK(leaf_count == 3); 200 | CHECK(container_count == 1); 201 | } 202 | SUBCASE("ignore_node") 203 | { 204 | auto leaf_count = 0; 205 | auto container_count = 0; 206 | dryad::visit_tree( 207 | tree, [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }, 208 | dryad::ignore_node); 209 | 210 | CHECK(leaf_count == 0); 211 | CHECK(container_count == 0); 212 | } 213 | SUBCASE("catch all first") 214 | { 215 | auto leaf_count = 0; 216 | auto node_count = 0; 217 | dryad::visit_tree( 218 | tree, [&](dryad::traverse_event, node*) { ++node_count; }, 219 | [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }); 220 | 221 | CHECK(leaf_count == 3); 222 | CHECK(node_count == 5); 223 | } 224 | SUBCASE("catch all last") 225 | { 226 | auto leaf_count = 0; 227 | auto node_count = 0; 228 | dryad::visit_tree( 229 | tree, [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }, 230 | [&](dryad::traverse_event, node*) { ++node_count; }); 231 | 232 | CHECK(leaf_count == 3); 233 | CHECK(node_count == 2); 234 | } 235 | SUBCASE("leaf only") 236 | { 237 | auto leaf_count = 0; 238 | dryad::visit_tree(tree, [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }); 239 | 240 | CHECK(leaf_count == 3); 241 | } 242 | SUBCASE("mutable") 243 | { 244 | auto leaf_count = 0; 245 | auto container_count = 0; 246 | dryad::visit_tree( 247 | tree, [&](dryad::traverse_event, leaf_node*) mutable { ++leaf_count; }, 248 | [&](dryad::traverse_event, container_node*) mutable { ++container_count; }); 249 | 250 | CHECK(leaf_count == 3); 251 | CHECK(container_count == 2); 252 | } 253 | } 254 | SUBCASE("empty container as root") 255 | { 256 | auto container = tree.create(); 257 | tree.set_root(container); 258 | CHECK(tree.root() == container); 259 | 260 | auto leaf_count = 0; 261 | auto container_count = 0; 262 | dryad::visit_tree( 263 | tree, [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }, 264 | [&](dryad::traverse_event, container_node*) { ++container_count; }); 265 | 266 | CHECK(leaf_count == 0); 267 | CHECK(container_count == 2); 268 | } 269 | SUBCASE("empty container as child") 270 | { 271 | auto container = tree.create(); 272 | auto child = tree.create(); 273 | container->insert_front(child); 274 | tree.set_root(container); 275 | CHECK(tree.root() == container); 276 | 277 | auto leaf_count = 0; 278 | auto container_count = 0; 279 | dryad::visit_tree( 280 | tree, [&](dryad::traverse_event, leaf_node*) { ++leaf_count; }, 281 | [&](dryad::traverse_event, container_node*) { ++container_count; }); 282 | 283 | CHECK(leaf_count == 0); 284 | CHECK(container_count == 4); 285 | } 286 | } 287 | 288 | -------------------------------------------------------------------------------- /tests/dryad/tuple_node.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jonathan Müller and dryad contributors 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace 10 | { 11 | enum class node_kind 12 | { 13 | leaf, 14 | container, 15 | }; 16 | 17 | using node = dryad::node; 18 | 19 | struct leaf_node : dryad::basic_node 20 | { 21 | DRYAD_NODE_CTOR(leaf_node); 22 | }; 23 | 24 | struct single_node : dryad::basic_node> 25 | { 26 | explicit single_node(dryad::node_ctor ctor, leaf_node* node) : node_base(ctor, node) {} 27 | }; 28 | } // namespace 29 | 30 | TEST_CASE("single_node") 31 | { 32 | dryad::tree tree; 33 | 34 | auto leaf = tree.create(); 35 | auto container = tree.create(leaf); 36 | CHECK(container->child() == leaf); 37 | 38 | auto new_leaf = tree.create(); 39 | CHECK(container->replace_child(new_leaf) == leaf); 40 | CHECK(container->child() == new_leaf); 41 | } 42 | 43 | namespace 44 | { 45 | struct array_node : dryad::basic_node> 46 | { 47 | explicit array_node(dryad::node_ctor ctor, leaf_node* a, leaf_node* b) : node_base(ctor, {a, b}) 48 | {} 49 | }; 50 | } // namespace 51 | 52 | TEST_CASE("array_node") 53 | { 54 | dryad::tree tree; 55 | 56 | auto leaf_a = tree.create(); 57 | auto leaf_b = tree.create(); 58 | 59 | auto container = tree.create(leaf_a, leaf_b); 60 | CHECK(!container->children().empty()); 61 | CHECK(container->children().size() == 2); 62 | CHECK(container->children()[0] == leaf_a); 63 | CHECK(container->children()[1] == leaf_b); 64 | CHECK(container->children().begin()[0] == leaf_a); 65 | CHECK(container->children().begin()[1] == leaf_b); 66 | 67 | auto new_leaf = tree.create(); 68 | CHECK(container->replace_child(1, new_leaf) == leaf_b); 69 | CHECK(container->children()[1] == new_leaf); 70 | } 71 | 72 | namespace 73 | { 74 | struct binary_node : dryad::basic_node> 75 | { 76 | explicit binary_node(dryad::node_ctor ctor, leaf_node* a, leaf_node* b) : node_base(ctor, a, b) 77 | {} 78 | }; 79 | } // namespace 80 | 81 | TEST_CASE("array_node") 82 | { 83 | dryad::tree tree; 84 | 85 | auto leaf_a = tree.create(); 86 | auto leaf_b = tree.create(); 87 | 88 | auto container = tree.create(leaf_a, leaf_b); 89 | CHECK(container->left_child() == leaf_a); 90 | CHECK(container->right_child() == leaf_b); 91 | 92 | auto new_leaf = tree.create(); 93 | CHECK(container->replace_left_child(new_leaf) == leaf_a); 94 | CHECK(container->left_child() == new_leaf); 95 | 96 | CHECK(container->replace_right_child(leaf_a) == leaf_b); 97 | CHECK(container->right_child() == leaf_a); 98 | } 99 | 100 | --------------------------------------------------------------------------------