diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e20916..44685c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,10 +99,21 @@ jobs: - name: Test working-directory: ${{ steps.strings.outputs.build-output-dir }} run: | + examples=( + oxide_match_example + oxide_functional_example + oxide_covariant_dispatch_example + oxide_vector_example + oxide_database_example + ) if [ "${{ runner.os }}" == "Windows" ]; then - ./${{ matrix.build_type }}/oxide_examples.exe + for ex in "${examples[@]}"; do + ./${{ matrix.build_type }}/$ex.exe || exit 1 + done else - ./oxide_examples + for ex in "${examples[@]}"; do + ./$ex || exit 1 + done fi shell: bash @@ -117,7 +128,7 @@ jobs: - name: Create Release and Upload oxide.hpp uses: softprops/action-gh-release@v2 with: - files: oxide.hpp + files: include/oxide.hpp name: RELEASE ${{ github.ref_name }} draft: false prerelease: false diff --git a/CMakeLists.txt b/CMakeLists.txt index ae47ef5..26d62c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ # DEALINGS IN THE SOFTWARE. cmake_minimum_required(VERSION 3.21) -project(oxide VERSION 1.0.1 LANGUAGES CXX) +project(oxide VERSION 1.0.2 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -27,28 +27,55 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(oxide INTERFACE) target_include_directories(oxide INTERFACE - $ + $ $ ) target_compile_features(oxide INTERFACE cxx_std_23) +if(MSVC) + target_compile_options(oxide INTERFACE /EHsc) +endif() + +target_compile_definitions(oxide INTERFACE + OXIDE_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} + OXIDE_VERSION_MINOR=${PROJECT_VERSION_MINOR} + OXIDE_VERSION_PATCH=${PROJECT_VERSION_PATCH} + OXIDE_VERSION="${PROJECT_VERSION}" +) + # Define installation directory for headers set(INCLUDE_INSTALL_DIR include) -# Create examples executable and link it to oxide -add_executable(oxide_examples examples.cpp) -target_link_libraries(oxide_examples oxide) +# Match example +add_executable(oxide_match_example examples/match.cpp) +target_link_libraries(oxide_match_example oxide) + +# Functional example +add_executable(oxide_functional_example examples/functional.cpp) +target_link_libraries(oxide_functional_example oxide) + +# Covariant dispatch example +add_executable(oxide_covariant_dispatch_example examples/covariant_dispatch.cpp) +target_link_libraries(oxide_covariant_dispatch_example oxide) + +# Vector example +add_executable(oxide_vector_example examples/vector.cpp) +target_link_libraries(oxide_vector_example oxide) + +# Database example +add_executable(oxide_database_example examples/database.cpp) +target_link_libraries(oxide_database_example oxide) install(TARGETS oxide - EXPORT oxideTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include + EXPORT oxideTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include ) -install(FILES oxide.hpp DESTINATION include) +install(FILES include/oxide.hpp DESTINATION include) install(EXPORT oxideTargets FILE oxideTargets.cmake diff --git a/README.md b/README.md index 5790850..dea5fab 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,10 @@ refer to the `examples.cpp` file or explorer the fully working examples below. * This resembles standard C++ operator overloading and is more intuitive. ```cpp +#include #include #include #include -#include struct Quit {}; struct Move { int x, y; }; @@ -71,10 +71,10 @@ int main() { ### Functional Example ```cpp +#include #include #include #include -#include struct Quit {}; struct Move { int x, y; }; @@ -135,10 +135,10 @@ oxide::Option> get_coordinates(const Message& msg) { (with optional settings affecting processing) ```cpp +#include #include #include #include -#include struct Quit {}; struct Move { int x, y; }; @@ -203,8 +203,8 @@ int main() { ### Results, Errors and Chaining ```cpp -#include #include +#include int main() { using oxide::Result; @@ -243,9 +243,9 @@ int main() { ### Database Example (Vec) ```cpp +#include #include #include -#include struct Insert { std::string key; int value; }; struct Update { std::string key; int new_value; }; @@ -369,8 +369,8 @@ int main() { ### Covariant Dispatch Example ```cpp -#include #include +#include // Define shape types (no inheritance) struct Circle { diff --git a/examples/covariant_dispatch.cpp b/examples/covariant_dispatch.cpp new file mode 100644 index 0000000..cf5cbbc --- /dev/null +++ b/examples/covariant_dispatch.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2025 Igal Alkon and ALKONTEK + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include + +#include + +// Define shape types (no inheritance) +struct Circle { + double radius = 1.0; + + [[nodiscard]] double area() const { + return 3.14159 * radius * radius; + } +}; + +struct Rectangle { + double width = 7.0, height = 14.0; + + [[nodiscard]] double perimeter() const { + return 2 * (width + height); + } +}; + +// Create a discriminated union for shapes +using ShapeVariant = oxide::Union; + +// Clone function using pattern matching on the Union +ShapeVariant clone(const ShapeVariant& shape) { + return std::visit(oxide::overloaded{ + [](const Circle& c) -> ShapeVariant { return Circle{c.radius}; }, + [](const Rectangle& r) -> ShapeVariant { return Rectangle{r.width, r.height}; } + }, shape); +} + +int main() { + constexpr ShapeVariant circle = Circle{}; + auto cloned_circle = clone(circle); + + constexpr ShapeVariant rectangle = Rectangle{}; + auto cloned_rectangle = clone(rectangle); + + // Use oxide's match and operator>> to polymorphically compute and print properties (akin to covariant dispatch) + cloned_circle >> oxide::match{ + [](const Circle& c) { std::cout << "Cloned Circle area: " << c.area() << std::endl; }, + [](const Rectangle& r) { std::cout << "Cloned Rectangle perimeter: " << r.perimeter() << std::endl; } + }; + + cloned_rectangle >> oxide::match{ + [](const Circle& c) { std::cout << "Cloned Circle area: " << c.area() << std::endl; }, + [](const Rectangle& r) { std::cout << "Cloned Rectangle perimeter: " << r.perimeter() << std::endl; } + }; + + return 0; +} diff --git a/examples/database.cpp b/examples/database.cpp new file mode 100644 index 0000000..87384e9 --- /dev/null +++ b/examples/database.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2025 Igal Alkon and ALKONTEK + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include + +#include +#include + + +struct Insert { std::string key; int value; }; +struct Update { std::string key; int new_value; }; +struct Delete { std::string key; }; +struct Select { std::string key; std::function callback; }; +struct Noop {}; + +// Your message types (can be one of these) +using Operation = oxide::Union; + +int main() { + oxide::Vec> db; + + // Demonstrate push() + for (int i = 0; i < 100; ++i) { + db.push({"user" + std::to_string(i), i * 10}); + } + + // Demonstrate len() + std::cout << "Initial size: " << db.len() << "\n"; + + // Demonstrate capacity() + std::cout << "Capacity: " << db.capacity() << "\n"; + + // Demonstrate get() and mutable access + if (const auto record = db.get(0)) { + std::cout << "First record: " << record->get().first << " -> " << record->get().second << "\n"; + record->get().second = 999; // Modify + } + + // Demonstrate iter() const + std::cout << "First 5 records: "; + size_t count = 0; + for (const auto& [key, val] : db.iter()) { + if (count >= 5) break; + std::cout << key << ":" << val << " "; + ++count; + } + std::cout << "\n"; + + // Demonstrate reserve() and shrink_to_fit() + db.reserve(10000); + std::cout << "After reserve(10000), capacity: " << db.capacity() << "\n"; + db.shrink_to_fit(); + std::cout << "After shrink_to_fit, capacity: " << db.capacity() << "\n"; + + // Message examples with pattern matching for database operations + const Operation op1 = Insert{"user100", 1000}; + const Operation op2 = Update{"user50", 500}; + const Operation op3 = Delete{"user25"}; + const Operation op4 = Select{ + "user75", + [](const int value){ + std::cout << "Queried value: " << value << "\n"; + }}; + const Operation op5 = Noop{}; + + // Demonstrate handler using oxide::match{} + auto db_handler = [&](const Operation& msg) { + msg >> oxide::match{ + [&](const Insert& ins) { + db.push({ins.key, ins.value}); + std::cout << "Inserted: " << ins.key << " -> " << ins.value << "\n"; + }, + [&](const Update& upd) { + bool found = false; + for (auto& [key, val] : db.iter_mut()) { + if (key == upd.key) { + val = upd.new_value; + std::cout << "Updated: " << upd.key << "=" << upd.new_value << "\n"; + found = true; + break; + } + } + if (!found) std::cout << "Update failed: key not found\n"; + }, + [&](const Delete& del) { + bool found = false; + for (size_t i = 0; i < db.len(); ++i) { + if (const auto rec = db.get(i); rec && rec->get().first == del.key) { + auto [key, val] = db.remove(i); + std::cout << "Deleted: " << key << "=" << val << "\n"; + found = true; + break; + } + } + if (!found) std::cout << "Delete failed: key not found\n"; + }, + [&](const Select& sel) { + bool found = false; + for (const auto& [key, val] : db.iter()) { + if (key == sel.key) { + sel.callback(val); + found = true; + break; + } + } + if (!found) std::cout << "Select failed: key not found\n"; + }, + [&](const Noop&) { + std::cout << "No operation performed\n"; + } + }; + }; + + // Process operations + db_handler(op1); + db_handler(op2); + db_handler(op3); + db_handler(op4); + db_handler(op5); + + // Final state + std::cout << "Final database size: " << db.len() << "\n"; + std::cout << "Is empty: " << (db.is_empty() ? "true" : "false") << "\n"; + + return 0; +} diff --git a/examples/functional.cpp b/examples/functional.cpp new file mode 100644 index 0000000..ffc68ec --- /dev/null +++ b/examples/functional.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2025 Igal Alkon and ALKONTEK + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include + +#include +#include + +// Functional usage example +int main() { + using namespace oxide; + + auto divide = [](const int a, const int b) -> Result { + if (b == 0) return std::unexpected("Division by zero"); + return a / b; + }; + + Result ok_res = divide(84, 2); + Result err_res = divide(84, 0); + + // Built-in methods: has_value() like is_ok, value() like unwrap (throws if error), error(), value_or(default) + if (ok_res.has_value()) { + std::cout << "Ok: " << ok_res.value() << "\n"; + } else { + std::cout << "Err: " << ok_res.error() << "\n"; + } + + // Monadic chaining (Rust-like map/and_then) - capture divide by reference + const auto chained = ok_res.and_then([÷](const int val) -> Result { + return divide(val, 3); // Now divide is accessible via capture + }).transform([](const int val) { return val * 2; }); // Maps success value + + if (chained.has_value()) { + std::cout << "Chained Ok: " << chained.value() << "\n"; + } + + // For err_res, similar handling or or_else for recovery + const auto recovered = err_res.or_else([](const std::string&) -> Result { + return 0; // Recover from error + }); + std::cout << "Recovered: " << recovered.value_or(-1) << "\n"; + + return 0; +} diff --git a/examples.cpp b/examples/match.cpp similarity index 68% rename from examples.cpp rename to examples/match.cpp index 28652f4..f41e9d9 100644 --- a/examples.cpp +++ b/examples/match.cpp @@ -40,9 +40,9 @@ Message write(std::string text) { return Message{Write{std::move(text)}}; } Message read(std::function callback) { return Message{Read{std::move(callback)}}; } // Example helper functions -oxide::Option> get_coordinates(const Message& msg); +static oxide::Option> get_coordinates(const Message& msg); -// Usage example +// Match usage example int main() { namespace ox = oxide; @@ -80,7 +80,7 @@ int main() { }; // Convert to vector for the find function - ox::Vec msg_vec(msgs_functional.begin(), msgs_functional.end()); + const ox::Vec msg_vec(msgs_functional.begin(), msgs_functional.end()); // Is 'Move' message predicate auto is_move_predicate = [](const Message& msg){ @@ -121,78 +121,6 @@ int main() { process_with_context(msg); } - using ox::Result; - - // Rust-like example with std::expected (built-in monadic ops: and_then, transform, etc.) - auto divide = [](const int a, const int b) -> Result { - if (b == 0) return std::unexpected("Division by zero"); - return a / b; - }; - - Result ok_res = divide(84, 2); - Result err_res = divide(84, 0); - - // Built-in methods: has_value() like is_ok, value() like unwrap (throws if error), error(), value_or(default) - if (ok_res.has_value()) { - std::cout << "Ok: " << ok_res.value() << "\n"; - } else { - std::cout << "Err: " << ok_res.error() << "\n"; - } - - // Monadic chaining (Rust-like map/and_then) - capture divide by reference - const auto chained = ok_res.and_then([÷](const int val) -> Result { - return divide(val, 3); // Now divide is accessible via capture - }).transform([](const int val) { return val * 2; }); // Maps success value - - if (chained.has_value()) { - std::cout << "Chained Ok: " << chained.value() << "\n"; - } - - // For err_res, similar handling or or_else for recovery - const auto recovered = err_res.or_else([](const std::string&) -> Result { - return 0; // Recover from error - }); - std::cout << "Recovered: " << recovered.value_or(-1) << "\n"; - - // Vector - oxide::Vec v{1, 2, 3}; - - // len() - std::cout << "Length: " << v.len() << "\n"; - - // pop() - if (auto popped = v.pop()) { - std::cout << "Popped: " << *popped << "\n"; - } - std::cout << "New length: " << v.len() << "\n"; - - auto empty_pop = v.pop(); - while (empty_pop) { empty_pop = v.pop(); } - - if (auto none = v.pop(); !none) { - std::cout << "Empty pop: None\n"; - } - - // get() - v = {10, 20}; - - if (auto val = v.get(0)) { - std::cout << "Get[0]: " << val->get() << "\n"; // Outputs: Get[0]: 10 (val is Option>) - val->get() = 100; // Mutable access (modifies v[0]) - } - - if (v.get(99)) { - // Won't reach here - } else { - std::cout << "Get[99]: None\n"; - } - - // Get contact reference - const oxide::Vec cv{100, 200}; - if (auto val = cv.get(0)) { - std::cout << "Const get[0]: " << val->get() << "\n"; - } - return 0; } diff --git a/examples/vector.cpp b/examples/vector.cpp new file mode 100644 index 0000000..8b6b546 --- /dev/null +++ b/examples/vector.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 Igal Alkon and ALKONTEK + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include + +#include +#include + +// Vector usage example +int main() { + using namespace oxide; + + Vec v{1, 2, 3}; + + // len() + std::cout << "Length: " << v.len() << "\n"; + + // pop() + if (const auto popped = v.pop()) { + std::cout << "Popped: " << *popped << "\n"; + } + std::cout << "New length: " << v.len() << "\n"; + + auto empty_pop = v.pop(); + while (empty_pop) { empty_pop = v.pop(); } + + if (const auto none = v.pop(); !none) { + std::cout << "Empty pop: None\n"; + } + + // get() + v = {10, 20}; + + if (const auto val = v.get(0)) { + std::cout << "Get[0]: " << val->get() << "\n"; // Outputs: Get[0]: 10 (val is Option>) + val->get() = 100; // Mutable access (modifies v[0]) + } + + if (v.get(99)) { + // Won't reach here + } else { + std::cout << "Get[99]: None\n"; + } + + // Get contact reference + const Vec cv{100, 200}; + if (const auto val = cv.get(0)) { + std::cout << "Const get[0]: " << val->get() << "\n"; + } + + return 0; +} diff --git a/oxide.hpp b/include/oxide.hpp similarity index 99% rename from oxide.hpp rename to include/oxide.hpp index f72a3b5..398f9a0 100644 --- a/oxide.hpp +++ b/include/oxide.hpp @@ -23,10 +23,6 @@ #ifndef OXIDE_HPP #define OXIDE_HPP -#define OXIDE_VERSION_MAJOR 1 -#define OXIDE_VERSION_MINOR 0 -#define OXIDE_VERSION_PATCH 1 - #include #include #include