├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── README.md ├── cmake └── xnode-config.cmake.in ├── doc ├── CHANGELOG ├── LICENSE.txt └── type_conversions.md ├── include ├── details │ ├── property_list.h │ └── xnode_builtins.h ├── xarray.h ├── xnode.h ├── xnode_long_double.h ├── xnode_type_ext.h ├── xnode_utils.h └── xobject.h ├── make.cmd ├── makefile.mgw ├── test.sh └── test ├── CMakeLists.txt ├── cunit.h ├── property_list_test.cpp ├── xarray_of_test.cpp ├── xarray_of_versions_test.cpp ├── xarray_test.cpp ├── xnode_convert_test.cpp ├── xnode_overflow_test.cpp ├── xnode_test.cpp ├── xnode_tree_test.cpp ├── xnode_type_test.cpp └── xobject_test.cpp /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main, master ] 6 | pull_request: 7 | branches: [ main, master ] 8 | 9 | jobs: 10 | build: 11 | name: ${{ matrix.config.name }} 12 | runs-on: ${{ matrix.config.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | config: 17 | - { 18 | name: "Ubuntu Latest GCC", 19 | os: ubuntu-latest, 20 | build_type: "Release", 21 | cc: "gcc", 22 | cxx: "g++" 23 | } 24 | - { 25 | name: "macOS Latest Clang", 26 | os: macos-latest, 27 | build_type: "Release", 28 | cc: "clang", 29 | cxx: "clang++" 30 | } 31 | - { 32 | name: "Windows Latest MSVC", 33 | os: windows-latest, 34 | build_type: "Release", 35 | cc: "cl", 36 | cxx: "cl" 37 | } 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | - name: Configure CMake 44 | env: 45 | CC: ${{ matrix.config.cc }} 46 | CXX: ${{ matrix.config.cxx }} 47 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} 48 | 49 | - name: Build 50 | run: cmake --build ${{github.workspace}}/build --config ${{matrix.config.build_type}} 51 | 52 | - name: Test 53 | working-directory: ${{github.workspace}}/build 54 | run: ctest -C ${{matrix.config.build_type}} --output-on-failure --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build/ 3 | bin/ 4 | lib/ 5 | install/ 6 | out/ 7 | **/cmake-build-debug/ 8 | 9 | # CMake generated files 10 | CMakeCache.txt 11 | CMakeFiles/ 12 | CMakeScripts/ 13 | cmake_install.cmake 14 | compile_commands.json 15 | CTestTestfile.cmake 16 | Testing/ 17 | **/Testing/Temporary/ 18 | 19 | # IDE files 20 | .vs/ 21 | .vscode/ 22 | *.user 23 | *.suo 24 | *.sdf 25 | *.opensdf 26 | *.vcxproj.filters 27 | .idea/ 28 | *.swp 29 | *.swo 30 | 31 | # Object files 32 | *.o 33 | *.obj 34 | *.dll 35 | *.so 36 | *.dylib 37 | *.exe 38 | 39 | # Dependency directories 40 | CMakeUserPresets.json 41 | 42 | # Generated documentation 43 | help/ 44 | doc/html/ 45 | doc/latex/ 46 | 47 | # Internal documentation 48 | doc/internal/ 49 | 50 | # Backup directory 51 | backup/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(xnode VERSION 1.0.0 LANGUAGES CXX 3 | DESCRIPTION "Lightweight, header-only C++ library for dynamically typed values" 4 | HOMEPAGE_URL "https://github.com/vpiotr/xnode") 5 | 6 | # Options 7 | option(XNODE_BUILD_TESTS "Build xnode tests" ON) 8 | option(XNODE_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) 9 | 10 | # Set C++ standard 11 | set(CMAKE_CXX_STANDARD 11) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_CXX_EXTENSIONS OFF) 14 | 15 | # Compiler warnings 16 | if(MSVC) 17 | add_compile_options(/W4) 18 | if(XNODE_WARNINGS_AS_ERRORS) 19 | add_compile_options(/WX) 20 | endif() 21 | else() 22 | add_compile_options(-Wall -Wextra -Wpedantic) 23 | if(XNODE_WARNINGS_AS_ERRORS) 24 | add_compile_options(-Werror) 25 | endif() 26 | endif() 27 | 28 | # Add include directory 29 | include_directories(include) 30 | 31 | # Create xnode library (header-only) 32 | add_library(xnode INTERFACE) 33 | target_include_directories(xnode INTERFACE 34 | $ 35 | $ 36 | ) 37 | 38 | # Add aliases to support find_package() and add_subdirectory() usage 39 | add_library(xnode::xnode ALIAS xnode) 40 | 41 | # Install targets 42 | install(TARGETS xnode 43 | EXPORT xnodeTargets 44 | INCLUDES DESTINATION include 45 | ) 46 | 47 | # Install headers 48 | install(DIRECTORY include/ DESTINATION include) 49 | 50 | # Generate and install package config files 51 | include(CMakePackageConfigHelpers) 52 | write_basic_package_version_file( 53 | "${CMAKE_CURRENT_BINARY_DIR}/xnode-config-version.cmake" 54 | VERSION ${PROJECT_VERSION} 55 | COMPATIBILITY SameMajorVersion 56 | ) 57 | 58 | configure_package_config_file( 59 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/xnode-config.cmake.in" 60 | "${CMAKE_CURRENT_BINARY_DIR}/xnode-config.cmake" 61 | INSTALL_DESTINATION lib/cmake/xnode 62 | ) 63 | 64 | install(FILES 65 | "${CMAKE_CURRENT_BINARY_DIR}/xnode-config.cmake" 66 | "${CMAKE_CURRENT_BINARY_DIR}/xnode-config-version.cmake" 67 | DESTINATION lib/cmake/xnode 68 | ) 69 | 70 | install(EXPORT xnodeTargets 71 | FILE xnode-targets.cmake 72 | NAMESPACE xnode:: 73 | DESTINATION lib/cmake/xnode 74 | ) 75 | 76 | # Export package for use from the build tree 77 | export(EXPORT xnodeTargets 78 | FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/xnode-targets.cmake" 79 | NAMESPACE xnode:: 80 | ) 81 | 82 | # Build tests 83 | if(XNODE_BUILD_TESTS) 84 | enable_testing() 85 | add_subdirectory(test) 86 | endif() -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to xnode 2 | 3 | Thank you for your interest in contributing to xnode! This document provides guidelines and instructions for contributing to this project. 4 | 5 | ## Building the Project 6 | 7 | xnode uses CMake as its build system. Follow these steps to build the project: 8 | 9 | ### Prerequisites 10 | 11 | - CMake 3.14 or higher 12 | - C++11 compatible compiler (GCC, Clang, MSVC) 13 | 14 | ### Build Steps 15 | 16 | 1. **Clone the repository**: 17 | ```bash 18 | git clone https://github.com/plikus/xnode.git 19 | cd xnode 20 | ``` 21 | 22 | 2. **Create a build directory**: 23 | ```bash 24 | mkdir build 25 | cd build 26 | ``` 27 | 28 | 3. **Configure the project**: 29 | ```bash 30 | cmake .. 31 | ``` 32 | 33 | 4. **Build the project**: 34 | ```bash 35 | cmake --build . 36 | ``` 37 | 38 | 5. **Run the tests**: 39 | ```bash 40 | ctest 41 | ``` 42 | 43 | ### CMake Options 44 | 45 | The following options can be used to customize the build: 46 | 47 | - `XNODE_BUILD_TESTS` - Build tests (ON by default) 48 | 49 | Example: 50 | ```bash 51 | cmake -DXNODE_BUILD_TESTS=OFF .. 52 | ``` 53 | 54 | ## Testing 55 | 56 | The project includes a comprehensive test suite. To run the tests: 57 | 58 | ```bash 59 | cd build 60 | ctest 61 | ``` 62 | 63 | To run specific tests with verbose output: 64 | 65 | ```bash 66 | ctest --verbose --tests-regex TestRegex 67 | ``` 68 | 69 | ## Code Style 70 | 71 | Please follow these style guidelines when contributing: 72 | 73 | - Use consistent indentation (4 spaces) 74 | - Follow the existing naming conventions 75 | - Add comments for complex code sections 76 | - Keep lines to a reasonable length (around 100 characters) 77 | 78 | ## Pull Request Process 79 | 80 | 1. Fork the repository 81 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 82 | 3. Make your changes 83 | 4. Run the tests to ensure they still pass 84 | 5. Commit your changes (`git commit -m 'Add some amazing feature'`) 85 | 6. Push to the branch (`git push origin feature/amazing-feature`) 86 | 7. Open a Pull Request 87 | 88 | ## Reporting Issues 89 | 90 | When reporting issues, please include: 91 | 92 | - A clear description of the problem 93 | - Steps to reproduce the issue 94 | - Expected vs. actual behavior 95 | - System information (OS, compiler, CMake version) 96 | 97 | ## License 98 | 99 | By contributing, you agree that your contributions will be licensed under the project's BSD license. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | xnode is a lightweight, header-only C++ library for dynamically typed values with automatic conversion between types. 3 | 4 | [![CI Build Status](https://github.com/vpiotr/xnode/actions/workflows/ci.yml/badge.svg)](https://github.com/vpiotr/xnode/actions/workflows/ci.yml) 5 | 6 | # License 7 | BSD 8 | 9 | # Project home 10 | https://github.com/vpiotr/xnode 11 | 12 | # Purpose 13 | Bringing the power and flexibility of dynamic typing to modern C++, allowing you to write more expressive and adaptable code. 14 | 15 | # Key Features 16 | * **Zero-cost Abstractions**: Header-only with no external dependencies beyond STL 17 | * **Seamless Type Flexibility**: Change types at runtime with automatic conversion 18 | * **Modern C++ Design**: Template-based, STL-compatible architecture 19 | * **Rich Data Structures**: Property lists and vector containers with insertion-order preservation 20 | * **Extensible Type System**: Easily add support for your custom types 21 | * **Developer-Friendly API**: Intuitive interface with fluent builder methods 22 | 23 | # Why xnode? 24 | * **Simplify Dynamic Data Handling** 25 | 26 | xnode value; 27 | value.set_as(5); // Store as integer 28 | value.set_as(false); // Change to boolean 29 | value.set_as("dynamic typing"); // Change to string 30 | 31 | // Type safety when you need it 32 | Assert(value.is()); 33 | 34 | * **Smart Type Conversions** - Automatic, transparent, and safe 35 | 36 | xnode value; 37 | value.set_as("123"); // Store as string 38 | 39 | // Automatic conversion when needed 40 | int num = value.get_as(); // Converts to 123 41 | 42 | // Type safety built-in 43 | Assert(value.is()); 44 | Assert(value.is_convertable_to()); 45 | 46 | * **Flexible Container Types** - Including dynamic arrays with tuple-like functionality 47 | 48 | // Modern C++17 version with direct values 49 | #if __cplusplus >= 201703L 50 | xarray modern = xarray::of(42, "Direct values", 3.14, true); 51 | Assert(modern[0].get_as() == 42); 52 | #endif 53 | 54 | // Create a heterogeneous collection (like a tuple) 55 | xarray values = xarray::of( 56 | xnode::value_of(42), // int 57 | xnode::value_of("Hello World"), // string 58 | xnode::value_of(3.14), // double 59 | xnode::value_of(true) // bool 60 | ); 61 | 62 | // Access elements type-safely 63 | int first = values[0].get_as(); // 42 64 | std::string second = values[1].get_as(); // "Hello World" 65 | 66 | 67 | * **Elegant Named Parameters** - Cleaner than builder pattern, more flexible than structs 68 | 69 | std::string printInFont(const xnode &font, const std::string &text) { 70 | const xobject &options = font.get_ref(); 71 | 72 | // Default values handled elegantly with get_def 73 | std::ostringstream out; 74 | out << "text in font ["; 75 | out << "color:" << options.get_def("color", xnode::value_of(0x00ff00)).get_as(); 76 | out << ", font_name:" << options.get_def("font_name", xnode::value_of("courier")).get_as(); 77 | out << ", size:" << options.get_def("size", xnode::value_of(10)).get_as(); 78 | out << ", bold:" << options.get_def("bold", xnode::value_of(false)).get_as(); 79 | out << "] = " << text; 80 | 81 | return out.str(); 82 | } 83 | 84 | // Create options with fluent static 'of' method: 85 | auto result = printInFont( 86 | xnode::value_of(xobject::of( 87 | "color", xnode::value_of(0xff0000), 88 | "font_name", xnode::value_of(std::string("arial")), 89 | "size", xnode::value_of(12), 90 | "bold", xnode::value_of(true) 91 | )), 92 | "Hello World!" 93 | ); 94 | 95 | * **Smart Object Ownership** - Automatic memory management for dynamic objects 96 | 97 | // Let xnode handle object lifecycle 98 | void useObjectOwnership() { 99 | struct CustomObject { 100 | int data1; 101 | int data2; 102 | }; 103 | 104 | // Create an object to be owned by xnode 105 | std::unique_ptr obj(new CustomObject{42, 100}); 106 | 107 | // Transfer ownership to xnode 108 | xnode container; 109 | container.hold(obj.release()); 110 | 111 | // Use the object safely 112 | CustomObject* ptr = container.get_ptr(); 113 | std::cout << "Values: " << ptr->data1 << ", " << ptr->data2 << std::endl; 114 | 115 | // Object is automatically deleted when container goes out of scope 116 | } 117 | 118 | * **Flexible Memory Models** - Store owned objects or reference existing memory 119 | 120 | // Work with existing memory without ownership concerns 121 | void workWithExistingData() { 122 | // External data we want to reference 123 | char existingData[] = "Hello xnode!"; 124 | 125 | // Store a pointer without taking ownership 126 | xnode reference; 127 | reference.set_as(existingData); 128 | 129 | // Use the data through xnode 130 | char* data = reference.get_as(); 131 | std::cout << data << std::endl; // Prints "Hello xnode!" 132 | 133 | // xnode won't attempt to free this memory 134 | } 135 | 136 | 137 | # Type Conversion System 138 | 139 | xnode comes with a rich set of built-in conversions for seamless interoperability: 140 | 141 | * **Fundamental Types**: All C++ numeric types (integer and floating-point) 142 | * **Logical Values**: bool with smart conversion from/to other types 143 | * **Text Support**: Full std::string integration 144 | * **Custom Types**: Easily extend with your own conversion rules 145 | 146 | Create your own conversion paths by implementing a simple policy class (see `xnode_long_double.h` for a practical example). 147 | 148 | # Compatibility 149 | 150 | * **Standards**: C++11 and later 151 | * **Platforms**: Cross-platform (Windows, Linux, macOS) 152 | * **Compilers**: 153 | * Modern GCC/Clang toolchains 154 | * Microsoft Visual Studio 2015+ 155 | * Any compiler with good C++11 support 156 | 157 | # Advantages Over Alternatives 158 | 159 | * **Lighter than Boost**: Self-contained with no external dependencies 160 | * **More Dynamic than std::any**: Built-in type conversion 161 | * **More Flexible than std::variant**: Change types at runtime 162 | * **More Modern than void\***: Type-safe with no casting required 163 | 164 | # Building 165 | xnode is a header-only library, so you can just include the headers in your project. 166 | 167 | ## Using CMake (Recommended) 168 | xnode uses modern CMake for its build system. To build and install: 169 | 170 | ```bash 171 | # Create build directory 172 | mkdir build 173 | cd build 174 | 175 | # Configure (customize your options as needed) 176 | cmake .. 177 | 178 | # Build the tests 179 | cmake --build . 180 | 181 | # Run the tests 182 | ctest 183 | 184 | # Install the library (may require elevated privileges) 185 | cmake --install . 186 | ``` 187 | 188 | ### CMake Options 189 | - `XNODE_BUILD_TESTS` - Build tests (ON by default) 190 | 191 | ## Using the library in your CMake project 192 | After installing, you can use the library in your own CMake project: 193 | 194 | ```cmake 195 | find_package(xnode REQUIRED) 196 | target_link_libraries(your_target PRIVATE xnode::xnode) 197 | ``` 198 | 199 | ## Manual Integration 200 | For simple projects, you can just copy the headers from the `include` directory into your project. 201 | 202 | # Contact information 203 | * [LinkedIn](http://pl.linkedin.com/pub/piotr-likus/2/307/7b9/) 204 | 205 | # Documentation 206 | To build documentation, use Code::Blocks 13 and command: 207 | 208 | DoxyBlocks \ Extract documentation 209 | 210 | Output will be generated into: "./help/html/" directory in html format. 211 | 212 | # Release History 213 | See the [CHANGELOG](doc/CHANGELOG). 214 | 215 | -------------------------------------------------------------------------------- /cmake/xnode-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/xnode-targets.cmake") 4 | 5 | check_required_components(xnode) -------------------------------------------------------------------------------- /doc/CHANGELOG: -------------------------------------------------------------------------------- 1 | This is the changelog file for the xnode library. 2 | 3 | 2015-09-12 4 | ============================ 5 | 6 | - updated for GCC 7 | 8 | Release 0.1 (2015-09-01) 9 | ============================ 10 | 11 | - initial version 12 | 13 | 14 | -------------------------------------------------------------------------------- /doc/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Piotr Likus 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Piotr Likus nor the names of his 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /doc/type_conversions.md: -------------------------------------------------------------------------------- 1 | # xnode Type Conversion Reference 2 | 3 | ## Overview 4 | 5 | xnode provides automatic type conversion between various built-in types. This document outlines the supported conversions based on the implementation in `xnode_builtins.h` and the test cases in `xnode_convert_test.cpp`. 6 | 7 | ## Supported Types 8 | 9 | The following types are supported for storage and conversion: 10 | 11 | - **Booleans**: `bool` 12 | - **Floating Point**: `float`, `double` 13 | - **Signed Integers**: `char`, `short`, `int`, `long`, `long long` 14 | - **Unsigned Integers**: `unsigned char`, `unsigned short`, `unsigned int`, `unsigned long`, `unsigned long long` 15 | - **Strings**: `std::string` 16 | 17 | ## Conversion Matrix 18 | 19 | The table below shows the supported conversions between types. Each cell indicates whether conversion from the source type (row) to the destination type (column) is supported. 20 | 21 | | From ↓ To → | bool | float | double | char | short | int | long | long long | uchar | ushort | uint | ulong | ullong | string | 22 | |-------------|------|-------|--------|------|-------|-----|------|-----------|-------|--------|------|-------|--------|--------| 23 | | bool | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 24 | | float | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 25 | | double | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 26 | | char | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 27 | | short | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 28 | | int | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 29 | | long | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 30 | | long long | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 31 | | uchar | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 32 | | ushort | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 33 | | uint | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 34 | | ulong | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 35 | | ullong | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 36 | | string | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 37 | 38 | ## Conversion Details 39 | 40 | ### Boolean Conversions 41 | 42 | - **To Boolean**: 43 | - Numeric types: `0` converts to `false`, any other value converts to `true` 44 | - String: `"true"` or `"1"` converts to `true`, other values convert to `false` 45 | 46 | ### Numeric Conversions 47 | 48 | - **Between numeric types**: 49 | - Standard C++ casting rules apply with additional runtime safety checks 50 | - Conversions that would cause overflow or underflow are detected and throw exceptions: 51 | - Converting a larger integer type to a smaller type that cannot represent the value 52 | - Converting a floating-point value to an integer type when the value exceeds integer range 53 | - Converting a signed integer to an unsigned type when the value is negative 54 | - Converting between unsigned types when the source value exceeds the destination type's range 55 | - **From string to numeric types**: 56 | - Comprehensive overflow and underflow detection with appropriate exceptions: 57 | - `std::overflow_error` if the string represents a value too large for the target type 58 | - `std::underflow_error` if trying to convert a negative string value to an unsigned type 59 | - `std::overflow_error` for extremely large floating-point strings (e.g., "1.0e+39" for float) 60 | 61 | ### String Conversions 62 | 63 | - **To String**: All types can be converted to strings using string representations 64 | - **From String**: 65 | - Numeric types: Parsed with overflow/underflow detection, will throw appropriate exceptions 66 | - Boolean: `"true"` or `"1"` converts to `true`, other values to `false` 67 | 68 | ### Special Floating Point Values 69 | 70 | - **NaN (Not a Number)**: 71 | - Converts to string as `"nan"` or similar representation 72 | - Cannot be converted to boolean (will throw an exception) 73 | - Converting to integer types may have undefined behavior 74 | 75 | - **Infinity**: 76 | - Converts to string as `"inf"` or similar representation 77 | - Converts to boolean as `true` 78 | - Converting to integer types may have undefined behavior 79 | 80 | ## Custom Type Conversions 81 | 82 | The xnode library supports defining custom type conversions by creating specialized casters. See `xnode_long_double.h` for an example of extending the type conversion system to support `long double`. 83 | 84 | ## Usage Example 85 | 86 | ```cpp 87 | xnode value; 88 | 89 | // Store as int 90 | value.set_as(42); 91 | 92 | // Convert to other types 93 | bool b = value.get_as(); // true 94 | double d = value.get_as(); // 42.0 95 | std::string s = value.get_as(); // "42" 96 | 97 | // Store as string 98 | value.set_as(std::string("123")); 99 | 100 | // Convert string to numeric 101 | int i = value.get_as(); // 123 102 | float f = value.get_as(); // 123.0 103 | 104 | // Check if conversion is possible 105 | bool canConvert = value.is_convertable_to(); // true 106 | 107 | // Get with default value (no exceptions if conversion fails) 108 | int safe = value.get_as_def(0); // 123 (or 0 if conversion fails) 109 | ``` 110 | -------------------------------------------------------------------------------- /include/details/property_list.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: property_list.h 3 | // Purpose: Key-value storage class with support for access order of insertion. 4 | // Author: Piotr Likus 5 | // Created: 01/09/2015 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #ifndef __PROP_LIST_H__ 10 | #define __PROP_LIST_H__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "xnode_utils.h" 19 | 20 | /// key-value pair container where key is unique 21 | /// container supports reading in insert order 22 | /// reading & deleting by key has O(1) complexity 23 | /// value is stored in just one place 24 | template 25 | class property_list { 26 | typedef std::vector key_container_type; 27 | typedef std::unordered_map value_container_type; 28 | public: 29 | typedef std::vector key_list_type; 30 | typedef property_list this_type; 31 | 32 | // Iterator typedefs 33 | typedef typename key_container_type::iterator key_iterator; 34 | typedef typename key_container_type::const_iterator key_const_iterator; 35 | typedef typename value_container_type::iterator value_iterator; 36 | typedef typename value_container_type::const_iterator value_const_iterator; 37 | 38 | // Static factory methods 39 | static this_type of(const KeyType& k1, const ValueType& v1) { 40 | this_type result; 41 | result.put(k1, v1); 42 | return result; 43 | } 44 | 45 | static this_type of(const KeyType& k1, const ValueType& v1, 46 | const KeyType& k2, const ValueType& v2) { 47 | this_type result; 48 | result.put(k1, v1); 49 | result.put(k2, v2); 50 | return result; 51 | } 52 | 53 | static this_type of(const KeyType& k1, const ValueType& v1, 54 | const KeyType& k2, const ValueType& v2, 55 | const KeyType& k3, const ValueType& v3) { 56 | this_type result; 57 | result.put(k1, v1); 58 | result.put(k2, v2); 59 | result.put(k3, v3); 60 | return result; 61 | } 62 | 63 | static this_type of(const KeyType& k1, const ValueType& v1, 64 | const KeyType& k2, const ValueType& v2, 65 | const KeyType& k3, const ValueType& v3, 66 | const KeyType& k4, const ValueType& v4) { 67 | this_type result; 68 | result.put(k1, v1); 69 | result.put(k2, v2); 70 | result.put(k3, v3); 71 | result.put(k4, v4); 72 | return result; 73 | } 74 | 75 | static this_type of(const KeyType& k1, const ValueType& v1, 76 | const KeyType& k2, const ValueType& v2, 77 | const KeyType& k3, const ValueType& v3, 78 | const KeyType& k4, const ValueType& v4, 79 | const KeyType& k5, const ValueType& v5) { 80 | this_type result; 81 | result.put(k1, v1); 82 | result.put(k2, v2); 83 | result.put(k3, v3); 84 | result.put(k4, v4); 85 | result.put(k5, v5); 86 | return result; 87 | } 88 | 89 | static this_type of(const KeyType& k1, const ValueType& v1, 90 | const KeyType& k2, const ValueType& v2, 91 | const KeyType& k3, const ValueType& v3, 92 | const KeyType& k4, const ValueType& v4, 93 | const KeyType& k5, const ValueType& v5, 94 | const KeyType& k6, const ValueType& v6) { 95 | this_type result; 96 | result.put(k1, v1); 97 | result.put(k2, v2); 98 | result.put(k3, v3); 99 | result.put(k4, v4); 100 | result.put(k5, v5); 101 | result.put(k6, v6); 102 | return result; 103 | } 104 | 105 | static this_type of(const KeyType& k1, const ValueType& v1, 106 | const KeyType& k2, const ValueType& v2, 107 | const KeyType& k3, const ValueType& v3, 108 | const KeyType& k4, const ValueType& v4, 109 | const KeyType& k5, const ValueType& v5, 110 | const KeyType& k6, const ValueType& v6, 111 | const KeyType& k7, const ValueType& v7) { 112 | this_type result; 113 | result.put(k1, v1); 114 | result.put(k2, v2); 115 | result.put(k3, v3); 116 | result.put(k4, v4); 117 | result.put(k5, v5); 118 | result.put(k6, v6); 119 | result.put(k7, v7); 120 | return result; 121 | } 122 | 123 | static this_type of(const KeyType& k1, const ValueType& v1, 124 | const KeyType& k2, const ValueType& v2, 125 | const KeyType& k3, const ValueType& v3, 126 | const KeyType& k4, const ValueType& v4, 127 | const KeyType& k5, const ValueType& v5, 128 | const KeyType& k6, const ValueType& v6, 129 | const KeyType& k7, const ValueType& v7, 130 | const KeyType& k8, const ValueType& v8) { 131 | this_type result; 132 | result.put(k1, v1); 133 | result.put(k2, v2); 134 | result.put(k3, v3); 135 | result.put(k4, v4); 136 | result.put(k5, v5); 137 | result.put(k6, v6); 138 | result.put(k7, v7); 139 | result.put(k8, v8); 140 | return result; 141 | } 142 | 143 | static this_type of(const KeyType& k1, const ValueType& v1, 144 | const KeyType& k2, const ValueType& v2, 145 | const KeyType& k3, const ValueType& v3, 146 | const KeyType& k4, const ValueType& v4, 147 | const KeyType& k5, const ValueType& v5, 148 | const KeyType& k6, const ValueType& v6, 149 | const KeyType& k7, const ValueType& v7, 150 | const KeyType& k8, const ValueType& v8, 151 | const KeyType& k9, const ValueType& v9) { 152 | this_type result; 153 | result.put(k1, v1); 154 | result.put(k2, v2); 155 | result.put(k3, v3); 156 | result.put(k4, v4); 157 | result.put(k5, v5); 158 | result.put(k6, v6); 159 | result.put(k7, v7); 160 | result.put(k8, v8); 161 | result.put(k9, v9); 162 | return result; 163 | } 164 | 165 | static this_type of(const KeyType& k1, const ValueType& v1, 166 | const KeyType& k2, const ValueType& v2, 167 | const KeyType& k3, const ValueType& v3, 168 | const KeyType& k4, const ValueType& v4, 169 | const KeyType& k5, const ValueType& v5, 170 | const KeyType& k6, const ValueType& v6, 171 | const KeyType& k7, const ValueType& v7, 172 | const KeyType& k8, const ValueType& v8, 173 | const KeyType& k9, const ValueType& v9, 174 | const KeyType& k10, const ValueType& v10) { 175 | this_type result; 176 | result.put(k1, v1); 177 | result.put(k2, v2); 178 | result.put(k3, v3); 179 | result.put(k4, v4); 180 | result.put(k5, v5); 181 | result.put(k6, v6); 182 | result.put(k7, v7); 183 | result.put(k8, v8); 184 | result.put(k9, v9); 185 | result.put(k10, v10); 186 | return result; 187 | } 188 | 189 | property_list() : dirty_keys_(false) {} 190 | 191 | property_list(const property_list &src): 192 | dirty_keys_(src.dirty_keys_), 193 | keys_(src.keys_), 194 | values_(src.values_) 195 | { 196 | 197 | } 198 | 199 | property_list(property_list &&src) : 200 | dirty_keys_(src.dirty_keys_), 201 | keys_(std::move(src.keys_)), 202 | values_(std::move(src.values_)) 203 | { 204 | 205 | } 206 | 207 | property_list& operator=(const property_list &rhs) 208 | { 209 | dirty_keys_ = rhs.dirty_keys_; 210 | keys_ = rhs.keys_; 211 | values_ = rhs.values_; 212 | return *this; 213 | } 214 | 215 | property_list& operator=(property_list &&rhs) 216 | { 217 | dirty_keys_ = rhs.dirty_keys_; 218 | keys_ = std::move(rhs.keys_); 219 | values_ = std::move(rhs.values_); 220 | return *this; 221 | } 222 | 223 | bool operator==(const this_type &rhs) const 224 | { 225 | if (this == &rhs) 226 | return true; 227 | 228 | if ( 229 | (values_ == rhs.values_) 230 | && 231 | (keys_ == rhs.keys_) 232 | ) 233 | { 234 | return true; 235 | } 236 | else { 237 | return false; 238 | } 239 | } 240 | 241 | /// inserts value in storage, if key already exists, old value will be replaced 242 | /// returns true if item was already there 243 | bool put(const KeyType &key, const ValueType &value) { 244 | typename value_container_type::iterator found = values_.find(key); 245 | bool notFound = (found == values_.end()); 246 | if (notFound) { 247 | keys_.push_back(key); 248 | } 249 | values_[key] = value; 250 | return !notFound; 251 | } 252 | 253 | /// inserts value in storage, if key already exists, old value will be replaced 254 | /// returns true if item was already there 255 | bool put(const KeyType &key, ValueType &&value) { 256 | typename value_container_type::iterator found = values_.find(key); 257 | bool notFound = (found == values_.end()); 258 | if (notFound) { 259 | keys_.push_back(key); 260 | values_.insert({ key, value }); 261 | } 262 | else { 263 | found->second = value; 264 | } 265 | return !notFound; 266 | } 267 | 268 | /// returns value selected by key 269 | /// throws error if key not found 270 | ValueType &get(const KeyType &key) { 271 | typename value_container_type::iterator found = values_.find(key); 272 | if (found == values_.end()) { 273 | throwNotFound(key); 274 | } 275 | return found->second; 276 | } 277 | 278 | /// returns value selected by key 279 | /// throws error if key not found 280 | const ValueType &get(const KeyType &key) const { 281 | typename value_container_type::const_iterator found = values_.find(key); 282 | if (found == values_.end()) { 283 | throwNotFound(key); 284 | } 285 | return found->second; 286 | } 287 | 288 | /// returns value selected by key 289 | /// returns provided default value if key not found 290 | const ValueType get_def(const KeyType &key, const ValueType &defValue) const { 291 | typename value_container_type::const_iterator found = values_.find(key); 292 | if (found == values_.end()) { 293 | return defValue; 294 | } 295 | return found->second; 296 | } 297 | 298 | /// returns value selected by key 299 | /// throws error if key not found 300 | ValueType &get(const KeyType &key, ValueType &output) const { 301 | typename value_container_type::iterator found = values_.find(key); 302 | if (found == values_.end()) { 303 | throwNotFound(key); 304 | } 305 | output = *found; 306 | return output; 307 | } 308 | 309 | /// returns pointer to value selected by key 310 | /// returns null if value not found 311 | ValueType *get_ptr(const KeyType &key) { 312 | typename value_container_type::iterator found = values_.find(key); 313 | if (found != values_.end()) { 314 | return &(found->second); 315 | } 316 | else { 317 | return nullptr; 318 | } 319 | } 320 | 321 | /// removes value selected by key 322 | void remove(const KeyType &key) { 323 | typename value_container_type::iterator found = values_.find(key); 324 | if (found != values_.end()) { 325 | values_.erase(found); 326 | dirty_keys_ = true; 327 | } 328 | } 329 | 330 | /// removes all items stored in container 331 | void clear() { 332 | values_.clear(); 333 | keys_.clear(); 334 | dirty_keys_ = false; 335 | } 336 | 337 | /// returns number of stored values 338 | size_t size() const { 339 | return values_.size(); 340 | } 341 | 342 | /// returns true if there are no values in the storage 343 | bool empty() const { 344 | return values_.empty(); 345 | } 346 | 347 | /// returns true if reorg is needed to be executed 348 | bool needs_reorg() const { 349 | return dirty_keys_; 350 | } 351 | 352 | /// reorganize structure after heavy changes 353 | void reorg() { 354 | if (dirty_keys_) 355 | purge_keys(); 356 | } 357 | 358 | /// returns true if container holds value for a given key 359 | bool contains(const KeyType &key) const { 360 | typename value_container_type::const_iterator found = values_.find(key); 361 | return found != values_.end(); 362 | } 363 | 364 | /// return keys in order of insertion 365 | key_list_type get_keys() { 366 | if (dirty_keys_) 367 | purge_keys(); 368 | key_list_type result(keys_); 369 | return result; 370 | } 371 | 372 | /// return keys in order of insertion 373 | /// param[in] helper, buffer optionally to be used 374 | /// return reference to key storage 375 | const key_list_type &get_keys(key_list_type &/* helper */) { 376 | if (dirty_keys_) 377 | purge_keys(); 378 | return keys_; 379 | } 380 | 381 | /// return values in order of insertion 382 | std::vector get_values() { 383 | std::vector result; 384 | if (dirty_keys_) 385 | purge_keys(); 386 | for (KeyType key : keys_) 387 | result.push_back(values_[key]); 388 | return result; 389 | } 390 | 391 | /// returns iterator to the beginning of keys container 392 | key_iterator keys_begin() { 393 | if (dirty_keys_) 394 | purge_keys(); 395 | return keys_.begin(); 396 | } 397 | 398 | /// returns iterator to the end of keys container 399 | key_iterator keys_end() { 400 | return keys_.end(); 401 | } 402 | 403 | /// returns const_iterator to the beginning of keys container 404 | key_const_iterator keys_cbegin() const { 405 | if (dirty_keys_) 406 | purge_keys(); 407 | return keys_.cbegin(); 408 | } 409 | 410 | /// returns const_iterator to the end of keys container 411 | key_const_iterator keys_cend() const { 412 | return keys_.cend(); 413 | } 414 | 415 | /// returns iterator to the beginning of values container 416 | value_iterator values_begin() { 417 | return values_.begin(); 418 | } 419 | 420 | /// returns iterator to the end of values container 421 | value_iterator values_end() { 422 | return values_.end(); 423 | } 424 | 425 | /// returns const_iterator to the beginning of values container 426 | value_const_iterator values_cbegin() const { 427 | return values_.cbegin(); 428 | } 429 | 430 | /// returns const_iterator to the end of values container 431 | value_const_iterator values_cend() const { 432 | return values_.cend(); 433 | } 434 | 435 | protected: 436 | void purge_keys() const { 437 | key_container_type new_keys; 438 | for (const KeyType& key : keys_) { 439 | if (values_.find(key) != values_.end()) 440 | new_keys.push_back(key); 441 | } 442 | // Cast away const to update the member variables 443 | // This is safe because we're maintaining logical constness 444 | key_container_type& keys_nonconst = const_cast(keys_); 445 | bool& dirty_keys_nonconst = const_cast(dirty_keys_); 446 | 447 | keys_nonconst = std::move(new_keys); 448 | dirty_keys_nonconst = false; 449 | } 450 | 451 | void throwNotFound(const KeyType &key) const { 452 | throw std::runtime_error("key not found: " + to_string(key)); 453 | } 454 | private: 455 | bool dirty_keys_; 456 | key_container_type keys_; 457 | value_container_type values_; 458 | }; 459 | 460 | #endif 461 | -------------------------------------------------------------------------------- /include/xarray.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xarray.h 3 | // Purpose: Custom array class for xnode objects. 4 | // Author: Piotr Likus 5 | // Created: 01/09/2015 6 | // Last change: 15/05/2025 7 | // License: BSD 8 | //---------------------------------------------------------------------------------- 9 | 10 | #ifndef __XNODE_ARRAY_H__ 11 | #define __XNODE_ARRAY_H__ 12 | 13 | #include 14 | #include 15 | #include 16 | #include "xnode.h" 17 | 18 | // Forward declaration 19 | class xarray; 20 | 21 | // Type code registration for xarray 22 | template<> 23 | struct xnode_type_code { 24 | enum { value = 16 }; // Ensure this doesn't conflict with other type codes 25 | }; 26 | 27 | /** 28 | * Custom array class for xnode objects. 29 | * Provides a limited array interface with static initializer "of" that accepts variable number of xnode objects. 30 | */ 31 | class xarray { 32 | public: 33 | // Type definitions 34 | using value_type = xnode; 35 | using container_type = std::vector; 36 | using reference = container_type::reference; 37 | using const_reference = container_type::const_reference; 38 | using iterator = container_type::iterator; 39 | using const_iterator = container_type::const_iterator; 40 | using size_type = container_type::size_type; 41 | using difference_type = container_type::difference_type; 42 | 43 | // Constructors 44 | xarray() = default; 45 | 46 | // Copy constructor 47 | xarray(const xarray& other) = default; 48 | 49 | // Move constructor 50 | xarray(xarray&& other) noexcept = default; 51 | 52 | // Construct from initializer list of xnodes 53 | xarray(std::initializer_list init) : data_(init) {} 54 | 55 | // Assignment operators 56 | xarray& operator=(const xarray& other) = default; 57 | xarray& operator=(xarray&& other) noexcept = default; 58 | 59 | // Basic functions 60 | bool empty() const { return data_.empty(); } 61 | size_type size() const { return data_.size(); } 62 | 63 | // Element access 64 | reference at(size_type pos) { return data_.at(pos); } 65 | const_reference at(size_type pos) const { return data_.at(pos); } 66 | 67 | reference operator[](size_type pos) { return data_[pos]; } 68 | const_reference operator[](size_type pos) const { return data_[pos]; } 69 | 70 | // Iterators 71 | iterator begin() { return data_.begin(); } 72 | const_iterator begin() const { return data_.begin(); } 73 | const_iterator cbegin() const { return data_.cbegin(); } 74 | 75 | iterator end() { return data_.end(); } 76 | const_iterator end() const { return data_.end(); } 77 | const_iterator cend() const { return data_.cend(); } 78 | 79 | // Capacity manipulation 80 | void reserve(size_type new_cap) { data_.reserve(new_cap); } 81 | size_type capacity() const { return data_.capacity(); } 82 | 83 | // Modifiers 84 | void clear() { data_.clear(); } 85 | void push_back(const value_type& value) { data_.push_back(value); } 86 | void push_back(value_type&& value) { data_.push_back(std::move(value)); } 87 | iterator insert(const_iterator pos, const value_type& value) { return data_.insert(pos, value); } 88 | iterator insert(const_iterator pos, value_type&& value) { return data_.insert(pos, std::move(value)); } 89 | iterator erase(const_iterator pos) { return data_.erase(pos); } 90 | void resize(size_type count, const value_type& value = value_type()) { data_.resize(count, value); } 91 | // C++17 and above: Static factory function that accepts variable number of arguments 92 | // and converts them directly to xnode objects 93 | 94 | #if __cplusplus >= 201703L 95 | // C++17 version that auto-converts values to xnode objects 96 | template 97 | static xarray of(Args&&... args) { 98 | xarray result; 99 | result.reserve(sizeof...(args)); 100 | (result.push_back(xnode::value_of(std::forward(args))), ...); // Fold expression (C++17) 101 | return result; 102 | } 103 | 104 | // C++17 version for direct xnode objects (to avoid unnecessary conversion) 105 | template 106 | static xarray of_nodes(const xnode& first, Args&&... args) { 107 | xarray result; 108 | result.reserve(sizeof...(args) + 1); 109 | result.push_back(first); 110 | (result.push_back(std::forward(args)), ...); // Fold expression (C++17) 111 | return result; 112 | } 113 | 114 | // Specialization for empty array 115 | static xarray of_nodes() { 116 | return xarray(); 117 | } 118 | #else 119 | // Pre-C++17: Recursive variadic template implementations 120 | 121 | // Version that requires explicit xnode arguments 122 | static xarray of() { 123 | return xarray(); 124 | } 125 | 126 | template 127 | static xarray of(const xnode& first, const Args&... rest) { 128 | xarray result; 129 | result.reserve(sizeof...(rest) + 1); 130 | result.push_back(first); 131 | appendToArray(result, rest...); 132 | return result; 133 | } 134 | 135 | // Alias for consistency with the C++17 version 136 | static xarray of_nodes() { 137 | return xarray(); 138 | } 139 | 140 | template 141 | static xarray of_nodes(const xnode& first, const Args&... rest) { 142 | return of(first, rest...); 143 | } 144 | 145 | // Helper function for pre-C++17 implementation 146 | private: 147 | static void appendToArray(xarray& /* array */) { 148 | // Base case: no more elements to add 149 | } 150 | 151 | template 152 | static void appendToArray(xarray& array, const xnode& first, const Args&... rest) { 153 | array.push_back(first); 154 | appendToArray(array, rest...); 155 | } 156 | public: 157 | #endif 158 | 159 | // Compatibility with algorithms that expect STL containers 160 | bool operator==(const xarray& other) const { return data_ == other.data_; } 161 | bool operator!=(const xarray& other) const { return data_ != other.data_; } 162 | bool operator<(const xarray& other) const { return data_ < other.data_; } 163 | 164 | private: 165 | container_type data_; 166 | }; 167 | 168 | #endif // __XNODE_ARRAY_H__ 169 | 170 | -------------------------------------------------------------------------------- /include/xnode.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode.h 3 | // Purpose: Union-like dynamic data type able to store any scalar or structure. 4 | // Author: Piotr Likus 5 | // Created: 01/09/2015 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #ifndef __XNODE_H__ 10 | #define __XNODE_H__ 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /// \file xnode.h 19 | /// Union-like data type able to store any scalar or structure. 20 | /// Similar to boost::any. 21 | /// 22 | /// Possible value types 23 | /// - null 24 | /// - pointer 25 | /// - scalar (float, integer): stored in pointer 26 | /// - dynamic value, allocated by new, with automatic destruction: scalar, struct or class object 27 | // 28 | /// Classic, C-like build-in arrays are not supported. 29 | // 30 | /// Benefits 31 | /// - any-type variable (dynamic typing) 32 | /// - automatic conversion supported between bool, char, string, floating point & integer values 33 | /// - custom conversion methods can be implemented (defined as politics) 34 | /// - no dynamic allocation for scalar values 35 | /// - move semantics implemented 36 | // 37 | /// Use cases 38 | /// - optional named parameters 39 | /// - dynamic structures with optional parts 40 | /// - suitable for storing values for JSON/XML (with help of included containers) 41 | /// - see unit tests for detailed use cases. 42 | 43 | #include "xnode_type_ext.h" 44 | #include "xnode_utils.h" 45 | 46 | class xnode_null_value 47 | { 48 | public: 49 | }; 50 | 51 | template 52 | struct xnode_is_null 53 | { 54 | static const bool value = false; 55 | }; 56 | 57 | template <> 58 | struct xnode_is_null 59 | { 60 | static const bool value = true; 61 | }; 62 | 63 | enum xnode_storage_type 64 | { 65 | xstNull, 66 | xstPointer, 67 | xstCasted, 68 | xstOwned, 69 | xstUndefined 70 | }; 71 | 72 | template 73 | struct xnode_type_meta 74 | { 75 | static const bool store_as_null = xnode_is_null::value; 76 | static const bool store_as_pointer = (!store_as_null) && xn_is_pointer::value; 77 | static const bool store_as_casted = (!store_as_null) && (!store_as_pointer) && (sizeof(void *) >= sizeof(T)) && (std::is_scalar::value); 78 | static const bool store_as_owned = !store_as_casted && !xnode_is_null::value; 79 | }; 80 | 81 | template 82 | struct xnode_storage_meta 83 | { 84 | static const int storage_type = xnode_is_null::value ? xstNull : (xnode_type_meta::store_as_casted ? xstCasted : (xnode_type_meta::store_as_pointer ? xstPointer : (xnode_type_meta::store_as_owned ? xstOwned : xstUndefined))); 85 | }; 86 | 87 | template <> 88 | struct xnode_storage_meta 89 | { 90 | static const int storage_type = xstNull; 91 | }; 92 | 93 | template 94 | class xnode_deleter 95 | { 96 | public: 97 | /* Required interface: 98 | 99 | static void destroy(void *ptr) { 100 | delete static_cast(ptr); 101 | } 102 | 103 | static bool needs_deleter() { 104 | return true; 105 | } 106 | */ 107 | }; 108 | 109 | template 110 | class xnode_deleter 111 | { 112 | public: 113 | static void destroy(void * /* ptr */) 114 | { 115 | // empty 116 | } 117 | 118 | static bool needs_deleter() 119 | { 120 | return false; 121 | } 122 | }; 123 | 124 | template 125 | class xnode_deleter 126 | { 127 | public: 128 | static void destroy(void * /* ptr */) 129 | { 130 | // empty 131 | } 132 | 133 | static bool needs_deleter() 134 | { 135 | return false; 136 | } 137 | }; 138 | 139 | template 140 | class xnode_deleter 141 | { 142 | public: 143 | static void destroy(void * /* ptr */) 144 | { 145 | // empty 146 | } 147 | 148 | static bool needs_deleter() 149 | { 150 | return false; 151 | } 152 | }; 153 | 154 | template 155 | class xnode_deleter 156 | { 157 | public: 158 | static void destroy(void *ptr) 159 | { 160 | delete static_cast(ptr); 161 | } 162 | 163 | static bool needs_deleter() 164 | { 165 | return true; 166 | } 167 | }; 168 | 169 | template 170 | class xnode_setter 171 | { 172 | public: 173 | static bool supports_copy() 174 | { 175 | return false; 176 | } 177 | 178 | // copy value to already existing object using assign operator 179 | static void copy(void * /* dest */, const void * /* src */) 180 | { 181 | // empty 182 | } 183 | 184 | // copy from value, optionally invoking change of object pointer at destination side 185 | static void assign_from_value(void **dest, const void *src) 186 | { 187 | // by default, copy as casted 188 | *dest = reinterpret_cast(*static_cast(src)); 189 | } 190 | 191 | // accepts pointer to value, takes ownership of it 192 | static void hold_ptr(void **dest, void *src) 193 | { 194 | std::unique_ptr holder(static_cast(src)); 195 | *dest = reinterpret_cast(*holder); 196 | } 197 | 198 | // copy from another node, ignore old contents (for contructors) 199 | static void init_from_node(void **dest, const void *src) 200 | { 201 | // by default, raw copy of pointers 202 | *dest = const_cast(src); 203 | } 204 | 205 | // copy from another node using assign operator, assume old value holds pointer to existing object 206 | static void copy_from_node(void * /* dest */, const void * /* src */) 207 | { 208 | // empty 209 | } 210 | }; 211 | 212 | template 213 | class xnode_setter 214 | { 215 | public: 216 | static bool supports_copy() 217 | { 218 | return false; 219 | } 220 | 221 | static void copy(void * /* dest */, const void * /* src */) 222 | { 223 | // empty 224 | } 225 | 226 | static void assign_from_value(void **dest, const void * /* src */) 227 | { 228 | *dest = nullptr; 229 | } 230 | 231 | static void hold_ptr(void **dest, void *src) 232 | { 233 | std::unique_ptr holder(static_cast(src)); 234 | *dest = nullptr; 235 | } 236 | 237 | static void init_from_node(void **dest, const void *src) 238 | { 239 | *dest = const_cast(src); 240 | } 241 | 242 | static void copy_from_node(void * /* dest */, const void * /* src */) 243 | { 244 | // empty 245 | } 246 | }; 247 | 248 | template 249 | class xnode_setter 250 | { 251 | public: 252 | static bool supports_copy() 253 | { 254 | return false; 255 | } 256 | 257 | // copy value 258 | static void copy(void * /* dest */, const void * /* src */) 259 | { 260 | // empty 261 | } 262 | 263 | // copy value, optionally change object pointer at destination side 264 | static void assign_from_value(void **dest, const void *src) 265 | { 266 | T *dptr = reinterpret_cast(dest); 267 | *dptr = *reinterpret_cast(src); 268 | } 269 | 270 | static void hold_ptr(void **dest, void *src) 271 | { 272 | std::unique_ptr holder(static_cast(src)); 273 | T *dptr = reinterpret_cast(dest); 274 | *dptr = *holder; 275 | } 276 | 277 | static void init_from_node(void **dest, const void *src) 278 | { 279 | // raw copy of pointers 280 | *dest = const_cast(src); 281 | } 282 | 283 | static void copy_from_node(void * /* dest */, const void * /* src */) 284 | { 285 | // empty 286 | } 287 | }; 288 | 289 | template 290 | class xnode_setter 291 | { 292 | public: 293 | static bool supports_copy() 294 | { 295 | return false; 296 | } 297 | 298 | // copy value 299 | static void copy(void * /* dest */, const void * /* src */) 300 | { 301 | // empty 302 | } 303 | 304 | // copy value, optionally change object pointer at destination side 305 | static void assign_from_value(void **dest, const void *src) 306 | { 307 | *dest = *reinterpret_cast(const_cast(src)); 308 | } 309 | 310 | static void hold_ptr(void **dest, void *src) 311 | { 312 | std::unique_ptr holder(static_cast(src)); 313 | *dest = const_cast(reinterpret_cast(*holder)); 314 | } 315 | 316 | static void init_from_node(void **dest, const void *src) 317 | { 318 | *dest = const_cast(src); 319 | } 320 | 321 | static void copy_from_node(void * /* dest */, const void * /* src */) 322 | { 323 | // empty 324 | } 325 | }; 326 | 327 | template 328 | class xnode_setter 329 | { 330 | public: 331 | static bool supports_copy() 332 | { 333 | return true; 334 | } 335 | 336 | // copy value 337 | static void copy(void *dest, const void *src) 338 | { 339 | T *ndest = static_cast(dest); 340 | T *nsrc = static_cast(const_cast(src)); 341 | if (ndest) 342 | *ndest = *nsrc; 343 | else 344 | assert(false); 345 | } 346 | 347 | // copy value, optionally change object pointer at destination side 348 | static void assign_from_value(void **dest, const void *src) 349 | { 350 | T **ndest = reinterpret_cast(dest); 351 | T *nsrc = reinterpret_cast(const_cast(src)); 352 | if (*ndest) 353 | { 354 | **ndest = *nsrc; 355 | } 356 | else 357 | { 358 | *ndest = new T(*nsrc); 359 | } 360 | } 361 | 362 | static void hold_ptr(void **dest, void *src) 363 | { 364 | *dest = src; 365 | } 366 | 367 | static void init_from_node(void **dest, const void *src) 368 | { 369 | T **ndest = reinterpret_cast(dest); 370 | T *nsrc = reinterpret_cast(const_cast(src)); 371 | *ndest = new T(*nsrc); 372 | } 373 | 374 | static void copy_from_node(void *dest, const void *src) 375 | { 376 | T *ndest = reinterpret_cast(dest); 377 | T *nsrc = reinterpret_cast(const_cast(src)); 378 | *ndest = *nsrc; 379 | } 380 | }; 381 | 382 | // default, same as null 383 | template 384 | class xnode_getter 385 | { 386 | public: 387 | // returns true if value_ptr can be invoked 388 | static bool supports_ptr() 389 | { 390 | return false; 391 | } 392 | 393 | // returns true if read_value can be invoked 394 | static bool supports_read_value() 395 | { 396 | return false; 397 | } 398 | 399 | // returns pointer to stored value 400 | static void *value_ptr(void ** /* src */) 401 | { 402 | return nullptr; 403 | } 404 | 405 | static void read_value(void * /* dest */, void ** /* src */) 406 | { 407 | // empty 408 | } 409 | 410 | static bool equals(void *lhs, void *rhs) 411 | { 412 | return lhs == rhs; 413 | } 414 | 415 | static bool less(void *lhs, void *rhs) 416 | { 417 | return lhs < rhs; 418 | } 419 | }; 420 | 421 | template 422 | class xnode_getter 423 | { 424 | public: 425 | static bool supports_ptr() 426 | { 427 | return true; 428 | } 429 | 430 | static bool supports_read_value() 431 | { 432 | return true; 433 | } 434 | 435 | // returns pointer to value 436 | static void *value_ptr(void **src) 437 | { 438 | return reinterpret_cast(src); 439 | } 440 | 441 | static void read_value(void *dest, void **src) 442 | { 443 | T *destPtr = reinterpret_cast(dest); 444 | *destPtr = reinterpret_cast(*src); 445 | } 446 | 447 | static bool equals(void *lhs, void *rhs) 448 | { 449 | return lhs == rhs; 450 | } 451 | 452 | static bool less(void *lhs, void *rhs) 453 | { 454 | return lhs < rhs; 455 | } 456 | }; 457 | 458 | template 459 | class xnode_getter 460 | { 461 | public: 462 | static bool supports_ptr() 463 | { 464 | return true; 465 | } 466 | 467 | static bool supports_read_value() 468 | { 469 | return true; 470 | } 471 | 472 | // returns pointer to value 473 | static void *value_ptr(void **src) 474 | { 475 | return reinterpret_cast(reinterpret_cast(src)); 476 | } 477 | 478 | static void read_value(void *dest, void **src) 479 | { 480 | T *destPtr = reinterpret_cast(dest); 481 | *destPtr = *reinterpret_cast(src); 482 | } 483 | 484 | static bool equals(void *lhs, void *rhs) 485 | { 486 | return lhs == rhs; 487 | } 488 | 489 | static bool less(void *lhs, void *rhs) 490 | { 491 | T *lptr = reinterpret_cast(&lhs); 492 | T *rptr = reinterpret_cast(&rhs); 493 | return *lptr < *rptr; 494 | } 495 | }; 496 | 497 | template 498 | class xnode_getter 499 | { 500 | public: 501 | static bool supports_ptr() 502 | { 503 | return true; 504 | } 505 | 506 | static bool supports_read_value() 507 | { 508 | return true; 509 | } 510 | 511 | // returns pointer to value 512 | static void *value_ptr(void **src) 513 | { 514 | return static_cast(static_cast(*src)); 515 | } 516 | 517 | static void read_value(void *dest, void **src) 518 | { 519 | T *destPtr = reinterpret_cast(dest); 520 | *destPtr = *reinterpret_cast(*src); 521 | } 522 | 523 | static bool equals(void *lhs, void *rhs) 524 | { 525 | T *lptr = reinterpret_cast(lhs); 526 | T *rptr = reinterpret_cast(rhs); 527 | return xn_equals(*lptr, *rptr); 528 | } 529 | 530 | static bool less(void *lhs, void *rhs) 531 | { 532 | T *lptr = reinterpret_cast(lhs); 533 | T *rptr = reinterpret_cast(rhs); 534 | return xn_less(*lptr, *rptr); 535 | } 536 | }; 537 | 538 | struct xnode_vtable 539 | { 540 | int type_code_; 541 | const std::type_info &value_type_id_; 542 | void (*deleter_)(void *); 543 | void (*copy_)(void *, const void *); 544 | void (*assign_)(void **, const void *); 545 | void (*hold_)(void **, void *); 546 | void (*init_from_node_)(void **, const void *); 547 | void (*copy_from_node_)(void *, const void *); 548 | void *(*value_ptr_)(void **); 549 | void (*read_value_)(void *, void **); 550 | bool (*equals_)(void *, void *); 551 | bool (*less_)(void *, void *); 552 | }; 553 | 554 | template 555 | const xnode_vtable *xnode_get_vtable() 556 | { 557 | static const xnode_vtable pt = { 558 | xnode_type_code::value, 559 | typeid(T), 560 | (xnode_deleter::storage_type>::needs_deleter() ? &xnode_deleter::storage_type>::destroy : nullptr), 561 | (xnode_setter::storage_type>::supports_copy() ? &xnode_setter::storage_type>::copy : nullptr), 562 | &xnode_setter::storage_type>::assign_from_value, 563 | &xnode_setter::storage_type>::hold_ptr, 564 | &xnode_setter::storage_type>::init_from_node, 565 | (xnode_setter::storage_type>::supports_copy() ? &xnode_setter::storage_type>::copy_from_node : nullptr), 566 | (xnode_getter::storage_type>::supports_ptr() ? &xnode_getter::storage_type>::value_ptr : nullptr), 567 | &xnode_getter::storage_type>::read_value, 568 | &xnode_getter::storage_type>::equals, 569 | &xnode_getter::storage_type>::less}; 570 | 571 | return &pt; 572 | } 573 | 574 | template 575 | T *xnode_get_ptr(void **storage) 576 | { 577 | return static_cast(xnode_getter::storage_type>::value_ptr(storage)); 578 | } 579 | 580 | template 581 | T xnode_get_scalar(void **storage) 582 | { 583 | return *static_cast(xnode_getter::storage_type>::value_ptr(storage)); 584 | } 585 | 586 | template 587 | void xnode_set_scalar(void **storage, T value) 588 | { 589 | xnode_setter::storage_type>::assign_from_value(storage, &value); 590 | } 591 | 592 | std::string xnode_pack_value_as_str(const char *text) 593 | { 594 | return std::string(text); 595 | } 596 | 597 | /// Main class for xnode library, 598 | /// based on generic_t from Advanced C++ Metaprogramming by Davide Di Gennaro. 599 | /// ValuePolicy used for selection of value casting classes. 600 | template 601 | class basic_xnode 602 | { 603 | public: 604 | typedef basic_xnode this_type; 605 | typedef ValuePolicy value_policy; 606 | 607 | basic_xnode() : vtable_(xnode_get_vtable()), value_(nullptr) 608 | { 609 | } 610 | 611 | basic_xnode(const basic_xnode &src) 612 | { 613 | vtable_ = src.vtable_; 614 | vtable_->init_from_node_(&value_, src.value_); 615 | } 616 | 617 | basic_xnode(basic_xnode &&src) : vtable_(xnode_get_vtable()), value_(nullptr) 618 | { 619 | std::swap(vtable_, src.vtable_); 620 | std::swap(value_, src.value_); 621 | } 622 | 623 | ~basic_xnode() 624 | { 625 | destroy(); 626 | } 627 | 628 | /// assigns type & value of right-hand argument 629 | basic_xnode &operator=(const basic_xnode &src) 630 | { 631 | if (this == &src) 632 | return *this; 633 | 634 | if ((type() == src.type()) && (vtable_->copy_from_node_ != nullptr)) 635 | { 636 | vtable_->copy_from_node_(value_, src.value_); 637 | } 638 | else 639 | { 640 | destroy(); 641 | vtable_ = src.vtable_; 642 | vtable_->init_from_node_(&value_, src.value_); 643 | } 644 | 645 | return *this; 646 | } 647 | 648 | basic_xnode &operator=(basic_xnode &&src) 649 | { 650 | if (this != &src) 651 | { 652 | std::swap(vtable_, src.vtable_); 653 | std::swap(value_, src.value_); 654 | } 655 | 656 | return *this; 657 | } 658 | 659 | /// compares type and value of nodes 660 | bool operator==(const basic_xnode &rhs) const 661 | { 662 | if (get_type_code() != rhs.get_type_code()) 663 | return false; 664 | if (type() != rhs.type()) 665 | return false; 666 | return vtable_->equals_(value_, rhs.value_); 667 | } 668 | 669 | /// compares type and value of nodes 670 | bool operator!=(const basic_xnode &rhs) const 671 | { 672 | return !(*this == rhs); 673 | } 674 | 675 | /// required for sorting 676 | bool operator<(const basic_xnode &rhs) const 677 | { 678 | if (get_type_code() != rhs.get_type_code()) 679 | return get_type_code() < rhs.get_type_code(); // for ordering by type in multi-type container 680 | if (type() != rhs.type()) 681 | return false; 682 | return vtable_->less_(value_, rhs.value_); 683 | } 684 | 685 | /* Not recommended to use / define (because of casting): 686 | bool operator<=(const basic_xnode& rhs) const { 687 | return (*this == rhs) || (*this < rhs); 688 | } 689 | 690 | bool operator>(const basic_xnode& rhs) const { 691 | return !(*this <= rhs); 692 | } 693 | 694 | bool operator>=(const basic_xnode& rhs) const { 695 | return !(*this < rhs); 696 | } 697 | */ 698 | 699 | /// returns code of type of value stored in node, =0 for types which have no casting defined 700 | int get_type_code() const 701 | { 702 | return vtable_->type_code_; 703 | } 704 | 705 | /// returns type of value stored in node, compatible with built-in typeid() 706 | const std::type_info &type() const 707 | { 708 | return vtable_->value_type_id_; 709 | } 710 | 711 | /// checks if node has a null value 712 | bool is_null() const 713 | { 714 | return (value_ == nullptr) && (vtable_->value_type_id_ == typeid(xnode_null_value)); 715 | } 716 | 717 | /// remove contents of node 718 | void reset() 719 | { 720 | destroy(); 721 | value_ = nullptr; 722 | vtable_ = xnode_get_vtable(); 723 | } 724 | 725 | /// set value and type of node 726 | template 727 | void set_as(const T &value, typename std::enable_if::value>::type * = 0) 728 | { 729 | if ((typeid(T) == type()) && (xnode_setter::storage_type>::supports_copy())) 730 | { 731 | vtable_->copy_(value_, static_cast(&value)); 732 | } 733 | else 734 | { 735 | rebuild_as(value); 736 | } 737 | } 738 | 739 | /// set value and type of node for character arrays (string literals) 740 | template 741 | void set_as(const T &value, typename std::enable_if::value>::type * = 0) 742 | { 743 | set_as(xnode_pack_value_as_str(value)); 744 | } 745 | 746 | /// set value without changing assigned type 747 | template 748 | void set_value(const T &value) 749 | { 750 | if ((typeid(T) == type()) && (xnode_setter::storage_type>::supports_copy())) 751 | { 752 | vtable_->copy_(value_, static_cast(&value)); 753 | } 754 | else 755 | { 756 | if (!xnode_caster::cast_from_value(&value_, vtable_->type_code_, value)) 757 | throwWrongCastFromValue(); 758 | } 759 | } 760 | 761 | /// get copy of value with optional conversion 762 | template 763 | T get_as() const 764 | { 765 | T result; 766 | if (xnode_getter::storage_type>::supports_read_value() && (typeid(T) == type())) 767 | { 768 | vtable_->read_value_(&result, const_cast(&value_)); 769 | return result; 770 | } 771 | 772 | if (!xnode_caster::cast_to_value(result, const_cast(&value_), vtable_->type_code_)) 773 | { 774 | throwWrongCastToValue(); 775 | } 776 | return result; 777 | } 778 | 779 | /// get copy of value with optional conversion 780 | template 781 | T &get_as(T &output) 782 | { 783 | if (xnode_getter::storage_type>::supports_read_value() && (typeid(T) == type())) 784 | { 785 | vtable_->read_value_(&output, &value_); 786 | return output; 787 | } 788 | 789 | if (!xnode_caster::cast_to_value(output, &value_, vtable_->type_code_)) 790 | throwWrongCastToValue(); 791 | 792 | return output; 793 | } 794 | 795 | /// get copy of value with optional conversion 796 | /// does not throw on wrong cast 797 | /// \param[in] def_value value to be used when stored value cannot be retrieved 798 | template 799 | T get_as_def(const T &def_value) const 800 | { 801 | T result; 802 | if (xnode_getter::storage_type>::supports_read_value() && (typeid(T) == type())) 803 | { 804 | vtable_->read_value_(&result, const_cast(&value_)); 805 | return result; 806 | } 807 | 808 | if (xnode_caster::cast_to_value(result, const_cast(&value_), vtable_->type_code_)) 809 | return result; 810 | 811 | return def_value; 812 | } 813 | 814 | /// get copy of value with optional conversion 815 | /// does not throw on wrong cast 816 | /// \param[in] def_value value to be used when stored value cannot be retrieved 817 | template 818 | T &get_as_def(T &output, const T &def_value) 819 | { 820 | if (xnode_getter::storage_type>::supports_read_value() && (typeid(T) == type())) 821 | { 822 | vtable_->read_value_(&output, &value_); 823 | return output; 824 | } 825 | 826 | if (xnode_caster::cast_to_value(output, &value_, vtable_->type_code_)) 827 | return output; 828 | 829 | return def_value; 830 | } 831 | 832 | /// returns true if node can be read as a given type 833 | template 834 | bool is_convertable_to() 835 | { 836 | if (xnode_getter::storage_type>::supports_read_value() && (typeid(T) == type())) 837 | { 838 | return true; 839 | } 840 | else 841 | { 842 | T tmp; 843 | return xnode_caster::cast_to_value(tmp, &value_, vtable_->type_code_); 844 | } 845 | } 846 | 847 | /// returns pointer to stored value, null if type is not matched or cannot be pointed 848 | template 849 | T *get_ptr() 850 | { 851 | if (!xnode_getter::storage_type>::supports_ptr()) 852 | return nullptr; 853 | 854 | if (typeid(T) == type()) 855 | return static_cast(vtable_->value_ptr_(&value_)); 856 | 857 | return nullptr; 858 | } 859 | 860 | /// returns pointer to stored value, null if type is not matched or cannot be pointed 861 | template 862 | const T *get_ptr() const 863 | { 864 | if (!xnode_getter::storage_type>::supports_ptr()) 865 | return nullptr; 866 | if (typeid(T) != type()) 867 | return nullptr; 868 | return static_cast(vtable_->value_ptr_(const_cast(&value_))); 869 | } 870 | 871 | /// returns (void *) pointer to value if supported, otherwise null 872 | void *get_vptr() 873 | { 874 | if (!vtable_->value_ptr_) 875 | return nullptr; 876 | 877 | return vtable_->value_ptr_(&value_); 878 | } 879 | 880 | /// returns (void *) pointer to value if supported, otherwise null 881 | const void *get_vptr() const 882 | { 883 | if (!vtable_->value_ptr_) 884 | return nullptr; 885 | 886 | return vtable_->value_ptr_(&value_); 887 | } 888 | 889 | /// returns reference to stored value, throws if cannot be retrieved 890 | template 891 | T &get_ref() 892 | { 893 | T *ptr = get_ptr(); 894 | 895 | if (!ptr) 896 | throwRefReadFail(); 897 | 898 | return *ptr; 899 | } 900 | 901 | /// returns reference to stored value, throws if cannot be retrieved 902 | template 903 | const T &get_ref() const 904 | { 905 | const T *ptr = get_ptr(); 906 | 907 | if (!ptr) 908 | throwRefReadFail(); 909 | 910 | return *ptr; 911 | } 912 | 913 | /// returns reference to stored value, throws if cannot be retrieved 914 | /// \param[in] def_value value to be used when stored value cannot be retrieved 915 | template 916 | const T &get_ref_def(const T &def_value) const 917 | { 918 | const T *ptr = get_ptr(); 919 | 920 | if (ptr) 921 | return *ptr; 922 | else 923 | return def_value; 924 | } 925 | 926 | /// release ownership of object and return pointer to it, 927 | /// \result returns null for non-owned values or if type is incorrect 928 | template 929 | T *release() 930 | { 931 | std::unique_ptr result; 932 | 933 | if (vtable_->deleter_ != nullptr) 934 | { 935 | result.reset(get_ptr()); 936 | } 937 | 938 | value_ = nullptr; 939 | vtable_ = xnode_get_vtable(); 940 | 941 | if (result.get()) 942 | return result.release(); 943 | else 944 | return nullptr; 945 | } 946 | 947 | /// take ownership of dynamic object 948 | template 949 | void hold(T *value) 950 | { 951 | std::unique_ptr holder(value); 952 | destroy(); 953 | vtable_ = xnode_get_vtable(); 954 | vtable_->hold_(&value_, holder.release()); 955 | } 956 | 957 | /// returns true if type is matching value stored in xnode 958 | template 959 | bool is() const 960 | { 961 | return typeid(T) == type(); 962 | } 963 | 964 | /// object builder function with optional type casting 965 | /// \param[in] DestType final value type 966 | /// \param[in] ValueType input value type 967 | /// \param[in] value input value to be converted to xnode 968 | /// \result Returns xnode of type DestType 969 | template 970 | static this_type value_of(const ValueType &value) 971 | { 972 | this_type valueNode; 973 | valueNode.set_as(value); 974 | 975 | DestType tempValue; 976 | this_type result; 977 | result.set_as(valueNode.get_as(tempValue)); 978 | return result; 979 | } 980 | 981 | /// object builder function 982 | /// \param[in] ValueType input value type 983 | /// \param[in] value input value to be converted to xnode 984 | /// \result Returns xnode of type ValueType 985 | template 986 | static this_type value_of(const ValueType &value, typename std::enable_if::value>::type * = 0) 987 | { 988 | this_type result; 989 | result.set_as(value); 990 | return result; 991 | } 992 | 993 | /// object builder function for arrays 994 | /// \param[in] ValueType input value type 995 | /// \param[in] value input value to be converted to xnode 996 | /// \result Returns xnode of type ValueType 997 | template 998 | static this_type value_of(const ValueType &value, typename std::enable_if::value>::type * = 0) 999 | { 1000 | this_type result; 1001 | result.set_as(xnode_pack_value_as_str(value)); 1002 | return result; 1003 | } 1004 | 1005 | protected: 1006 | void destroy() 1007 | { 1008 | if (vtable_->deleter_ != nullptr) 1009 | { 1010 | vtable_->deleter_(value_); 1011 | } 1012 | } 1013 | 1014 | template 1015 | void rebuild_as(const T &value) 1016 | { 1017 | destroy(); 1018 | value_ = nullptr; 1019 | vtable_ = xnode_get_vtable(); 1020 | vtable_->assign_(&value_, static_cast(&value)); 1021 | } 1022 | 1023 | void throwRefReadFail() const 1024 | { 1025 | throw std::runtime_error(std::string("Reference read failed, data type: ") + to_string(vtable_->type_code_) + ", name: " + vtable_->value_type_id_.name()); 1026 | } 1027 | 1028 | template 1029 | void throwWrongCastToValue() const 1030 | { 1031 | throw std::runtime_error(std::string("Conversion to value failed, storage type: [ code: ") + to_string(vtable_->type_code_) + ", name: " + vtable_->value_type_id_.name() + " ], value type name: " + typeid(T).name()); 1032 | } 1033 | 1034 | template 1035 | void throwWrongCastFromValue() const 1036 | { 1037 | throw std::runtime_error(std::string("Conversion from value failed, storage type: [ code: ") + to_string(vtable_->type_code_) + ", name: " + vtable_->value_type_id_.name() + " ], value type name: " + typeid(T).name()); 1038 | } 1039 | 1040 | private: 1041 | const xnode_vtable *vtable_; 1042 | void *value_; 1043 | }; 1044 | 1045 | typedef basic_xnode<> xnode; 1046 | 1047 | #include "details/xnode_builtins.h" 1048 | 1049 | #endif 1050 | -------------------------------------------------------------------------------- /include/xnode_long_double.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode_long_double.h 3 | // Purpose: Example how to define own casting basing on existing. 4 | // Author: Piotr Likus 5 | // Created: 03/09/2015 6 | // Last change: 7 | // License: BSD 8 | //---------------------------------------------------------------------------------- 9 | 10 | #ifndef __XNODE_LONG_DOUBLE_H__ 11 | #define __XNODE_LONG_DOUBLE_H__ 12 | 13 | #include "xnode.h" 14 | #include 15 | 16 | // Define a specific type code for long double to distinguish it 17 | template<> 18 | struct xnode_type_code { 19 | enum { value = 17 }; // Ensure this doesn't conflict with other type codes 20 | }; 21 | 22 | struct xnode_ld_cast_policy { 23 | 24 | }; 25 | 26 | typedef xnode_def_cast_policy xnode_ld_value_policy_base; 27 | 28 | struct xnode_ld_value_policy : xnode_ld_value_policy_base { 29 | typedef xnode_ld_cast_policy cast_policy; 30 | }; 31 | 32 | // Specialized caster for double to handle long double conversion 33 | template<> 34 | class xnode_caster : xnode_caster_base { 35 | public: 36 | typedef double ValueType; 37 | 38 | static bool cast_to_value(ValueType &output, void **storage, int srcTypeCode) { 39 | // Special handling for long double 40 | if (srcTypeCode == xnode_type_code::value) { 41 | long double value; 42 | 43 | if (xnode_storage_meta::storage_type == xstOwned) { 44 | value = *reinterpret_cast(*storage); 45 | } else { 46 | value = xnode_get_scalar(storage); 47 | } 48 | 49 | output = static_cast(value); 50 | return true; 51 | } 52 | 53 | // For all other types, use default policy 54 | return xnode_caster::cast_to_value(output, storage, srcTypeCode); 55 | } 56 | 57 | static bool cast_from_value(void **storage, int destTypeCode, const ValueType &value) { 58 | // Special handling for long double 59 | if (destTypeCode == xnode_type_code::value) { 60 | if (xnode_storage_meta::storage_type == xstOwned) { 61 | *reinterpret_cast(*storage) = static_cast(value); 62 | } else { 63 | xnode_set_scalar(storage, static_cast(value)); 64 | } 65 | return true; 66 | } 67 | 68 | // For all other types, use default policy 69 | return xnode_caster::cast_from_value(storage, destTypeCode, value); 70 | } 71 | }; 72 | 73 | // Specialized caster for long double 74 | template<> 75 | class xnode_caster : xnode_caster_base { 76 | public: 77 | typedef long double ValueType; 78 | 79 | static bool cast_to_value(ValueType &output, void **storage, int srcTypeCode) { 80 | bool result = true; 81 | 82 | switch (srcTypeCode) { 83 | case xnode_type_code::value: { 84 | output = xnode_get_scalar(storage); 85 | break; 86 | } 87 | case xnode_type_code::value: { 88 | output = xnode_get_scalar(storage); 89 | break; 90 | } 91 | case xnode_type_code::value: { 92 | output = xnode_get_scalar(storage); 93 | break; 94 | } 95 | case xnode_type_code::value: { 96 | output = xnode_get_scalar(storage); 97 | break; 98 | } 99 | default: { 100 | result = false; 101 | } 102 | } 103 | return result; 104 | } 105 | 106 | static bool cast_from_value(void **storage, int destTypeCode, const ValueType &value) { 107 | bool result = true; 108 | 109 | switch (destTypeCode) { 110 | case xnode_type_code::value: { 111 | *reinterpret_cast(*storage) = static_cast(value); 112 | break; 113 | } 114 | case xnode_type_code::value: { 115 | *reinterpret_cast(*storage) = static_cast(value); 116 | break; 117 | } 118 | case xnode_type_code::value: { 119 | *reinterpret_cast(*storage) = value; 120 | break; 121 | } 122 | default: { 123 | result = false; 124 | } 125 | } 126 | return result; 127 | } 128 | }; 129 | 130 | #include "details/xnode_builtins.h" 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /include/xnode_type_ext.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode_type_ext.h 3 | // Purpose: utility base classes & definitions required for type extensions 4 | // Author: Piotr Likus 5 | // Created: 01/09/2015 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #ifndef __XNODE_TYPEE_EXT_H__ 10 | #define __XNODE_TYPEE_EXT_H__ 11 | #include 12 | #include "xnode_utils.h" 13 | 14 | /// \file xnode_type_ext.h 15 | /// utility base classes & definitions required for type extensions 16 | struct xnode_def_cast_policy { 17 | }; 18 | 19 | struct xnode_def_value_policy { 20 | typedef xnode_def_cast_policy cast_policy; // caster selection policy 21 | }; 22 | 23 | template 24 | struct xnode_cast_policy_silent_cast { 25 | enum { 26 | null_code = 0 27 | }; 28 | }; 29 | 30 | template<> 31 | struct xnode_cast_policy_silent_cast { 32 | enum { 33 | null_code = -1 34 | }; 35 | }; 36 | 37 | template 38 | struct xnode_cast_policy_type_codes { 39 | enum { 40 | null_code = xnode_cast_policy_silent_cast::null_code, 41 | def_code = 0, 42 | max_code = 0 43 | }; 44 | }; 45 | 46 | template 47 | struct xnode_type_code { 48 | enum { value = 0 }; 49 | }; 50 | 51 | class xnode_caster_base { 52 | protected: 53 | template 54 | static void throwWrongCast(int typeCode) { 55 | throw std::runtime_error(std::string("Conversion failed, source type: ") + to_string(typeCode) + ", target: " + typeid(DestType).name()); 56 | } 57 | }; 58 | 59 | template 60 | class xnode_caster { 61 | public: 62 | 63 | static bool cast_to_value(ValueType & /* output */, void * /* storage */, int /* srcTypeCode */) { 64 | return false; 65 | } 66 | 67 | static bool cast_from_value(void ** /* storage */, int /* destTypeCode */, const ValueType & /* value */) { 68 | return false; 69 | } 70 | }; 71 | 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /include/xnode_utils.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode_utils.h 3 | // Purpose: utility classes not related directly to xnode class. 4 | // Author: Piotr Likus 5 | // Created: 01/09/2015 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #ifndef __XNODE_UTILS_H__ 10 | #define __XNODE_UTILS_H__ 11 | 12 | #include 13 | #include 14 | 15 | template < class T > 16 | std::string to_string(const T &arg) 17 | { 18 | std::ostringstream out; 19 | out << arg; 20 | return(out.str()); 21 | } 22 | 23 | template 24 | T from_string(const std::string &str) { 25 | std::istringstream is(str); 26 | T t; 27 | is >> t; 28 | return t; 29 | } 30 | 31 | template 32 | bool from_string(T &output, const std::string &str) { 33 | std::istringstream is(str); 34 | return !(!(is >> output)); 35 | } 36 | 37 | template 38 | T from_string_def(const std::string &str, const T &defValue) { 39 | std::istringstream is(str); 40 | T res; 41 | if (!(is >> res)) 42 | res = defValue; 43 | return res; 44 | } 45 | 46 | template 47 | struct xnSelector { 48 | static const bool value = Parameter; 49 | }; 50 | 51 | template 52 | struct xnEnableIfType { 53 | typedef T type; 54 | }; 55 | 56 | template 57 | struct xnEnableIfType { 58 | }; 59 | 60 | template 61 | struct xnEnableIf : public xnEnableIfType { 62 | }; 63 | 64 | template 65 | struct xnDisableIf : public xnEnableIfType<(!Condition::value), T> { 66 | }; 67 | 68 | template 69 | struct xn_is_pointer { static const bool value = false; }; 70 | 71 | template 72 | struct xn_is_pointer { static const bool value = true; }; 73 | 74 | namespace XN_CHECK_EQUALS 75 | { 76 | template 77 | struct opEqualExists : std::false_type {}; 78 | 79 | template 80 | struct opEqualExists() == std::declval()))> : std::true_type {}; 81 | } 82 | 83 | namespace XN_CHECK_LESS 84 | { 85 | template 86 | struct opLessExists : std::false_type {}; 87 | 88 | template 89 | struct opLessExists() < std::declval()))> : std::true_type {}; 90 | } 91 | 92 | template 93 | struct has_equals_operator { 94 | static const bool value = (XN_CHECK_EQUALS::opEqualExists::value != 0); 95 | }; 96 | 97 | template 98 | struct has_less_operator { 99 | static const bool value = (XN_CHECK_LESS::opLessExists::value != 0); 100 | }; 101 | 102 | // checks if objects are equal, throws if operator == not implemented 103 | template 104 | typename xnEnableIf< 105 | xnSelector< 106 | has_equals_operator::value>, bool>::type 107 | xn_equals(const T& lhs, const T& rhs) 108 | { 109 | return lhs == rhs; 110 | } 111 | 112 | // checks if objects are equal, throws if operator == not implemented 113 | template 114 | typename xnEnableIf< 115 | xnSelector< 116 | !has_equals_operator::value>, bool>::type 117 | xn_equals(const T& /* lhs */, const T& /* rhs */) 118 | { 119 | throw std::runtime_error(std::string("Equals not implemented, type: ") + typeid(T).name()); 120 | } 121 | 122 | // checks if objects left is less than right, throws if operator == not implemented 123 | template 124 | typename xnEnableIf< 125 | xnSelector< 126 | has_less_operator::value>, bool>::type 127 | xn_less(const T& lhs, const T& rhs) 128 | { 129 | return lhs < rhs; 130 | } 131 | 132 | // checks if objects are equal, throws if operator == not implemented 133 | template 134 | typename xnEnableIf< 135 | xnSelector< 136 | !has_less_operator::value>, bool>::type 137 | xn_less(const T& /* lhs */, const T& /* rhs */) 138 | { 139 | throw std::runtime_error(std::string("Less operator not implemented, type: ") + typeid(T).name()); 140 | } 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /include/xobject.h: -------------------------------------------------------------------------------- 1 | #ifndef XOBJECT_H 2 | #define XOBJECT_H 3 | 4 | #include "details/property_list.h" 5 | #include "xnode.h" 6 | 7 | typedef property_list xobject; 8 | 9 | // Define a specific type code for long double to distinguish it 10 | template<> 11 | struct xnode_type_code { 12 | enum { value = 15 }; // Ensure this doesn't conflict with other type codes 13 | }; 14 | 15 | #endif // XOBJECT_H -------------------------------------------------------------------------------- /make.cmd: -------------------------------------------------------------------------------- 1 | set path=%path%;%MINGW_HOME%\bin 2 | mkdir bin 3 | mingw32-make.exe -f makefile.mgw BUILD=all 4 | pause -------------------------------------------------------------------------------- /makefile.mgw: -------------------------------------------------------------------------------- 1 | # makefile for Windows & MinGW 2 | 3 | CFLAGS = -Wall -O0 -std=c++11 -g -Wl,--subsystem,console 4 | CC = mingw32-g++.exe 5 | 6 | INCLUDE_PATHS = -I.\include 7 | #LINKER_FLAGS = -lmingw32 8 | LINKER_FLAGS = 9 | 10 | TARGET_NAME =xnode_test 11 | OBJ_LIST = xnode_test.o 12 | 13 | all: clean $(TARGET_NAME).exe clean_build 14 | 15 | xnode_test.exe: $(OBJ_LIST) 16 | $(CC) $(LINKER_FLAGS) -o $(TARGET_NAME).exe $(OBJ_LIST) 17 | 18 | xnode_test.o: test\xnode_test.cpp 19 | $(CC) $(CFLAGS) -c test\xnode_test.cpp $(INCLUDE_PATHS) 20 | 21 | clean: 22 | del *.o 23 | 24 | clean_build: 25 | del *.o 26 | copy $(TARGET_NAME).exe bin\$(TARGET_NAME).exe 27 | del $(TARGET_NAME).exe 28 | 29 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | rm -r build 2 | 3 | # Configure 4 | cmake -B build 5 | 6 | # Build 7 | cmake --build build 8 | 9 | # Test with verbose output 10 | cd build && ctest -V -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(xnode_test xnode_test.cpp) 2 | target_link_libraries(xnode_test PRIVATE xnode) 3 | 4 | # Add test to CTest 5 | add_test(NAME xnode_test COMMAND xnode_test) 6 | 7 | # Add conversion tests 8 | add_executable(xnode_convert_test xnode_convert_test.cpp) 9 | target_link_libraries(xnode_convert_test PRIVATE xnode) 10 | 11 | # Add conversion test to CTest 12 | add_test(NAME xnode_convert_test COMMAND xnode_convert_test) 13 | 14 | # Add type tests 15 | add_executable(xnode_type_test xnode_type_test.cpp) 16 | target_link_libraries(xnode_type_test PRIVATE xnode) 17 | 18 | # Add test to CTest 19 | add_test(NAME xnode_type_test COMMAND xnode_type_test) 20 | 21 | # Add overflow tests 22 | add_executable(xnode_overflow_test xnode_overflow_test.cpp) 23 | target_link_libraries(xnode_overflow_test PRIVATE xnode) 24 | 25 | # Add overflow test to CTest 26 | add_test(NAME xnode_overflow_test COMMAND xnode_overflow_test) 27 | 28 | # Add xarray tests 29 | add_executable(xarray_test xarray_test.cpp) 30 | target_link_libraries(xarray_test PRIVATE xnode) 31 | 32 | # Add xarray test to CTest 33 | add_test(NAME xarray_test COMMAND xarray_test) 34 | 35 | # Add xarray of test 36 | add_executable(xarray_of_test xarray_of_test.cpp) 37 | target_link_libraries(xarray_of_test PRIVATE xnode) 38 | 39 | # Add xarray of test to CTest 40 | add_test(NAME xarray_of_test COMMAND xarray_of_test) 41 | 42 | # Add xarray of versions test 43 | add_executable(xarray_of_versions_test xarray_of_versions_test.cpp) 44 | target_link_libraries(xarray_of_versions_test PRIVATE xnode) 45 | 46 | # Add xarray of versions test to CTest 47 | add_test(NAME xarray_of_versions_test COMMAND xarray_of_versions_test) 48 | 49 | # Add xobject tests 50 | add_executable(xobject_test xobject_test.cpp) 51 | target_link_libraries(xobject_test PRIVATE xnode) 52 | 53 | # Add xobject test to CTest 54 | add_test(NAME xobject_test COMMAND xobject_test) 55 | 56 | # Add property_list tests 57 | add_executable(property_list_test property_list_test.cpp) 58 | target_link_libraries(property_list_test PRIVATE xnode) 59 | 60 | # Add property_list test to CTest 61 | add_test(NAME property_list_test COMMAND property_list_test) 62 | 63 | # Install the test executable if needed (optional) 64 | install(TARGETS xnode_test xnode_convert_test xnode_type_test xnode_overflow_test xarray_test xarray_of_test xarray_of_versions_test xobject_test property_list_test 65 | RUNTIME DESTINATION bin 66 | OPTIONAL 67 | ) -------------------------------------------------------------------------------- /test/cunit.h: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: cunit.h 3 | // Purpose: Unit testing utility functions 4 | // Author: Piotr Likus 5 | // Created: 01/09/2015 6 | // Last change: 12/09/2015 7 | // License: BSD 8 | //---------------------------------------------------------------------------------- 9 | 10 | #ifndef __CUNIT_H__ 11 | #define __CUNIT_H__ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | /// \file cunit.h 18 | /// Unit testing utility functions 19 | 20 | inline void Assert(bool assertion, const char *message = nullptr) 21 | { 22 | if (!assertion) { 23 | if (message != nullptr) 24 | throw std::runtime_error(std::string("assertion = [") + std::string(message) + "]"); 25 | else 26 | throw std::runtime_error("assertion failed"); 27 | } 28 | } 29 | 30 | inline void Assert(bool assertion, const std::string &message) 31 | { 32 | Assert(assertion, message.c_str()); 33 | } 34 | 35 | inline void AssertFalse(bool assertion, const char *message = nullptr) { 36 | Assert(!assertion, message); 37 | } 38 | 39 | inline void AssertFalse(bool assertion, const std::string &message) { 40 | Assert(!assertion, message); 41 | } 42 | 43 | inline void AssertTrue(bool assertion, const char *message = nullptr) { 44 | Assert(assertion, message); 45 | } 46 | 47 | inline void AssertTrue(bool assertion, const std::string &message) { 48 | Assert(assertion, message); 49 | } 50 | 51 | template 52 | inline void AssertEquals(T expected, U actual, const std::string &message) { 53 | if (!(expected == actual)) { 54 | std::ostringstream oss; 55 | oss << "assertion equals failed: [" << message << "] expected: [" << expected << "] actual: [" << actual << "]"; 56 | throw std::runtime_error(oss.str()); 57 | } 58 | } 59 | 60 | template 61 | inline void AssertEquals(T expected, U actual, const char *message = nullptr) { 62 | if (!(expected == actual)) { 63 | std::ostringstream oss; 64 | oss << "assertion equals failed: "; 65 | if (message != nullptr) 66 | oss << "[" << message << "] "; 67 | oss << "expected: [" << expected << "] actual: [" << actual << "]"; 68 | throw std::runtime_error(oss.str()); 69 | } 70 | } 71 | 72 | template 73 | inline void AssertThrows(Func assertion, const char *message = nullptr) 74 | { 75 | bool throwFound = false; 76 | try { 77 | assertion(); 78 | } 79 | catch (...) { 80 | throwFound = true; 81 | } 82 | 83 | if (!throwFound) { 84 | if (message != nullptr) 85 | throw std::runtime_error(std::string("assertion.throws didn't throw = [") + std::string(message) + "]"); 86 | else 87 | throw std::runtime_error("assertion.throws didn't throw"); 88 | } 89 | } 90 | 91 | template 92 | inline void AssertNoThrow(Func assertion, const char *message = nullptr) 93 | { 94 | bool throwFound = false; 95 | try { 96 | assertion(); 97 | } 98 | catch (...) { 99 | throwFound = true; 100 | } 101 | 102 | if (throwFound) { 103 | if (message != nullptr) 104 | throw std::runtime_error(std::string("assertion.throws = [") + std::string(message) + "]"); 105 | else 106 | throw std::runtime_error("assertion.throws failed"); 107 | } 108 | } 109 | 110 | template 111 | void AssertThrowsMethod(ObjectType obj, Func func, const char *message = nullptr) 112 | { 113 | bool throwFound = false; 114 | try { 115 | (obj.*func)(); 116 | } 117 | catch (...) { 118 | throwFound = true; 119 | } 120 | 121 | if (!throwFound) { 122 | if (message != nullptr) 123 | throw std::runtime_error(std::string("assertion.throws = [") + std::string(message) + "]"); 124 | else 125 | throw std::runtime_error("assertion.throws failed"); 126 | } 127 | } 128 | 129 | template 130 | void testFunc(const char *name, Func f, bool &failed) 131 | { 132 | std::cout << "Starting test [" << std::string(name) << "] " << std::endl; 133 | try { 134 | f(); 135 | std::cout << "OK: Test [" << std::string(name) << "] " << "succeeded\n"; 136 | } 137 | catch (std::exception &e) { 138 | std::cout << "Error: Test [" << std::string(name) << "] failed!, error: " << e.what() << "\n"; 139 | failed = true; 140 | } 141 | } 142 | 143 | #define TEST_FUNC(a) testFunc(#a, Test##a, errorFound) 144 | 145 | #define TEST_PROLOG() bool errorFound = false 146 | 147 | #define TEST_EPILOG() do { if (errorFound) { \ 148 | cout << "Failures!\n"; \ 149 | return EXIT_FAILURE; \ 150 | } \ 151 | else { \ 152 | cout << "Success!\n"; \ 153 | return EXIT_SUCCESS; \ 154 | } } while(0) 155 | 156 | #endif 157 | -------------------------------------------------------------------------------- /test/property_list_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: property_list_test.cpp 3 | // Purpose: Unit tests for property_list class 4 | // Author: Piotr Likus 5 | // Created: 15/05/2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "../include/details/property_list.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "cunit.h" 16 | 17 | using namespace std; 18 | 19 | // Test keys iterators 20 | void TestKeysIterators() { 21 | property_list props; 22 | 23 | // Add some test data 24 | props.put("one", 1); 25 | props.put("two", 2); 26 | props.put("three", 3); 27 | 28 | // Test keys_begin() and keys_end() 29 | { 30 | std::vector keys; 31 | for (auto it = props.keys_begin(); it != props.keys_end(); ++it) { 32 | keys.push_back(*it); 33 | } 34 | 35 | // Check iteration order matches insertion order 36 | Assert(keys.size() == 3, "Should iterate over 3 keys"); 37 | Assert(keys[0] == "one", "First key should be 'one'"); 38 | Assert(keys[1] == "two", "Second key should be 'two'"); 39 | Assert(keys[2] == "three", "Third key should be 'three'"); 40 | } 41 | 42 | // Test for-range loop with begin/end 43 | { 44 | std::vector keys; 45 | for (const auto& key : props.get_keys()) { 46 | keys.push_back(key); 47 | } 48 | 49 | Assert(keys.size() == 3, "Range-based for should iterate over 3 keys"); 50 | Assert(keys[0] == "one", "First key should be 'one'"); 51 | Assert(keys[1] == "two", "Second key should be 'two'"); 52 | Assert(keys[2] == "three", "Third key should be 'three'"); 53 | } 54 | } 55 | 56 | // Test const keys iterators 57 | void TestConstKeysIterators() { 58 | property_list props; 59 | 60 | // Add some test data 61 | props.put("one", 1); 62 | props.put("two", 2); 63 | props.put("three", 3); 64 | 65 | // Test with const object 66 | const property_list& const_props = props; 67 | 68 | // Test keys_cbegin() and keys_cend() 69 | { 70 | std::vector keys; 71 | for (auto it = const_props.keys_cbegin(); it != const_props.keys_cend(); ++it) { 72 | keys.push_back(*it); 73 | } 74 | 75 | // Check iteration order matches insertion order 76 | Assert(keys.size() == 3, "Should iterate over 3 keys with const iterators"); 77 | Assert(keys[0] == "one", "First key should be 'one'"); 78 | Assert(keys[1] == "two", "Second key should be 'two'"); 79 | Assert(keys[2] == "three", "Third key should be 'three'"); 80 | } 81 | } 82 | 83 | // Test values iterators 84 | void TestValuesIterators() { 85 | property_list props; 86 | 87 | // Add some test data 88 | props.put("one", 1); 89 | props.put("two", 2); 90 | props.put("three", 3); 91 | 92 | // Test values_begin() and values_end() 93 | { 94 | int sum = 0; 95 | for (auto it = props.values_begin(); it != props.values_end(); ++it) { 96 | sum += it->second; 97 | } 98 | 99 | // Check sum of values 100 | Assert(sum == 6, "Sum of values should be 6"); 101 | } 102 | 103 | // Test finding a specific value using values iterator 104 | { 105 | bool found_two = false; 106 | for (auto it = props.values_begin(); it != props.values_end(); ++it) { 107 | if (it->first == "two" && it->second == 2) { 108 | found_two = true; 109 | break; 110 | } 111 | } 112 | 113 | Assert(found_two, "Should find value 2 for key 'two'"); 114 | } 115 | } 116 | 117 | // Test const values iterators 118 | void TestConstValuesIterators() { 119 | property_list props; 120 | 121 | // Add some test data 122 | props.put("one", 1); 123 | props.put("two", 2); 124 | props.put("three", 3); 125 | 126 | // Test with const object 127 | const property_list& const_props = props; 128 | 129 | // Test values_cbegin() and values_cend() 130 | { 131 | int sum = 0; 132 | for (auto it = const_props.values_cbegin(); it != const_props.values_cend(); ++it) { 133 | sum += it->second; 134 | } 135 | 136 | // Check sum of values 137 | Assert(sum == 6, "Sum of values with const iterators should be 6"); 138 | } 139 | } 140 | 141 | // Test key iterators with reorganization 142 | void TestKeysIteratorsWithReorg() { 143 | property_list props; 144 | 145 | // Add some test data 146 | props.put("one", 1); 147 | props.put("two", 2); 148 | props.put("three", 3); 149 | 150 | // Remove an item to mark keys as dirty 151 | props.remove("two"); 152 | 153 | // Test keys_begin() and keys_end() after removal 154 | // This should trigger reorg 155 | { 156 | std::vector keys; 157 | for (auto it = props.keys_begin(); it != props.keys_end(); ++it) { 158 | keys.push_back(*it); 159 | } 160 | 161 | // Check iteration order matches insertion order minus removed item 162 | Assert(keys.size() == 2, "Should iterate over 2 keys after removal"); 163 | Assert(keys[0] == "one", "First key should be 'one'"); 164 | Assert(keys[1] == "three", "Second key should be 'three'"); 165 | 166 | // Verify dirty_keys_ was reset by checking needs_reorg() 167 | Assert(!props.needs_reorg(), "Should not need reorg after iterator use"); 168 | } 169 | } 170 | 171 | // Test iterator modification 172 | void TestKeyIteratorModification() { 173 | // This is just a compile-time test to ensure key iterators work with standard algorithms 174 | property_list props; 175 | props.put("one", 1); 176 | props.put("two", 2); 177 | 178 | // Test with standard algorithm that requires modifiable iterators 179 | std::vector keys(props.keys_begin(), props.keys_end()); 180 | std::sort(keys.begin(), keys.end()); 181 | 182 | Assert(keys.size() == 2, "Should copy 2 keys to vector"); 183 | } 184 | 185 | // Test static of() methods 186 | void TestStaticOfMethods() { 187 | // Test of() with 1 pair 188 | { 189 | auto props = property_list::of("one", 1); 190 | Assert(props.size() == 1, "Should create list with 1 item"); 191 | Assert(props.get("one") == 1, "Should have correct value for key 'one'"); 192 | } 193 | 194 | // Test of() with 2 pairs 195 | { 196 | auto props = property_list::of("one", 1, "two", 2); 197 | Assert(props.size() == 2, "Should create list with 2 items"); 198 | Assert(props.get("one") == 1, "Should have correct value for key 'one'"); 199 | Assert(props.get("two") == 2, "Should have correct value for key 'two'"); 200 | 201 | // Check insertion order 202 | auto keys = props.get_keys(); 203 | Assert(keys[0] == "one", "First key should be 'one'"); 204 | Assert(keys[1] == "two", "Second key should be 'two'"); 205 | } 206 | 207 | // Test of() with 3 pairs 208 | { 209 | auto props = property_list::of("one", 1, "two", 2, "three", 3); 210 | Assert(props.size() == 3, "Should create list with 3 items"); 211 | Assert(props.get("one") == 1, "Should have correct value for key 'one'"); 212 | Assert(props.get("two") == 2, "Should have correct value for key 'two'"); 213 | Assert(props.get("three") == 3, "Should have correct value for key 'three'"); 214 | 215 | // Check insertion order 216 | auto keys = props.get_keys(); 217 | Assert(keys[0] == "one", "First key should be 'one'"); 218 | Assert(keys[1] == "two", "Second key should be 'two'"); 219 | Assert(keys[2] == "three", "Third key should be 'three'"); 220 | } 221 | 222 | // Test of() with 5 pairs 223 | { 224 | auto props = property_list::of( 225 | "one", 1, "two", 2, "three", 3, "four", 4, "five", 5 226 | ); 227 | Assert(props.size() == 5, "Should create list with 5 items"); 228 | Assert(props.get("one") == 1, "Should have correct value for key 'one'"); 229 | Assert(props.get("five") == 5, "Should have correct value for key 'five'"); 230 | 231 | // Check insertion order preserved 232 | auto keys = props.get_keys(); 233 | Assert(keys[0] == "one", "First key should be 'one'"); 234 | Assert(keys[4] == "five", "Fifth key should be 'five'"); 235 | } 236 | 237 | // Test of() with 10 pairs 238 | { 239 | auto props = property_list::of( 240 | "one", 1, "two", 2, "three", 3, "four", 4, "five", 5, 241 | "six", 6, "seven", 7, "eight", 8, "nine", 9, "ten", 10 242 | ); 243 | Assert(props.size() == 10, "Should create list with 10 items"); 244 | Assert(props.get("one") == 1, "Should have correct value for key 'one'"); 245 | Assert(props.get("ten") == 10, "Should have correct value for key 'ten'"); 246 | 247 | // Check insertion order preserved 248 | auto keys = props.get_keys(); 249 | Assert(keys[0] == "one", "First key should be 'one'"); 250 | Assert(keys[9] == "ten", "Tenth key should be 'ten'"); 251 | } 252 | 253 | // Test overwriting keys 254 | { 255 | auto props = property_list::of( 256 | "one", 1, "two", 2, "one", 3 257 | ); 258 | Assert(props.size() == 2, "Should have 2 items when key is repeated"); 259 | Assert(props.get("one") == 3, "Should have last value for repeated key"); 260 | } 261 | } 262 | 263 | // Main test runner 264 | int main() { 265 | try { 266 | std::cout << "Running property_list iterator tests..." << std::endl; 267 | 268 | TestKeysIterators(); 269 | TestConstKeysIterators(); 270 | TestValuesIterators(); 271 | TestConstValuesIterators(); 272 | TestKeysIteratorsWithReorg(); 273 | TestKeyIteratorModification(); 274 | TestStaticOfMethods(); 275 | 276 | std::cout << "All tests passed!" << std::endl; 277 | return 0; 278 | } 279 | catch (const std::exception &ex) { 280 | std::cerr << "Exception: " << ex.what() << std::endl; 281 | return 1; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /test/xarray_of_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xarray_of_test.cpp 3 | // Purpose: Unit tests for xarray of() static initializer 4 | // Author: Piotr Likus 5 | // Created: May 15, 2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "xnode.h" 10 | #include "xarray.h" 11 | #include 12 | #include 13 | #include "cunit.h" 14 | #include "xobject.h" 15 | 16 | using namespace std; 17 | 18 | void TestArrayOf() { 19 | // Test the static "of" initializer with different numbers of arguments 20 | 21 | // Empty array 22 | xarray empty = xarray::of_nodes(); 23 | Assert(empty.empty(), "Empty array should be empty"); 24 | Assert(empty.size() == 0, "Empty array size should be 0"); 25 | 26 | // Single element (using appropriate version based on C++ standard) 27 | #if __cplusplus >= 201703L 28 | // C++17: Use direct value 29 | xarray single = xarray::of(42); 30 | #else 31 | // Pre-C++17: Use xnode objects 32 | xarray single = xarray::of(xnode::value_of(42)); 33 | #endif 34 | Assert(single.size() == 1, "Single element array size should be 1"); 35 | Assert(single[0].get_as() == 42, "Single element should be 42"); 36 | 37 | // Multiple elements of the same type (using of_nodes which works on all standards) 38 | xarray multi = xarray::of_nodes( 39 | xnode::value_of(10), 40 | xnode::value_of(20), 41 | xnode::value_of(30) 42 | ); 43 | Assert(multi.size() == 3, "Multi-element array size should be 3"); 44 | Assert(multi[0].get_as() == 10, "First element should be 10"); 45 | Assert(multi[1].get_as() == 20, "Second element should be 20"); 46 | Assert(multi[2].get_as() == 30, "Third element should be 30"); 47 | 48 | // Different types 49 | #if __cplusplus >= 201703L 50 | // C++17: Use direct values 51 | xarray mixed = xarray::of(10, "test", true, 3.14); 52 | #else 53 | // Pre-C++17: Use xnode objects 54 | xarray mixed = xarray::of( 55 | xnode::value_of(10), 56 | xnode::value_of("test"), 57 | xnode::value_of(true), 58 | xnode::value_of(3.14) 59 | ); 60 | #endif 61 | Assert(mixed.size() == 4, "Mixed type array size should be 4"); 62 | Assert(mixed[0].is(), "First element should be int"); 63 | Assert(mixed[1].is(), "Second element should be string"); 64 | Assert(mixed[2].is(), "Third element should be bool"); 65 | Assert(mixed[3].is(), "Fourth element should be double"); 66 | } 67 | 68 | void TestArrayOfWithNestedStructures() { 69 | // Create an object 70 | xobject person; 71 | person.put("name", xnode::value_of("John Doe")); 72 | person.put("age", xnode::value_of(30)); 73 | 74 | // Create a nested array 75 | xarray hobbies = xarray::of( 76 | xnode::value_of("reading"), 77 | xnode::value_of("coding"), 78 | xnode::value_of("hiking") 79 | ); 80 | 81 | // Create main array with nested structures 82 | xarray data = xarray::of( 83 | xnode::value_of(person), 84 | xnode::value_of(hobbies), 85 | xnode::value_of(42) 86 | ); 87 | 88 | // Verify the structure 89 | Assert(data.size() == 3, "Main array size should be 3"); 90 | Assert(data[0].is(), "First element should be xobject"); 91 | Assert(data[1].is(), "Second element should be xarray"); 92 | Assert(data[2].is(), "Third element should be int"); 93 | 94 | // Verify nested object 95 | Assert(data[0].get_ptr()->contains("name"), "Person object should contain name"); 96 | Assert(data[0].get_ptr()->get("name").get_as() == "John Doe", 97 | "Person name should be John Doe"); 98 | 99 | // Verify nested array 100 | Assert(data[1].get_ptr()->size() == 3, "Hobbies array should have 3 elements"); 101 | Assert(data[1].get_ptr()->at(0).get_as() == "reading", 102 | "First hobby should be reading"); 103 | } 104 | 105 | void TestArrayOfIterations() { 106 | // Test iteration over array created with "of" 107 | xarray numbers = xarray::of( 108 | xnode::value_of(10), 109 | xnode::value_of(20), 110 | xnode::value_of(30), 111 | xnode::value_of(40) 112 | ); 113 | 114 | // Range-based for loop 115 | int sum = 0; 116 | for (const auto& item : numbers) { 117 | sum += item.get_as(); 118 | } 119 | Assert(sum == 100, "Sum of elements should be 100"); 120 | 121 | // STL algorithms with array created by "of" 122 | int product = 1; 123 | for (auto it = numbers.begin(); it != numbers.end(); ++it) { 124 | product *= it->get_as(); 125 | } 126 | Assert(product == 240000, "Product of elements should be 240000"); 127 | } 128 | 129 | int xarray_of_test() { 130 | TEST_PROLOG(); 131 | TEST_FUNC(ArrayOf); 132 | TEST_FUNC(ArrayOfWithNestedStructures); 133 | TEST_FUNC(ArrayOfIterations); 134 | TEST_EPILOG(); 135 | } 136 | 137 | int main() { 138 | return xarray_of_test(); 139 | } 140 | -------------------------------------------------------------------------------- /test/xarray_of_versions_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xarray_of_versions_test.cpp 3 | // Purpose: Unit tests for xarray of() and of_nodes() static initializers 4 | // Author: Piotr Likus 5 | // Created: May 15, 2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "xnode.h" 10 | #include "xarray.h" 11 | #include 12 | #include 13 | #include "cunit.h" 14 | #include "xobject.h" 15 | 16 | using namespace std; 17 | 18 | #if __cplusplus >= 201703L 19 | void TestArrayOfWithAutomaticConversion() { 20 | // Test the C++17 "of" initializer with automatic value conversion 21 | 22 | // Basic types 23 | xarray numbers = xarray::of(10, 20, 30); 24 | Assert(numbers.size() == 3, "Array size should be 3"); 25 | Assert(numbers[0].get_as() == 10, "First element should be 10"); 26 | Assert(numbers[1].get_as() == 20, "Second element should be 20"); 27 | Assert(numbers[2].get_as() == 30, "Third element should be 30"); 28 | 29 | // Mixed types 30 | xarray mixed = xarray::of(42, "hello", 3.14, true); 31 | Assert(mixed.size() == 4, "Mixed array size should be 4"); 32 | Assert(mixed[0].get_as() == 42, "First element should be 42"); 33 | Assert(mixed[1].get_as() == "hello", "Second element should be 'hello'"); 34 | Assert(abs(mixed[2].get_as() - 3.14) < 0.0001, "Third element should be 3.14"); 35 | Assert(mixed[3].get_as() == true, "Fourth element should be true"); 36 | } 37 | #endif 38 | 39 | void TestArrayOfNodesVersions() { 40 | // Test the "of_nodes" initializer (compatible with all C++ standards) 41 | 42 | // Empty array 43 | xarray empty = xarray::of_nodes(); 44 | Assert(empty.empty(), "Empty array should be empty"); 45 | 46 | // Single element 47 | xarray single = xarray::of_nodes(xnode::value_of(100)); 48 | Assert(single.size() == 1, "Single element array size should be 1"); 49 | Assert(single[0].get_as() == 100, "Element should be 100"); 50 | 51 | // Multiple elements 52 | xarray multi = xarray::of_nodes( 53 | xnode::value_of(1), 54 | xnode::value_of(2), 55 | xnode::value_of(3) 56 | ); 57 | Assert(multi.size() == 3, "Multi-element array size should be 3"); 58 | Assert(multi[0].get_as() == 1, "First element should be 1"); 59 | Assert(multi[1].get_as() == 2, "Second element should be 2"); 60 | Assert(multi[2].get_as() == 3, "Third element should be 3"); 61 | 62 | // Mixed types 63 | xarray mixed = xarray::of_nodes( 64 | xnode::value_of(123), 65 | xnode::value_of("string value"), 66 | xnode::value_of(4.56), 67 | xnode::value_of(false) 68 | ); 69 | Assert(mixed.size() == 4, "Mixed type array size should be 4"); 70 | Assert(mixed[0].is(), "First element should be int"); 71 | Assert(mixed[1].is(), "Second element should be string"); 72 | Assert(mixed[2].is(), "Third element should be double"); 73 | Assert(mixed[3].is(), "Fourth element should be bool"); 74 | } 75 | 76 | void TestArrayOfCompatibility() { 77 | // Test the compatibility between different of() implementations 78 | 79 | // Create arrays using both methods 80 | #if __cplusplus >= 201703L 81 | xarray direct = xarray::of(10, 20, 30); 82 | #else 83 | xarray direct = xarray::of( 84 | xnode::value_of(10), 85 | xnode::value_of(20), 86 | xnode::value_of(30) 87 | ); 88 | #endif 89 | 90 | xarray nodes = xarray::of_nodes( 91 | xnode::value_of(10), 92 | xnode::value_of(20), 93 | xnode::value_of(30) 94 | ); 95 | 96 | // Verify they're the same 97 | Assert(direct.size() == nodes.size(), "Both arrays should have the same size"); 98 | 99 | for (size_t i = 0; i < direct.size(); ++i) { 100 | Assert(direct[i].get_as() == nodes[i].get_as(), 101 | "Values should match between arrays"); 102 | } 103 | 104 | // Test equality operators 105 | Assert(direct == nodes, "Arrays should be equal"); 106 | } 107 | 108 | int xarray_of_versions_test() { 109 | TEST_PROLOG(); 110 | #if __cplusplus >= 201703L 111 | TEST_FUNC(ArrayOfWithAutomaticConversion); 112 | #endif 113 | TEST_FUNC(ArrayOfNodesVersions); 114 | TEST_FUNC(ArrayOfCompatibility); 115 | TEST_EPILOG(); 116 | } 117 | 118 | int main() { 119 | return xarray_of_versions_test(); 120 | } 121 | -------------------------------------------------------------------------------- /test/xarray_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xarray_test.cpp 3 | // Purpose: Unit tests for xarray functionality 4 | // Author: Piotr Likus 5 | // Created: May 13, 2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "xnode.h" 10 | #include "xarray.h" 11 | #include 12 | 13 | // for std::accumulate 14 | #include 15 | 16 | // for std::sort, std::is_sorted, std::find_if, std::transform, std::copy_if 17 | #include 18 | 19 | // for std::back_inserter 20 | #include 21 | 22 | // for std::to_string 23 | #include 24 | 25 | #include "cunit.h" 26 | #include "xobject.h" 27 | 28 | using namespace std; 29 | 30 | void TestArraySum() { 31 | // Create array using the new of() static initializer 32 | xarray v = xarray::of( 33 | xnode::value_of(1), 34 | xnode::value_of(8), 35 | xnode::value_of(2), 36 | xnode::value_of(3), 37 | xnode::value_of(4) 38 | ); 39 | 40 | // Sum as integers using std::accumulate with a lambda that extracts int values 41 | int intSum = std::accumulate(v.begin(), v.end(), 0, 42 | [](int acc, const xnode& x) { return acc + x.get_as(); }); 43 | Assert(intSum == 18); 44 | 45 | // Sum as longs using std::accumulate with a lambda that extracts long values 46 | long longSum = std::accumulate(v.begin(), v.end(), 0L, 47 | [](long acc, const xnode& x) { return acc + x.get_as(); }); 48 | Assert(longSum == 18L); 49 | } 50 | 51 | void TestArraySort() { 52 | xarray v = xarray::of( 53 | xnode::value_of(1), 54 | xnode::value_of(18), 55 | xnode::value_of(128), 56 | xnode::value_of(3), 57 | xnode::value_of(23) 58 | ); 59 | 60 | std::sort(v.begin(), v.end()); 61 | Assert(std::is_sorted(v.begin(), v.end())); 62 | } 63 | 64 | void TestArrayFind() { 65 | // Demonstrate finding elements in an xarray 66 | xarray v; 67 | v.push_back(xnode::value_of(10)); 68 | v.push_back(xnode::value_of(20)); 69 | v.push_back(xnode::value_of(30)); 70 | v.push_back(xnode::value_of(40)); 71 | v.push_back(xnode::value_of("test string")); 72 | v.push_back(xnode::value_of(true)); 73 | 74 | // Find using find_if with a lambda - finding a specific int value 75 | auto it = std::find_if(v.begin(), v.end(), [](const xnode& n) { 76 | return n.is() && n.get_as() == 30; 77 | }); 78 | Assert(it != v.end(), "Failed to find element with value 30"); 79 | Assert(it->get_as() == 30, "Found element has incorrect value"); 80 | 81 | // Find using find_if with a lambda - finding a string 82 | auto strIt = std::find_if(v.begin(), v.end(), [](const xnode& n) { 83 | return n.is() && n.get_as() == "test string"; 84 | }); 85 | Assert(strIt != v.end(), "Failed to find element with string value"); 86 | Assert(strIt->get_as() == "test string", "Found string element has incorrect value"); 87 | 88 | // Count elements of a specific type 89 | int intCount = std::count_if(v.begin(), v.end(), [](const xnode& n) { return n.is(); }); 90 | Assert(intCount == 4, "Incorrect count of int elements"); 91 | } 92 | 93 | void TestArrayTransform() { 94 | // Demonstrate transforming an xarray 95 | xarray source; 96 | source.push_back(xnode::value_of(1)); 97 | source.push_back(xnode::value_of(2)); 98 | source.push_back(xnode::value_of(3)); 99 | source.push_back(xnode::value_of(4)); 100 | source.push_back(xnode::value_of(5)); 101 | 102 | // Transform to a new array with doubled values 103 | xarray result; 104 | result.reserve(source.size()); 105 | 106 | std::transform(source.begin(), source.end(), std::back_inserter(result), 107 | [](const xnode& n) { return xnode::value_of(n.get_as() * 2); }); 108 | 109 | // Verify transformation 110 | Assert(result.size() == source.size(), "Result size should match source size"); 111 | for (size_t i = 0; i < source.size(); ++i) { 112 | Assert(result[i].get_as() == source[i].get_as() * 2, 113 | "Transformed value should be double the original"); 114 | } 115 | } 116 | 117 | void TestArrayMixedTypes() { 118 | // Demonstrate storing and processing mixed data types in an xarray 119 | xarray v; 120 | 121 | // Add different types 122 | v.push_back(xnode::value_of(42)); // int 123 | v.push_back(xnode::value_of(3.14159)); // double 124 | v.push_back(xnode::value_of("Hello, world!")); // string 125 | v.push_back(xnode::value_of(true)); // bool 126 | 127 | // Create a long value explicitly to avoid ambiguity 128 | xnode longNode; 129 | long longValue = 1000000L; 130 | longNode.set_as(longValue); 131 | v.push_back(longNode); // long 132 | 133 | // Check types and values 134 | Assert(v[0].is(), "Element 0 should be an int"); 135 | Assert(v[1].is(), "Element 1 should be a double"); 136 | Assert(v[2].is(), "Element 2 should be a string"); 137 | Assert(v[3].is(), "Element 3 should be a bool"); 138 | Assert(v[4].is(), "Element 4 should be a long"); 139 | 140 | // Create a string representation of each element 141 | std::vector stringReps; 142 | for (const auto& node : v) { 143 | if (node.is()) { 144 | stringReps.push_back(node.get_as()); 145 | } else if (node.is()) { 146 | stringReps.push_back("Int: " + std::to_string(node.get_as())); 147 | } else if (node.is()) { 148 | stringReps.push_back("Double: " + std::to_string(node.get_as())); 149 | } else if (node.is()) { 150 | stringReps.push_back(node.get_as() ? "Bool: true" : "Bool: false"); 151 | } else if (node.is()) { 152 | stringReps.push_back("Long: " + std::to_string(node.get_as())); 153 | } 154 | } 155 | 156 | // Verify string representations 157 | Assert(stringReps.size() == 5, "Should have 5 string representations"); 158 | Assert(stringReps[0] == "Int: 42", "First string rep should be 'Int: 42'"); 159 | Assert(stringReps[3] == "Bool: true", "Fourth string rep should be 'Bool: true'"); 160 | } 161 | 162 | void TestArrayFilter() { 163 | // Demonstrate filtering an array 164 | xarray original; 165 | for (int i = 1; i <= 10; ++i) { 166 | original.push_back(xnode::value_of(i)); 167 | } 168 | 169 | // Create a filtered copy with only even numbers 170 | xarray evens; 171 | std::copy_if(original.begin(), original.end(), std::back_inserter(evens), 172 | [](const xnode& n) { return n.get_as() % 2 == 0; }); 173 | 174 | // Verify filter results 175 | Assert(evens.size() == 5, "Filtered array should have 5 elements"); 176 | for (const auto& n : evens) { 177 | Assert(n.get_as() % 2 == 0, "All elements should be even"); 178 | } 179 | } 180 | 181 | void TestArrayNestedStructures() { 182 | // Demonstrate an xarray containing other containers (objects and arrays) 183 | xarray root; 184 | 185 | // Add primitive types 186 | root.push_back(xnode::value_of(1)); 187 | root.push_back(xnode::value_of("root level string")); 188 | 189 | // Create and add an object 190 | xobject obj; 191 | obj.put("name", xnode::value_of(std::string("test object"))); 192 | obj.put("value", xnode::value_of(42)); 193 | root.push_back(xnode::value_of(obj)); 194 | 195 | // Create and add a nested array 196 | xarray nested; 197 | nested.push_back(xnode::value_of(10)); 198 | nested.push_back(xnode::value_of(20)); 199 | nested.push_back(xnode::value_of("nested string")); 200 | root.push_back(xnode::value_of(nested)); 201 | 202 | // Verify root structure 203 | Assert(root.size() == 4, "Root array should have 4 elements"); 204 | Assert(root[0].is(), "First element should be int"); 205 | Assert(root[1].is(), "Second element should be string"); 206 | Assert(root[2].is(), "Third element should be xobject"); 207 | Assert(root[3].is(), "Fourth element should be xarray"); 208 | 209 | // Verify object content 210 | Assert(root[2].get_ptr()->contains("name"), "Object should contain 'name' property"); 211 | Assert(root[2].get_ptr()->get("value").get_as() == 42, "Object 'value' should be 42"); 212 | 213 | // Verify nested array content 214 | Assert(root[3].get_ptr()->size() == 3, "Nested array should have 3 elements"); 215 | Assert(root[3].get_ptr()->at(2).get_as() == "nested string", 216 | "Nested array string element has wrong value"); 217 | } 218 | 219 | void TestArrayCopy() { 220 | // Demonstrate copying and comparing arrays 221 | xarray original; 222 | original.reserve(3); 223 | original.push_back(xnode::value_of(1)); 224 | original.push_back(xnode::value_of(2)); 225 | original.push_back(xnode::value_of(3)); 226 | 227 | // Test copying using vector's copy constructor 228 | xarray copy(original); 229 | 230 | // Verify the copy 231 | Assert(copy.size() == original.size(), "Copy size should match original"); 232 | for (size_t i = 0; i < original.size(); ++i) { 233 | Assert(copy[i].get_as() == original[i].get_as(), 234 | "Copy values should match original"); 235 | } 236 | 237 | // Modify the copy and ensure original is unchanged 238 | copy[0].set_as(100); 239 | Assert(original[0].get_as() == 1, "Original should be unchanged"); 240 | Assert(copy[0].get_as() == 100, "Copy should be modified"); 241 | } 242 | 243 | void TestArrayIteration() { 244 | // Demonstrate various ways to iterate through an xarray 245 | xarray v = xarray::of( 246 | xnode::value_of(10), 247 | xnode::value_of(20), 248 | xnode::value_of(30) 249 | ); 250 | 251 | // Method 1: Index-based for loop 252 | int sum1 = 0; 253 | for (size_t i = 0; i < v.size(); ++i) { 254 | sum1 += v[i].get_as(); 255 | } 256 | Assert(sum1 == 60, "Index-based sum should be 60"); 257 | 258 | // Method 2: Iterator-based for loop 259 | int sum2 = 0; 260 | for (xarray::iterator it = v.begin(); it != v.end(); ++it) { 261 | sum2 += it->get_as(); 262 | } 263 | Assert(sum2 == 60, "Iterator-based sum should be 60"); 264 | 265 | // Method 3: Range-based for loop (C++11) 266 | int sum3 = 0; 267 | for (const auto& node : v) { 268 | sum3 += node.get_as(); 269 | } 270 | Assert(sum3 == 60, "Range-based sum should be 60"); 271 | 272 | // Method 4: Use STL algorithm 273 | int sum4 = 0; 274 | std::for_each(v.begin(), v.end(), [&sum4](const xnode& node) { 275 | sum4 += node.get_as(); 276 | }); 277 | Assert(sum4 == 60, "STL algorithm sum should be 60"); 278 | } 279 | 280 | void TestArrayManipulation() { 281 | // Demonstrate various vector manipulation operations on xarray 282 | xarray v; 283 | 284 | // Test push_back 285 | v.push_back(xnode::value_of(1)); 286 | v.push_back(xnode::value_of(2)); 287 | Assert(v.size() == 2, "Size should be 2 after push_back"); 288 | 289 | // Test insert 290 | v.insert(v.begin() + 1, xnode::value_of(3)); 291 | Assert(v.size() == 3, "Size should be 3 after insert"); 292 | Assert(v[1].get_as() == 3, "Inserted value should be 3"); 293 | 294 | // Test erase 295 | v.erase(v.begin()); 296 | Assert(v.size() == 2, "Size should be 2 after erase"); 297 | Assert(v[0].get_as() == 3, "First value should be 3 after erase"); 298 | 299 | // Test clear 300 | v.clear(); 301 | Assert(v.empty(), "Array should be empty after clear"); 302 | 303 | // Test resize 304 | v.resize(3, xnode::value_of(5)); 305 | Assert(v.size() == 3, "Size should be 3 after resize"); 306 | Assert(v[0].get_as() == 5, "Resized element should have default value"); 307 | } 308 | 309 | int xarray_test() { 310 | TEST_PROLOG(); 311 | TEST_FUNC(ArraySum); 312 | TEST_FUNC(ArraySort); 313 | TEST_FUNC(ArrayFind); 314 | TEST_FUNC(ArrayTransform); 315 | TEST_FUNC(ArrayMixedTypes); 316 | TEST_FUNC(ArrayFilter); 317 | TEST_FUNC(ArrayNestedStructures); 318 | TEST_FUNC(ArrayCopy); 319 | TEST_FUNC(ArrayIteration); 320 | TEST_FUNC(ArrayManipulation); 321 | TEST_EPILOG(); 322 | } 323 | 324 | int main() 325 | { 326 | return xarray_test(); 327 | } 328 | -------------------------------------------------------------------------------- /test/xnode_overflow_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode_overflow_test.cpp 3 | // Purpose: Tests for numeric overflow/underflow in type conversion for xnode 4 | // Author: GitHub Copilot 5 | // Created: 02/05/2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "xnode.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "cunit.h" 17 | #include "xarray.h" 18 | 19 | using namespace std; 20 | 21 | // Helper function to log the test being executed 22 | void LogOverflowTest(const std::string& fromType, const std::string& toType, const std::string& testType) { 23 | std::cout << "Testing " << testType << " conversion from " << std::left << std::setw(15) << fromType 24 | << " to " << std::setw(15) << toType << std::endl; 25 | } 26 | 27 | // Helper struct for string conversion tests 28 | template 29 | struct StringToTypeConverter { 30 | std::string value_; 31 | xnode &node_; 32 | 33 | StringToTypeConverter(const std::string &value, xnode &node): value_(value), node_(node) { 34 | node_.set_as(value_); 35 | } 36 | 37 | void operator()() { 38 | T result = node_.get_as(); 39 | std::cout << "Converted " << value_ << " to " << result << std::endl; // This shouldn't execute if overflow happens 40 | } 41 | }; 42 | 43 | // Helper struct for numeric conversion tests 44 | template 45 | struct NumericConverter { 46 | FromType value_; 47 | xnode &node_; 48 | 49 | NumericConverter(const FromType &value, xnode &node): value_(value), node_(node) { 50 | node_.set_as(value_); 51 | } 52 | 53 | void operator()() { 54 | ToType result = node_.get_as(); 55 | std::cout << "Converted " << value_ << " to " << result << std::endl; // This shouldn't execute if overflow happens 56 | } 57 | }; 58 | 59 | //---------------------------------------------------------------------- 60 | // String to numeric overflow tests 61 | //---------------------------------------------------------------------- 62 | 63 | void TestStringToNumericOverflow() { 64 | xnode node; 65 | 66 | // Test string to int overflow 67 | LogOverflowTest("string", "int", "overflow"); 68 | std::string intMaxOverflow = "2147483648"; // INT_MAX + 1 (assuming 32-bit int) 69 | AssertThrows(StringToTypeConverter(intMaxOverflow, node), "String to int overflow"); 70 | 71 | // Test string to int underflow 72 | LogOverflowTest("string", "int", "underflow"); 73 | std::string intMinUnderflow = "-2147483649"; // INT_MIN - 1 (assuming 32-bit int) 74 | AssertThrows(StringToTypeConverter(intMinUnderflow, node), "String to int underflow"); 75 | 76 | // Test string to short overflow 77 | LogOverflowTest("string", "short", "overflow"); 78 | std::string shortMaxOverflow = "32768"; // SHRT_MAX + 1 79 | AssertThrows(StringToTypeConverter(shortMaxOverflow, node), "String to short overflow"); 80 | 81 | // Test string to short underflow 82 | LogOverflowTest("string", "short", "underflow"); 83 | std::string shortMinUnderflow = "-32769"; // SHRT_MIN - 1 84 | AssertThrows(StringToTypeConverter(shortMinUnderflow, node), "String to short underflow"); 85 | 86 | // Test string to char overflow 87 | LogOverflowTest("string", "char", "overflow"); 88 | std::string charMaxOverflow = "128"; // CHAR_MAX + 1 (assuming signed char) 89 | AssertThrows(StringToTypeConverter(charMaxOverflow, node), "String to char overflow"); 90 | 91 | // Test string to char underflow 92 | LogOverflowTest("string", "char", "underflow"); 93 | std::string charMinUnderflow = "-129"; // CHAR_MIN - 1 (assuming signed char) 94 | AssertThrows(StringToTypeConverter(charMinUnderflow, node), "String to char underflow"); 95 | 96 | // Test string to unsigned conversions (underflow) 97 | LogOverflowTest("string", "unsigned int", "underflow"); 98 | std::string uintUnderflow = "-1"; // Minimum unsigned value is 0 99 | AssertThrows(StringToTypeConverter(uintUnderflow, node), "String to unsigned int underflow"); 100 | 101 | LogOverflowTest("string", "unsigned short", "underflow"); 102 | std::string ushortUnderflow = "-1"; // Minimum unsigned value is 0 103 | AssertThrows(StringToTypeConverter(ushortUnderflow, node), "String to unsigned short underflow"); 104 | 105 | LogOverflowTest("string", "unsigned char", "underflow"); 106 | std::string ucharUnderflow = "-1"; // Minimum unsigned value is 0 107 | AssertThrows(StringToTypeConverter(ucharUnderflow, node), "String to unsigned char underflow"); 108 | 109 | // Test string to floating point special values 110 | LogOverflowTest("string", "float", "special"); 111 | std::string floatOverflow = "1.0e+39"; // Beyond float range 112 | AssertThrows(StringToTypeConverter(floatOverflow, node), "String to float overflow"); 113 | } 114 | 115 | //---------------------------------------------------------------------- 116 | // Integer type overflow tests 117 | //---------------------------------------------------------------------- 118 | 119 | void TestIntegerTypeOverflow() { 120 | xnode node; 121 | 122 | // Test int to short overflow 123 | LogOverflowTest("int", "short", "overflow"); 124 | int intToShortOverflow = std::numeric_limits::max() + 1; 125 | AssertThrows((NumericConverter(intToShortOverflow, node)), "Int to short overflow"); 126 | 127 | // Test int to short underflow 128 | LogOverflowTest("int", "short", "underflow"); 129 | int intToShortUnderflow = std::numeric_limits::min() - 1; 130 | AssertThrows((NumericConverter(intToShortUnderflow, node)), "Int to short underflow"); 131 | 132 | // Test int to char overflow 133 | LogOverflowTest("int", "char", "overflow"); 134 | int intToCharOverflow = std::numeric_limits::max() + 1; 135 | AssertThrows((NumericConverter(intToCharOverflow, node)), "Int to char overflow"); 136 | 137 | // Test int to char underflow 138 | LogOverflowTest("int", "char", "underflow"); 139 | int intToCharUnderflow = std::numeric_limits::min() - 1; 140 | AssertThrows((NumericConverter(intToCharUnderflow, node)), "Int to char underflow"); 141 | 142 | // Test int to unsigned int underflow 143 | LogOverflowTest("int", "unsigned int", "underflow"); 144 | int intToUintUnderflow = -1; 145 | AssertThrows((NumericConverter(intToUintUnderflow, node)), "Int to unsigned int underflow"); 146 | 147 | // Test long long to int overflow 148 | LogOverflowTest("long long", "int", "overflow"); 149 | long long llToIntOverflow = static_cast(std::numeric_limits::max()) + 1LL; 150 | AssertThrows((NumericConverter(llToIntOverflow, node)), "Long long to int overflow"); 151 | 152 | // Test long long to int underflow 153 | LogOverflowTest("long long", "int", "underflow"); 154 | long long llToIntUnderflow = static_cast(std::numeric_limits::min()) - 1LL; 155 | AssertThrows((NumericConverter(llToIntUnderflow, node)), "Long long to int underflow"); 156 | } 157 | 158 | //---------------------------------------------------------------------- 159 | // Floating point overflow tests 160 | //---------------------------------------------------------------------- 161 | 162 | void TestFloatingPointOverflow() { 163 | xnode node; 164 | 165 | // Test double to float overflow 166 | LogOverflowTest("double", "float", "overflow"); 167 | double doubleToFloatOverflow = static_cast(std::numeric_limits::max()) * 2.0; 168 | AssertThrows((NumericConverter(doubleToFloatOverflow, node)), "Double to float overflow"); 169 | 170 | // Test double to float underflow (smallest negative number beyond float range) 171 | LogOverflowTest("double", "float", "underflow"); 172 | double doubleToFloatUnderflow = -static_cast(std::numeric_limits::max()) * 2.0; 173 | AssertThrows((NumericConverter(doubleToFloatUnderflow, node)), "Double to float underflow"); 174 | 175 | // Test double to int overflow 176 | LogOverflowTest("double", "int", "overflow"); 177 | double doubleToIntOverflow = static_cast(std::numeric_limits::max()) + 1000.0; 178 | AssertThrows((NumericConverter(doubleToIntOverflow, node)), "Double to int overflow"); 179 | 180 | // Test double to int underflow 181 | LogOverflowTest("double", "int", "underflow"); 182 | double doubleToIntUnderflow = static_cast(std::numeric_limits::min()) - 1000.0; 183 | AssertThrows((NumericConverter(doubleToIntUnderflow, node)), "Double to int underflow"); 184 | 185 | // Test float to short overflow 186 | LogOverflowTest("float", "short", "overflow"); 187 | float floatToShortOverflow = static_cast(std::numeric_limits::max()) + 1000.0f; 188 | AssertThrows((NumericConverter(floatToShortOverflow, node)), "Float to short overflow"); 189 | 190 | // Test float to short underflow 191 | LogOverflowTest("float", "short", "underflow"); 192 | float floatToShortUnderflow = static_cast(std::numeric_limits::min()) - 1000.0f; 193 | AssertThrows((NumericConverter(floatToShortUnderflow, node)), "Float to short underflow"); 194 | } 195 | 196 | //---------------------------------------------------------------------- 197 | // Unsigned types overflow tests 198 | //---------------------------------------------------------------------- 199 | 200 | void TestUnsignedTypesOverflow() { 201 | xnode node; 202 | 203 | // Test unsigned int to short overflow 204 | LogOverflowTest("unsigned int", "short", "overflow"); 205 | unsigned int uintToShortOverflow = static_cast(std::numeric_limits::max()) + 1U; 206 | AssertThrows((NumericConverter(uintToShortOverflow, node)), "Unsigned int to short overflow"); 207 | 208 | // Test unsigned int to int overflow (when unsigned int max > int max) 209 | LogOverflowTest("unsigned int", "int", "overflow"); 210 | unsigned int uintToIntOverflow = static_cast(std::numeric_limits::max()) + 1U; 211 | AssertThrows((NumericConverter(uintToIntOverflow, node)), "Unsigned int to int overflow"); 212 | 213 | // Test unsigned long long to unsigned int overflow 214 | LogOverflowTest("unsigned long long", "unsigned int", "overflow"); 215 | unsigned long long ullToUintOverflow = static_cast(std::numeric_limits::max()) + 1ULL; 216 | AssertThrows((NumericConverter(ullToUintOverflow, node)), "Unsigned long long to unsigned int overflow"); 217 | 218 | // Test unsigned long to int overflow 219 | LogOverflowTest("unsigned long", "int", "overflow"); 220 | unsigned long ulToIntOverflow = static_cast(std::numeric_limits::max()) + 1UL; 221 | AssertThrows((NumericConverter(ulToIntOverflow, node)), "Unsigned long to int overflow"); 222 | } 223 | 224 | //---------------------------------------------------------------------- 225 | // Special case tests for numeric extremes 226 | //---------------------------------------------------------------------- 227 | 228 | void TestNumericExtremes() { 229 | xnode node; 230 | 231 | // Test max int to float (should be fine, but may lose precision) 232 | LogOverflowTest("int", "float", "max value"); 233 | int maxInt = std::numeric_limits::max(); 234 | node.set_as(maxInt); 235 | float maxIntAsFloat = node.get_as(); 236 | float maxIntAsFloatExpected = static_cast(maxInt); 237 | // This is not an overflow, but we should check precision 238 | cout << "Max int: " << maxInt << ", Max int as float: " << maxIntAsFloat << std::endl; 239 | Assert(abs(maxIntAsFloatExpected - (maxIntAsFloat)) < 1e5f, "Max int to float precision check"); 240 | 241 | // Test min int to float (should be fine, but may lose precision) 242 | LogOverflowTest("int", "float", "min value"); 243 | int minInt = std::numeric_limits::min(); 244 | node.set_as(minInt); 245 | float minIntAsFloat = node.get_as(); 246 | // This is not an overflow, but we should check precision 247 | AssertEquals(static_cast(minIntAsFloat), minInt, "Min int to float precision check"); 248 | 249 | // Test extreme unsigned values 250 | LogOverflowTest("unsigned int", "int", "max value"); 251 | unsigned int maxUint = std::numeric_limits::max(); 252 | AssertThrows((NumericConverter(maxUint, node)), "Max unsigned int to int overflow"); 253 | 254 | // Test string representation of extreme values 255 | LogOverflowTest("string", "long", "max value"); 256 | std::string maxLongStr = std::to_string(std::numeric_limits::max()); 257 | node.set_as(maxLongStr); 258 | AssertEquals(node.get_as(), std::numeric_limits::max(), "String max long to long"); 259 | 260 | // Create a string just beyond max long 261 | std::string beyondMaxLongStr = maxLongStr + "0"; // Adding a digit makes it exceed max long 262 | AssertThrows(StringToTypeConverter(beyondMaxLongStr, node), "String beyond max long to long"); 263 | } 264 | 265 | //---------------------------------------------------------------------- 266 | // Main test function 267 | //---------------------------------------------------------------------- 268 | 269 | int xnode_overflow_test() { 270 | TEST_PROLOG(); 271 | 272 | TEST_FUNC(StringToNumericOverflow); 273 | TEST_FUNC(IntegerTypeOverflow); 274 | TEST_FUNC(FloatingPointOverflow); 275 | TEST_FUNC(UnsignedTypesOverflow); 276 | TEST_FUNC(NumericExtremes); 277 | 278 | TEST_EPILOG(); 279 | } 280 | 281 | int main() 282 | { 283 | cout << "-------------------- XNODE OVERFLOW TESTS --------------------" << endl; 284 | return xnode_overflow_test(); 285 | } -------------------------------------------------------------------------------- /test/xnode_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode_test.cpp 3 | // Purpose: Unit tests for xnode library 4 | // Author: Piotr Likus 5 | // Created: 01/09/2015 6 | // Last change: 12/09/2015 7 | // License: BSD 8 | //---------------------------------------------------------------------------------- 9 | 10 | #include "xnode.h" 11 | #include 12 | 13 | // for std::accumulate 14 | #include 15 | 16 | // for std::sort 17 | #include 18 | 19 | #include "cunit.h" 20 | #include "xnode_long_double.h" 21 | 22 | using namespace std; 23 | 24 | void TestDefCntr() { 25 | xnode value; 26 | Assert(value.is_null()); 27 | } 28 | 29 | 30 | void TestCopyConstr() { 31 | xnode val1; 32 | const int testv = 123; 33 | val1.set_as(testv); 34 | xnode val2(val1); 35 | xnode val3 = val1; 36 | 37 | Assert(val1.get_as() == testv); 38 | Assert(val2.get_as() == testv); 39 | Assert(val3.get_as() == testv); 40 | } 41 | 42 | void TestAssignOper() { 43 | xnode val1; 44 | const int testv = 123; 45 | val1.set_as(testv); 46 | xnode val2; 47 | 48 | val2 = val1; 49 | 50 | Assert(val1.get_as() == testv); 51 | Assert(val2.get_as() == testv); 52 | } 53 | 54 | void TestDestructor() { 55 | struct TestStr { 56 | int &counterCnt_; 57 | 58 | TestStr(const TestStr &src) : counterCnt_(src.counterCnt_) {} 59 | TestStr(int &counterCnt) :counterCnt_(counterCnt) { } 60 | TestStr &operator=(const TestStr &src) { counterCnt_ = src.counterCnt_; return *this; } 61 | 62 | ~TestStr() { 63 | counterCnt_--; 64 | } 65 | }; 66 | 67 | int a = 0; 68 | TestStr b(a); 69 | 70 | xnode svalue; 71 | svalue.set_as(b); 72 | Assert(!svalue.is_null()); 73 | 74 | a = 3; 75 | svalue.reset(); 76 | Assert(svalue.is_null()); 77 | Assert(a == 2); 78 | } 79 | 80 | void TestSetAsInt() { 81 | xnode ivalue; 82 | ivalue.set_as(5); 83 | Assert(ivalue.type() == typeid(int)); 84 | } 85 | 86 | void TestGetAsInt() { 87 | xnode ivalue; 88 | ivalue.set_as(5); 89 | Assert(ivalue.get_as() == 5); 90 | } 91 | 92 | void TestTypeChangeOnSet() { 93 | xnode value; 94 | value.set_as(5); 95 | Assert(value.type() == typeid(int)); 96 | value.set_as(false); 97 | Assert(value.type() == typeid(bool)); 98 | } 99 | 100 | void TestReset() { 101 | xnode ivalue; 102 | ivalue.set_as(5); 103 | Assert(ivalue.type() == typeid(int)); 104 | ivalue.reset(); 105 | Assert(ivalue.is_null()); 106 | } 107 | 108 | void TestSetAsString() { 109 | xnode value; 110 | std::string s("test"); 111 | value.set_as(s); 112 | Assert(value.get_as() == s); 113 | } 114 | 115 | void TestConvIntToLong() { 116 | int a = 5; 117 | xnode ivalue; 118 | ivalue.set_as(a); 119 | Assert(ivalue.type() == typeid(int)); 120 | Assert(ivalue.get_as() == a); 121 | } 122 | 123 | void TestConvIntToString() { 124 | int a = 5; 125 | xnode ivalue; 126 | ivalue.set_as(a); 127 | Assert(ivalue.type() == typeid(int)); 128 | Assert(ivalue.get_as() == to_string(a)); 129 | } 130 | 131 | void TestConvStringToInt() { 132 | xnode ivalue; 133 | std::string s("123"); 134 | ivalue.set_as(s); 135 | Assert(ivalue.type() == typeid(std::string)); 136 | Assert(ivalue.get_as() == 123); 137 | } 138 | 139 | void TestSetValueInt() { 140 | xnode ivalue = xnode::value_of(12); 141 | Assert(ivalue.type() == typeid(int)); 142 | Assert(ivalue.is()); 143 | 144 | long b = 123; 145 | ivalue.set_value(b); 146 | Assert(ivalue.is()); 147 | Assert(ivalue.get_as() == 123); 148 | } 149 | 150 | void TestSetValueStr() { 151 | std::string s("test"); 152 | xnode value = xnode::value_of(s); 153 | Assert(value.type() == typeid(std::string)); 154 | Assert(value.is()); 155 | 156 | long b = 123; 157 | value.set_value(b); 158 | Assert(value.is()); 159 | Assert(value.get_as() == 123); 160 | Assert(value.get_as() == "123"); 161 | } 162 | 163 | void TestCharPtr() { 164 | std::string s("test"); 165 | 166 | xnode value; 167 | 168 | const char *cptr = s.c_str(); 169 | value.set_as(cptr); 170 | Assert(value.type() == typeid(const char *), "typeid"); 171 | Assert(value.get_as() == cptr, "text pointer"); 172 | } 173 | 174 | void TestCharLiteral() { 175 | xnode value = xnode::value_of("test"); 176 | 177 | Assert(value.is(), "typeid"); 178 | Assert(value.get_as() == "test", "get as string"); 179 | } 180 | 181 | void TestStringLiteralSetAs() { 182 | // Test setting string literals directly with set_as 183 | xnode value; 184 | 185 | // Test with direct char array (C-style string literal) 186 | value.set_as("Direct String Literal"); 187 | Assert(value.is(), "Direct string literal should be stored as std::string"); 188 | Assert(value.get_as() == "Direct String Literal", "Direct string literal should match expected value"); 189 | 190 | // Test empty char array 191 | value.set_as(""); 192 | Assert(value.is(), "Empty direct string literal should be stored as std::string"); 193 | Assert(value.get_as().empty(), "Empty direct string literal should result in empty string"); 194 | 195 | // Test with special characters in char array 196 | value.set_as("Special\tChars\nTest"); 197 | Assert(value.is(), "Direct string literal with special chars should be stored as std::string"); 198 | Assert(value.get_as() == "Special\tChars\nTest", "Special chars should be preserved"); 199 | 200 | // Test with numeric char array 201 | value.set_as("98765"); 202 | Assert(value.is(), "Numeric char array should be stored as std::string"); 203 | Assert(value.get_as() == 98765, "Numeric char array should convert to correct integer"); 204 | } 205 | 206 | void TestStringLiteralValueOf() { 207 | // Test creation with value_of for string literals 208 | 209 | // Basic string literal 210 | xnode value1 = xnode::value_of("Test String"); 211 | Assert(value1.is(), "String literal should be stored as std::string"); 212 | Assert(value1.get_as() == "Test String", "String should match the literal"); 213 | 214 | // Longer string literal with special characters 215 | xnode value2 = xnode::value_of("Line 1\nLine 2\tTabbed"); 216 | Assert(value2.is(), "String with escape chars should be stored as std::string"); 217 | Assert(value2.get_as() == "Line 1\nLine 2\tTabbed", "String should preserve escape chars"); 218 | 219 | // Unicode characters in string literal 220 | xnode value3 = xnode::value_of("Unicode: \u00A9 \u2022 \u00AE"); 221 | Assert(value3.is(), "Unicode string should be stored as std::string"); 222 | } 223 | 224 | void TestStringLiteralConversions() { 225 | // Test conversions between string literals and other types 226 | 227 | // String literal to numeric types 228 | xnode value1 = xnode::value_of("42"); 229 | Assert(value1.is(), "Numeric string should be stored as std::string"); 230 | Assert(value1.is_convertable_to(), "Numeric string should be convertible to int"); 231 | Assert(value1.get_as() == 42, "String should convert to correct integer"); 232 | Assert(value1.get_as() == 42.0, "String should convert to correct double"); 233 | 234 | // String literal with floating point 235 | xnode value2 = xnode::value_of("3.14159"); 236 | Assert(value2.is(), "Float string should be stored as std::string"); 237 | Assert(value2.is_convertable_to(), "Float string should be convertible to double"); 238 | Assert(value2.is_convertable_to(), "Float string should be convertible to float"); 239 | Assert(abs(value2.get_as() - 3.14159f) < 0.0001f, "String should convert to correct float"); 240 | 241 | // String literal to boolean 242 | xnode value3 = xnode::value_of("true"); 243 | Assert(value3.is(), "Boolean string should be stored as std::string"); 244 | Assert(value3.get_as(), "String 'true' should convert to boolean true"); 245 | 246 | xnode value4 = xnode::value_of("1"); 247 | Assert(value4.is(), "Numeric string should be stored as std::string"); 248 | Assert(value4.get_as(), "String '1' should convert to boolean true"); 249 | 250 | // Non-convertible string literals 251 | xnode value5 = xnode::value_of("not a number"); 252 | Assert(value5.is(), "Text string should be stored as std::string"); 253 | AssertFalse(value5.is_convertable_to(), "Text string should not be convertible to int"); 254 | 255 | // Test exception throwing with a lambda 256 | try { 257 | value5.get_as(); 258 | Assert(false, "Should have thrown exception for non-convertible string"); 259 | } catch (...) { 260 | // Expected exception 261 | } 262 | } 263 | 264 | void TestStringLiteralComparisons() { 265 | // Test comparison operations with string literals 266 | 267 | // Equality comparisons 268 | xnode value1 = xnode::value_of("test"); 269 | xnode value2 = xnode::value_of("test"); 270 | xnode value3 = xnode::value_of("different"); 271 | 272 | Assert(value1 == value2, "Identical string literals should be equal"); 273 | Assert(value1 != value3, "Different string literals should not be equal"); 274 | 275 | // Direct comparison with string literals 276 | Assert(value1.get_as() == "test", "String should equal literal"); 277 | AssertFalse(value1.get_as() == "different", "String should not equal different literal"); 278 | 279 | // Case sensitivity 280 | xnode value4 = xnode::value_of("TEST"); 281 | AssertFalse(value1 == value4, "String comparison should be case sensitive"); 282 | 283 | // Length comparison 284 | xnode value5 = xnode::value_of("test_longer"); 285 | Assert(value1 != value5, "Different length strings should not be equal"); 286 | } 287 | 288 | void TestStringLiteralWithRawPointers() { 289 | // Test interaction between string literals and char pointers 290 | 291 | // Create from literal vs pointer 292 | const char* cstr = "test string"; 293 | xnode value1 = xnode::value_of(std::string("test string")); 294 | xnode value2 = xnode::value_of(std::string(cstr)); 295 | 296 | Assert(value1.get_as() == value2.get_as(), 297 | "String from literal should equal string from pointer"); 298 | Assert(value1.is(), "String from literal should be std::string"); 299 | Assert(value2.is(), "String from pointer should be std::string"); 300 | 301 | // Raw char array 302 | char array[] = "hello world"; 303 | xnode value3 = xnode::value_of(std::string(array)); 304 | Assert(value3.is(), "String from array should be std::string"); 305 | Assert(value3.get_as() == "hello world", "String should equal original array"); 306 | 307 | // Modifying the original should not affect xnode value 308 | array[0] = 'H'; 309 | Assert(value3.get_as() == "hello world", "Modifying source array should not affect xnode"); 310 | } 311 | 312 | void TestAnyScalar() { 313 | // assuming there is no matching caster & xnode_type_code for this type 314 | typedef signed char schar; 315 | 316 | xnode svalue; 317 | 318 | schar a, b; 319 | a = 2; 320 | b = 3; 321 | 322 | svalue.set_as(a); 323 | Assert(svalue.get_type_code() == xnode_cast_policy_type_codes::def_code, "type code = default"); 324 | b = svalue.get_as(); 325 | Assert(a == b, "read value same as written"); 326 | } 327 | 328 | void TestAnyStruct() { 329 | struct TestStr { 330 | int a; 331 | int b; 332 | }; 333 | 334 | TestStr c, d; 335 | c.a = 12; 336 | c.b = 21; 337 | 338 | xnode svalue; 339 | svalue.set_as(c); 340 | 341 | d = svalue.get_as(); 342 | Assert(c.a == d.a); 343 | Assert(c.b == d.b); 344 | } 345 | 346 | 347 | void TestRelease() { 348 | struct TestStr { 349 | int a; 350 | int b; 351 | }; 352 | 353 | TestStr c; 354 | c.a = 12; 355 | c.b = 21; 356 | 357 | xnode svalue; 358 | svalue.set_as(c); 359 | 360 | Assert(!svalue.is_null()); 361 | delete svalue.release(); 362 | Assert(svalue.is_null()); 363 | 364 | } 365 | 366 | void TestReleaseNonOwned() { 367 | xnode svalue; 368 | 369 | char test[] = "Test"; 370 | svalue.set_as(&test[0]); 371 | Assert(svalue.release() == nullptr); 372 | 373 | svalue.set_as(5); 374 | Assert(svalue.release() == nullptr); 375 | } 376 | 377 | void TestHoldObj() { 378 | struct TestStr { 379 | int a; 380 | int b; 381 | }; 382 | 383 | std::unique_ptr holder(new TestStr()); 384 | holder->a = 12; 385 | holder->b = 21; 386 | 387 | xnode value; 388 | TestStr *ptr = holder.get(); 389 | value.hold(holder.release()); 390 | 391 | Assert(value.get_ptr() == ptr); 392 | Assert(value.get_ptr()->a == 12); 393 | Assert(value.get_ptr()->b == 21); 394 | Assert(value.is()); 395 | } 396 | 397 | void TestHoldInt() { 398 | 399 | std::unique_ptr holder(new int); 400 | *holder = 12; 401 | 402 | xnode value; 403 | value.hold(holder.release()); 404 | 405 | Assert(value.get_as() == 12); 406 | Assert(value.is()); 407 | } 408 | 409 | void TestMove() { 410 | std::string s("test"); 411 | xnode value = std::move(xnode::value_of(s)); 412 | Assert(value.is()); 413 | Assert(value.get_as() == "test"); 414 | } 415 | 416 | void TestIs() { 417 | xnode value; 418 | value.set_as(123); 419 | Assert(value.is()); 420 | } 421 | 422 | void TestValueOf1Type() { 423 | xnode value = xnode::value_of(123); 424 | Assert(value.get_as() == 123); 425 | Assert(value.is()); 426 | } 427 | 428 | void TestValueOf2Types() { 429 | xnode value = xnode::value_of(123); 430 | Assert(value.is()); 431 | Assert(value.get_as() == 123); 432 | } 433 | 434 | void TestEquals() { 435 | xnode value1 = xnode::value_of(123); 436 | xnode value2 = xnode::value_of(123); 437 | xnode value3 = xnode::value_of(3); 438 | xnode value4; 439 | 440 | Assert(value1 == value2, "check 1"); 441 | Assert(value1 != value3, "check 2"); 442 | Assert(value1 != value4, "check 3"); 443 | } 444 | 445 | void TestLess() { 446 | xnode value1 = xnode::value_of(123); 447 | xnode value2 = xnode::value_of(200); 448 | xnode value3 = xnode::value_of(3); 449 | xnode value4; 450 | 451 | Assert(value1 < value2, "check 1"); 452 | Assert(value3 < value2, "check 2"); 453 | Assert(value3 < value1, "check 3"); 454 | } 455 | 456 | void TestFloatCast() { 457 | xnode a, b, c; 458 | a = xnode::value_of(12.1f); 459 | b = xnode::value_of(3.0); 460 | Assert(a.is(), "check 1"); 461 | Assert(b.is(), "check 2"); 462 | Assert(a.get_as() == 12, "check 3"); 463 | 464 | a.set_as(a.get_as() + b.get_as()); 465 | Assert(a.get_as() == 15, "check 4"); 466 | } 467 | 468 | // example of polymorphism 469 | // read object as (void *) 470 | // then cast via static cast to base class 471 | // then check if it is a derived class using dynamic_cast 472 | void TestDynamicCastVptr() { 473 | 474 | class BaseNode { 475 | public: 476 | virtual ~BaseNode() {} 477 | virtual int getValue() { return 13; } 478 | }; 479 | 480 | class MyNode : public BaseNode { 481 | public: 482 | virtual int getValue() { return 10; } 483 | }; 484 | 485 | class OtherNode : public BaseNode { 486 | public: 487 | virtual int getValue() { return 111; } 488 | }; 489 | 490 | std::unique_ptr value(new MyNode()); 491 | 492 | xnode wrap; 493 | wrap.hold(value.release()); 494 | 495 | Assert(wrap.is(), "check is 1"); 496 | Assert(wrap.get_ptr()->getValue() == 10, "check value 1"); 497 | 498 | void *vptr = wrap.get_vptr(); 499 | Assert(vptr != nullptr, "check ptr 1"); 500 | 501 | BaseNode *basePtr = static_cast(vptr); 502 | Assert(basePtr->getValue() == 10, "check value 2"); 503 | 504 | MyNode *myPtr = dynamic_cast(basePtr); 505 | Assert(myPtr != nullptr, "check dynamic cast 1"); 506 | 507 | OtherNode *othPtr = dynamic_cast(basePtr); 508 | Assert(othPtr == nullptr, "check dynamic cast 2"); 509 | } 510 | 511 | void TestRef() { 512 | xnode intNode = xnode::value_of(15); 513 | Assert(intNode.get_as() == 15, "check 1"); 514 | Assert(intNode.get_ref() == 15, "check 2"); 515 | intNode.get_ref() = 100; 516 | Assert(intNode.get_as() == 100, "check 3"); 517 | } 518 | 519 | struct callGetRefNull { 520 | xnode &value_; 521 | callGetRefNull(xnode &value): value_(value) { 522 | } 523 | 524 | void operator()() { 525 | value_.get_ref(); 526 | } 527 | }; 528 | 529 | void TestRefThrows() { 530 | xnode value; 531 | AssertThrows(callGetRefNull(value), "check 1"); 532 | value = xnode::value_of(10); 533 | Assert(value.get_as() == 10, "check 2"); 534 | } 535 | 536 | void TestIsConvertable() { 537 | xnode value = xnode::value_of(3.14f); 538 | AssertTrue(value.is()); 539 | AssertTrue(value.is_convertable_to(), "is_convertable to double"); 540 | AssertFalse(value.is_convertable_to(), "is_convertable to void *"); 541 | } 542 | 543 | struct readByDouble { 544 | xnode &value_; 545 | readByDouble(xnode &value): value_(value) { 546 | } 547 | 548 | void operator()() { 549 | double d = value_.get_as(); 550 | d += 1.1; 551 | value_.set_as(d); 552 | } 553 | }; 554 | 555 | struct readByDoubleDef { 556 | xnode &value_; 557 | double def_value_; 558 | 559 | readByDoubleDef(xnode &value, double def_value): value_(value), def_value_(def_value) { 560 | } 561 | 562 | void operator()() { 563 | double d = value_.get_as_def(def_value_); 564 | d += 1.1; 565 | d += value_.get_ref_def(def_value_); 566 | value_.set_as(d); 567 | } 568 | }; 569 | 570 | void TestWrongParseThrows() { 571 | xnode value = xnode::value_of("@!@%!"); 572 | AssertFalse(value.is_convertable_to()); 573 | AssertThrows(readByDouble(value), "check 1"); 574 | } 575 | 576 | void TestCorrectParse() { 577 | xnode value = xnode::value_of("1234"); 578 | AssertTrue(value.is_convertable_to()); 579 | AssertNoThrow(readByDouble(value), "check 1"); 580 | } 581 | 582 | void TestSafeParse() { 583 | xnode value = xnode::value_of("@&^&!@"); 584 | AssertFalse(value.is_convertable_to()); 585 | AssertNoThrow(readByDoubleDef(value, 2.71), "check 1"); 586 | } 587 | 588 | void TestLongDoubleNotConvertable() { 589 | long double d = 12.14; 590 | xnode value = xnode::value_of(d); 591 | Assert(value.is()); 592 | AssertThrows(readByDouble(value)); 593 | } 594 | 595 | void TestLongDoubleWithPolicyConstruct() { 596 | typedef basic_xnode xnode_ld; 597 | long double d = 12.14; 598 | xnode_ld value = xnode_ld::value_of(d); 599 | Assert(value.is()); 600 | Assert(value.get_as() > 0.0); 601 | } 602 | 603 | void TestLongDoubleWithPolicySet() { 604 | typedef basic_xnode xnode_ld; 605 | long double d = 12.14; 606 | xnode_ld value; 607 | value.set_as(d); 608 | Assert(value.is()); 609 | Assert(value.get_as() > 0.0); 610 | } 611 | 612 | void TestLongDoubleCastWithPolicyConstruct() { 613 | typedef basic_xnode xnode_ld; 614 | long double d = 12.14; 615 | xnode_ld value = xnode_ld::value_of(d); 616 | Assert(value.is()); 617 | Assert(value.get_as() > 0.0); 618 | Assert(value.get_as() > 0.0); 619 | } 620 | 621 | void TestLongDoubleCastWithPolicySet() { 622 | typedef basic_xnode xnode_ld; 623 | long double d = 12.14; 624 | xnode_ld value; 625 | value.set_as(d); 626 | Assert(value.is()); 627 | Assert(value.get_as() > 0.0); 628 | Assert(value.get_as() > 0.0); 629 | } 630 | 631 | void TestWrongCastThrows() { 632 | void *vptr = nullptr; 633 | xnode value = xnode::value_of(vptr); 634 | 635 | AssertFalse(value.is_null(), "is_null"); 636 | AssertTrue(value.is(), "is void*"); 637 | AssertFalse(value.is_convertable_to(), "is_convertable"); 638 | AssertThrows(readByDouble(value), "read"); 639 | } 640 | 641 | void TestSafeCastNoThrow() { 642 | void *vptr = nullptr; 643 | xnode value = xnode::value_of(vptr); 644 | 645 | AssertFalse(value.is_null(), "is_null"); 646 | AssertTrue(value.is(), "is void*"); 647 | AssertFalse(value.is_convertable_to()); 648 | AssertNoThrow(readByDoubleDef(value, 13.1)); 649 | } 650 | 651 | int xnode_test() { 652 | TEST_PROLOG(); 653 | TEST_FUNC(DefCntr); 654 | TEST_FUNC(CopyConstr); 655 | TEST_FUNC(AssignOper); 656 | TEST_FUNC(Destructor); 657 | TEST_FUNC(SetAsInt); 658 | TEST_FUNC(GetAsInt); 659 | TEST_FUNC(TypeChangeOnSet); 660 | TEST_FUNC(Reset); 661 | TEST_FUNC(SetAsString); 662 | TEST_FUNC(SetValueInt); 663 | TEST_FUNC(SetValueStr); 664 | TEST_FUNC(ConvIntToLong); 665 | TEST_FUNC(ConvIntToString); 666 | TEST_FUNC(ConvStringToInt); 667 | TEST_FUNC(CharPtr); 668 | TEST_FUNC(CharLiteral); 669 | TEST_FUNC(StringLiteralSetAs); 670 | TEST_FUNC(StringLiteralValueOf); 671 | TEST_FUNC(StringLiteralConversions); 672 | TEST_FUNC(StringLiteralComparisons); 673 | TEST_FUNC(StringLiteralWithRawPointers); 674 | TEST_FUNC(AnyScalar); 675 | TEST_FUNC(AnyStruct); 676 | TEST_FUNC(Release); 677 | TEST_FUNC(ReleaseNonOwned); 678 | TEST_FUNC(HoldObj); 679 | TEST_FUNC(HoldInt); 680 | TEST_FUNC(Move); 681 | TEST_FUNC(Is); 682 | TEST_FUNC(ValueOf1Type); 683 | TEST_FUNC(ValueOf2Types); 684 | TEST_FUNC(Equals); 685 | TEST_FUNC(Less); 686 | TEST_FUNC(FloatCast); 687 | TEST_FUNC(DynamicCastVptr); 688 | TEST_FUNC(Ref); 689 | TEST_FUNC(RefThrows); 690 | TEST_FUNC(IsConvertable); 691 | TEST_FUNC(WrongParseThrows); 692 | TEST_FUNC(CorrectParse); 693 | TEST_FUNC(SafeParse); 694 | TEST_FUNC(LongDoubleNotConvertable); 695 | TEST_FUNC(LongDoubleWithPolicyConstruct); 696 | TEST_FUNC(LongDoubleWithPolicySet); 697 | TEST_FUNC(LongDoubleCastWithPolicySet); 698 | TEST_FUNC(LongDoubleCastWithPolicyConstruct); 699 | TEST_FUNC(WrongCastThrows); 700 | TEST_FUNC(SafeCastNoThrow); 701 | TEST_EPILOG(); 702 | } 703 | 704 | int main() 705 | { 706 | return xnode_test(); 707 | } 708 | 709 | -------------------------------------------------------------------------------- /test/xnode_tree_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode_tree_test.cpp 3 | // Purpose: Unit tests for tree structure of xnode 4 | // Author: Piotr Likus 5 | // Created: May 13, 2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "xnode.h" 10 | #include "xarray.h" 11 | #include "xobject.h" 12 | #include 13 | 14 | // for std::accumulate 15 | #include 16 | 17 | #include "cunit.h" 18 | 19 | using namespace std; 20 | 21 | void TestTreeOfNodesWithArray() { 22 | xnode root; 23 | 24 | // Create object 25 | xobject obj; 26 | obj.put("z", xnode::value_of(12)); 27 | obj.put("a", xnode::value_of(1L)); 28 | 29 | // Create nested array using of() 30 | xarray arr2 = xarray::of( 31 | xnode::value_of(1), 32 | xnode::value_of(1L), 33 | xnode::value_of(false) 34 | ); 35 | 36 | // Create main array using of() 37 | xarray arr1 = xarray::of( 38 | xnode::value_of(3), 39 | xnode::value_of(5), 40 | xnode::value_of(obj), 41 | xnode::value_of(arr2) 42 | ); 43 | 44 | root.set_as(arr1); 45 | 46 | Assert(root.is()); 47 | Assert(root.get_ptr()->size() == 4); 48 | Assert(root.get_ptr()->at(0).is()); 49 | Assert(root.get_ptr()->at(1).is()); 50 | Assert(root.get_ptr()->at(2).is()); 51 | Assert(root.get_ptr()->at(3).is()); 52 | 53 | Assert(root.get_ptr()->at(2).get_ptr()->contains("z")); 54 | } 55 | 56 | void TestTreeOfNodesWithObject() { 57 | xnode root; 58 | xarray v; 59 | xnode value; 60 | 61 | v.push_back(xnode::value_of(3)); 62 | v.push_back(xnode::value_of(5)); 63 | 64 | xobject pl; 65 | pl.put("z", xnode::value_of(12)); 66 | pl.put("a", xnode::value_of(1L)); 67 | 68 | v.push_back(xnode::value_of(pl)); 69 | 70 | root.set_as(v); 71 | 72 | Assert(root.is()); 73 | Assert(root.get_ptr()->size() == 3); 74 | Assert(root.get_ptr()->at(0).is()); 75 | Assert(root.get_ptr()->at(1).is()); 76 | Assert(root.get_ptr()->at(2).is()); 77 | 78 | Assert(root.get_ptr()->at(2).get_ptr()->contains("z")); 79 | } 80 | 81 | void TestNestedTreeStructure() { 82 | // Create a complex nested structure with arrays and objects 83 | xnode root; 84 | xobject mainObj; 85 | 86 | // Add some primitive values 87 | mainObj.put("name", xnode::value_of(std::string("test-tree"))); 88 | mainObj.put("version", xnode::value_of(2.5)); 89 | 90 | // Add a nested object 91 | xobject metadata; 92 | metadata.put("author", xnode::value_of(std::string("Piotr Likus"))); 93 | metadata.put("created", xnode::value_of(std::string("2025-05-13"))); 94 | mainObj.put("metadata", xnode::value_of(metadata)); 95 | 96 | // Add a nested array 97 | xarray tags; 98 | tags.push_back(xnode::value_of(std::string("test"))); 99 | tags.push_back(xnode::value_of(std::string("tree"))); 100 | tags.push_back(xnode::value_of(std::string("xnode"))); 101 | mainObj.put("tags", xnode::value_of(tags)); 102 | 103 | // Add object with nested array with objects 104 | xarray items; 105 | for (int i = 0; i < 3; i++) { 106 | xobject item; 107 | item.put("id", xnode::value_of(i)); 108 | item.put("value", xnode::value_of(i * 10)); 109 | items.push_back(xnode::value_of(item)); 110 | } 111 | mainObj.put("items", xnode::value_of(items)); 112 | 113 | root.set_as(mainObj); 114 | 115 | // Validate the structure 116 | Assert(root.is()); 117 | Assert(root.get_ptr()->contains("name")); 118 | Assert(root.get_ptr()->get("name").is()); 119 | Assert(root.get_ptr()->get("name").get_as() == "test-tree"); 120 | 121 | // Check metadata object 122 | Assert(root.get_ptr()->contains("metadata")); 123 | Assert(root.get_ptr()->get("metadata").is()); 124 | Assert(root.get_ptr()->get("metadata").get_ptr()->contains("author")); 125 | 126 | // Check tags array 127 | Assert(root.get_ptr()->contains("tags")); 128 | Assert(root.get_ptr()->get("tags").is()); 129 | Assert(root.get_ptr()->get("tags").get_ptr()->size() == 3); 130 | 131 | // Check items array with nested objects 132 | Assert(root.get_ptr()->contains("items")); 133 | Assert(root.get_ptr()->get("items").is()); 134 | Assert(root.get_ptr()->get("items").get_ptr()->size() == 3); 135 | Assert(root.get_ptr()->get("items").get_ptr()->at(1).is()); 136 | Assert(root.get_ptr()->get("items").get_ptr()->at(1).get_ptr()->get("id").get_as() == 1); 137 | Assert(root.get_ptr()->get("items").get_ptr()->at(1).get_ptr()->get("value").get_as() == 10); 138 | } 139 | 140 | int xnode_tree_test() { 141 | TEST_PROLOG(); 142 | TEST_FUNC(TreeOfNodesWithArray); 143 | TEST_FUNC(TreeOfNodesWithObject); 144 | TEST_FUNC(NestedTreeStructure); 145 | TEST_EPILOG(); 146 | } 147 | 148 | int main() 149 | { 150 | return xnode_tree_test(); 151 | } 152 | -------------------------------------------------------------------------------- /test/xnode_type_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xnode_type_test.cpp 3 | // Purpose: Comprehensive type tests for xnode library 4 | // Author: Generated by GitHub Copilot 5 | // Created: May 2, 2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "xnode.h" 10 | #include 11 | #include "cunit.h" 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | // Test functions for each supported data type 18 | void TestBoolType() { 19 | // Test scenario 1 20 | xnode node; 21 | bool testValue = true; 22 | node.set_as(testValue); 23 | 24 | AssertTrue(node.is(), "is() should return true"); 25 | AssertTrue(node.get_as() == testValue, "get_as() should return the test value"); 26 | 27 | // Test with false value 28 | bool testValueFalse = false; 29 | node.set_as(testValueFalse); 30 | 31 | AssertTrue(node.is(), "is() should return true after setting false"); 32 | AssertTrue(node.get_as() == testValueFalse, "get_as() should return false"); 33 | 34 | // Test with initialization from constructor 35 | xnode constructedNode = xnode::value_of(testValue); 36 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 37 | AssertTrue(constructedNode.get_as() == testValue, "get_as() should return test value for constructed node"); 38 | } 39 | 40 | void TestFloatType() { 41 | // Test scenario 1 42 | xnode node; 43 | float testValue = 3.14159f; 44 | node.set_as(testValue); 45 | 46 | AssertTrue(node.is(), "is() should return true"); 47 | 48 | // Use approximately equal for floating point comparison 49 | float epsilon = 0.00001f; 50 | float retrievedValue = node.get_as(); 51 | AssertTrue(std::abs(retrievedValue - testValue) < epsilon, 52 | "get_as() should return approximately the test value"); 53 | 54 | // Test with negative value 55 | float testNegValue = -2.71828f; 56 | node.set_as(testNegValue); 57 | 58 | AssertTrue(node.is(), "is() should return true for negative value"); 59 | retrievedValue = node.get_as(); 60 | AssertTrue(std::abs(retrievedValue - testNegValue) < epsilon, 61 | "get_as() should return approximately the negative test value"); 62 | 63 | // Test with initialization from constructor 64 | xnode constructedNode = xnode::value_of(testValue); 65 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 66 | retrievedValue = constructedNode.get_as(); 67 | AssertTrue(std::abs(retrievedValue - testValue) < epsilon, 68 | "get_as() should return approximately the test value for constructed node"); 69 | } 70 | 71 | void TestDoubleType() { 72 | // Test scenario 1 73 | xnode node; 74 | double testValue = 2.7182818284590452353602874; 75 | node.set_as(testValue); 76 | 77 | AssertTrue(node.is(), "is() should return true"); 78 | 79 | // Use approximately equal for floating point comparison 80 | double epsilon = 0.0000000000001; 81 | double retrievedValue = node.get_as(); 82 | AssertTrue(std::abs(retrievedValue - testValue) < epsilon, 83 | "get_as() should return approximately the test value"); 84 | 85 | // Test with negative value 86 | double testNegValue = -3.141592653589793238462643; 87 | node.set_as(testNegValue); 88 | 89 | AssertTrue(node.is(), "is() should return true for negative value"); 90 | retrievedValue = node.get_as(); 91 | AssertTrue(std::abs(retrievedValue - testNegValue) < epsilon, 92 | "get_as() should return approximately the negative test value"); 93 | 94 | // Test with initialization from constructor 95 | xnode constructedNode = xnode::value_of(testValue); 96 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 97 | retrievedValue = constructedNode.get_as(); 98 | AssertTrue(std::abs(retrievedValue - testValue) < epsilon, 99 | "get_as() should return approximately the test value for constructed node"); 100 | } 101 | 102 | void TestStringType() { 103 | // Test scenario 1 104 | xnode node; 105 | std::string testValue = "Hello, xnode!"; 106 | node.set_as(testValue); 107 | 108 | AssertTrue(node.is(), "is() should return true"); 109 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 110 | 111 | // Test with empty string 112 | std::string emptyString = ""; 113 | node.set_as(emptyString); 114 | 115 | AssertTrue(node.is(), "is() should return true for empty string"); 116 | AssertEquals(emptyString, node.get_as(), "get_as() should return empty string"); 117 | 118 | // Test with initialization from constructor 119 | xnode constructedNode = xnode::value_of(testValue); 120 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 121 | AssertEquals(testValue, constructedNode.get_as(), 122 | "get_as() should return test value for constructed node"); 123 | 124 | // Test with string literal 125 | xnode literalNode = xnode::value_of("String literal"); 126 | AssertTrue(literalNode.is(), "is() should return true for string literal"); 127 | AssertEquals(std::string("String literal"), literalNode.get_as(), 128 | "get_as() should return the string literal"); 129 | } 130 | 131 | void TestCharType() { 132 | // Test scenario 1 133 | xnode node; 134 | char testValue = 'A'; 135 | node.set_as(testValue); 136 | 137 | AssertTrue(node.is(), "is() should return true"); 138 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 139 | 140 | // Test with special character 141 | char specialChar = '\n'; 142 | node.set_as(specialChar); 143 | 144 | AssertTrue(node.is(), "is() should return true for special character"); 145 | AssertEquals(specialChar, node.get_as(), "get_as() should return the special character"); 146 | 147 | // Test with numeric character 148 | char numericChar = '9'; 149 | node.set_as(numericChar); 150 | 151 | AssertTrue(node.is(), "is() should return true for numeric character"); 152 | AssertEquals(numericChar, node.get_as(), "get_as() should return the numeric character"); 153 | 154 | // Test with initialization from constructor 155 | xnode constructedNode = xnode::value_of(testValue); 156 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 157 | AssertEquals(testValue, constructedNode.get_as(), 158 | "get_as() should return test value for constructed node"); 159 | } 160 | 161 | void TestShortType() { 162 | // Test scenario 1 163 | xnode node; 164 | short testValue = 12345; 165 | node.set_as(testValue); 166 | 167 | AssertTrue(node.is(), "is() should return true"); 168 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 169 | 170 | // Test with negative value 171 | short negValue = -12345; 172 | node.set_as(negValue); 173 | 174 | AssertTrue(node.is(), "is() should return true for negative value"); 175 | AssertEquals(negValue, node.get_as(), "get_as() should return the negative value"); 176 | 177 | // Test with max value 178 | short maxValue = std::numeric_limits::max(); 179 | node.set_as(maxValue); 180 | 181 | AssertTrue(node.is(), "is() should return true for max value"); 182 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 183 | 184 | // Test with min value 185 | short minValue = std::numeric_limits::min(); 186 | node.set_as(minValue); 187 | 188 | AssertTrue(node.is(), "is() should return true for min value"); 189 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 190 | 191 | // Test with initialization from constructor 192 | xnode constructedNode = xnode::value_of(testValue); 193 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 194 | AssertEquals(testValue, constructedNode.get_as(), 195 | "get_as() should return test value for constructed node"); 196 | } 197 | 198 | void TestIntType() { 199 | // Test scenario 1 200 | xnode node; 201 | int testValue = 123456789; 202 | node.set_as(testValue); 203 | 204 | AssertTrue(node.is(), "is() should return true"); 205 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 206 | 207 | // Test with negative value 208 | int negValue = -987654321; 209 | node.set_as(negValue); 210 | 211 | AssertTrue(node.is(), "is() should return true for negative value"); 212 | AssertEquals(negValue, node.get_as(), "get_as() should return the negative value"); 213 | 214 | // Test with max value 215 | int maxValue = std::numeric_limits::max(); 216 | node.set_as(maxValue); 217 | 218 | AssertTrue(node.is(), "is() should return true for max value"); 219 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 220 | 221 | // Test with min value 222 | int minValue = std::numeric_limits::min(); 223 | node.set_as(minValue); 224 | 225 | AssertTrue(node.is(), "is() should return true for min value"); 226 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 227 | 228 | // Test with initialization from constructor 229 | xnode constructedNode = xnode::value_of(testValue); 230 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 231 | AssertEquals(testValue, constructedNode.get_as(), 232 | "get_as() should return test value for constructed node"); 233 | } 234 | 235 | void TestLongType() { 236 | // Test scenario 1 237 | xnode node; 238 | long testValue = 1234567890L; 239 | node.set_as(testValue); 240 | 241 | AssertTrue(node.is(), "is() should return true"); 242 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 243 | 244 | // Test with negative value 245 | long negValue = -9876543210L; 246 | node.set_as(negValue); 247 | 248 | AssertTrue(node.is(), "is() should return true for negative value"); 249 | AssertEquals(negValue, node.get_as(), "get_as() should return the negative value"); 250 | 251 | // Test with max value 252 | long maxValue = std::numeric_limits::max(); 253 | node.set_as(maxValue); 254 | 255 | AssertTrue(node.is(), "is() should return true for max value"); 256 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 257 | 258 | // Test with min value 259 | long minValue = std::numeric_limits::min(); 260 | node.set_as(minValue); 261 | 262 | AssertTrue(node.is(), "is() should return true for min value"); 263 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 264 | 265 | // Test with initialization from constructor 266 | xnode constructedNode = xnode::value_of(testValue); 267 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 268 | AssertEquals(testValue, constructedNode.get_as(), 269 | "get_as() should return test value for constructed node"); 270 | } 271 | 272 | void TestLongLongType() { 273 | // Test scenario 1 274 | xnode node; 275 | long long testValue = std::numeric_limits::max(); 276 | node.set_as(testValue); 277 | 278 | AssertTrue(node.is(), "is() should return true"); 279 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 280 | 281 | // Test with negative value 282 | long long negValue = std::numeric_limits::min(); 283 | node.set_as(negValue); 284 | 285 | AssertTrue(node.is(), "is() should return true for negative value"); 286 | AssertEquals(negValue, node.get_as(), "get_as() should return the negative value"); 287 | 288 | // Test with max value 289 | long long maxValue = std::numeric_limits::max(); 290 | node.set_as(maxValue); 291 | 292 | AssertTrue(node.is(), "is() should return true for max value"); 293 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 294 | 295 | // Test with min value 296 | long long minValue = std::numeric_limits::min(); 297 | node.set_as(minValue); 298 | 299 | AssertTrue(node.is(), "is() should return true for min value"); 300 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 301 | 302 | // Test with initialization from constructor 303 | xnode constructedNode = xnode::value_of(testValue); 304 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 305 | AssertEquals(testValue, constructedNode.get_as(), 306 | "get_as() should return test value for constructed node"); 307 | } 308 | 309 | void TestUnsignedCharType() { 310 | // Test scenario 1 311 | xnode node; 312 | unsigned char testValue = 200; 313 | node.set_as(testValue); 314 | 315 | AssertTrue(node.is(), "is() should return true"); 316 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 317 | 318 | // Test with max value 319 | unsigned char maxValue = std::numeric_limits::max(); 320 | node.set_as(maxValue); 321 | 322 | AssertTrue(node.is(), "is() should return true for max value"); 323 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 324 | 325 | // Test with min value (0) 326 | unsigned char minValue = std::numeric_limits::min(); 327 | node.set_as(minValue); 328 | 329 | AssertTrue(node.is(), "is() should return true for min value"); 330 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 331 | 332 | // Test with initialization from constructor 333 | xnode constructedNode = xnode::value_of(testValue); 334 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 335 | AssertEquals(testValue, constructedNode.get_as(), 336 | "get_as() should return test value for constructed node"); 337 | } 338 | 339 | void TestUnsignedShortType() { 340 | // Test scenario 1 341 | xnode node; 342 | unsigned short testValue = 12345; 343 | node.set_as(testValue); 344 | 345 | AssertTrue(node.is(), "is() should return true"); 346 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 347 | 348 | // Test with max value 349 | unsigned short maxValue = std::numeric_limits::max(); 350 | node.set_as(maxValue); 351 | 352 | AssertTrue(node.is(), "is() should return true for max value"); 353 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 354 | 355 | // Test with min value (0) 356 | unsigned short minValue = std::numeric_limits::min(); 357 | node.set_as(minValue); 358 | 359 | AssertTrue(node.is(), "is() should return true for min value"); 360 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 361 | 362 | // Test with higher values beyond signed short 363 | unsigned short highValue = 60000; 364 | node.set_as(highValue); 365 | 366 | AssertTrue(node.is(), "is() should return true for high value"); 367 | AssertEquals(highValue, node.get_as(), "get_as() should return the high value"); 368 | 369 | // Test with initialization from constructor 370 | xnode constructedNode = xnode::value_of(testValue); 371 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 372 | AssertEquals(testValue, constructedNode.get_as(), 373 | "get_as() should return test value for constructed node"); 374 | } 375 | 376 | void TestUnsignedIntType() { 377 | // Test scenario 1 378 | xnode node; 379 | unsigned int testValue = 4000000000U; 380 | node.set_as(testValue); 381 | 382 | AssertTrue(node.is(), "is() should return true"); 383 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 384 | 385 | // Test with max value 386 | unsigned int maxValue = std::numeric_limits::max(); 387 | node.set_as(maxValue); 388 | 389 | AssertTrue(node.is(), "is() should return true for max value"); 390 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 391 | 392 | // Test with min value (0) 393 | unsigned int minValue = std::numeric_limits::min(); 394 | node.set_as(minValue); 395 | 396 | AssertTrue(node.is(), "is() should return true for min value"); 397 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 398 | 399 | // Test with initialization from constructor 400 | xnode constructedNode = xnode::value_of(testValue); 401 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 402 | AssertEquals(testValue, constructedNode.get_as(), 403 | "get_as() should return test value for constructed node"); 404 | } 405 | 406 | void TestUnsignedLongType() { 407 | // Test scenario 1 408 | xnode node; 409 | unsigned long testValue = 4000000000UL; 410 | node.set_as(testValue); 411 | 412 | AssertTrue(node.is(), "is() should return true"); 413 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 414 | 415 | // Test with max value 416 | unsigned long maxValue = std::numeric_limits::max(); 417 | node.set_as(maxValue); 418 | 419 | AssertTrue(node.is(), "is() should return true for max value"); 420 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 421 | 422 | // Test with min value (0) 423 | unsigned long minValue = std::numeric_limits::min(); 424 | node.set_as(minValue); 425 | 426 | AssertTrue(node.is(), "is() should return true for min value"); 427 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 428 | 429 | // Test with initialization from constructor 430 | xnode constructedNode = xnode::value_of(testValue); 431 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 432 | AssertEquals(testValue, constructedNode.get_as(), 433 | "get_as() should return test value for constructed node"); 434 | } 435 | 436 | void TestUnsignedLongLongType() { 437 | // Test scenario 1 438 | xnode node; 439 | unsigned long long testValue = 18000000000000000000ULL; 440 | node.set_as(testValue); 441 | 442 | AssertTrue(node.is(), "is() should return true"); 443 | AssertEquals(testValue, node.get_as(), "get_as() should return the test value"); 444 | 445 | // Test with max value 446 | unsigned long long maxValue = std::numeric_limits::max(); 447 | node.set_as(maxValue); 448 | 449 | AssertTrue(node.is(), "is() should return true for max value"); 450 | AssertEquals(maxValue, node.get_as(), "get_as() should return the max value"); 451 | 452 | // Test with min value (0) 453 | unsigned long long minValue = std::numeric_limits::min(); 454 | node.set_as(minValue); 455 | 456 | AssertTrue(node.is(), "is() should return true for min value"); 457 | AssertEquals(minValue, node.get_as(), "get_as() should return the min value"); 458 | 459 | // Test with initialization from constructor 460 | xnode constructedNode = xnode::value_of(testValue); 461 | AssertTrue(constructedNode.is(), "is() should return true for constructed node"); 462 | AssertEquals(testValue, constructedNode.get_as(), 463 | "get_as() should return test value for constructed node"); 464 | } 465 | 466 | void TestXNodeConstructors() { 467 | // Default constructor 468 | xnode defaultNode; 469 | AssertTrue(defaultNode.is_null(), "Default constructor should create null node"); 470 | 471 | // value_of() constructor with different types 472 | xnode boolNode = xnode::value_of(true); 473 | AssertTrue(boolNode.is(), "value_of(bool) should create bool node"); 474 | AssertEquals(true, boolNode.get_as(), "value_of(bool) should preserve value"); 475 | 476 | xnode intNode = xnode::value_of(42); 477 | AssertTrue(intNode.is(), "value_of(int) should create int node"); 478 | AssertEquals(42, intNode.get_as(), "value_of(int) should preserve value"); 479 | 480 | xnode floatNode = xnode::value_of(3.14159f); 481 | AssertTrue(floatNode.is(), "value_of(float) should create float node"); 482 | float floatEpsilon = 0.00001f; 483 | AssertTrue(std::abs(floatNode.get_as() - 3.14159f) < floatEpsilon, 484 | "value_of(float) should approximately preserve value"); 485 | 486 | xnode stringNode = xnode::value_of(std::string("Test String")); 487 | AssertTrue(stringNode.is(), "value_of(string) should create string node"); 488 | AssertEquals(std::string("Test String"), stringNode.get_as(), 489 | "value_of(string) should preserve value"); 490 | 491 | // Copy constructor 492 | xnode copyNode(intNode); 493 | AssertTrue(copyNode.is(), "Copy constructor should preserve type"); 494 | AssertEquals(42, copyNode.get_as(), "Copy constructor should preserve value"); 495 | 496 | // Move constructor 497 | xnode sourceNode = xnode::value_of(123.456); 498 | xnode moveNode(std::move(sourceNode)); 499 | AssertTrue(moveNode.is(), "Move constructor should preserve type"); 500 | double doubleEpsilon = 0.000001; 501 | AssertTrue(std::abs(moveNode.get_as() - 123.456) < doubleEpsilon, 502 | "Move constructor should preserve value"); 503 | 504 | // Assignment operator 505 | xnode assignNode; 506 | assignNode = intNode; 507 | AssertTrue(assignNode.is(), "Assignment operator should preserve type"); 508 | AssertEquals(42, assignNode.get_as(), "Assignment operator should preserve value"); 509 | 510 | // Move assignment operator 511 | xnode moveAssignSource = xnode::value_of(7654ULL); 512 | xnode moveAssignTarget; 513 | moveAssignTarget = std::move(moveAssignSource); 514 | AssertTrue(moveAssignTarget.is(), "Move assignment should preserve type"); 515 | AssertEquals(7654ULL, moveAssignTarget.get_as(), 516 | "Move assignment should preserve value"); 517 | 518 | // value_of with specified target type 519 | xnode convertedNode = xnode::value_of(42); 520 | AssertTrue(convertedNode.is(), "value_of() should create node of specified type"); 521 | AssertTrue(std::abs(convertedNode.get_as() - 42.0) < doubleEpsilon, 522 | "value_of() should convert to specified type"); 523 | } 524 | 525 | int xnode_type_test() { 526 | TEST_PROLOG(); 527 | 528 | // Test each data type individually 529 | TEST_FUNC(BoolType); 530 | TEST_FUNC(FloatType); 531 | TEST_FUNC(DoubleType); 532 | TEST_FUNC(StringType); 533 | TEST_FUNC(CharType); 534 | TEST_FUNC(ShortType); 535 | TEST_FUNC(IntType); 536 | TEST_FUNC(LongType); 537 | TEST_FUNC(LongLongType); 538 | TEST_FUNC(UnsignedCharType); 539 | TEST_FUNC(UnsignedShortType); 540 | TEST_FUNC(UnsignedIntType); 541 | TEST_FUNC(UnsignedLongType); 542 | TEST_FUNC(UnsignedLongLongType); 543 | 544 | // Test constructors 545 | TEST_FUNC(XNodeConstructors); 546 | 547 | TEST_EPILOG(); 548 | } 549 | 550 | int main() { 551 | return xnode_type_test(); 552 | } -------------------------------------------------------------------------------- /test/xobject_test.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------- 2 | // Name: xobject_test.cpp 3 | // Purpose: Unit tests for xobject functionality 4 | // Author: Piotr Likus 5 | // Created: May 13, 2025 6 | // License: BSD 7 | //---------------------------------------------------------------------------------- 8 | 9 | #include "xnode.h" 10 | #include "xobject.h" 11 | #include 12 | #include 13 | #include 14 | #include // for std::find, std::sort 15 | #include // for std::numeric_limits 16 | #include // for std::isinf, std::isnan 17 | 18 | #include "cunit.h" 19 | 20 | using namespace std; 21 | 22 | void TestPropertyListPutGet() { 23 | xobject v; 24 | v.put("test1", xnode::value_of(12)); 25 | v.put("test2", xnode::value_of(12L)); 26 | v.put("test3", xnode::value_of(std::string("ala"))); 27 | 28 | Assert(v.contains("test1")); 29 | Assert(v.get("test2").get_as() == 12); 30 | Assert(v.get_ptr("test3") != nullptr); 31 | Assert(v.get_ptr("test3")->get_as() == "ala"); 32 | } 33 | 34 | void TestPropertyListKeysInOrder() { 35 | xobject v; 36 | v.put("z", xnode::value_of(12)); 37 | v.put("a", xnode::value_of(1L)); 38 | v.put("ba", xnode::value_of(std::string("ala"))); 39 | v.put("d", xnode::value_of(3)); 40 | 41 | std::vector keys = v.get_keys(); 42 | Assert(keys[0] == "z"); 43 | Assert(keys[1] == "a"); 44 | Assert(keys[2] == "ba"); 45 | Assert(keys[3] == "d"); 46 | 47 | v.remove("a"); 48 | keys = v.get_keys(); 49 | Assert(keys[2] == "d"); 50 | Assert(v.get("d").is()); 51 | Assert(v.get("d").get_as() == 3); 52 | } 53 | 54 | void TestPropertyListValuesInOrder() { 55 | xobject v; 56 | v.put("z", xnode::value_of(12)); 57 | v.put("a", xnode::value_of(1L)); 58 | v.put("ba", xnode::value_of(std::string("ala"))); 59 | v.put("d", xnode::value_of(3)); 60 | 61 | std::vector values = v.get_values(); 62 | Assert(values[0].get_as() == 12); 63 | Assert(values[1].get_as() == 1); 64 | Assert(values[2].get_as() == "ala"); 65 | Assert(values[3].get_as() == 3); 66 | 67 | v.remove("a"); 68 | values = v.get_values(); 69 | Assert(values[2].get_as() == 3); 70 | } 71 | 72 | void TestPropertyListSum() { 73 | xobject v; 74 | v.put("z", xnode::value_of(12)); 75 | v.put("a", xnode::value_of(1L)); 76 | v.put("ba", xnode::value_of(7)); 77 | v.put("d", xnode::value_of(3)); 78 | 79 | auto keys = v.get_keys(); 80 | Assert(keys.size() == 4); 81 | 82 | int sum = 0; 83 | for (std::string key : keys) 84 | sum += v.get(key).get_as(); 85 | 86 | Assert(sum == 23); 87 | } 88 | 89 | void TestPropertyListKeysNoCopy() { 90 | // faster version of get_keys 91 | 92 | xobject v; 93 | v.put("z", xnode::value_of(12)); 94 | v.put("a", xnode::value_of(1L)); 95 | v.put("ba", xnode::value_of(7)); 96 | v.put("bc", xnode::value_of(1)); 97 | 98 | v.remove("a"); 99 | 100 | xobject::key_list_type keys_helper; 101 | auto keys = v.get_keys(keys_helper); 102 | 103 | Assert(keys.size() == 3); 104 | } 105 | 106 | bool checkStructOk(const xnode &value) { 107 | // d,z are required int values 108 | bool result = 109 | value.is() && 110 | value.get_ptr()->contains("z") && 111 | value.get_ptr()->contains("d") && 112 | value.get_ptr()->get("z").is() && 113 | value.get_ptr()->get("d").is(); 114 | 115 | // a is optional long 116 | if (result) 117 | if (value.get_ptr()->contains("a")) 118 | result = value.get_ptr()->get("a").is(); 119 | 120 | return result; 121 | } 122 | 123 | void TestIterators() { 124 | // Create a test object with some key-value pairs 125 | xobject obj; 126 | obj.put("one", xnode::value_of(1)); 127 | obj.put("two", xnode::value_of(2)); 128 | obj.put("three", xnode::value_of(3)); 129 | obj.put("four", xnode::value_of(4)); 130 | 131 | // Test keys_begin and keys_end iterators 132 | { 133 | std::vector keys; 134 | int keyCount = 0; 135 | 136 | for (auto it = obj.keys_begin(); it != obj.keys_end(); ++it) { 137 | keys.push_back(*it); 138 | keyCount++; 139 | } 140 | 141 | Assert(keyCount == 4, "Should iterate over 4 keys"); 142 | Assert(keys[0] == "one", "First key should be 'one'"); 143 | Assert(keys[1] == "two", "Second key should be 'two'"); 144 | Assert(keys[2] == "three", "Third key should be 'three'"); 145 | Assert(keys[3] == "four", "Fourth key should be 'four'"); 146 | } 147 | 148 | // Test keys_cbegin and keys_cend const iterators 149 | { 150 | const xobject& const_obj = obj; 151 | int sum = 0; 152 | int keyCount = 0; 153 | 154 | for (auto it = const_obj.keys_cbegin(); it != const_obj.keys_cend(); ++it) { 155 | sum += const_obj.get(*it).get_as(); 156 | keyCount++; 157 | } 158 | 159 | Assert(keyCount == 4, "Should iterate over 4 keys with const iterators"); 160 | Assert(sum == 10, "Sum of values should be 10"); 161 | } 162 | 163 | // Test values_begin and values_end iterators 164 | { 165 | int sum = 0; 166 | int valueCount = 0; 167 | 168 | for (auto it = obj.values_begin(); it != obj.values_end(); ++it) { 169 | sum += it->second.get_as(); 170 | valueCount++; 171 | } 172 | 173 | Assert(valueCount == 4, "Should iterate over 4 values"); 174 | Assert(sum == 10, "Sum of values should be 10"); 175 | } 176 | 177 | // Test values_cbegin and values_cend const iterators 178 | { 179 | const xobject& const_obj = obj; 180 | int sum = 0; 181 | int valueCount = 0; 182 | 183 | for (auto it = const_obj.values_cbegin(); it != const_obj.values_cend(); ++it) { 184 | sum += it->second.get_as(); 185 | valueCount++; 186 | } 187 | 188 | Assert(valueCount == 4, "Should iterate over 4 values with const iterators"); 189 | Assert(sum == 10, "Sum of values should be 10"); 190 | } 191 | 192 | // Test iterator with reorganization 193 | { 194 | obj.remove("two"); 195 | 196 | std::vector keys; 197 | for (auto it = obj.keys_begin(); it != obj.keys_end(); ++it) { 198 | keys.push_back(*it); 199 | } 200 | 201 | Assert(keys.size() == 3, "Should have 3 keys after removal"); 202 | Assert(std::find(keys.begin(), keys.end(), "two") == keys.end(), "Removed key should not be found"); 203 | Assert(!obj.needs_reorg(), "Keys should be reorganized during iteration"); 204 | } 205 | 206 | // Test with range-based for loop 207 | { 208 | int sum = 0; 209 | std::vector keys = obj.get_keys(); 210 | 211 | for (const auto& key : keys) { 212 | sum += obj.get(key).get_as(); 213 | } 214 | 215 | Assert(sum == 8, "Sum after removal should be 8"); 216 | } 217 | 218 | // Test using iterators with std algorithms 219 | { 220 | std::vector keys(obj.keys_begin(), obj.keys_end()); 221 | Assert(keys.size() == 3, "Should copy 3 keys to vector"); 222 | 223 | std::sort(keys.begin(), keys.end()); 224 | Assert(keys[0] == "four", "First key after sorting should be 'four'"); 225 | Assert(keys[1] == "one", "Second key after sorting should be 'one'"); 226 | Assert(keys[2] == "three", "Third key after sorting should be 'three'"); 227 | } 228 | } 229 | 230 | std::string printInFont(const xnode &font, const std::string &text) { 231 | std::ostringstream out; 232 | 233 | const xobject &list = font.get_ref(); 234 | 235 | out << "text in font ["; 236 | out << "color:" << list.get_def("color", xnode::value_of(0x00ff00)).get_as(); 237 | out << ", font_name:" << list.get_def("font_name", xnode::value_of("courier")).get_as(); 238 | out << ", size:" << list.get_def("size", xnode::value_of(10)).get_as(); 239 | out << ", bold:" << list.get_def("bold", xnode::value_of(false)).get_as(); 240 | out << "] = " << text; 241 | 242 | return(out.str()); 243 | } 244 | 245 | void TestOptionalNamedParams() { 246 | xobject v; 247 | v.put("color", xnode::value_of(0xff0000)); 248 | v.put("font_name", xnode::value_of(std::string("arial"))); 249 | v.put("size", xnode::value_of(12)); 250 | 251 | xnode value = std::move(xnode::value_of(v)); 252 | 253 | std::string s = printInFont(value, "test"); 254 | Assert(s.find("bold:") != std::string::npos); 255 | } 256 | 257 | void TestDefNamedParams() { 258 | xobject v; 259 | v.put("z", xnode::value_of(12)); 260 | v.put("a", xnode::value_of(1L)); 261 | v.put("d", xnode::value_of(7)); 262 | 263 | Assert(v.get_def("c", xnode::value_of(-1)).get_as() == -1); 264 | } 265 | 266 | void TestStaticOfMethod() { 267 | // Test with 3 key-value pairs of different types (int, string, float) 268 | auto obj = xobject::of( 269 | "intKey", xnode::value_of(42), 270 | "strKey", xnode::value_of(std::string("hello")), 271 | "floatKey", xnode::value_of(3.14f) 272 | ); 273 | 274 | // Assert size is correct 275 | Assert(obj.size() == 3, "Object should contain 3 items"); 276 | 277 | // Assert all keys exist 278 | Assert(obj.contains("intKey"), "Object should contain 'intKey'"); 279 | Assert(obj.contains("strKey"), "Object should contain 'strKey'"); 280 | Assert(obj.contains("floatKey"), "Object should contain 'floatKey'"); 281 | 282 | // Assert values are of correct types 283 | Assert(obj.get("intKey").is(), "intKey should be of type int"); 284 | Assert(obj.get("strKey").is(), "strKey should be of type string"); 285 | Assert(obj.get("floatKey").is(), "floatKey should be of type float"); 286 | 287 | // Assert values are correct 288 | Assert(obj.get("intKey").get_as() == 42, "intKey should have value 42"); 289 | Assert(obj.get("strKey").get_as() == "hello", "strKey should have value 'hello'"); 290 | Assert(obj.get("floatKey").get_as() == 3.14f, "floatKey should have value 3.14f"); 291 | 292 | // Assert insertion order is preserved 293 | std::vector keys = obj.get_keys(); 294 | Assert(keys.size() == 3, "Keys vector should contain 3 items"); 295 | Assert(keys[0] == "intKey", "First key should be 'intKey'"); 296 | Assert(keys[1] == "strKey", "Second key should be 'strKey'"); 297 | Assert(keys[2] == "floatKey", "Third key should be 'floatKey'"); 298 | 299 | // Test with other value types including limits 300 | auto obj2 = xobject::of( 301 | "boolKey", xnode::value_of(true), 302 | "doubleKey", xnode::value_of(2.71828), 303 | "longKey", xnode::value_of(std::numeric_limits::max()) // max long 304 | ); 305 | 306 | Assert(obj2.size() == 3, "Object should contain 3 items"); 307 | Assert(obj2.get("boolKey").get_as() == true, "boolKey should have value true"); 308 | Assert(obj2.get("doubleKey").get_as() == 2.71828, "doubleKey should have value 2.71828"); 309 | Assert(obj2.get("longKey").get_as() == std::numeric_limits::max(), "longKey should have correct value"); 310 | 311 | // Test with empty string and zero values 312 | auto obj3 = xobject::of( 313 | "emptyStr", xnode::value_of(std::string("")), 314 | "zero", xnode::value_of(0), 315 | "zeroF", xnode::value_of(0.0f) 316 | ); 317 | 318 | Assert(obj3.get("emptyStr").get_as() == "", "emptyStr should be empty"); 319 | Assert(obj3.get("zero").get_as() == 0, "zero should be 0"); 320 | Assert(obj3.get("zeroF").get_as() == 0.0f, "zeroF should be 0.0f"); 321 | 322 | // Test with numeric limits for integer types 323 | auto objIntLimits = xobject::of( 324 | "int_max", xnode::value_of(std::numeric_limits::max()), 325 | "int_min", xnode::value_of(std::numeric_limits::min()), 326 | "uint_max", xnode::value_of(std::numeric_limits::max()) 327 | ); 328 | 329 | Assert(objIntLimits.get("int_max").get_as() == std::numeric_limits::max(), 330 | "int_max should have max integer value"); 331 | Assert(objIntLimits.get("int_min").get_as() == std::numeric_limits::min(), 332 | "int_min should have min integer value"); 333 | Assert(objIntLimits.get("uint_max").get_as() == std::numeric_limits::max(), 334 | "uint_max should have max unsigned integer value"); 335 | 336 | // Test with numeric limits for floating point types 337 | auto objFloatLimits = xobject::of( 338 | "float_max", xnode::value_of(std::numeric_limits::max()), 339 | "float_min", xnode::value_of(std::numeric_limits::min()), 340 | "double_max", xnode::value_of(std::numeric_limits::max()) 341 | ); 342 | 343 | Assert(objFloatLimits.get("float_max").get_as() == std::numeric_limits::max(), 344 | "float_max should have max float value"); 345 | Assert(objFloatLimits.get("float_min").get_as() == std::numeric_limits::min(), 346 | "float_min should have min float value"); 347 | Assert(objFloatLimits.get("double_max").get_as() == std::numeric_limits::max(), 348 | "double_max should have max double value"); 349 | 350 | // Test with special values 351 | auto objSpecial = xobject::of( 352 | "inf", xnode::value_of(std::numeric_limits::infinity()), 353 | "nan", xnode::value_of(std::numeric_limits::quiet_NaN()), 354 | "epsilon", xnode::value_of(std::numeric_limits::epsilon()) 355 | ); 356 | 357 | Assert(std::isinf(objSpecial.get("inf").get_as()), 358 | "inf should be recognized as infinity"); 359 | Assert(std::isnan(objSpecial.get("nan").get_as()), 360 | "nan should be recognized as NaN"); 361 | Assert(objSpecial.get("epsilon").get_as() == std::numeric_limits::epsilon(), 362 | "epsilon should have correct value"); 363 | } 364 | 365 | int xobject_test() { 366 | TEST_PROLOG(); 367 | TEST_FUNC(PropertyListPutGet); 368 | TEST_FUNC(PropertyListKeysInOrder); 369 | TEST_FUNC(PropertyListValuesInOrder); 370 | TEST_FUNC(PropertyListSum); 371 | TEST_FUNC(PropertyListKeysNoCopy); 372 | TEST_FUNC(OptionalNamedParams); 373 | TEST_FUNC(DefNamedParams); 374 | TEST_FUNC(StaticOfMethod); 375 | TEST_FUNC(Iterators); 376 | TEST_EPILOG(); 377 | } 378 | 379 | int main() 380 | { 381 | return xobject_test(); 382 | } 383 | --------------------------------------------------------------------------------