diff --git a/xo-reflect/.github/workflows/cmake-single-platform.yml b/xo-reflect/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..49c1a81c --- /dev/null +++ b/xo-reflect/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,117 @@ +name: xo-reflect ubuntu build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Configure self (reflect) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (reflect) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Test self (reflect) + working-directory: ${{github.workspace}}/build_reflect + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} diff --git a/xo-reflect/.gitignore b/xo-reflect/.gitignore new file mode 100644 index 00000000..3d3a7826 --- /dev/null +++ b/xo-reflect/.gitignore @@ -0,0 +1,8 @@ +# emacs workspace config +.projectile +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/xo-reflect/CMakeLists.txt b/xo-reflect/CMakeLists.txt new file mode 100644 index 00000000..be7c1d7f --- /dev/null +++ b/xo-reflect/CMakeLists.txt @@ -0,0 +1,29 @@ +# xo-reflect/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(reflect VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(src/reflect) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/xo-reflect/FILESYSTEM b/xo-reflect/FILESYSTEM new file mode 100644 index 00000000..9af86a7a --- /dev/null +++ b/xo-reflect/FILESYSTEM @@ -0,0 +1 @@ +repo -- git submoduules here diff --git a/xo-reflect/README.md b/xo-reflect/README.md new file mode 100644 index 00000000..d39421b6 --- /dev/null +++ b/xo-reflect/README.md @@ -0,0 +1,55 @@ +# reflection library + +## Getting Started + +### build + install `xo-cmake` dependency + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. + +### build + install other XO dependencies +``` +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-refnct +$ xo-build --clone --configure --build --install xo-subsys +$ xo-build --clone --configure --build --install xo-reflectutil +``` + +Note: can use `-n` to dry-run here + +### copy `xo-reflect` repository locally +``` +$ xo-build --clone xo-reflect +``` + +or equivalently +``` +$ git clone git@github.com:Rconybea/xo-reflect.git +``` + +### build + install `xo-reflect` +``` +$ xo-build --configure --build --install xo-reflect +``` + +or equivalently: + +``` +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-reflect -B xo-reflect/.build +$ cmake --build xo-reflect/.build +$ cmake --install xo-reflect/.build +``` + +### build for unit test coverage +``` +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-reflect/.build-ccov +$ cmake --build xo-reflect/.build-ccov +``` + +### LSP support +``` +$ cd xo-reflect +$ ln -s .build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` diff --git a/xo-reflect/cmake/reflectConfig.cmake.in b/xo-reflect/cmake/reflectConfig.cmake.in new file mode 100644 index 00000000..ce449a35 --- /dev/null +++ b/xo-reflect/cmake/reflectConfig.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(refcnt) +find_dependency(indentlog) +find_dependency(subsys) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-reflect/cmake/run-external-ctest b/xo-reflect/cmake/run-external-ctest new file mode 100755 index 00000000..386c45a4 --- /dev/null +++ b/xo-reflect/cmake/run-external-ctest @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# $1 = build directory + +cd $1 +shift + +ctest "${@}" diff --git a/xo-reflect/cmake/xo-bootstrap-macros.cmake b/xo-reflect/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-reflect/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-reflect/include/xo/reflect/CMakeLists.txt b/xo-reflect/include/xo/reflect/CMakeLists.txt new file mode 100644 index 00000000..a7e07dd6 --- /dev/null +++ b/xo-reflect/include/xo/reflect/CMakeLists.txt @@ -0,0 +1,32 @@ +# reflect/CMakeLists.txt + +set(SELF_LIBRARY_NAME reflect) + +# build shared library 'reflect' +add_library(${SELF_LIBRARY_NAME} SHARED TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) + +set_target_properties(${SELF_LIBRARY_NAME} PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1 + PUBLIC_HEADER TypeDescr.hpp) + +# ---------------------------------------------------------------- +# all the errors+warnings! +# +#target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_include_options(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +# ---------------------------------------------------------------- +# 3rd party dependency: boost: + +#xo_boost_dependency(${SELF_LIBRARY_NAME}) + +xo_install_library(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/xo-reflect/include/xo/reflect/EstablishTypeDescr.hpp b/xo-reflect/include/xo/reflect/EstablishTypeDescr.hpp new file mode 100644 index 00000000..0f18d84a --- /dev/null +++ b/xo-reflect/include/xo/reflect/EstablishTypeDescr.hpp @@ -0,0 +1,76 @@ +/* file EstablishTypeDescr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" + +namespace xo { + namespace reflect { + /** @class EstablishTypeDescr + * @brief class to establish globally-unique TypeDescr object for a type T + * + * We don't require the full definition of T to use EstablishTypeDescr::establish(). + * In particular, a forward declaration is sufficient. + * + * Additional information (that depends on full definition) may be attached later, + * by assigning (once-only) to @ref TypeDescr::tdextra_ + * + * @note Application code will use @ref Reflect::require; that in turn relies on the + * template @ref EstablishTdx to leverage template pattern-matching for + * recurring patterns. + **/ + class EstablishTypeDescr { + public: + /* implementation method; expect this to be used only within reflect/ library. + * avoids some otherwise-cyclic #include paths + * between specialized headers such as vector/VectorTdx.hpp and this + * EstablishTypeDescr.hpp + */ +#ifdef OBSOLETE + template + static TaggedPtr establish_tp(T * x) { return TaggedPtr(establish(), x); } +#endif + template + static TaggedPtr establish_most_derived_tp(T * x) { return establish()->most_derived_self_tp(x); } + + template + static TypeDescrW establish() { + TypeDescrW td = TypeDescrBase::require(&typeid(T), + std::string(type_name()), + nullptr /*tdextra*/, + nullptr /*invoker*/); + +#ifdef NOT_USING + std::function to_self_tp; + + if (std::is_base_of_v) { + /* T is a descendant of SelfTagging (or T = SelfTagging); + * use SelfTagging.self_tp() + */ + to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; + } else { + /* T is not a descendant of SelfTagging. + * want to return + */ + to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; + } + + td->assign_to_self_tp(to_self_tp); +#endif + return td; + } + }; /*EstablishTypeDescr*/ + + template + inline TaggedPtr establish_most_derived_tp(T * x) { + return EstablishTypeDescr::establish_most_derived_tp(x); + } + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end EstablishTypeDescr.hpp */ diff --git a/xo-reflect/include/xo/reflect/Metatype.hpp b/xo-reflect/include/xo/reflect/Metatype.hpp new file mode 100644 index 00000000..549d098e --- /dev/null +++ b/xo-reflect/include/xo/reflect/Metatype.hpp @@ -0,0 +1,41 @@ +/* @file Metatype.hpp */ + +#pragma once + +#include + +namespace xo { + namespace reflect { + enum class Metatype { mt_invalid, mt_atomic, mt_pointer, mt_vector, mt_struct, mt_function }; + + inline std::ostream & operator<<(std::ostream & os, + Metatype x) { + switch(x) { + case Metatype::mt_invalid: + os << "invalid!"; + break; + case Metatype::mt_atomic: + os << "atomic"; + break; + case Metatype::mt_pointer: + os << "pointer"; + break; + case Metatype::mt_vector: + os << "vector"; + break; + case Metatype::mt_struct: + os << "struct"; + break; + case Metatype::mt_function: + os << "function"; + break; + default: + os << "???"; + } + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Metatype.hpp */ diff --git a/xo-reflect/include/xo/reflect/Object.hpp b/xo-reflect/include/xo/reflect/Object.hpp new file mode 100644 index 00000000..91a5cedd --- /dev/null +++ b/xo-reflect/include/xo/reflect/Object.hpp @@ -0,0 +1,42 @@ +/** @file Object.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +//#include + +namespace xo { + namespace reflect { + /** @class Object + * + * @brief A swiss-army-knife base class for runtime polymorphism. + * + * Promote using this: + * - for interpreter integration (see xo-expression / xo-jit) + * - to allow reasonably efficient type dispatching - + * don't need to pay for a function call to find out dispatching type. + **/ + class Object : public reflect::SelfTagging { + public: + Object(TypeId type_id) : type_id_{type_id} {} + + private: + /** unique id number for this object's type + * + * Caches the value of this->self_tp().td()->id() + * + * Notes: + * 1. may want to record metatype also + * 2. a few builtin types have well-known type_ids. + * see TypeDescrTable ctor in xo-reflect. + **/ + TypeId type_id_; + }; + } /*namespace obj*/ +} /*namespace xo*/ + + +/** end Object.hpp **/ diff --git a/xo-reflect/include/xo/reflect/Reflect.hpp b/xo-reflect/include/xo/reflect/Reflect.hpp new file mode 100644 index 00000000..f75c3e44 --- /dev/null +++ b/xo-reflect/include/xo/reflect/Reflect.hpp @@ -0,0 +1,424 @@ +/* file Reflect.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "SelfTagging.hpp" +#include "EstablishTypeDescr.hpp" +#include "atomic/AtomicTdx.hpp" +#include "pointer/PointerTdx.hpp" +#include "vector/VectorTdx.hpp" +#include "struct/StructTdx.hpp" +#include "function/FunctionTdx.hpp" +#include "xo/refcnt/Refcounted.hpp" +#include +#include +#include // for std::pair<> + +namespace xo { + namespace reflect { + template + class EstablishTdx { + public: + /** Create auxiliary reflection info for type @tparam T, + * once full definition is available. + * + * This includes: + * - metatype + * - component structure (types for navigable component objects) + * + **/ + static std::unique_ptr make() { return AtomicTdx::make(); } + }; + + // ----- xo::ref::rp ----- + + template + class EstablishTdx> { + public: + /* definition provide after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- std::array ----- + + template + class EstablishTdx> { + public: + /* definition provide after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- std::vector ----- + + template + class EstablishTdx> { + public: + /* definition provide after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- std::pair ----- + + template + class EstablishTdx> { + public: + /* definition provide after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- Retval (*)(A1 .. An) ----- + + template + class EstablishTdx { + public: + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- Retval (*)(A1 .. An) noexcept ----- + + template + class EstablishTdx { + public: + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- EstasblishFunctionTdx ------------------------------------- + + template + class EstablishFunctionTdx; + + // ----- Retval (*)(A1 .. An) ----- + + template + class EstablishFunctionTdx { + public: + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + template + class EstablishFunctionTdx { + public: + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- MakeTagged ----- + + template + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(T * x); + static TaggedRcptr make_rctp(T * x); + }; + + template<> + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(SelfTagging * x) { + return x->self_tp(); + } /*make_tp*/ + + static TaggedRcptr make_rctp(SelfTagging * x) { + return x->self_tp(); + } /*make_rctp*/ + }; /*TaggedPtrMaker*/ + + // ----- Reflect ----- + + class Reflect { + public: + /* Use: + * using mytype = ...; + * if (Reflect::is_reflected()) { ... } + */ + template + static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); } + + /* Use: + * using mytype = ...; + * TypeDescrW td = Reflect::require(); + * + * Note: + * To avoid cyclic header dependencies + * (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.}, + * we use a 2-stage setup process: + * + * 1. EstablishTypeDescr::establish() creates a TypeDescr* object + * with lowest-common-denominator .tdextra AtomicTdx. + * (see [reflect/EstablishTypeDescr.hpp]) + * + * 2. Reflect::require() upgrades .tdextra to suitable implementation + * depending on T; this means also need to visit reflection info + * (TypeDescr objects) for nested types to upgrade them too. + * + * This allows template-fu for a compound type (like std::vector), + * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to + * refer to reflection info for T without having to pull in all the + * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) + * + */ + template + static TypeDescrW require() { + TypeDescrW retval_td = EstablishTypeDescr::establish(); + + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ + + auto final_tdx = EstablishTdx::make(); + + retval_td->assign_tdextra(Reflect::get_final_invoker(), + std::move(final_tdx)); + + /* also need to require for each child */ + } + + return retval_td; + } /*require*/ + + /* can optionally use this when reflecting a function pointer. + * Should get the same result as reflect(), + * but will not fallback to AtomicTdx if T is not recognized as a function pointer + */ + template + static TypeDescrW require_function() { + //static_assert(std::is_function_v); + + TypeDescrW retval_td = EstablishTypeDescr::establish(); + + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ + + auto final_tdx = EstablishFunctionTdx::make(); + + retval_td->assign_tdextra(Reflect::get_final_invoker(), + std::move(final_tdx)); + + /* also need to require for each child */ + } + + return retval_td; + } + + /** true iff @p src_td is a type-description for @tparam T **/ + template + static bool is_native(TypeDescr src_td) { + return (require() == src_td); + } + + /** given address @p src_address of a value with type described by @p src, + * return typed pointer of type @tparam T, provided that @p src_td + * actually describes @tparam T. Otherwise returns nullptr + **/ + template + static T * recover_native(TypeDescr src_td, void * src_address) { + return TaggedPtr(src_td, src_address).recover_native(); + } + + /* Use: + * T * xyz = ...; + * TaggedPtr xyz_tp = Reflect::make_tp(xyz); + */ + template + static TaggedPtr make_tp(T * x) { return TaggedPtrMaker::make_tp(x); } + + template + static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } + + private: + + template + static detail::InvokerAux * get_final_invoker() { + static detail::InvokerAux s_final_invoker; + + return &s_final_invoker; + } + + }; /*Reflect*/ + + // ----- MakeTagged ----- + + template + TaggedPtr + TaggedPtrMaker::make_tp(T * x) { + return TaggedPtr(Reflect::require(), x); + } /*make_tp*/ + + template + TaggedRcptr + TaggedPtrMaker::make_rctp(T * x) { + return TaggedRcptr(Reflect::require(), x); + } /*make_rctp*/ + + // ----- xo::ref::rp ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Object is property reflected. + * + * In practice must be a class type, since has to store refcount + * + supply assoc'd incr/decr methods + */ + Reflect::require(); + + return RefPointerTdx>::make(); + } /*make*/ + + // ----- std::array ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); + + return StdArrayTdx::make(); + } /*make*/ + + // ----- std::vector ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); + + return StdVectorTdx::make(); + } /*make*/ + + // ----- std::pair ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Lhs, Rhs are properly reflected */ + Reflect::require(); + Reflect::require(); + + return StructTdx::pair(); + } /*make*/ + + // ----- Retval(A1 .. An) ----- + + namespace detail { + /** @class AssembleArgv + * @brief create vector of complete TypeDescr objects comprising all template arguments + * + * Use: + * std::vector v; + * AssembleArgv::append_argv(&v); + * // do something with v + **/ + template + struct AssembleArgv; + + template <> + struct AssembleArgv<> { + static void append_argv(std::vector * /*p_v*/) {} + }; + + template + struct AssembleArgv { + static void append_argv(std::vector * p_v) { + p_v->push_back(Reflect::require()); + AssembleArgv::append_argv(p_v); + } + }; + } /*detail*/ + + template + std::unique_ptr + EstablishFunctionTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + false /*!is_noexcept*/); + } + + template + std::unique_ptr + EstablishFunctionTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + true /*is_noexcept*/); + } + + /* declared above before + * class Reflect { ... } + */ + template + std::unique_ptr + EstablishTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + false /*!is_noexcept*/); + } + + /* declared above before + * class Reflect { ... } + */ + template + std::unique_ptr + EstablishTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + true /*is_noexcept*/); + } + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Reflect.hpp */ diff --git a/xo-reflect/include/xo/reflect/SelfTagging.hpp b/xo-reflect/include/xo/reflect/SelfTagging.hpp new file mode 100644 index 00000000..12eb279f --- /dev/null +++ b/xo-reflect/include/xo/reflect/SelfTagging.hpp @@ -0,0 +1,31 @@ +/* file SelfTagging.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "TypeDescr.hpp" +#include "TaggedRcptr.hpp" + +namespace xo { + namespace reflect { + /* a self-tagging object uses reflection to preserve type information + * until runtime. Can use the reflected information to traverse + * object representation (e.g. for printing / serialization) + * without repetitive/bulky boilerplate. + * + * For pybind11 need to have concrete (non-template) apis, + * helpful to have various classes inherit SelfTagging + * + * For example see [printjson/PrintJson.hpp] + */ + class SelfTagging : public ref::Refcount { + public: + virtual TaggedRcptr self_tp() = 0; + }; /*SelfTagging*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end SelfTagging.hpp */ diff --git a/xo-reflect/include/xo/reflect/StructReflector.hpp b/xo-reflect/include/xo/reflect/StructReflector.hpp new file mode 100644 index 00000000..b443950f --- /dev/null +++ b/xo-reflect/include/xo/reflect/StructReflector.hpp @@ -0,0 +1,165 @@ +/* @file StructReflector.hpp */ + +#pragma once + +#include "Reflect.hpp" +#include "TypeDescr.hpp" +#include "struct/StructMember.hpp" +#include "struct/StructTdx.hpp" +#include + +namespace xo { + namespace reflect { + template + class SelfTagger {}; + + template + struct SelfTagger { + static TaggedPtr self_tp(void * object) { + return (reinterpret_cast(object))->self_tp(); + } + }; + + template + struct SelfTagger { + static TaggedPtr self_tp(void * /*object*/) { assert(false); return TaggedPtr::universal_null(); } + }; + + /* RAII pattern for reflecting a struct. + * + * Use: + * struct Foo { int x_; double y_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, x_); + * REFLECT_LITERAL_MEMBER(sr, y_); + * + * // optional: regardless, reflection will be completed when sr goes out of scope + * sr.require_complete(); + */ + template + class StructReflector { + public: + using struct_t = StructT; + + public: + StructReflector() : td_{EstablishTypeDescr::establish()} {} + ~StructReflector() { + this->require_complete(); + } + + bool is_complete() const { return s_reflected_flag; } + bool is_incomplete() const { return !s_reflected_flag; } + TypeDescr td() const { return td_; } + + template + void reflect_member(std::string const & member_name, + MemberT OwnerT::* member_addr) { + + auto accessor + (GeneralStructMemberAccessor::make(member_addr)); + + /* used to do this in GeneralStructMemberAccessor<> ctor, + * but that introduces #include cycle + */ + Reflect::require(); + + this->member_v_.emplace_back(member_name, std::move(accessor)); + } /*reflect_member*/ + + void require_complete() { + if(!s_reflected_flag) { + s_reflected_flag = true; + + constexpr bool have_to_self_tp = std::is_base_of_v; + + /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ + auto to_self_tp_fn + = ([](void * object) + { + return SelfTagger::self_tp(object); + }); + + static detail::InvokerAux s_final_invoker; + + auto tdx = StructTdx::make(std::move(this->member_v_), + have_to_self_tp, + to_self_tp_fn); + + this->td_->assign_tdextra(&s_final_invoker, + std::move(tdx)); + } + } /*complete*/ + + template + void adopt_ancestors() { + assert(Reflect::is_reflected()); + + TypeDescr ancestor_td = Reflect::require(); + + /* requires that reflection of AncestorT has completed */ + { + assert(ancestor_td->is_struct()); + assert(ancestor_td->complete_flag()); + } + + /* for structs, + * we know that object argument to TypeDescr::n_child() is unused + */ + for (uint32_t i = 0, n = ancestor_td->n_child(nullptr); i < n; ++i) { + StructMember const & member = ancestor_td->struct_member(i); + + this->member_v_.push_back(member.for_descendant()); + } + } /*adopt_ancestors*/ + + private: + /* set irrevocably to true when .complete() runs. + * + * want to reflect a particular type once; + * short-circuit 2nd or later attempts on the same type + */ + static bool s_reflected_flag; + + /* type description object for StructT */ + TypeDescrW td_; + + /* members of StructT (at least those we're choosing to reflect) */ + std::vector member_v_; + }; /*StructReflector*/ + + template + bool StructReflector::s_reflected_flag = false; + } /*namespace reflect*/ + + /* e.g. + * struct Foo { int bar_; }; + * struct Bar : public Foo { .. }; + * + * StructReflector sr; + * REFLECT_EXPLICIT_MEMBER(sr, "bar", &Foo::bar_); + */ +#define REFLECT_EXPLICIT_MEMBER(sr, member_name, member) sr.reflect_member(member_name, member) + + /* e.g. + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, bar_); + * + * then REFLECT_LITERAL_MEMBER() expands to something like: + * sr.reflect_member("bar_", &StructReflector::struct_t::bar_) + */ +#define REFLECT_LITERAL_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name) + + /* like REFLECT_LITERAL_MEMBER(), but append trailing underscore + * + * minor convenience, so we can write + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_MEMBER(sr, bar); // reflects Foo::bar_ as "bar" + */ +#define REFLECT_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name##_) + +} /*namespace xo*/ diff --git a/xo-reflect/include/xo/reflect/TaggedPtr.hpp b/xo-reflect/include/xo/reflect/TaggedPtr.hpp new file mode 100644 index 00000000..fbffaa04 --- /dev/null +++ b/xo-reflect/include/xo/reflect/TaggedPtr.hpp @@ -0,0 +1,128 @@ +/* @file TaggedPtr.hpp */ + +#pragma once + +#include "TypeDescr.hpp" +#include + +namespace xo { + namespace reflect { + class TaggedRcptr; /* see [reflect/TaggedRcptr.hpp] */ + + class TaggedPtr { + public: + TaggedPtr(TypeDescr td, void * x) : td_{td}, address_{x} {} + + static TaggedPtr universal_null() { return TaggedPtr(nullptr, nullptr); } + + /* would be clean to put make() here; + * however it leads to cyclic #include paths, + * so put it elsewhere + */ +#ifdef NOT_USING + template + static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require(), x); } +#endif + + /* visit an object tree. calls preorder_visit_fn() on tp, + * and all objects reachable directly-or-indirectly from tp. + * will call preorder_visit_fn() multiple times if there are multiple paths + * to a node. + * + * require: no cycles in object graph -- undefined behavior if a cycle is present + */ + template + static void visit_tree_preorder(TaggedPtr tp, Fn && preorder_visit_fn) { + using std::uint32_t; + + preorder_visit_fn(tp); + + for(uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_tree_preorder(tp.get_child(i), preorder_visit_fn); + } + } /*visit_tree_preorder*/ + + /* visit object graph. calls preorder_visit_fn() on tp in depth-first + * order. detects and silently prunes duplicate/cyclic references. + */ + template + static void visit_graph(TaggedPtr tp, Fn && visit_fn) { + std::unordered_set visited_set; + + visit_graph_aux(tp, visit_fn, &visited_set); + } /*visit_graph*/ + + TypeDescr td() const { return td_; } + void * address() const { return address_; } + + void assign_td(TypeDescr x) { td_ = x; } + void assign_address(void * x) { address_ = x; } + + bool is_universal_null() const { return (td_ == nullptr) && (address_ == nullptr); } + bool is_pointer() const { return td_ && td_->is_pointer(); } + bool is_vector() const { return td_ && td_->is_vector(); } + bool is_struct() const { return td_ && td_->is_struct(); } + bool is_function() const { return td_ && td_->is_function(); } + + /* returns pointer-to-T, if in fact this tagged pointer is understood + * to refer to a T-instance; otherwise nullptr + */ + template + T * recover_native() const { return this->td_->recover_native2(this->td_, this->address_); } + + uint32_t n_child() const { + return this->td_->n_child(this->address_); + } + + TaggedPtr get_child(uint32_t i) const { + return this->td_->child_tp(i, this->address_); + } + + /* if reflected function (.is_function() = true): + * number of arguments to that function + */ + uint32_t n_fn_arg() const { return this->td_->n_fn_arg(); } + + /* require: + * - .is_struct() is true + */ + std::string const & struct_member_name(uint32_t i) const { + return this->td_->struct_member_name(i); + } + + private: + template + static void visit_graph_aux(TaggedPtr tp, + Fn && visit_fn, + std::unordered_set * p_visited_set) + { + if (tp.address() == nullptr) + return; + + if (p_visited_set->find(tp.address()) == p_visited_set->end()) { + p_visited_set->insert(tp.address()); + + visit_fn(tp); + + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_graph_aux(tp.get_child(i), visit_fn, p_visited_set); + } + } + } /*visit_graph_aux*/ + + private: + friend class TaggedRcptr; + + private: + /* describes the actual type stored at *address. + * can be null if .address is null + */ + TypeDescr td_; + /* address with type information preserved at runtime */ + void * address_; + }; /*TaggedPtr*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedPtr.hpp */ diff --git a/xo-reflect/include/xo/reflect/TaggedRcptr.hpp b/xo-reflect/include/xo/reflect/TaggedRcptr.hpp new file mode 100644 index 00000000..5d051070 --- /dev/null +++ b/xo-reflect/include/xo/reflect/TaggedRcptr.hpp @@ -0,0 +1,88 @@ +/* file TaggedRcptr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "TaggedPtr.hpp" +// causes #include cycle, reflect/Reflect.hpp includes this header +//#include "reflect/Reflect.hpp" +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace reflect { + /* Tagged reference-counted pointer. + * Like TaggedPtr, but also maintains reference count. + * + * note that refcounting behavior is lost if assigned to a TaggedPtr variable! + */ + class TaggedRcptr : public TaggedPtr { + public: + using Refcount = ref::Refcount; + + public: + TaggedRcptr(TypeDescr td, Refcount * x) : TaggedPtr(td, x) { + ref::intrusive_ptr_add_ref(x); + } + TaggedRcptr(TaggedRcptr const & x) : TaggedPtr(x) { + ref::intrusive_ptr_add_ref(x.rc_address()); + } + TaggedRcptr(TaggedRcptr && x) : TaggedPtr(std::move(x)) { + /* since we're moving from x, need to make sure x.dtor + * doesn't decrement refcount + */ + x.assign_address(nullptr); + } + ~TaggedRcptr() { + ref::intrusive_ptr_release(this->rc_address()); + } + + /* causes #include cycle, see [reflect/Reflect.hpp] */ +#ifdef NOT_IN_USE + /* require: T --isa--> ref::Refcount */ + template + static TaggedRcptr make(T * x) { return TaggedRcptr(Reflect::require(), x); } +#endif + + Refcount * rc_address() const { + return reinterpret_cast(this->address()); + } /*rc_address*/ + + TaggedRcptr & operator=(TaggedRcptr const & rhs) { + Refcount * x = rhs.rc_address(); + Refcount * old = this->rc_address(); + + TaggedPtr::operator=(rhs); + + if (x != old) { + intrusive_ptr_release(old); + intrusive_ptr_add_ref(x); + } + + return *this; + } /*operator=*/ + + TaggedRcptr & operator=(TaggedRcptr && rhs) { + /* swap pointers + type descriptions; + * then don't need to touch refcounts + */ + std::swap(this->td_, rhs.td_); + std::swap(this->address_, rhs.address_); + + return *this; + } /*operator=*/ + + void display(std::ostream & os) const; + std::string display_string() const; + }; /*TaggedRcptr*/ + + inline std::ostream & operator<<(std::ostream & os, TaggedRcptr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedRcptr.hpp */ diff --git a/xo-reflect/include/xo/reflect/TypeDescr.hpp b/xo-reflect/include/xo/reflect/TypeDescr.hpp new file mode 100644 index 00000000..53247ad0 --- /dev/null +++ b/xo-reflect/include/xo/reflect/TypeDescr.hpp @@ -0,0 +1,571 @@ +/* @file TypeDescr.hpp */ + +#pragma once + +#include "TypeDescrExtra.hpp" +#include "xo/cxxutil/demangle.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xo { + namespace reflect { + class TaggedPtr; /* see [reflect/TaggedPtr.hpp] */ + + /* A reflected type is a type for which we keep information around at runtime + * Assign reflected types unique (within an executable) ids, + * allocating consecutively, starting from 1. + * Reserve 0 as a sentinel + */ + class TypeId { + public: + /* allocate a new TypeId value. + * promise: + * - retval.id() > 0 + */ + static TypeId allocate() { return TypeId(s_next_id++); } + + std::uint32_t id() const { return id_; } + + private: + explicit TypeId(std::uint32_t id) : id_{id} {} + + private: + static std::uint32_t s_next_id; + + /* unique index# for this type. + * 0 reserved for sentinel + */ + std::uint32_t id_ = 0; + }; /*TypeId*/ + + inline std::ostream & + operator<<(std::ostream & os, TypeId x) { + os << x.id(); + return os; + } /*operator<<*/ + + /* runtime description of a struct/class instance variable */ + class StructMember; + + class TypeDescrBase; + + using TypeDescr = TypeDescrBase const *; + using TypeDescrW = TypeDescrBase *; + + /* convenience wrapper for a std::type_info pointer. + * works properly with pybind11, since python doens't encounter + * native type_info pointer, it won't try to delete it. + */ + class TypeInfoRef { + public: + explicit TypeInfoRef(std::type_info const * tinfo) : tinfo_{tinfo} {} + TypeInfoRef(TypeInfoRef const & x) = default; + + /* use: + * TypeInfoRef tinfo = TypeInfoRef::make(); + */ + template + TypeInfoRef make() { return TypeInfoRef(&typeid(T)); } + + std::size_t hash_code() const { return this->tinfo_->hash_code(); } + char const * impl_name() const { return this->tinfo_->name(); } + + static bool is_equal(TypeInfoRef x, TypeInfoRef y) noexcept { + if (x.hash_code() != y.hash_code()) + return false; + + return ::strcmp(x.impl_name(), y.impl_name()) == 0; + } /*is_equal*/ + + private: + /* native type_info object for encapsulated type */ + std::type_info const * tinfo_ = nullptr; + }; /*TypeInfoRef*/ + + namespace detail { + struct Invoker { + virtual void dtor(void * addr) const = 0; + }; + + /** Auxiliary template for capturing destructor for type T, + * if it has one. + * + * Example + * T * p = new T(...); + * DestructorAux::dtor(p); + **/ + template + struct InvokerAux : public Invoker { + virtual void dtor(void * addr) const override { + T * obj = static_cast(addr); + + obj->~T(); + } + }; + + template<> + struct InvokerAux : public Invoker { + virtual void dtor(void *) const override {} + }; + } /*namespace detail*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +namespace std { + template <> struct hash { + std::size_t operator()(xo::reflect::TypeInfoRef x) const noexcept { return x.hash_code(); } + }; +} /*namespace std*/ + +namespace xo { + namespace reflect { + inline bool operator==(TypeInfoRef x, TypeInfoRef y) { return TypeInfoRef::is_equal(x, y); } + inline bool operator!=(TypeInfoRef x, TypeInfoRef y) { return !TypeInfoRef::is_equal(x, y); } + +#ifdef NOT_IN_USE + namespace detail { + class HashTypeInfoRef { + public: + std::size_t operator()(TypeInfoRef x) const noexcept { return x.hash_code(); } + }; /*HashTypeInfoRef*/ + + class EqualTypeInfoRef { + public: + bool operator()(TypeInfoRef x, TypeInfoRef y) const noexcept { return TypeInfoRef::is_equal(x, y); } + }; /*EqualTypeInfoRef*/ + } /*namespace detail*/ +#endif + + /* hashable contents of a FunctionTdx instance (without requiring decl of TypeDescrExtra), + * for unique-ification of manually-constructed function types + */ + class FunctionTdxInfo { + public: + FunctionTdxInfo() = default; + FunctionTdxInfo(TypeDescr retval_td, + const std::vector & arg_td_v, + bool is_noexcept) + : retval_td_{retval_td}, + arg_td_v_{arg_td_v}, + is_noexcept_{is_noexcept} + {} + + /** compare two FunctionTdxInfo objects for equality + **/ + inline bool operator==(const FunctionTdxInfo & other) const noexcept { + if (retval_td_ != other.retval_td_) + return true; + if (arg_td_v_.size() != other.arg_td_v_.size()) + return false; + + for (std::size_t i = 0, n = arg_td_v_.size(); i < n; ++i) { + if (arg_td_v_[i] != other.arg_td_v_[i]) + return false; + } + + if (is_noexcept_ != other.is_noexcept_) + return false; + + return true; + } + + /** construct canonical description for this type + * will be like + * Retval(*)(Arg1,..,Argn) + **/ + std::string make_canonical_name() const; + + public: + /** function return value **/ + TypeDescr retval_td_ = nullptr; + /** function arguments, in positional order **/ + std::vector arg_td_v_; + /** true iff function promises never to throw **/ + bool is_noexcept_ = false; + }; /*FunctionTdxInfo*/ + + class TypeDescrExtra; + + /* run-time description for a native c++ type */ + class TypeDescrBase { + public: + /* type-description objects for a type T is unique, + * --> can always use its address + */ + TypeDescrBase(TypeDescrBase const & x) = delete; + + /* test whether a type has been reflected. + * introducing this for unit testing + */ + static bool is_reflected(std::type_info const * tinfo) { + return (s_native_type_table_map.find(TypeInfoRef(tinfo)) + != s_native_type_table_map.end()); + } /*is_reflected*/ + + /* NOTE: + * implementation here will be defeated if std::type_info + * objects violate ODR. This occurs with clang + 2-level namespaces, + * so important to linke with --flat_namespace defined. + * See FAQ + * [Build Issues|Q2 - dynamic_cast> fails] + */ + static TypeDescrW require(const std::type_info * tinfo, + const std::string & canonical_name, + detail::Invoker * invoker, + std::unique_ptr tdextra); + + /** Create type-description for function from input ingredients. **/ + static TypeDescrW require_by_fn_info(const FunctionTdxInfo & fn_info); + + /** lookup type by canonical name **/ + static TypeDescr lookup_by_name(const std::string & canonical_name); + + /** print table of reflected types to os **/ + static void print_reflected_types(std::ostream & os); + /** print table of function types to os **/ + static void print_function_types(std::ostream & os); + + TypeId id() const { return id_; } + const std::type_info * native_typeinfo() const { return native_typeinfo_; } + const std::string & canonical_name() const { return canonical_name_; } + const std::string_view & short_name() const { return short_name_; } + bool complete_flag() const { return complete_flag_; } + TypeDescrExtra * tdextra() const { return tdextra_.get(); } + Metatype metatype() const { return tdextra_->metatype(); } + + /* true iff the type represented by *this is the same as the type + * represented by T. + * + * Warning: comparing typeinfo address can give false negatives. + * suspect this is caused by problems coalescing linker symbols + * in the clang toolchain. + */ + template + [[deprecated]] + bool is_native() const { + if (this->native_typeinfo()) { + /* reminder: typeid(T).name() is 'interesting' but not intended + * to be human-readable. It's not how compiler labels + * a type for a human reader + */ + return ((this->native_typeinfo() == &typeid(T)) + || (this->native_typeinfo()->hash_code() == typeid(T).hash_code()) + || (this->native_typeinfo()->name() == typeid(T).name())); + } else { + /** if this type was established via Reflect::require(), + * then .canonical_name is computed by type_name() + * + * (see demangle.hh in xo-refcnt, which post-processes __PRETTY_FUNCTION__ + * or __FUNCSIG__) + * + * To manually construct an equivalent type, + * it's necessary to: + * 1. construct a unique and unambiguous canonical name for the type + * 2. be aware that type will only be recognized as equivalent to + * a natively-reflected type if canonical name matches exactly. + **/ + + /** FOR NOW: give up. **/ + throw std::runtime_error("TypeDescrBase::is_native: not implemented for manually-constructed TypeDescr objects. Prefer is_native2()"); + } + } /*is_native*/ + + /** safe downcast -- like dynamic_cast<>, but does not require a source type. + * + * TODO: need variation on this to correctly-handle function types, + * since for exampple cast from void* -> void (*)() is not allowed + * + * WARNING: relies on deprecated is_native(). Application code should prefer any of: + * 1. recover_native2(src_td, src_address) + * 2. Reflect::recover_native(src_td, src_address) + * 3. TaggedPtr(src_td,src_address).recover_native() + * instead of src_td->recover_native() + * + * (note: awkwardness here is that we don't have access to {Reflect.hpp, TaggedPtr.hpp} + * from this .hpp file, since TypeDescr.hpp is included by those headers) + **/ + template + [[deprecated]] + T * recover_native(void * address) const { + if (this->is_native()) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native*/ + + /** safe downcast -- like dynamic_cast<>, but does not require a source type. + * + * Application code should prefer TaggedPtr::recover_native() + * + * TODO: need variation on this to correctly-handle function types, + * since for exampple cast from void* -> void (*)() is not allowed + **/ + template + T * recover_native2(TypeDescr address_td, void * address) const { + if (this == address_td) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native2*/ + + bool is_pointer() const { return this->tdextra_->is_pointer(); } + bool is_vector() const { return this->tdextra_->is_vector(); } + bool is_struct() const { return this->tdextra_->is_struct(); } + bool is_function() const { return this->tdextra_->is_function(); } + + /* given a T-instance object, return tagged pointer with T replaced + * by the most-derived-subtype of T to which *object belongs. + * This works only for descendants of reflect::SelfTagging + */ + TaggedPtr most_derived_self_tp(void * object) const; + + /* if generalized vector (std::vector, std::array, ..): + * .n_child() reports #of elements + * if struct/class: + * .n_child() reports #of instance variables (that have been reflected) + */ + uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } + /** number of children, if that number is fixed at compile time. otherwise 0 + **/ + uint32_t n_child_fixed() const { return this->tdextra_->n_child_fixed(); } + /** TypeDescr for i'th child, using only information available at compile time. + * e.g. for vectors/pointers, always returns ElementType. + **/ + TypeDescr fixed_child_td(uint32_t i) const { return this->tdextra_->fixed_child_td(i); } + /** TaggedPtr to child @p i. + * Will report most-derived-type for type tag, + * so may refer to a proper subtype (e.g. derived class) of the type + * reported by @c fixed_child_td(i) + **/ + TaggedPtr child_tp(uint32_t i, void * object) const; + + /* require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + std::string const & struct_member_name(uint32_t i) const { + return this->tdextra_->struct_member_name(i); + } + + /* fetch runtime description for i'th reflected instance variable. + * + * require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + StructMember const & struct_member(uint32_t i) const { + StructMember const * sm = this->tdextra_->struct_member(i); + + assert(sm); + return *sm; + } /*struct_member*/ + + /** nullptr for non-function types **/ + const FunctionTdxInfo * fn_info() const { return this->tdextra_->fn_info(); } + uint32_t n_fn_arg() const { return this->tdextra_->n_fn_arg(); } + + /* require: + * - .is_function() = true + */ + TypeDescr fn_retval() const { return this->tdextra_->fn_retval(); } + TypeDescr fn_arg(uint32_t i) const { return this->tdextra_->fn_arg(i); } + bool fn_is_noexcept() const { return this->tdextra_->fn_is_noexcept(); } + + void display(std::ostream & os) const; + std::string display_string() const; + + /* mark this TypeDescr complete; + * returns the value of .complete_flag from _before_ + * this call + */ + bool mark_complete(); + + /* call this once to attach extended type information to a type-description + * (e.g. description of struct members for a record type) + */ + void assign_tdextra(detail::Invoker * invoker, + std::unique_ptr tdx) { + this->complete_flag_ = true; + this->invoker_ = invoker; + this->tdextra_ = std::move(tdx); + } + + // ----- actions ----- + + private: + TypeDescrBase(TypeId id, + const std::type_info * tinfo, + const std::string & canonical_name, + std::unique_ptr tdextra, + detail::Invoker * invoker); + + void assign_native_tinfo(const std::type_info * tinfo) { + assert(!native_typeinfo_); + native_typeinfo_ = tinfo; + } + + private: + /* invariant: + * - for all TypeDescrImpl instances x: + * - s_type_table_v[x->id()] = x + * - s_native_type_table_map[TypeInfoRef(x->typeinfo())] = x + */ + + /** vector of all TypeDescr instances, indexed by TypeId. singleton. **/ + static std::vector> s_type_table_v; + + /** hashmap of all TypeDescr instances, + * indexed by canonical_name. + * + * For manually-constructed TypeDescr instances + * (see xo-expression for use-case) we require: + * + * - TypeDescr::canonical_name uniquely identifies type + * - to interact with an actually-equivalent type T + * constructed by c++ compiler, we need + * to use the same canonical name that the compiler uses. + * + * See type xo::reflect::type_name<>() [in demangle.hpp under xo-refcnt] + * for implementation + **/ + static std::unordered_map s_canonical_type_table_map; + + /** hashmap of all native TypeDescr instances, + * indexed by typeinfo. singleton. + **/ + static std::unordered_map s_native_type_table_map; + + /** hashmap of (presumed) duplicate TypeInfoRef values. + * This happens with clang sometimes when the same type is referenced + * from multiple modules (i.e. shared libs). + **/ + static std::unordered_map s_coalesced_type_table_map; + + /** map from a vector of TypeDescr objects: + * [Retval, Arg1, ...Argn] + * to TypeDescr for function type + * Retval(*)(Arg1..Argn) + * + * Use these to unique-ify function types across: + * - types sourced natively from c++ compiler + * - types manually constructed (e.g. see Lambda.cpp in xo-expression) + **/ + static std::unordered_map s_function_type_map; + + private: + /** unique id# for this type **/ + TypeId id_; + + /** typeinfo for type T, if available. nullptr otherwise. + * + * 1. Always available for type-descriptions constructed via Reflect::require. + * 2. Always missing for manually-constructed TypeDescr instances, for example + * see Lambda.cpp in xo-expression. + **/ + std::type_info const * native_typeinfo_ = nullptr; + + /** canonical name for this type (see demangle.hpp for type_name()) + * e.g. + * xo::option::Px2 + * + * NOTE: if we only had to deal with types created via Reflect::reflect(), + * then canonical_name could be string_view. For manually-constructed + * types, there is no compiler-generated C-string constant to reference, + * so need to use std::string here + **/ + std::string canonical_name_; + + /** substring .canonical_name, just after last ':' + * e.g. + * Px2 + **/ + std::string_view short_name_; + + /** set to true once final value for .tdextra is established + * intially all TypeDescr objects will use AtomicTdx for .tdextra + * Reflect::require() upgrades .tdextra for particular types. + * When that procedure makes a decision for a type T, + * .complete_flag will be set to true for the corresponding TypeDescrBase instance + **/ + bool complete_flag_ = false; + + /** capture basic instance-management operations for this type. + * Given an instance T x: + * - invoker_->dtor(&x) invokes T::~T() + **/ + detail::Invoker * invoker_; + + /** additional type information that either: + * (a) isn't universal across all types, + * e.g. dereferencing instance of a pointer type + * (b) can't be captured with template-fu, + * e.g. struct member names + * + * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. + * This is necessary because of (b) above, also because of possibility of recursive + * types. + **/ + std::unique_ptr tdextra_; + }; /*TypeDescrBase*/ + + inline std::ostream & + operator<<(std::ostream & os, TypeDescrBase const & x) { + x.display(os); + return os; + } /*operator<<*/ + + /* tag to drive overload resolution */ + struct reflected_types_printer {}; + + inline std::ostream & + operator<<(std::ostream & os, reflected_types_printer) { + TypeDescrBase::print_reflected_types(os); + return os; + } + + class TypeDescrTable { + public: + TypeDescrTable * instance() { return &s_instance; } + + private: + /** initialize with builtin atomic types: + * float, double, char, short, int, long, bool + **/ + TypeDescrTable(); + + private: + static TypeDescrTable s_instance; + }; + } /*namespace reflect*/ +} /*namespace xo*/ + +namespace std { + /** @brief overload for hashing xo::reflect::FunctionTdxInfo objects + **/ + template <> + struct hash { + inline size_t operator()(const xo::reflect::FunctionTdxInfo & x) const noexcept { + /* we can hash on addresses, since TypeDescr objects are immutable */ + std::size_t h = hash{}(x.retval_td_); + + for (std::size_t i = 0, n = x.arg_td_v_.size(); i < n; ++i) { + h = (h << 1) ^ hash{}(x.arg_td_v_[i]); + } + + h = (h << 1) ^ (x.is_noexcept_ ? 1 : 0); + + return h; + } + }; +} + +/* end TypeDescr.hpp */ diff --git a/xo-reflect/include/xo/reflect/TypeDescrExtra.hpp b/xo-reflect/include/xo/reflect/TypeDescrExtra.hpp new file mode 100644 index 00000000..f53508ae --- /dev/null +++ b/xo-reflect/include/xo/reflect/TypeDescrExtra.hpp @@ -0,0 +1,91 @@ +/* @file TypeDescrExtra.hpp */ + +#pragma once + +#include "Metatype.hpp" +#include +/* note: this file #include'd into TypeDescr.hpp */ +#include + +namespace xo { + namespace reflect { + /* forward-declaring here. see [reflect/struct/StructMember.hpp] */ + class StructMember; + class FunctionTdxInfo; + class TypeDescrBase; + class TaggedPtr; + + /* information associated with a c++ type. + * distinct from TypeDescrImpl: + * 1. want to use reflection to support for runtime polymorphism over similar but + * not directly-related types: for example + * std::vector + * and + * std::list + * are both ordered collections + * 2. some information can't be universally established via template-fu, + * for example struct member names + * 3. descriptions for recursive types require 2-stage construction + * + * A TypeDescrImpl instance will contain a pointer to a suitable + * TypeDescrExtra instance. + * + * The single TypeDescrImpl instance for some type T can be established + * automatically, see Reflect::require(). + * + * A specific TypeDescrExtra instance may be attached in a non-automated way + * later + */ + class TypeDescrExtra { + public: + using uint32_t = std::uint32_t; + + public: + virtual ~TypeDescrExtra() = default; + + bool is_pointer() const { return this->metatype() == Metatype::mt_pointer; } + bool is_vector() const { return this->metatype() == Metatype::mt_vector; } + bool is_struct() const { return this->metatype() == Metatype::mt_struct; } + bool is_function() const { return this->metatype() == Metatype::mt_function; } + + virtual Metatype metatype() const = 0; + /* given a T-instance, report most-derived subtype of T to which *object belongs. + * this works only for types that are derived from reflect::SelfTagging. + */ + virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, void * object) const; + virtual uint32_t n_child(void * object) const = 0; + /** number of children, fixed at compile time. + * Will return 0 for types like std::vector<..> (because number is unknown); + * Will also return 0 for types like {bool, int, long} (because number is zero) + **/ + virtual uint32_t n_child_fixed() const = 0; + /** type description for i'th child, based on information available at compile time. + * For vectors/pointers, this always refers to element type. + * + * nullptr for atomics + **/ + virtual const TypeDescrBase * fixed_child_td(uint32_t i) const = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const = 0; + /* require: + * .is_struct() + */ + virtual std::string const & struct_member_name(uint32_t i) const = 0; + /* nullptr unless *this represents a struct/class type */ + virtual StructMember const * struct_member(uint32_t i) const; + + // methods for working with reflected functions/methods + + /** number of arguments to function-like value + * + * @pre @ref TypeDescrExtra::is_function() is true + **/ + virtual const FunctionTdxInfo * fn_info() const { return nullptr; } + virtual const TypeDescrBase * fn_retval() const { return nullptr; } + virtual uint32_t n_fn_arg() const { return 0; } + virtual const TypeDescrBase * fn_arg(uint32_t /*i_arg*/) const { return nullptr; } + virtual bool fn_is_noexcept() const { return false; } + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescrExtra.hpp */ diff --git a/xo-reflect/include/xo/reflect/TypeDrivenMap.hpp b/xo-reflect/include/xo/reflect/TypeDrivenMap.hpp new file mode 100644 index 00000000..3eb42660 --- /dev/null +++ b/xo-reflect/include/xo/reflect/TypeDrivenMap.hpp @@ -0,0 +1,47 @@ +/* @file TypeDrivenMap.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "TypeDescr.hpp" +#include + +namespace xo { + namespace reflect { + /* represents a map :: TypeId -> Value */ + template + class TypeDrivenMap { + public: + Value const * lookup(TypeId id) const { return this->lookup_slot(id); } + + Value * require(TypeId id) { return this->require_slot(id); } + Value * require(TypeDescr td) { return this->require_slot(td->id()); } + + private: + Value const * lookup_slot(TypeId id) const { + if (this->contents_v_.size() <= id.id()) + return nullptr; + + return &(this->contents_v_[id.id()]); + } /*lookup_slot*/ + + Value * require_slot(TypeId id) { + if (this->contents_v_.size() <= id.id()) + this->contents_v_.resize(id.id() + 1); + + return &(this->contents_v_[id.id()]); + } /*require_slot*/ + + private: + /* since TypeId/s are unique, compact sequence numbers, + * can efficiently store mapping to Values using a vector indexed by TypeId + */ + std::vector contents_v_; + }; /*TypeDrivenMap*/ + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end TypeDrivenMap.hpp */ diff --git a/xo-reflect/include/xo/reflect/atomic/AtomicTdx.hpp b/xo-reflect/include/xo/reflect/atomic/AtomicTdx.hpp new file mode 100644 index 00000000..d5068199 --- /dev/null +++ b/xo-reflect/include/xo/reflect/atomic/AtomicTdx.hpp @@ -0,0 +1,39 @@ +/* @file AtomicTdx.hpp */ + +#pragma once + +#include "xo/reflect/TypeDescrExtra.hpp" +//#include "reflect/TaggedPtr.hpp" +#include + +namespace xo { + namespace reflect { + class TaggedPtr; + + /* Extra type-associated information for an atomic type. + * We use this as degenerate catch-all case for types that aren't known + * to have additional structure (std::vector, std::map, int*, etc.) + */ + class AtomicTdx : public TypeDescrExtra { + public: + virtual ~AtomicTdx() = default; + + static std::unique_ptr make(); + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_atomic; } + virtual uint32_t n_child(void * /*object*/) const override { return 0; } + virtual uint32_t n_child_fixed() const override { return 0; } + virtual TaggedPtr child_tp(uint32_t /*i*/, void * /*object*/) const override; + virtual const TypeDescrBase * fixed_child_td(uint32_t /*i*/) const override; + virtual std::string const & struct_member_name(uint32_t i) const override; + //virtual StructMember const * struct_member(uint32_t /*i*/) const override { return nullptr; } + + private: + AtomicTdx() = default; + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end AtomicTdx.hpp */ diff --git a/xo-reflect/include/xo/reflect/function/FunctionTdx.hpp b/xo-reflect/include/xo/reflect/function/FunctionTdx.hpp new file mode 100644 index 00000000..02f60558 --- /dev/null +++ b/xo-reflect/include/xo/reflect/function/FunctionTdx.hpp @@ -0,0 +1,58 @@ +/** @file FunctionTdx.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" + +namespace xo { + namespace reflect { + /** Additional type-associated information for a function/procedure **/ + class FunctionTdx : public TypeDescrExtra { + public: + virtual ~FunctionTdx() = default; + + /** create instance. Will be invoked exactly once for each reflected function type + * + * @param retval_td. type description for return value + * @param arg_td_v. type descriptions for arguments, in positional order + * @param is_noexcept. true iff function marked noexcept + **/ + static std::unique_ptr make_function(TypeDescr retval_td, + std::vector arg_td_v, + bool is_noexcept); + /** create instance from FunctionTdxInfo + * @param fn_info. function ingredients -- return type, arg types, noexcept + **/ + static std::unique_ptr make_function(const FunctionTdxInfo & fn_info); + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_function; } + virtual uint32_t n_child(void * /*object*/) const override { return 0; } + virtual uint32_t n_child_fixed() const override { return 0; } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + virtual TypeDescr fixed_child_td(uint32_t i) const override; + const std::string & struct_member_name(uint32_t i) const override; + + virtual const FunctionTdxInfo * fn_info() const override { return &info_; } + virtual TypeDescr fn_retval() const override { return info_.retval_td_; } + virtual uint32_t n_fn_arg() const override { return info_.arg_td_v_.size(); } + virtual TypeDescr fn_arg(uint32_t i) const override { return info_.arg_td_v_[i]; } + virtual bool fn_is_noexcept() const override { return info_.is_noexcept_; } + + private: + FunctionTdx(const FunctionTdxInfo & fn_info); + + private: + /** ingredients in complete function type description **/ + FunctionTdxInfo info_; + }; /*FunctionTdx*/ + } /*namespace reflect*/ +} /*namespace xo*/ + + +/** end FunctionTdx.hpp **/ diff --git a/xo-reflect/include/xo/reflect/init_reflect.hpp b/xo-reflect/include/xo/reflect/init_reflect.hpp new file mode 100644 index 00000000..5f69eadd --- /dev/null +++ b/xo-reflect/include/xo/reflect/init_reflect.hpp @@ -0,0 +1,22 @@ +/* file init_reflect.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the reflect/ subsystem within ordered initialization */ + enum S_reflect_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + + +/* end init_reflect.hpp */ diff --git a/xo-reflect/include/xo/reflect/pointer/PointerTdx.hpp b/xo-reflect/include/xo/reflect/pointer/PointerTdx.hpp new file mode 100644 index 00000000..4484c6ed --- /dev/null +++ b/xo-reflect/include/xo/reflect/pointer/PointerTdx.hpp @@ -0,0 +1,85 @@ +/* file PointerTdx.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + namespace reflect { + /* Extra type-associated information for a pointer-like type + * + * Treat a pointer as a container that has 0 or 1 children; + * - 0 children if null + * - 1 child otherwise + */ + class PointerTdx : public TypeDescrExtra { + public: + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_pointer; } + virtual uint32_t n_child(void * object) const override = 0; + + /* number of children unknown at compile time. + * null-pointer -> 0, non-null pointer -> 1 + */ + virtual uint32_t n_child_fixed() const override { return 0; /*unknown*/ } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; + /* (forbidden) */ + virtual std::string const & struct_member_name(uint32_t i) const override; + }; /*PointerTdx*/ + + // ----- RefPointerTdx ----- + + /* Pointer = xo::ref::intrusive_ptr for some T */ + template + class RefPointerTdx : public PointerTdx { + public: + using target_t = Pointer; + + static std::unique_ptr make() { + return std::unique_ptr(new RefPointerTdx()); + } /*make*/ + + virtual uint32_t n_child(void * object) const override { + /* e.g: + * target_t = ref::rp + */ + target_t * ptr = reinterpret_cast(object); + + if (*ptr) + return 1; + else + return 0; + } /*n_child*/ + + virtual TypeDescrBase * fixed_child_td(uint32_t /*i*/) const override { + return EstablishTypeDescr::establish(); + } + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + using xo::tostr; + using xo::xtag; + + target_t * ptr = reinterpret_cast(object); + + if (i > 0) { + throw std::runtime_error(tostr("RefPointerTdx::child_tp" + ": attempt to fetch child #i from a ref::rp", + xtag("T", type_name()), + xtag("i", i), + xtag("n", this->n_child(object)))); + } + + return establish_most_derived_tp(ptr->get()); + } /*child_tp*/ + }; /*RefPointerTdx*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end PointerTdx.hpp */ diff --git a/xo-reflect/include/xo/reflect/reflect_struct.hpp b/xo-reflect/include/xo/reflect/reflect_struct.hpp new file mode 100644 index 00000000..8c0c843a --- /dev/null +++ b/xo-reflect/include/xo/reflect/reflect_struct.hpp @@ -0,0 +1,71 @@ +/** @file reflect_struct.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "StructReflector.hpp" +#include "xo/reflectutil/reflect_struct_info.hpp" + +namespace xo { + namespace reflect { + namespace detail { + /** + * @pre reflect_struct_member will separately + * have been specialized for T. + * See discussion in [reflect_struct_info.hpp] + * + * + **/ + template + struct sr_member_helper { + /** reflect members starting from member with index number @tparam MemberIx + * + * @pre Members [0,..,MemberIx-1] must be already represented in @p *p_sr + **/ + static void add_members_from(StructReflector * p_sr) { + const auto & member_info + = reflect_struct_member().get(); + + p_sr->reflect_member(member_info.member_name_.c_str(), + member_info.member_addr_); + + /** reflect remaining members **/ + sr_member_helper::add_members_from(p_sr); + } + }; + + template + struct sr_member_helper { + /** base case -- all members have been refleccted **/ + static void add_members_from(StructReflector *) {} + }; + } /*namespace detail*/ + + + /** It's awkward to have Reflect::reflect<>() do the right thing, + * because there's no way to specialize on whether a type T is a struct. + * + * Use + * xo::reflect::Reflect::reflect_struct() instead + **/ + template + TypeDescr reflect_struct() { + StructReflector sr; + + if (sr.is_incomplete()) + detail::sr_member_helper::n_members>::add_members_from(&sr); + + /* TODO: handle composition: where T inherits another reflected type */ + /* TODO: handle multiple inheritance **/ + + return sr.td(); + } + } /*namespace reflect*/ +} /*namespace xo*/ + + +/** end reflect_struct.hpp **/ diff --git a/xo-reflect/include/xo/reflect/struct/StructMember.hpp b/xo-reflect/include/xo/reflect/struct/StructMember.hpp new file mode 100644 index 00000000..e7b76413 --- /dev/null +++ b/xo-reflect/include/xo/reflect/struct/StructMember.hpp @@ -0,0 +1,235 @@ +/* @file StructMember.hpp */ + +#pragma once + +#include "xo/reflect/TypeDescr.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include +#include + +namespace xo { + namespace reflect { + class AbstractStructMemberAccessor { + public: + virtual ~AbstractStructMemberAccessor() = default; + + /* get tagged pointer referring to this member of the object at *struct_addr */ + TaggedPtr member_tp(void * struct_addr) const; + + /* get type-description object for struct + * containing this member. useful for consistency checking. + */ + virtual TypeDescr struct_td() const = 0; + + /* get type-description object for this member + * e.g. if this member represents Foo::bar_ in + * struct Foo { int bar_; }; + * then + * .member_td() => Reflect::require(); + */ + virtual TypeDescr member_td() const = 0; + + /* get address of a particular member, given parent address */ + virtual void * address(void * struct_addr) const = 0; + + virtual std::unique_ptr clone() const = 0; + }; /*AbstractStructMemberAccessor*/ + + /* GeneralStructMemberAccessor + * + * Use this to handle access to possibly-inherited struct members: + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * struct Quux : public Foo, public Bar { bool z_; } + * + * want to be able to access Bar::y from a Quux instance. + * in example, would use GenericStructMemberAccessor<> + * with: + * StructT = Quux, + * OwnerT = Bar, + * MemberT = char* + * + * Require: + * StructT* is assignable to OwnerT* (because StructT --isa--> OwnerT) + */ + template + class GeneralStructMemberAccessor : public AbstractStructMemberAccessor { + public: + /* pointer to a OwnerT member of type MemberT */ + using Memptr = MemberT OwnerT::*; + + public: + GeneralStructMemberAccessor(Memptr memptr) : member_td_{EstablishTypeDescr::establish()}, + memptr_{memptr} {} + GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default; + virtual ~GeneralStructMemberAccessor() = default; + + static std::unique_ptr make(Memptr memptr) { + return std::unique_ptr(new GeneralStructMemberAccessor(memptr)); } + + /* get member address given address of parent struct + * (i.e. from Struct*, not from OwnerT*) + */ + MemberT * address_impl(StructT * self_addr) const { + OwnerT * owner_addr = self_addr; + + return &(owner_addr->*memptr_); + } /*address_impl*/ + + // ----- Inherited from AbstractStructMemberAccessor ----- + +#ifdef OBSOLETE + virtual TaggedPtr member_tp(void * struct_addr) const override { + /* FIXME: this reports declared type of member, instead of + * (possibly narrower) actual type of member + */ + + return this->member_td_->most_derived_self_tp(this->address(struct_addr)); + //return TaggedPtr(this->member_td_, this->address(struct_addr)); + } /*member_tp*/ +#endif + + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + + virtual TypeDescr member_td() const override { return this->member_td_; } + + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } /*address*/ + + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new GeneralStructMemberAccessor(*this)); + } /*clone*/ + + private: + /* type description for MemberT; .memptr is pointer-to-member-of-OwnerT, + * where that member has type MemberT + */ + TypeDescr member_td_ = nullptr; + /* pointer to member of OwnerT */ + Memptr memptr_ = nullptr; + }; /*GeneralStructMemberAccessor*/ + + /* struct-member accessor via delegation, + * to accessor of a parent (or some other ancestor) class. + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * + * auto bar_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * + * or equivalently: + * auto foo_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * auto bar_x_access = AncestorStructMemberAccessor::adopt(foo_x_access); + * + * can use the 2nd form to adopt accessors from an already-reflected ancestor class + * + * Require: + * - StructT -isa-> AncestorT + */ + template + class AncestorStructMemberAccessor : public AbstractStructMemberAccessor { + public: + AncestorStructMemberAccessor(std::unique_ptr ancestor_accessor) + : ancestor_accessor_{std::move(ancestor_accessor)} {} + AncestorStructMemberAccessor(AncestorStructMemberAccessor const & x) = default; + virtual ~AncestorStructMemberAccessor() = default; + + static std::unique_ptr + adopt(std::unique_ptr ancestor_accessor) { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); + } /*adopt*/ + + void * address_impl(StructT * self_addr) const { + /* to use access-via-ancestor, need to convert to ancestor pointer */ + AncestorT * ancestor_addr = self_addr; + + return this->ancestor_accessor_->address(ancestor_addr); + } /*address_impl*/ + + // ----- inherited from AbstractStructMemberAccessor ----- + +#ifdef OBSOLETE + virtual TaggedPtr member_tp(void * struct_addr) const override { + AncestorT * ancestor_addr = reinterpret_cast(struct_addr); + + return this->ancestor_accessor_->member_tp(ancestor_addr); + } /*member_tp*/ +#endif + + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + virtual TypeDescr member_td() const override { return this->ancestor_accessor_->member_td(); } + + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } + + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); + } /*clone*/ + + private: + /* .ancestor_accessor fetches some particular member of AncestorT */ + std::unique_ptr ancestor_accessor_; + }; /*AncestorStructMemberAccessor*/ + + /* describes a member of a struct/class + * see [reflect/StructReflector.hpp] + */ + class StructMember { + public: + StructMember() = default; + StructMember(std::string const & name, + std::unique_ptr accessor) + : member_name_{name}, accessor_{std::move(accessor)} {} + StructMember(StructMember && x) + : member_name_{std::move(x.member_name_)}, + accessor_{std::move(x.accessor_)} {} + + static StructMember null(); + + std::string const & member_name() const { return member_name_; } + + TaggedPtr get_member_tp(void * struct_addr) const { return this->accessor_->member_tp(struct_addr); } + TypeDescr get_struct_td() const { return this->accessor_->struct_td(); } + TypeDescr get_member_td() const { return this->accessor_->member_td(); } + //void * get_member_addr(void * struct_addr) const { return this->accessor_->address(struct_addr); } + + /* make copy that accesses this member, but starting + * from pointer to some derived class DescendantT, + * instead of from container type StructT known to (but not exposed by) *this + */ + template + StructMember for_descendant() const { + assert(EstablishTypeDescr::establish() == this->get_struct_td()); + + return StructMember(this->member_name(), + std::move(AncestorStructMemberAccessor::adopt + (std::move(this->accessor_->clone())))); + } /*for_descendant*/ + + StructMember & operator=(StructMember && x) { + member_name_ = std::move(x.member_name_); + accessor_ = std::move(x.accessor_); + return *this; + } + + private: + /* member name, e.g. foo if + * struct StructT { MemberT foo; } + */ + std::string member_name_; + /* T recd; + * this->accessor_->address_impl(&recd) ==> &(recd.member) + */ + std::unique_ptr accessor_; + }; /*StructMember*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructMember.hpp */ diff --git a/xo-reflect/include/xo/reflect/struct/StructTdx.hpp b/xo-reflect/include/xo/reflect/struct/StructTdx.hpp new file mode 100644 index 00000000..57afe277 --- /dev/null +++ b/xo-reflect/include/xo/reflect/struct/StructTdx.hpp @@ -0,0 +1,98 @@ +/* @file StructTdx.hpp */ + +#pragma once + +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "StructMember.hpp" +//#include "xo/reflect/struct/StructMember.hpp" +#include +#include +#include + +namespace xo { + namespace reflect { + /* Extra type-associated information for a struct/class. + * We use this to preserve information about memory layout + * at runtime + */ + class StructTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for struct with given member list + * + * to_self_tp. use this function to support .most_derived_self_tp() + */ + static std::unique_ptr make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp); + + /* specialization for std::pair + * coordinates with [reflect/Reflect.hpp] + */ + template + static std::unique_ptr pair() { + using struct_t = std::pair; + + std::vector mv; + { + auto lhs_access + (GeneralStructMemberAccessor::make + (&struct_t::first)); + + mv.push_back(StructMember("first", std::move(lhs_access))); + } + { + auto rhs_access + (GeneralStructMemberAccessor::make + (&struct_t::second)); + + mv.push_back(StructMember("second", std::move(rhs_access))); + } + + std::function null_to_self_tp; + + return make(std::move(mv), + false /*!have_to_self_tp*/, + null_to_self_tp); + } /*pair*/ + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_struct; } + virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, + void * object) const override { + if (this->have_to_self_tp_) { + return this->to_self_tp_(object); + } else { + return TypeDescrExtra::most_derived_self_tp(object_td, object); + } + } + /* object argument ignored for structs, since size is fixed */ + virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); } + virtual uint32_t n_child_fixed() const override { return this->member_v_.size(); } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + virtual TypeDescr fixed_child_td(uint32_t i) const override; + virtual std::string const & struct_member_name(uint32_t i) const override; + virtual StructMember const * struct_member(uint32_t i) const override; + + private: + StructTdx(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + : member_v_{std::move(member_v)}, + have_to_self_tp_{have_to_self_tp}, + to_self_tp_{std::move(to_self_tp)} {} + + private: + /* per-instance-variable reflection details */ + std::vector member_v_; + /* true if .to_self_tp() is defined */ + bool have_to_self_tp_ = false; + /* get TaggedPtr for most-derived subtype of supplied T-instance */ + std::function to_self_tp_; + }; /*StructTdx*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructTdx.hpp */ diff --git a/xo-reflect/include/xo/reflect/vector/VectorTdx.hpp b/xo-reflect/include/xo/reflect/vector/VectorTdx.hpp new file mode 100644 index 00000000..ebd318c2 --- /dev/null +++ b/xo-reflect/include/xo/reflect/vector/VectorTdx.hpp @@ -0,0 +1,123 @@ +/* file VectorTdx.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" + +namespace xo { + namespace reflect { + /* Extra type-associated information for a vector/array. + */ + class VectorTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for a vector type */ + //static std::unique_ptr make(); + + /** @brief true if array elements are stored at regularly-spaced offsetts **/ + virtual bool has_contiguous_storage() const = 0; + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_vector; } + virtual uint32_t n_child(void * object) const override = 0; + virtual uint32_t n_child_fixed() const override = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; + virtual TypeDescr fixed_child_td(uint32_t i) const override = 0; + /* (forbidden) */ + virtual std::string const & struct_member_name(uint32_t i) const override; + }; /*VectorTdx*/ + + // ----- StlVectorTdx ----- + + /* require: + * - VectorT::value_type + * - VectorT.size() + * - VectorT[int] :: lvalue + */ + template + class StlVectorTdx : public VectorTdx { + public: + using target_t = VectorT; + + static std::unique_ptr make() { + return std::unique_ptr(new StlVectorTdx()); + } /*make*/ + + virtual bool has_contiguous_storage() const override { return true; } + + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + virtual uint32_t n_child_fixed() const override { return 0; /*unknown*/ } + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); + + return establish_most_derived_tp(&((*vec)[i])); + } /*child_tp*/ + + virtual TypeDescr fixed_child_td(uint32_t /*i*/) const override { + return EstablishTypeDescr::establish(); + } + }; /*StlVectorTdx*/ + + // ----- std::array ----- + + /* coordinates with EstablishTdx>::make(), + * see [reflect/Reflect.hpp] + */ + + template + class StdArrayTdx : public StlVectorTdx> { + virtual uint32_t n_child(void * /*object*/) const override { return N; } + virtual uint32_t n_child_fixed() const override { return N; } + }; /*StdArrayTdx*/ + + // ----- std::vector ----- + + /* coordinates with EstablishTdx>::make() + * see [reflect/Reflect.hpp] + */ + template + class StdVectorTdx : public VectorTdx { + public: + using target_t = std::vector; + + static std::unique_ptr make() { + return std::unique_ptr(new StdVectorTdx()); + } /*make*/ + + virtual bool has_contiguous_storage() const override { return true; } + + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + virtual uint32_t n_child_fixed() const override { + return 0; /* not known without object */ + } + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); + + return establish_most_derived_tp(&((*vec)[i])); + } + + virtual TypeDescr fixed_child_td(uint32_t /*i*/) const override { + return EstablishTypeDescr::establish(); + } + }; /*StdVectorTdx*/ + } /*namespace reflect*/ + +} /*namespace xo*/ + +/* end VectorTdx.hpp */ diff --git a/xo-reflect/src/reflect/CMakeLists.txt b/xo-reflect/src/reflect/CMakeLists.txt new file mode 100644 index 00000000..87417066 --- /dev/null +++ b/xo-reflect/src/reflect/CMakeLists.txt @@ -0,0 +1,19 @@ +# reflect/CMakeLists.txt + +set(SELF_LIB reflect) +set(SELF_SRCS + TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp + atomic/AtomicTdx.cpp + pointer/PointerTdx.cpp + vector/VectorTdx.cpp + struct/StructTdx.cpp struct/StructMember.cpp + function/FunctionTdx.cpp + init_reflect.cpp) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} refcnt) +xo_dependency(${SELF_LIB} indentlog) +xo_dependency(${SELF_LIB} subsys) +#xo_boost_dependency(${SELF_LIB}) + +# end CMakeLists.txt diff --git a/xo-reflect/src/reflect/TaggedRcptr.cpp b/xo-reflect/src/reflect/TaggedRcptr.cpp new file mode 100644 index 00000000..8d68a60b --- /dev/null +++ b/xo-reflect/src/reflect/TaggedRcptr.cpp @@ -0,0 +1,30 @@ +/* file TaggedRcptr.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "TaggedRcptr.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + using xo::xtag; + using xo::tostr; + + namespace reflect { + void + TaggedRcptr::display(std::ostream & os) const + { + os << "td()->canonical_name()) + << xtag("addr", this->rc_address()) + << ">"; + } /*display*/ + + std::string + TaggedRcptr::display_string() const { + return tostr(*this); + } /*display_string*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedRcptr.cpp */ diff --git a/xo-reflect/src/reflect/TypeDescr.cpp b/xo-reflect/src/reflect/TypeDescr.cpp new file mode 100644 index 00000000..4d048015 --- /dev/null +++ b/xo-reflect/src/reflect/TypeDescr.cpp @@ -0,0 +1,330 @@ +/* @file TypeDescr.cpp */ + +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" +#include "TypeDescrExtra.hpp" +#include "Reflect.hpp" +#include "atomic/AtomicTdx.hpp" +#include "function/FunctionTdx.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::xtag; + using xo::tostr; + + namespace reflect { + uint32_t + TypeId::s_next_id = 1; + + std::string + FunctionTdxInfo::make_canonical_name() const + { + std::ostringstream ss; + + ss << retval_td_->canonical_name(); + ss << " (*)("; + for (std::size_t i = 0, n = arg_td_v_.size(); i < n; ++i) { + if (i > 0) + ss << ","; + ss << arg_td_v_[i]->canonical_name(); + } + ss << ")"; + + return ss.str(); + } /*make_canonical_name*/ + + // ----- TypeDescrBase ----- + + std::unordered_map + TypeDescrBase::s_function_type_map; + + std::unordered_map + TypeDescrBase::s_canonical_type_table_map; + + std::unordered_map + TypeDescrBase::s_native_type_table_map; + + std::unordered_map + TypeDescrBase::s_coalesced_type_table_map; + + std::vector> + TypeDescrBase::s_type_table_v; + + TypeDescrW + TypeDescrBase::require(const std::type_info * native_tinfo, + const std::string & canonical_name, + detail::Invoker * invoker, + std::unique_ptr tdextra) + { + if (native_tinfo) { + /* 1. lookup by tinfo hash_code in s_type_table_map + * Not available for manually-constructed type descriptions. + */ + { + auto ix = s_native_type_table_map.find(TypeInfoRef(native_tinfo)); + + if ((ix != s_native_type_table_map.end()) && ix->second) + return ix->second; + } + + /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ + { + auto ix = s_coalesced_type_table_map.find(TypeInfoRef(native_tinfo)); + + if ((ix != s_coalesced_type_table_map.end()) && ix->second) + return ix->second; + } + } + + /* 3. lookup by canonical_name, before we create a new slot. + * + * Have to accept that on clang type_info objects aren't always unique (!$@#!!) + */ + { + auto ix = s_canonical_type_table_map.find(canonical_name); + + if (ix != s_canonical_type_table_map.end()) { + /** assume existing slot, with same canonical name, + * represents the same type as native_tinfo + **/ + if (native_tinfo) { + auto existing_tinfo = ix->second->native_typeinfo(); + + /* given we have a match: + * - on existing TypeDescr + * - with same canonical name as type assoc'd with native_tinfo + * then: + * it's possible existing TypeDescr was manually constructed + * (i.e. without capturing std::type_info). + * + * With that in mind, attach that typeinfo now + */ + if (!existing_tinfo) { + ix->second->assign_native_tinfo(native_tinfo); + + s_native_type_table_map[TypeInfoRef(native_tinfo)] + = ix->second; + } + + if (existing_tinfo + && (existing_tinfo != native_tinfo)) + { + /* we have encountered distinct std::type_info objects + * that appear to represent the same type. + * (at least types with the same canonical name) + * + * We observe this happening sometimes with clang-prepared + * shared libraries; perhaps something going wrong with + * symbol coalescing. + * + * Store the dups in s_coalesced_type_table_map for future reference. + */ + auto jx = s_coalesced_type_table_map.find(TypeInfoRef(native_tinfo)); + + if (jx == s_coalesced_type_table_map.end()) + s_coalesced_type_table_map[TypeInfoRef(native_tinfo)] + = ix->second; + } + } + + return ix->second; + } + } + + /* when control here: + * need type added to: + * - s_type_table_v + * - s_canonical_type_table_map + * - s_native_type_table_map + * - s_coalesced_type_table_map (omit, only used for dups) + * - s_function_type_map (if type represents a function) + */ + + /* allocate slot for a new TypeDescr instance: */ + + TypeId new_td_id = TypeId::allocate(); + + if (s_type_table_v.size() <= new_td_id.id()) + s_type_table_v.resize(new_td_id.id() + 1); + + auto & new_slot = s_type_table_v[new_td_id.id()]; + + auto new_td = new TypeDescrBase(new_td_id, + native_tinfo, + canonical_name, + std::move(tdextra), + invoker); + + new_slot.reset(new_td); + + s_canonical_type_table_map[std::string(new_slot->canonical_name())] = new_td; + if (native_tinfo) + s_native_type_table_map[TypeInfoRef(native_tinfo)] = new_td; + + if (new_td->tdextra() && new_td->is_function()) { + s_function_type_map[*(new_td->fn_info())] = new_td; + } + + return new_slot.get(); + } /*require*/ + + TypeDescrW + TypeDescrBase::require_by_fn_info(const FunctionTdxInfo & fn_info) { + auto ix = s_function_type_map.find(fn_info); + + if (ix != s_function_type_map.end()) + return ix->second; + + auto fn_tdextra = FunctionTdx::make_function(fn_info); + + return require(nullptr /*native_tinfo - n/avail on this path*/, + fn_info.make_canonical_name(), + nullptr /*invoker*/, + std::move(fn_tdextra)); + } /*require_by_fn_info*/ + + TypeDescr + TypeDescrBase::lookup_by_name(const std::string & name) { + auto ix = s_canonical_type_table_map.find(name); + + if (ix == s_canonical_type_table_map.end()) { + throw std::runtime_error(tostr("TypeDescrBase::lookup_by_name" + ": no registered type with canonical name T", + xtag("T", name))); + } + + return ix->second; + } /*lookup_by_name*/ + + void + TypeDescrBase::print_reflected_types(std::ostream & os) + { + os << "display(os); + } + } + + os << ">\n"; + } /*print_reflected_types*/ + + namespace { + /* readability hack: + * foo::bar::Quux ==> Quux + * but lookout for template names: + * std::pair ==> pair + */ + std::string_view + unqualified_name(std::string_view const & canonical_name) + { + size_t m = canonical_name.find_first_of('<'); + + /* skip ':', but only in range [0..m) */ + size_t p = canonical_name.find_last_of(':', m); + + if (p == std::string_view::npos) { + return canonical_name; + } else { + if ((canonical_name.substr(0, 9) == "std::pair") + || (canonical_name.substr(0, 13) == "std::_1::pair")) + { + return std::string_view("pair"); + } else { + return std::string_view(canonical_name.substr(p+1)); + } + } + } /*unqualified_name*/ + } /*namespace*/ + + TypeDescrBase::TypeDescrBase(TypeId id, + const std::type_info * native_tinfo, + const std::string & canonical_name, + std::unique_ptr tdextra, + detail::Invoker * invoker) + : id_{std::move(id)}, + native_typeinfo_{native_tinfo}, + canonical_name_{std::move(canonical_name)}, + short_name_{unqualified_name(canonical_name_)}, + invoker_{invoker}, + tdextra_{std::move(tdextra)} + { + } + + TaggedPtr + TypeDescrBase::most_derived_self_tp(void * object) const + { + return this->tdextra_->most_derived_self_tp(this, object); + } /*most_derived_self_tp*/ + + TaggedPtr + TypeDescrBase::child_tp(uint32_t i, void * object) const + { + return this->tdextra_->child_tp(i, object); + } /*child_tp*/ + + void + TypeDescrBase::display(std::ostream & os) const + { + os << "metatype()) + << ">"; + } /*display*/ + + std::string + TypeDescrBase::display_string() const + { + return tostr(*this); + } /*display_string*/ + + bool + TypeDescrBase::mark_complete() + { + bool retval = this->complete_flag_; + + this->complete_flag_ = true; + + return retval; + } /*mark_complete*/ + +#ifdef NOT_USING + void + TypeDescrBase::assign_tdextra(std::unique_ptr tdx) + { + scope log(XO_ENTER0(verbose), + xtag("canonical_name", this->canonical_name()), + xtag("tdextra.old", this->tdextra_.get()), + xtag("metatype.old", (this->tdextra_ + ? this->tdextra_->metatype() + : Metatype::mt_invalid)), + xtag("metatype.new", tdx->metatype())); + + this->complete_flag_ = true; + this->tdextra_ = std::move(tdx); + } /*assign_tdextra*/ +#endif + + TypeDescrTable::TypeDescrTable() { + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + } /*ctor*/ + + TypeDescrTable + TypeDescrTable::s_instance; + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescr.cpp */ diff --git a/xo-reflect/src/reflect/TypeDescrExtra.cpp b/xo-reflect/src/reflect/TypeDescrExtra.cpp new file mode 100644 index 00000000..9e82fc8e --- /dev/null +++ b/xo-reflect/src/reflect/TypeDescrExtra.cpp @@ -0,0 +1,37 @@ +/* file TypeDescrExtra.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "TypeDescrExtra.hpp" +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" +#include + +namespace xo { + namespace reflect { + TaggedPtr + TypeDescrExtra::most_derived_self_tp(TypeDescrBase const * object_td, + void * object) const + { + return TaggedPtr(object_td, object); + } /*most_derived_self_tp*/ + + std::string const & + TypeDescrExtra::struct_member_name(uint32_t /*i*/) const { + assert(false); + + static std::string s_null; + return s_null; + } /*struct_member_name*/ + + StructMember const * + TypeDescrExtra::struct_member(uint32_t /*i*/) const { + assert(false); + + return nullptr; + } /*struct_member*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescrExtra.cpp */ diff --git a/xo-reflect/src/reflect/atomic/AtomicTdx.cpp b/xo-reflect/src/reflect/atomic/AtomicTdx.cpp new file mode 100644 index 00000000..bb7e1b8d --- /dev/null +++ b/xo-reflect/src/reflect/atomic/AtomicTdx.cpp @@ -0,0 +1,32 @@ +/* @file AtomicTdx.cpp */ + +#include "atomic/AtomicTdx.hpp" +#include "TaggedPtr.hpp" +#include "TypeDescr.hpp" +#include + +namespace xo { + namespace reflect { + std::unique_ptr + AtomicTdx::make() { + return std::unique_ptr(new AtomicTdx()); + } /*make*/ + + TaggedPtr + AtomicTdx::child_tp(uint32_t /*i*/, void * /*object*/) const { + return TaggedPtr::universal_null(); + } /*child_tp*/ + + TypeDescr + AtomicTdx::fixed_child_td(uint32_t /*i*/) const { + return nullptr; + } + + std::string const & + AtomicTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end AtomicTdx.cpp */ diff --git a/xo-reflect/src/reflect/function/FunctionTdx.cpp b/xo-reflect/src/reflect/function/FunctionTdx.cpp new file mode 100644 index 00000000..551cfef0 --- /dev/null +++ b/xo-reflect/src/reflect/function/FunctionTdx.cpp @@ -0,0 +1,54 @@ +/* @file FunctionTdx.cpp */ + +#include "function/FunctionTdx.hpp" +#include "TaggedPtr.hpp" +#include "TypeDescr.hpp" + +namespace xo { + namespace reflect { + /** create instance. Will be invoked exactly once for each reflected function type **/ + std::unique_ptr + FunctionTdx::make_function(TypeDescr retval_td, + std::vector arg_td_v, + bool is_noexcept) + { + return make_function(FunctionTdxInfo(retval_td, + std::move(arg_td_v), + is_noexcept)); + } + + std::unique_ptr + FunctionTdx::make_function(const FunctionTdxInfo & fn_info) + { + return std::unique_ptr(new FunctionTdx(fn_info)); + } + + FunctionTdx::FunctionTdx(const FunctionTdxInfo & fn_info) + : info_{fn_info} + { + if (!info_.retval_td_) { + throw std::runtime_error("FunctionTdx::ctor: null return type?"); + } + } + + TaggedPtr + FunctionTdx::child_tp(uint32_t /*i*/, void * /*object*/) const + { + return TaggedPtr::universal_null(); + } + + TypeDescr + FunctionTdx::fixed_child_td(uint32_t /*i*/) const { + return nullptr; + } + + const std::string & + FunctionTdx::struct_member_name(uint32_t i) const + { + return TypeDescrExtra::struct_member_name(i); + } + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end FunctionTdx.cpp */ diff --git a/xo-reflect/src/reflect/init_reflect.cpp b/xo-reflect/src/reflect/init_reflect.cpp new file mode 100644 index 00000000..d95bf2e3 --- /dev/null +++ b/xo-reflect/src/reflect/init_reflect.cpp @@ -0,0 +1,23 @@ +/* file init_reflect.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_reflect.hpp" +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + void + InitSubsys::init() + { + /* placeholder -- expecting there to be non-trivial content soon */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + return Subsystem::provide("reflect", &init); + } /*require*/ +} /*namespace xo*/ + +/* end init_reflect.cpp */ diff --git a/xo-reflect/src/reflect/pointer/PointerTdx.cpp b/xo-reflect/src/reflect/pointer/PointerTdx.cpp new file mode 100644 index 00000000..cc81e391 --- /dev/null +++ b/xo-reflect/src/reflect/pointer/PointerTdx.cpp @@ -0,0 +1,17 @@ +/* file PointerTdx.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "pointer/PointerTdx.hpp" + +namespace xo { + namespace reflect { + std::string const & + PointerTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end PointerTdx.cpp */ diff --git a/xo-reflect/src/reflect/struct/StructMember.cpp b/xo-reflect/src/reflect/struct/StructMember.cpp new file mode 100644 index 00000000..7f27af8c --- /dev/null +++ b/xo-reflect/src/reflect/struct/StructMember.cpp @@ -0,0 +1,36 @@ +/* file StructMember.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "struct/StructMember.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + using xo::scope; + using xo::xtag; + + namespace reflect { + static_assert(std::is_move_constructible_v); + + TaggedPtr + AbstractStructMemberAccessor::member_tp(void * struct_addr) const + { + //XO_SCOPE(lscope); + + TaggedPtr retval = (this + ->member_td() + ->most_derived_self_tp(this->address(struct_addr))); + + //lscope.log(xtag("self_td", this->struct_td()->short_name()), + // xtag("member_td.declared", this->member_td()->short_name()), + // xtag("member_td.actual", retval.td()->short_name())); + + return retval; + } /*member_tp*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructMember.cpp */ diff --git a/xo-reflect/src/reflect/struct/StructTdx.cpp b/xo-reflect/src/reflect/struct/StructTdx.cpp new file mode 100644 index 00000000..c9edc11b --- /dev/null +++ b/xo-reflect/src/reflect/struct/StructTdx.cpp @@ -0,0 +1,67 @@ +/* @file StructTdx.cpp */ + +#include "struct/StructTdx.hpp" +#include "TypeDescr.hpp" + +namespace xo { + using std::uint32_t; + + namespace reflect { + std::unique_ptr + StructTdx::make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + { + return std::unique_ptr(new StructTdx(std::move(member_v), + have_to_self_tp, + std::move(to_self_tp))); + } /*make*/ + + TaggedPtr + StructTdx::child_tp(uint32_t i, void * object) const + { + if (i >= this->member_v_.size()) { + /* TODO: raise exception here? */ + return TaggedPtr::universal_null(); + } + + const StructMember & member_info = this->member_v_[i]; + + return member_info.get_member_tp(object); + + } /*get_child*/ + + TypeDescr + StructTdx::fixed_child_td(uint32_t i ) const + { + if (i >= this->member_v_.size()) + return nullptr; + + const StructMember & member_info = this->member_v_[i]; + + return member_info.get_member_td(); + } /*fixed_child_td*/ + + std::string const & + StructTdx::struct_member_name(uint32_t i) const + { + StructMember const * sm = this->struct_member(i); + + return sm->member_name(); + } /*struct_member_name*/ + + StructMember const * + StructTdx::struct_member(uint32_t i) const + { + if (i >= this->member_v_.size()) { + /* TODO: raise exception here */ + assert(false); + return nullptr; + } + + return &(this->member_v_[i]); + } /*struct_member*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructTdx.cpp */ diff --git a/xo-reflect/src/reflect/vector/VectorTdx.cpp b/xo-reflect/src/reflect/vector/VectorTdx.cpp new file mode 100644 index 00000000..fd2e40e4 --- /dev/null +++ b/xo-reflect/src/reflect/vector/VectorTdx.cpp @@ -0,0 +1,20 @@ +/* file VectorTdx.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "vector/VectorTdx.hpp" + +namespace xo { + namespace reflect { + std::string const & + VectorTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + + } /*namespace reflect*/ + +} /*namespace xo*/ + + +/* end VectorTdx.cpp */ diff --git a/xo-reflect/utest/CMakeLists.txt b/xo-reflect/utest/CMakeLists.txt new file mode 100644 index 00000000..535907a5 --- /dev/null +++ b/xo-reflect/utest/CMakeLists.txt @@ -0,0 +1,15 @@ +# build unittest reflect/utest + +set(SELF_EXECUTABLE_NAME utest.reflect) +set(SELF_SOURCE_FILES + reflect_utest_main.cpp + StructReflector.test.cpp + VectorTdx.test.cpp + StructTdx.test.cpp + FunctionTdx.test.cpp) + +xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/xo-reflect/utest/FunctionTdx.test.cpp b/xo-reflect/utest/FunctionTdx.test.cpp new file mode 100644 index 00000000..278a2532 --- /dev/null +++ b/xo-reflect/utest/FunctionTdx.test.cpp @@ -0,0 +1,40 @@ +/* @file FunctionTdx.test.cpp */ + +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("function-reflect1", "[reflect]") { + using FunctionType = double (*)(double); + + FunctionType fn = ::sqrt; + + TaggedPtr tp = Reflect::make_tp(&fn); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &fn); + REQUIRE(tp.is_function()); + REQUIRE(tp.is_pointer() == false); + REQUIRE(tp.is_vector() == false); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_function); + REQUIRE(tp.recover_native() == &fn); + REQUIRE(tp.n_child() == 0); /*not a composite*/ + // REQUIRE(tp.child_td(0) == ... + REQUIRE(tp.td()->fn_retval() == Reflect::require()); + REQUIRE(tp.n_fn_arg() == 1); + REQUIRE(tp.td()->fn_arg(0) == Reflect::require()); + } /*TEST_CASE(function-reflect1)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end FunctionTdx.test.cpp */ diff --git a/xo-reflect/utest/StructReflector.test.cpp b/xo-reflect/utest/StructReflector.test.cpp new file mode 100644 index 00000000..4ab0772d --- /dev/null +++ b/xo-reflect/utest/StructReflector.test.cpp @@ -0,0 +1,142 @@ +/* file StructReflector.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" +#include + +#define STRINGIFY(x) #x + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::StructReflector; + using xo::reflect::Reflect; + + namespace ut { + namespace { + struct TestStruct0 {}; + struct TestStruct1 {}; + } + + TEST_CASE("struct-reflect-empty", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == false); + REQUIRE(Reflect::is_reflected() == true); + + TestStruct0 recd0; + TaggedPtr tp = Reflect::make_tp(&recd0); + + REQUIRE(tp.address() == &recd0); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 0); + + REQUIRE(tp.get_child(0).is_universal_null()); + REQUIRE(tp.get_child(0).td() == nullptr); + REQUIRE(tp.get_child(0).address() == nullptr); + } /*TEST_CASE(struct-reflect-empty)*/ + + namespace { + struct TestStructS1 { int x_; }; + } + + TEST_CASE("struct-reflect-s1", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + //sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_); + REFLECT_LITERAL_MEMBER(sr, x_); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + } /*TEST_CASE(struct-reflect-s1)*/ + + namespace { + struct TestStructS2 { int x_; }; + } + + TEST_CASE("struct-reflect-s2", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + //sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_); + REFLECT_MEMBER(sr, x); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + + TestStructS2 recd1{666}; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + REQUIRE(tp.address() == &recd1); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 1); + + REQUIRE(tp.get_child(0).td() == Reflect::require()); + REQUIRE(tp.get_child(0).address() == &(recd1.x_)); + + REQUIRE(tp.get_child(1).is_universal_null()); + } /*TEST_CASE(struct-reflect-s2)*/ + + namespace { + struct TestStructS3 { int x_; char y_; double z_; }; + } + + TEST_CASE("struct-reflect-s3", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + REFLECT_MEMBER(sr, x); + REFLECT_MEMBER(sr, y); + REFLECT_MEMBER(sr, z); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + + /* verify we can traverse reflected instances */ + TestStructS3 recd1{666, 'Y', -1.234}; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + REQUIRE(tp.address() == &recd1); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 3); + + REQUIRE(tp.get_child(0).td() == Reflect::require()); + REQUIRE(tp.get_child(0).address() == &(recd1.x_)); + + REQUIRE(tp.get_child(1).td() == Reflect::require()); + REQUIRE(tp.get_child(1).address() == &(recd1.y_)); + + REQUIRE(tp.get_child(2).td() == Reflect::require()); + REQUIRE(tp.get_child(2).address() == &(recd1.z_)); + + REQUIRE(tp.get_child(3).is_universal_null()); + REQUIRE(tp.get_child(3).td() == nullptr); + REQUIRE(tp.get_child(3).address() == nullptr); + + } /*TEST_CASE(struct-reflect-s3)*/ + } /*namespace ut */ +} /*namespace xo*/ + + +/* end StructReflector.test.cpp */ diff --git a/xo-reflect/utest/StructTdx.test.cpp b/xo-reflect/utest/StructTdx.test.cpp new file mode 100644 index 00000000..6b1fb2cd --- /dev/null +++ b/xo-reflect/utest/StructTdx.test.cpp @@ -0,0 +1,57 @@ +/* file StructTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("std-pair-reflect", "[reflect]") { + std::pair p; + + TaggedPtr tp = Reflect::make_tp(&p); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &p); + REQUIRE(tp.is_struct()); + REQUIRE(tp.is_vector() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_struct); + REQUIRE(tp.recover_native>() == &p); + REQUIRE(tp.n_child() == 2); /* struct with 2 members */ + REQUIRE(tp.struct_member_name(0) == "first"); + REQUIRE(tp.struct_member_name(1) == "second"); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(p.first)); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(p.first)); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(p.second)); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(p.second)); + REQUIRE(tp1.n_child() == 0); + + } /*TEST_CASE(std-pair-reflect)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end StructTdx.test.cpp */ diff --git a/xo-reflect/utest/VectorTdx.test.cpp b/xo-reflect/utest/VectorTdx.test.cpp new file mode 100644 index 00000000..fe4e58a4 --- /dev/null +++ b/xo-reflect/utest/VectorTdx.test.cpp @@ -0,0 +1,181 @@ +/* file VectorTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("std-vector-reflect-empty", "[reflect]") { + std::vector v; + + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-vector-reflect-empty)*/ + + TEST_CASE("std-vector-reflect-one", "[reflect]") { + std::vector v = { 1.123 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-vector-reflect-one)*/ + + TEST_CASE("std-vector-reflect-two", "[reflect]") { + std::vector v = { 1.123, 2.234 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-vector-reflect-two)*/ + + // ----- std::array ----- + + TEST_CASE("std-array-reflect-empty", "[reflect]") { + std::array v; + + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-array-reflect-empty)*/ + + TEST_CASE("std-array-reflect-one", "[reflect]") { + std::array v = { 1.123 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-array-reflect-one)*/ + + TEST_CASE("std-array-reflect-two", "[reflect]") { + std::array v = { 1.123, 2.234 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-array-reflect-two)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end VectorTdx.test.cpp */ diff --git a/xo-reflect/utest/reflect_utest_main.cpp b/xo-reflect/utest/reflect_utest_main.cpp new file mode 100644 index 00000000..91c62d09 --- /dev/null +++ b/xo-reflect/utest/reflect_utest_main.cpp @@ -0,0 +1,6 @@ +/* file reflect_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end reflect_utest_main.cpp */