diff --git a/.github/DockerClang b/.github/DockerClang new file mode 100644 index 0000000..7d7bdd9 --- /dev/null +++ b/.github/DockerClang @@ -0,0 +1,25 @@ +FROM ubuntu:latest + +WORKDIR / +RUN apt-get update +RUN apt-get install -y cmake python3 ninja-build git lsb-release software-properties-common wget binutils gcc g++ +#RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" +RUN bash -c "\ + set -ex && \ + git clone -b p2996 https://github.com/Yaraslaut/clang-p2996.git && \ + cmake -S /clang-p2996/llvm \ + -B build \ + -G Ninja \ + #-D CMAKE_CXX_COMPILER=clang++-18 \ + #-D CMAKE_C_COMPILER=clang-18 \ + -D CMAKE_BUILD_TYPE=Release \ + -D CMAKE_INSTALL_PREFIX=\/usr \ + -D LLVM_ENABLE_RUNTIMES='libcxx;libcxxabi;libunwind'\ + -D LLVM_ENABLE_PROJECTS=clang\ + -D LLVM_TARGETS_TO_BUILD=X86 && \ + cmake --build build --parallel && \ + cmake --build build --target install && \ + echo "/usr/lib/x86_64-unknown-linux-gnu" >> /etc/ld.so.conf.d/x86_64-linux-gnu.conf && \ + ldconfig && \ + rm -rf /build && \ + rm -rf /clang-p2996" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ffe6b3b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: Build + +on: + push: + branches: + - main + pull_request: + branches: + - main + + +jobs: + build: + name: "Build" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + with: + platforms: all + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + with: + version: latest + - name: Build + shell: bash + run: | + docker buildx build \ + --tag reflection_test \ + --progress=plain \ + --load . diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9981018 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.25) + + +project(form + VERSION 1.0 + DESCRIPTION "Form" + LANGUAGES CXX +) + +set(CMAKE_CXX_STANDARD 26) +set(CMAKE_CXX_FLAGS "-freflection -stdlib=libc++ ") + +file( + DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.38.3/CPM.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake + EXPECTED_HASH SHA256=cc155ce02e7945e7b8967ddfaff0b050e958a723ef7aad3766d368940cb15494 +) +include(${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake) + + + +CPMAddPackage("gh:jbeder/yaml-cpp#yaml-cpp-0.6.3@0.6.3") +CPMAddPackage("gh:contour-terminal/boxed-cpp#master") + +add_library(form INTERFACE) +target_include_directories(form INTERFACE + $ + $ +) +target_link_libraries(form INTERFACE yaml-cpp boxed-cpp) + +add_subdirectory(src) + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..715667c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# docker build . -t reflection --progress=plain +# clang++ -std=c++26 -freflection -stdlib=libc++ + +FROM yaraslaut/clang-p2996:latest + +COPY . /mnt/src +WORKDIR /mnt/src +RUN cmake -S . -B build -G Ninja -D CMAKE_CXX_COMPILER=clang++ +RUN cmake --build build --verbose +RUN ./build/src/test +RUN ./build/src/example diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..42d4570 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +# form + +> Collection of static reflection usage examples + +Collection utilize existing c++26 reflection support from [clang-p2996](https://github.com/bloomberg/clang-p2996/tree/p2996) + +To test it you can use provided Dockerfile to get compiler and build project + +``` sh +docker build . --progress=plain +``` + +Some additional examples can be found in [proposal](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2996r3.html) + +## Create variant of all types inside namespace + +``` c++ +namespace list { +struct CancelSelection {}; +struct ClearHistoryAndReset {}; +} // namespace list +using list_variant = [:form::util::create_variant(^list):]; +``` + +## Enum/Variant to string + +``` c++ + +enum class Color { red, green, blue }; + +void VariantToString() { + list_variant v{list::CancelSelection{}}; + std::println("{}", form::variant_type_to_string(v)); // CancelSelection +} + +void EnumToString() { + std::println("{}", form::enum_to_string(Color::red)); // red +} + +``` + +## Universal formatter + +This will create formatter for any type that is not specified by the standard library. + +``` c++ + +struct S { + unsigned i : 2, j : 6; +}; + +struct X { + int m1 = 1; +}; + +struct Y { + int m2 = 2; +}; + +class Z : public X, private Y { + int m3 = 3; + int m4 = 4; +}; + + +template +struct std::formatter : form::universal_formatter {}; + +int main() { + std::println("{}", Z()); // Z{X{.m1=1}, Y{.m2=2}, .m3=3, .m4=4} +} + + +``` + + + +## Serialization into different formats +```c++ + +using ColumnCount = boxed::boxed; +using LineCount = boxed::boxed; + +struct PageSize { + LineCount lines; + ColumnCount columns; +}; + +struct Config { + bool live{false}; + int v{90}; + double b{90.0}; + PageSize page_size{LineCount{10}, ColumnCount{10}}; +}; + + +void SerializationIntoDifferentFormats() { + Config c; + std::println("===== JSON ====="); + std::println("{}", form::format_json(c)); + std::println("===== YAML ====="); + std::println("{}", form::format_yaml(c)); +} + +/* +===== JSON ===== +{"live":false,"v":90,"b":90,page_size: {lines: {"value":10},columns: {"value":10}}} +===== YAML ===== +live: "false" +v: "90" +b: "90" +page_size: + lines: + value: "10" + columns: + value: "10" +*/ +``` + + +## Deserialization into different formats + +Only YAML supported at the moment + +``` c++ + +int main() { + Config c; + + auto yaml_input = form::format_yaml(c); + + auto from_yaml = form::from_yaml(yaml_input); + + return form::compare(c,from_yaml); +} + +``` + +## Run all function from namespace in serial or parallel + +To run function in serial use `form::run_seq<^namespace>()` for parallel `form::run_par<^namespace>()` + +Example usage of similar technique for running tests +``` c++ + +namespace for_tests { +bool test_true() { return true; } +bool test_false() { return false; } +void test_void() {}; +} // namespace for_tests + +void runTests() { form::run_tests<^for_tests>(); } + +/* + 🔥 test_true + 💩 test_false + 🍀 test_void +*/ + +``` + +` diff --git a/include/form/form.h b/include/form/form.h new file mode 100644 index 0000000..3f82dfd --- /dev/null +++ b/include/form/form.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once +#include +#include
+#include +#include +#include +#include +#include + +namespace form { + +template constexpr std::string variant_type_to_string(E value) { + std::string out = ""; + [:util::expand(template_arguments_of(^E)):] >> [&] { + if (auto *pval = std::get_if(&value)) { + std::format_to(std::back_inserter(out), "{}", util::name_of(e)); + } + }; + return out; +} + +template + requires std::is_enum_v +constexpr std::string enum_to_string(E value) { + std::string result = ""; + [:util::expand(std::meta::enumerators_of(^E)):] >> [&] { + if (value == [:e:]) { + result = util::name_of(e); + } + }; + return result; +} + +template std::string format_yaml(T const &t) { + return format<^yaml>(t); +} + +template std::string format_json(T const &t) { + return format<^json>(t); +} + +template T from_yaml(auto input) { + auto node = YAML::Load(input); + T t; + from_yaml_node(node, t); + return t; +} + +template bool compare(T const &lhs, T const &rhs) { + bool result = true; + if constexpr (std::is_arithmetic_v) + return lhs == rhs; + if constexpr (std::is_class_v) + util::for_range<0, util::number_of_members()>([&]() { + constexpr auto mem = util::member_info(I); + if (!compare(lhs.[:mem:], rhs.[:mem:])) { + result = result && false; + } + }); + return result; +} + +template void run_par() { + std::vector pool; + [:util::expand(members_of(refl)):] >> [&] { + pool.emplace_back(std::thread([&]() { [:mem:](); })); + }; + + for (auto &t : pool) { + t.join(); + } +} + +template void run_seq() { + [:util::expand(members_of(refl)):] >> [&] { [:mem:](); }; +} + +template void run_tests() { + std::vector pool; + [:util::expand(members_of(refl) + ):] >> [&] + requires std::invocable + { + pool.emplace_back(std::thread([&]() { + std::string out; + if constexpr (std::is_same_v< + std::invoke_result_t, bool>) { + if ([:mem:]()) + std::format_to(std::back_inserter(out), "{}", "\N{FIRE}"); + else + std::format_to(std::back_inserter(out), "{}", "\N{PILE OF POO}"); + } else { + [:mem:](); + std::format_to(std::back_inserter(out), "{}", "\N{FOUR LEAF CLOVER}"); + } + std::format_to(std::back_inserter(out), "{}", util::name_of(mem)); + std::println(" {}", out); + })); + }; + + for (auto &t : pool) { + t.join(); + } +} + +template constexpr void print_members() { + std::println("Members of: {}", util::name_of(^T)); + [:util::expand(nonstatic_data_members_of(^T)):] >> [&] { + std::println("{}", util::name_of(mem)); + }; +} + +} // namespace form + +template <> struct std::formatter> { + template + constexpr ParseContext::iterator parse(ParseContext &ctx) { + return ctx.begin(); + } + template + FmtContext::iterator format(std::basic_string_view s, + FmtContext &ctx) const { + return format_to(ctx.out(), "{}", std::string(s.begin(), s.end())); + } +}; diff --git a/include/form/format.h b/include/form/format.h new file mode 100644 index 0000000..3cf97d0 --- /dev/null +++ b/include/form/format.h @@ -0,0 +1,157 @@ +#pragma once +#include +#include +#include + +namespace form { + +namespace yaml { + +int yaml_indent = 0; +auto withLevel(std::string const in, int indent) { + auto out = std::string(indent * 2, ' '); + out += in; + return out; +} +auto withLevel(std::string const in) { return withLevel(in, yaml_indent); } +auto withLevelKnown() { return withLevel("{}: \"{}\""); } +auto withLevelNested() { return withLevel("{}:\n{}"); } +void increase_level() { yaml_indent += 1; } +void decrease_level() { yaml_indent -= 1; } +constexpr auto delimiter() { return "\n"; } +constexpr auto start() { return ""; } +constexpr auto end() { return ""; } + +} // namespace yaml + +namespace json { + +constexpr auto delimiter() { return ","; } +constexpr auto start() { return "{"; } +constexpr auto end() { return "}"; } +auto withLevelKnown() { return "\"{}\":{}"; } +auto withLevelNested() { return "{}: {}"; } +void increase_level() {} +void decrease_level() {} + +} // namespace json + +template struct universal_formatter : std::formatter { + auto format(T const &t, auto &ctx) const { + auto out = std::format_to(ctx.out(), "{}{{", name_of(^T)); + + auto delim = [first = true, &out]() mutable { + if (!first) { + *out++ = ','; + *out++ = ' '; + } + first = false; + }; + + [:util::expand(bases_of(^T)):] >> [&] { + delim(); + out = std::format_to(out, "{}", (typename[:type_of(base):] const &)(t)); + }; + + [:util::expand(nonstatic_data_members_of(^T)):] >> [&] { + delim(); + out = std::format_to(out, ".{}={}", name_of(mem), + t.[:mem:]); + }; + + *out++ = '}'; + return out; + } +}; + +template std::string format(T const &t) { + std::string out; + + auto delim = [&, first = true]() mutable { + if (!first) { + std::format_to(std::back_inserter(out), [:refl:] ::delimiter()); + } + first = false; + }; + + std::format_to(std::back_inserter(out), "{}", [:refl:] ::start()); + util::for_range<0, util::number_of_base()>([&]() { + constexpr auto base = util::base_info(I); + if constexpr (is_accessible(base)) { + delim(); + std::format_to(std::back_inserter(out), std::runtime_format("{}"), + format(static_cast<[:type_of(base):] const &>(t))); + } + }); + + util::for_range<0, util::number_of_members()>([&]() { + constexpr auto mem = util::member_info(I); + constexpr auto name = util::name_of(mem); + constexpr auto type = type_of(mem); + delim(); + + // std::println("Handling member: {} with type: {}", name, + // name_of(type_of(mem))); + if constexpr (util::is_trivial_type<[:type_of(mem):]>) { + std::format_to(std::back_inserter(out), + std::runtime_format([:refl:] ::withLevelKnown()), name, + t.[:mem:]); + } else if constexpr (std::is_constructible_v< + std::formatter<[:type_of(mem):]>>) { + std::format_to(std::back_inserter(out), + std::runtime_format([:refl:] ::withLevelKnown()), name, + std::format("{}", t.[:mem:])); + } else { + std::format_to(std::back_inserter(out), + std::runtime_format([:refl:] ::withLevelNested()), name, + [&]() { + [:refl:] ::increase_level(); + return format(t.[:mem:]); + }()); + [:refl:] ::decrease_level(); + } + }); + std::format_to(std::back_inserter(out), "{}", [:refl:] ::end()); + + return out; +} + +template void from_yaml_node(YAML::Node const &node, T &t) { + + util::for_range<0, util::number_of_members()>([&]() { + constexpr auto mem = util::member_info(I); + auto name = std::string{util::name_of(mem)}; + // std::println("get value of: {}", name_of(mem)); + if constexpr (util::is_trivial_type<[:type_of(mem):]>) { + t.[:mem:] = node[name].template as<[:type_of(mem):]>(); + } else if constexpr (std::is_constructible_v< + std::formatter<[:type_of(mem):]>>) { + // std::println("get value from std::format {}", name); + constexpr auto type_of_mem = + util::template_arguments<[:type_of(mem):]>(0); + t.[:mem:] = util::construct_from<[:type_of(mem):]>( + node[name].template as<[:type_of_mem:]>()); + } else { + // std::println("diving into: {}", name); + from_yaml_node(node[name], t.[:mem:]); + } + }); + + util::for_range<0, util::number_of_base()>([&]() { + constexpr auto base = util::base_info(I); + if constexpr (is_accessible(base)) + util::for_range<0, util::number_of_members<[:type_of(base):]>()>( + [&]() { + constexpr auto mem = util::member_info<[:type_of(base):]>(II); + if constexpr (util::is_trivial_type<[:type_of(mem):]>) { + auto name = std::string{util::name_of(mem)}; + t.[:mem:] = node[name].template as<[:type_of(mem):]>(); + } else if constexpr (std::formattable<[:type_of(mem):], char>) { + } else { + from_yaml_node(node[util::name_of(mem)], t.[:mem:]); + } + }); + }); +} + +} // namespace form diff --git a/include/form/util.h b/include/form/util.h new file mode 100644 index 0000000..c444325 --- /dev/null +++ b/include/form/util.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include +#include +#include + +namespace form::util { + +consteval auto name_of(auto type) { return name_of(type); } + +template +concept is_trivial_type = + std::is_arithmetic::value || std::is_same_v; + +template constexpr void enumerate_types(F &&f) { + [&f](std::index_sequence) { + (f.template operator()(), ...); + }(std::index_sequence_for{}); +} + +template constexpr void for_values(F &&f) { + (f.template operator()(), ...); +} + +template constexpr void for_range(F &&f) { + using t = std::common_type_t; + + [&f](std::integer_sequence) { + for_values<(B + Xs)...>(f); + }(std::make_integer_sequence{}); +} + +template consteval auto member_info(int n) { + return nonstatic_data_members_of(^T)[n]; +} + +template consteval auto base_info(int n) { + return bases_of(^T)[n]; +} + +template consteval auto template_arguments(int n) { + return template_arguments_of(^T)[n]; +} + +template consteval auto number_of_members() { + return nonstatic_data_members_of(^T).size(); +} + +template consteval auto number_of_base() { + return bases_of(^T).size(); +} + +template +concept has_value_type = requires { typename T::value_type; }; + +template +concept has_inner_type = requires { typename T::inner_type; }; + +namespace __impl { +template struct replicator_type { + template constexpr void operator>>(F body) const { + (body.template operator()(), ...); + } +}; + +template replicator_type replicator = {}; +} // namespace __impl + +template consteval auto expand(R range) { + std::vector args; + for (auto r : range) { + args.push_back(reflect_value(r)); + } + return substitute(^__impl::replicator, args); +} + +template T construct_from(F f) { return T{f}; } + +/** + * + * reflection of namespace + * using variant_type = [:create_variant(^namespace):]; + * + **/ +consteval auto create_variant(auto reflection) { + + return substitute(^std::variant, std::vector{ + members_of(reflection)}); +} + +} // namespace form::util diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..51dd1d3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,7 @@ + + +add_executable(test test.cpp) +add_executable(example main.cpp) + +target_link_libraries(test form) +target_link_libraries(example form) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..32cf18e --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include + +using ColumnCount = boxed::boxed; +using LineCount = boxed::boxed; + +struct PageSize { + LineCount lines; + ColumnCount columns; +}; + +struct Config { + bool live{false}; + int v{90}; + double b{90.0}; + PageSize page_size{LineCount{10}, ColumnCount{10}}; +}; + +namespace list { +struct CancelSelection {}; +struct ClearHistoryAndReset {}; +} // namespace list +using list_variant = [:form::util::create_variant(^list):]; + +enum class Color { red, green, blue }; + +namespace for_tests { +bool test_true() { return true; } +bool test_false() { return false; } +void test_void() {}; +} // namespace for_tests + +namespace run { + +void SerializationIntoDifferentFormats() { + Config c; + std::println("===== JSON ====="); + std::println("{}", form::format_json(c)); + std::println("===== YAML ====="); + std::println("{}", form::format_yaml(c)); +} + +void VariantToString() { + list_variant v{list::CancelSelection{}}; + std::println("{}", form::variant_type_to_string(v)); +} + +void EnumToString() { std::println("{}", form::enum_to_string(Color::red)); } + +void runTests() { form::run_tests<^for_tests>(); } + +} // namespace run + +int main() { + form::run_seq<^run>(); + + return 0; +} diff --git a/src/test.cpp b/src/test.cpp new file mode 100644 index 0000000..13228cd --- /dev/null +++ b/src/test.cpp @@ -0,0 +1,9 @@ +#include "test.h" + +int main() { + form::run_tests<^form::tests>(); + form::run_tests<^form::tests>(); + form::run_tests<^form::round_trip_tests>(); + form::run_tests<^form::examples>(); + std::println("{}", Z()); +} diff --git a/src/test.h b/src/test.h new file mode 100644 index 0000000..f89d6e7 --- /dev/null +++ b/src/test.h @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once +#include +#include +#include +#include +#include + +struct S { + unsigned i : 2, j : 6; +}; + +struct X { + int m1 = 1; +}; + +struct Y { + int m2 = 2; +}; + +class Z : public X, private Y { + int m3 = 3; + int m4 = 4; +}; + +struct A { + int a; + int b; +}; + +struct AA { + A a; + A b; +}; + +template + requires std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v +struct std::formatter : form::universal_formatter {}; + +/// ColumnCount simply represents a number of columns. +using ColumnCount = boxed::boxed; + +/// LineCount represents a number of lines. +using LineCount = boxed::boxed; + +struct PageSize { + LineCount lines; + ColumnCount columns; +}; + +struct Config { + bool live{false}; + int v{90}; + double b{90.0}; + PageSize page_size{LineCount{10}, ColumnCount{10}}; +}; + +namespace documentation { + +template struct StringLiteral { + constexpr StringLiteral(const char (&str)[N]) : value{} { + std::copy_n(str, N, value); + } + + char value[N]; // NOLINT +}; + +constexpr StringLiteral Dummy{"{comment} Dummy config entry \n"}; + +} // namespace documentation + +template struct ConfigEntry { + using value_type = T; + + std::string documentation = doc.value; + constexpr ConfigEntry() : _value{} {} + //clang-format off + constexpr explicit ConfigEntry(T &&in) : _value{std::forward(in)} {} + + template + constexpr explicit ConfigEntry(F &&in) : _value{std::forward(in)} {} + //clang-format on + + [[nodiscard]] constexpr T const &value() const { return _value; } + [[nodiscard]] constexpr T &value() { return _value; } + + constexpr ConfigEntry &operator=(T const &value) { + _value = value; + return *this; + } + + constexpr ConfigEntry &operator=(T &&value) noexcept { + _value = std::move(value); + return *this; + } + + constexpr ConfigEntry(ConfigEntry const &) = default; + constexpr ConfigEntry &operator=(ConfigEntry const &) = default; + constexpr ConfigEntry(ConfigEntry &&) noexcept = default; + constexpr ConfigEntry &operator=(ConfigEntry &&) noexcept = default; + ~ConfigEntry() = default; + +private: + T _value; +}; + +template +struct std::formatter> { + using Entry = ConfigEntry; + template + constexpr ParseContext::iterator parse(ParseContext &ctx) { + return ctx.begin(); + } + + template + FmtContext::iterator format(Entry s, FmtContext &ctx) const { + return format_to(ctx.out(), "{}", s.value()); + } +}; + +template +struct std::formatter> { + using Boxed = boxed::detail::boxed; + constexpr auto parse(format_parse_context &ctx) { return ctx.begin(); } + + auto format(const Boxed &obj, format_context &ctx) const { + return std::format_to(ctx.out(), "{}", obj.value); + } +}; + +template void serialize(T const &val) { + std::println("================================== [Serialized] YAML"); + auto data_yaml = form::format_yaml(val); + std::println("{}", data_yaml); + + std::println("================================== [Serialized] JSON"); + auto data_json = form::format_json(val); + std::println("{}", data_json); +} + +template void deserialize(T const &val) { + std::println("================================== [Deserialized]"); + try { + auto data = form::format_yaml(val); + auto deserialized = form::from_yaml(data); + std::println("{}", form::format_yaml(deserialized)); + } catch (const std::exception &e) { + std::println("Failed"); + } +} + +template bool round_trip(T const &val) { + const auto data = form::format_yaml(val); + const auto deserialized = form::from_yaml(data); + return form::compare(val, deserialized); +} + +using StrongType = boxed::boxed; + +struct ConfigEntries { + ConfigEntry int_entry; + ConfigEntry double_entry; + // ConfigEntry strong_entry; +}; + +namespace detail { + +template struct CreateUniqueT; + +// clang-format off +template constexpr auto CreateClass() { + return define_class(^T, { + data_member_spec(^int,{.name = "i"}), + data_member_spec(^int, {.name = "j"}) + }); +} +// clang-format on + +} // namespace detail +namespace form::tests { + +void testZ() { + Z z; + z.m1 = -3; + serialize(z); + deserialize(z); +} + +void testPrintMembers() { form::print_members(); } + +void testCreateClass() { + constexpr auto cls = detail::CreateClass>(); + form::print_members<[:cls:]>(); +} + +void testAA() { + AA aa; + aa.a.a = 123; + aa.a.b = 0; + aa.b.a = 0; + aa.b.b = 321; + serialize(aa); + deserialize(aa); +} + +void testPageSize() { + PageSize ps; + ps.lines = LineCount{5}; + ps.columns = ColumnCount{7}; + serialize(ps); + deserialize(ps); +} + +void testConfig() { + Config config; + config.b = 3.1; + config.page_size = PageSize{LineCount{5}, ColumnCount{7}}; + serialize(config); + deserialize(config); +} + +void testConfigEntry() { + ConfigEntries entries; + entries.int_entry = 1; + entries.double_entry = 1.1; + // entries.strong_entry = StrongType(3); + serialize(entries); + deserialize(entries); +} + +void testSJson() { + S s; + s.i = 1; + s.j = 2; + std::println("{}", form::format_json(s)); +} +} // namespace form::tests + +namespace form::round_trip_tests { + +bool compareTrue() { + return form::compare(3.0, 3.0) && form::compare(3, 3) && + form::compare("a", "a"); +} + +bool compareFalse() { + bool res = form::compare(3.0, 3.1) || + form::compare(3, 4); //|| form::compare("a", "b"); + return res == false; +} + +bool compareS() { + S s; + s.i = 1; + s.j = 2; + S s2; + return compare(s, s2) == false; +} + +bool compareX() { + X x; + X x2; + return compare(x, x2) == true; +} + +bool compareA() { + A a; + a.a = 1; + a.b = 2; + A a2; + return compare(a, a2) == false; +} + +bool compareAA() { + AA aa; + aa.a.a = 123; + aa.a.b = 321; + AA aa2; + return compare(aa, aa2) == false; +} + +bool SRoundTrip() { + S s; + s.i = 1; + s.j = 2; + return round_trip(s); +} + +bool XRoundTrip() { + X x; + return round_trip(x); +} + +bool YRoundTrip() { + Y y; + return round_trip(y); +} + +bool ZRoundTrip() { + Z z; + z.m1 = -3; + return round_trip(z); +} + +bool ARoundTrip() { + A a; + a.a = 1; + a.b = 2; + return round_trip(a); +} + +bool ConfigRoundTrip() { + Config config; + config.b = 3.1; + config.page_size = PageSize{LineCount{5}, ColumnCount{7}}; + return round_trip(config); +} + +} // namespace form::round_trip_tests + +namespace list { +struct CancelSelection {}; +struct ClearHistoryAndReset {}; +} // namespace list +using list_variant = [:form::util::create_variant(^list):]; + +enum class Color { red, green, blue }; + +namespace form::examples { +bool VariantCreate() { + list_variant v{list::CancelSelection{}}; + return std::holds_alternative(v); +} + +bool VariantToString() { + list_variant v{list::CancelSelection{}}; + auto variant_name = form::variant_type_to_string(v); + return "CancelSelection" == variant_name; +} + +bool EnumToString() { return form::enum_to_string(Color::red) == "red"; } +} // namespace form::examples + +void ExampleConfig() { + std::println("{}", form::format_yaml([]() { + Config c; + c.b = 3.1; + c.v = 10; + c.page_size = PageSize(LineCount(3), ColumnCount(5)); + return c; + }())); + /* + live: false + v: 10 + b: 3.1 + page_size: + lines: + value: 3 + columns: + value: 5 + */ +}