From fdb4ca37f44105d8062f31a3d1dcffeaaf3c0efc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:49:42 -0400 Subject: [PATCH] reflect: initial implementation --- CMakeLists.txt | 184 +++++++ FILESYSTEM | 1 + cmake/code-coverage.cmake | 678 ++++++++++++++++++++++++ cmake/cxx.cmake | 98 ++++ cmake/reflectConfig.cmake.in | 4 + cmake/run-external-ctest | 8 + include/reflect/CMakeLists.txt | 32 ++ include/reflect/EstablishTypeDescr.hpp | 62 +++ include/reflect/Metatype.hpp | 38 ++ include/reflect/Reflect.hpp | 235 ++++++++ include/reflect/SelfTagging.hpp | 31 ++ include/reflect/StructReflector.hpp | 161 ++++++ include/reflect/TaggedPtr.hpp | 125 +++++ include/reflect/TaggedRcptr.hpp | 88 +++ include/reflect/TypeDescr.hpp | 302 +++++++++++ include/reflect/TypeDescrExtra.hpp | 65 +++ include/reflect/TypeDrivenMap.hpp | 47 ++ include/reflect/atomic/AtomicTdx.hpp | 37 ++ include/reflect/init_reflect.hpp | 22 + include/reflect/pointer/PointerTdx.hpp | 76 +++ include/reflect/struct/StructMember.hpp | 236 +++++++++ include/reflect/struct/StructTdx.hpp | 94 ++++ include/reflect/vector/VectorTdx.hpp | 100 ++++ repo/README.md | 5 + src/reflect/CMakeLists.txt | 52 ++ src/reflect/TaggedRcptr.cpp | 30 ++ src/reflect/TypeDescr.cpp | 194 +++++++ src/reflect/TypeDescrExtra.cpp | 37 ++ src/reflect/atomic/AtomicTdx.cpp | 24 + src/reflect/init_reflect.cpp | 23 + src/reflect/pointer/PointerTdx.cpp | 17 + src/reflect/struct/StructMember.cpp | 36 ++ src/reflect/struct/StructTdx.cpp | 55 ++ src/reflect/vector/VectorTdx.cpp | 20 + utest/CMakeLists.txt | 57 ++ utest/StructReflector.test.cpp | 142 +++++ utest/StructTdx.test.cpp | 59 +++ utest/VectorTdx.test.cpp | 181 +++++++ utest/reflect_utest_main.cpp | 6 + 39 files changed, 3662 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 FILESYSTEM create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 cmake/reflectConfig.cmake.in create mode 100755 cmake/run-external-ctest create mode 100644 include/reflect/CMakeLists.txt create mode 100644 include/reflect/EstablishTypeDescr.hpp create mode 100644 include/reflect/Metatype.hpp create mode 100644 include/reflect/Reflect.hpp create mode 100644 include/reflect/SelfTagging.hpp create mode 100644 include/reflect/StructReflector.hpp create mode 100644 include/reflect/TaggedPtr.hpp create mode 100644 include/reflect/TaggedRcptr.hpp create mode 100644 include/reflect/TypeDescr.hpp create mode 100644 include/reflect/TypeDescrExtra.hpp create mode 100644 include/reflect/TypeDrivenMap.hpp create mode 100644 include/reflect/atomic/AtomicTdx.hpp create mode 100644 include/reflect/init_reflect.hpp create mode 100644 include/reflect/pointer/PointerTdx.hpp create mode 100644 include/reflect/struct/StructMember.hpp create mode 100644 include/reflect/struct/StructTdx.hpp create mode 100644 include/reflect/vector/VectorTdx.hpp create mode 100644 repo/README.md create mode 100644 src/reflect/CMakeLists.txt create mode 100644 src/reflect/TaggedRcptr.cpp create mode 100644 src/reflect/TypeDescr.cpp create mode 100644 src/reflect/TypeDescrExtra.cpp create mode 100644 src/reflect/atomic/AtomicTdx.cpp create mode 100644 src/reflect/init_reflect.cpp create mode 100644 src/reflect/pointer/PointerTdx.cpp create mode 100644 src/reflect/struct/StructMember.cpp create mode 100644 src/reflect/struct/StructTdx.cpp create mode 100644 src/reflect/vector/VectorTdx.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/StructReflector.test.cpp create mode 100644 utest/StructTdx.test.cpp create mode 100644 utest/VectorTdx.test.cpp create mode 100644 utest/reflect_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4f777c5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,184 @@ +# reflect/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(reflect VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +set(XO_PROJECT_NAME reflect) +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") + +add_definitions(${PROJECT_CXX_FLAGS}) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +# ---------------------------------------------------------------- +# external projects (need these to exist before add_subdirectory() below) +# +# we are expecting these projects to coexist peacefully in build/local +# (i.e. can run their `make install` steps independently with prefix build/local, +# without any collisions) +# + +include(ExternalProject) + +## ----- indentlog ------ + +# NOTE: we could have cmake handle git interaction, +# but we want source for certain dependencies to live in a location +# that's suitable for accepting changes + coordinated commits. +# In particular, not in the build directory! +# +externalproject_add( + project_indentlog + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/indentlog + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/indentlog + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(indentlog INTERFACE IMPORTED) +#set_property(TARGET indentlog PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/libindentlog.so) +add_dependencies(indentlog project_indentlog) + +# runs ctest in indentlog build dir +add_test(NAME indentlog COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/indentlog) +#target_code_coverage(indentlog EXTERNAL AUTO ALL) + +# ----- subsys ----- + +externalproject_add( + project_subsys + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/subsys + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/subsys + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(subsys INTERFACE IMPORTED) +add_dependencies(subsys project_subsys) + +# runs ctest in subsys build dir +add_test(NAME subsys COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/subsys) + +# ----- refcnt ----- + +# CMAKE_ARGS +# CMAKE_BUILD_TYPE propagate Debug/Release build type +# CODE_COVERAGE propagate code coverage setting +# CMAKE_PREFIX_PATH path for support cmake files of dependencies (needed for find_package() to work) +# CMAKE_INSTALL_PREFIX install subproject here +# SOURCE_DIR -- where to find already established source code +# BINARY_DIR -- run build for external project here +# INSTALL_DIR -- (temporarily) install external project here +# +externalproject_add( + project_refcnt + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/refcnt + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/refcnt + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(refcnt SHARED IMPORTED) +set_property(TARGET refcnt PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/librefcnt.so) +add_dependencies(refcnt project_refcnt) +add_dependencies(refcnt project_indentlog) + +# runs ctest in refcnt build dir +add_test(NAME refcnt COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/refcnt) + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/reflect) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# cmake export: +# +# populate .cmake files in $CMAKE_INSTALL_LIBDIR/cmake/reflect. +# cmake projects that include this directory in $CMAKE_PREFIX_PATH +# can use +# find_package(reflect REQUIRED) +# and +# target_link_libraries(${sometarget} PUBLIC reflect) +# to use the reflect library + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) + +# generates build/reflectConfigVersion.cmake +write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +# generates build/reflectConfig.cmake +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} +) + +# creates {reflectTargets.cmake, reflectTargets-noconfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +# requires +# install(.. EXPORT reflectTargets ..) +# +install( + EXPORT ${XO_PROJECT_NAME}Targets + DESTINATION lib/cmake/${XO_PROJECT_NAME} +) + +# creates {reflectConfigVersion.cmake, reflectConfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) + +# ---------------------------------------------------------------- +# install .hpp files + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) diff --git a/FILESYSTEM b/FILESYSTEM new file mode 100644 index 0000000..9af86a7 --- /dev/null +++ b/FILESYSTEM @@ -0,0 +1 @@ +repo -- git submoduules here diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 0000000..b6b3606 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 0000000..fa6efb8 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,98 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# variable +# XO_ADDRESS_SANITIZE +# determines whether to enable address sanitizer for the XO project +# (see toplevel CMakeLists.txt) +# ---------------------------------------------------------------- +if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + +# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON +# +# address sanitizer build complains about _FORTIFY_SOURCE redefines +# In file included from :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# EXPORT drives .cmake config files intended for consumption +# by higher-level cmake projects via find_package() +# +macro(xo_install_library target) + install( + TARGETS ${target} + EXPORT ${target}Targets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlog_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in new file mode 100644 index 0000000..e13a2c5 --- /dev/null +++ b/cmake/reflectConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/cmake/run-external-ctest b/cmake/run-external-ctest new file mode 100755 index 0000000..386c45a --- /dev/null +++ b/cmake/run-external-ctest @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# $1 = build directory + +cd $1 +shift + +ctest "${@}" diff --git a/include/reflect/CMakeLists.txt b/include/reflect/CMakeLists.txt new file mode 100644 index 0000000..a7e07dd --- /dev/null +++ b/include/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/include/reflect/EstablishTypeDescr.hpp b/include/reflect/EstablishTypeDescr.hpp new file mode 100644 index 0000000..2b0e25a --- /dev/null +++ b/include/reflect/EstablishTypeDescr.hpp @@ -0,0 +1,62 @@ +/* file EstablishTypeDescr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include "reflect/TaggedPtr.hpp" + +namespace xo { + namespace reflect { + 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), + type_name(), + nullptr); + +#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/include/reflect/Metatype.hpp b/include/reflect/Metatype.hpp new file mode 100644 index 0000000..23f3fd1 --- /dev/null +++ b/include/reflect/Metatype.hpp @@ -0,0 +1,38 @@ +/* @file Metatype.hpp */ + +#pragma once + +#include + +namespace xo { + namespace reflect { + enum class Metatype { mt_invalid, mt_atomic, mt_pointer, mt_vector, mt_struct }; + + 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; + default: + os << "???"; + } + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Metatype.hpp */ diff --git a/include/reflect/Reflect.hpp b/include/reflect/Reflect.hpp new file mode 100644 index 0000000..73ef68f --- /dev/null +++ b/include/reflect/Reflect.hpp @@ -0,0 +1,235 @@ +/* file Reflect.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/SelfTagging.hpp" +#include "reflect/EstablishTypeDescr.hpp" +#include "reflect/atomic/AtomicTdx.hpp" +#include "reflect/pointer/PointerTdx.hpp" +#include "reflect/vector/VectorTdx.hpp" +#include "reflect/struct/StructTdx.hpp" +#include "refcnt/Refcounted.hpp" +#include +#include +#include // for std::pair<> + +namespace xo { + namespace reflect { + template + class EstablishTdx { + public: + static std::unique_ptr make() { return AtomicTdx::make(); } + }; /*EstablishTdx*/ + + // ----- xo::ref::rp ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::array ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::vector ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::pair ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- 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(std::move(final_tdx)); + + /* also need to require for each child */ + } + + return retval_td; + } /*require*/ + + /* 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); } + }; /*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*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Reflect.hpp */ diff --git a/include/reflect/SelfTagging.hpp b/include/reflect/SelfTagging.hpp new file mode 100644 index 0000000..29517e3 --- /dev/null +++ b/include/reflect/SelfTagging.hpp @@ -0,0 +1,31 @@ +/* file SelfTagging.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "refcnt/Refcounted.hpp" +#include "reflect/TypeDescr.hpp" +#include "reflect/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/include/reflect/StructReflector.hpp b/include/reflect/StructReflector.hpp new file mode 100644 index 0000000..f223757 --- /dev/null +++ b/include/reflect/StructReflector.hpp @@ -0,0 +1,161 @@ +/* @file StructReflector.hpp */ + +#pragma once + +#include "reflect/Reflect.hpp" +#include "reflect/TypeDescr.hpp" +#include "reflect/struct/StructMember.hpp" +#include "reflect/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; } + + 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); + }); + + auto tdx = StructTdx::make(std::move(this->member_v_), + have_to_self_tp, + to_self_tp_fn); + + this->td_->assign_tdextra(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/include/reflect/TaggedPtr.hpp b/include/reflect/TaggedPtr.hpp new file mode 100644 index 0000000..653c8ad --- /dev/null +++ b/include/reflect/TaggedPtr.hpp @@ -0,0 +1,125 @@ +/* @file TaggedPtr.hpp */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +//#include "reflect/EstablishTypeDescr.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_vector() const { return td_ && td_->is_vector(); } + bool is_struct() const { return td_ && td_->is_struct(); } + + + /* 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_native(this->address_); } + + uint32_t n_child() const { + return this->td_->n_child(this->address_); + } /*n_child*/ + + TaggedPtr get_child(uint32_t i) const { + return this->td_->child_tp(i, this->address_); + } /*get_child*/ + + /* 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/include/reflect/TaggedRcptr.hpp b/include/reflect/TaggedRcptr.hpp new file mode 100644 index 0000000..9ca8b15 --- /dev/null +++ b/include/reflect/TaggedRcptr.hpp @@ -0,0 +1,88 @@ +/* file TaggedRcptr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TaggedPtr.hpp" +// causes #include cycle, reflect/Reflect.hpp includes this header +//#include "reflect/Reflect.hpp" +#include "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/include/reflect/TypeDescr.hpp b/include/reflect/TypeDescr.hpp new file mode 100644 index 0000000..0861407 --- /dev/null +++ b/include/reflect/TypeDescr.hpp @@ -0,0 +1,302 @@ +/* @file TypeDescr.hpp */ + +#pragma once + +//#include "reflect/atomic/AtomicTdx.hpp" +#include "reflect/TypeDescrExtra.hpp" +#include "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 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 + + 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_type_table_map.find(TypeInfoRef(tinfo)) + != s_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(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); + + /* print table of reflected types to os */ + static void print_reflected_types(std::ostream & os); + + TypeId id() const { return id_; } + std::type_info const * typeinfo() const { return typeinfo_; } + std::string_view const & canonical_name() const { return canonical_name_; } + std::string_view const & 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 T. + * + * Warning: comparing typeinfo address can give false negatives. + * suspect this is caused by problems coalescing linker symbols + * in the clang toolchain. + */ + template + bool is_native() const { + return ((this->typeinfo() == &typeid(T)) + || (this->typeinfo()->hash_code() == typeid(T).hash_code()) + || (this->typeinfo()->name() == typeid(T).name())); + } /*is_native*/ + + /* safe downcast -- like dynamic_cast<>, but does not require a source type */ + template + T * recover_native(void * address) const { + if (this->is_native()) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native*/ + + bool is_vector() const { return this->tdextra_->is_vector(); } + bool is_struct() const { return this->tdextra_->is_struct(); } + + /* 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); } + 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*/ + + 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(std::unique_ptr tdx); + + private: + TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); + + private: + /* invariant: + * - for all TypeDescrImpl instances x: + * - s_type_table_v[x->id()] = x + * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x + */ + + /* hashmap of all TypeDescr instances, indexed by . singleton */ + static std::unordered_map> s_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; + + /* vector of all TypeDescr instances. singleton. */ + static std::vector s_type_table_v; + + private: + /* unique id# for this type */ + TypeId id_; + /* typeinfo for type T */ + std::type_info const * typeinfo_ = nullptr; + /* canonical name for this type (see demangle.hpp for type_name()) + * e.g. + * xo::option::Px2 + */ + std::string_view canonical_name_; + /* suffix of .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; + /* 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<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescr.hpp */ diff --git a/include/reflect/TypeDescrExtra.hpp b/include/reflect/TypeDescrExtra.hpp new file mode 100644 index 0000000..2d2be7f --- /dev/null +++ b/include/reflect/TypeDescrExtra.hpp @@ -0,0 +1,65 @@ +/* @file TypeDescrExtra.hpp */ + +#pragma once + +#include "reflect/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 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_vector() const { return this->metatype() == Metatype::mt_vector; } + bool is_struct() const { return this->metatype() == Metatype::mt_struct; } + + 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; + 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; + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescrExtra.hpp */ diff --git a/include/reflect/TypeDrivenMap.hpp b/include/reflect/TypeDrivenMap.hpp new file mode 100644 index 0000000..c38c873 --- /dev/null +++ b/include/reflect/TypeDrivenMap.hpp @@ -0,0 +1,47 @@ +/* @file TypeDrivenMap.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/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/include/reflect/atomic/AtomicTdx.hpp b/include/reflect/atomic/AtomicTdx.hpp new file mode 100644 index 0000000..7b2e042 --- /dev/null +++ b/include/reflect/atomic/AtomicTdx.hpp @@ -0,0 +1,37 @@ +/* @file AtomicTdx.hpp */ + +#pragma once + +#include "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 TaggedPtr child_tp(uint32_t /*i*/, void * /*object*/) 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/include/reflect/init_reflect.hpp b/include/reflect/init_reflect.hpp new file mode 100644 index 0000000..00506a2 --- /dev/null +++ b/include/reflect/init_reflect.hpp @@ -0,0 +1,22 @@ +/* file init_reflect.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "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/include/reflect/pointer/PointerTdx.hpp b/include/reflect/pointer/PointerTdx.hpp new file mode 100644 index 0000000..d2d3b86 --- /dev/null +++ b/include/reflect/pointer/PointerTdx.hpp @@ -0,0 +1,76 @@ +/* file PointerTdx.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +#include "reflect/EstablishTypeDescr.hpp" +#include "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; + 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 ----- + + /* 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 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/include/reflect/struct/StructMember.hpp b/include/reflect/struct/StructMember.hpp new file mode 100644 index 0000000..eaddf3b --- /dev/null +++ b/include/reflect/struct/StructMember.hpp @@ -0,0 +1,236 @@ +/* @file StructMember.hpp */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include "reflect/EstablishTypeDescr.hpp" +//#include "reflect/Reflect.hpp" +#include "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/include/reflect/struct/StructTdx.hpp b/include/reflect/struct/StructTdx.hpp new file mode 100644 index 0000000..5e170ef --- /dev/null +++ b/include/reflect/struct/StructTdx.hpp @@ -0,0 +1,94 @@ +/* @file StructTdx.hpp */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +#include "reflect/TaggedPtr.hpp" +#include "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); + } + } + virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); } + virtual TaggedPtr child_tp(uint32_t i, void * object) 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/include/reflect/vector/VectorTdx.hpp b/include/reflect/vector/VectorTdx.hpp new file mode 100644 index 0000000..4f3309c --- /dev/null +++ b/include/reflect/vector/VectorTdx.hpp @@ -0,0 +1,100 @@ +/* file VectorTdx.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +//#include "reflect/TaggedPtr.hpp" +#include "reflect/EstablishTypeDescr.hpp" +//#include "reflect/TaggedPtr.hpp" +//#include +//#include + +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(); + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_vector; } + virtual uint32_t n_child(void * object) const override = 0; + 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; + }; /*VectorTdx*/ + + // ----- StlVectorTdx ----- + + /* require: + * - 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 uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + 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*/ + }; /*StlVectorTdx*/ + + // ----- std::array ----- + + /* coordinates with EstablishTdx>::make(), + * see [reflect/Reflect.hpp] + */ + + template + using StdArrayTdx = StlVectorTdx>; + + // ----- 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 uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); + + return establish_most_derived_tp(&((*vec)[i])); + } + }; /*StdVectorTdx*/ + } /*namespace reflect*/ + +} /*namespace xo*/ + +/* end VectorTdx.hpp */ diff --git a/repo/README.md b/repo/README.md new file mode 100644 index 0000000..3acdcde --- /dev/null +++ b/repo/README.md @@ -0,0 +1,5 @@ + +``` +cd reflect/repo +git submodule add git@github.com/someusername/someproject.git +``` diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt new file mode 100644 index 0000000..e1c1f65 --- /dev/null +++ b/src/reflect/CMakeLists.txt @@ -0,0 +1,52 @@ +# reflect/CMakeLists.txt + +set(SELF_LIBRARY_NAME reflect) +set(SELF_SOURCE_FILES TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) + +# build shared library 'reflect' +add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) + +set_target_properties(${SELF_LIBRARY_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1) + +# ---------------------------------------------------------------- +# 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}) +xo_install_library(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# dependencies: logutil, ... + +#xo_refcnt_dependency(${SELF_LIBRARY_NAME}) +#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) + +add_dependencies(${SELF_LIBRARY_NAME} refcnt) +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +add_dependencies(${SELF_LIBRARY_NAME} indentlog) +# note: can't use find_package() here, +# because find_package() needs to run successfully before +# dependency gets installed. +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) + +# ---------------------------------------------------------------- +# 3rd party dependency: boost: + +#xo_boost_dependency(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/src/reflect/TaggedRcptr.cpp b/src/reflect/TaggedRcptr.cpp new file mode 100644 index 0000000..cc461e0 --- /dev/null +++ b/src/reflect/TaggedRcptr.cpp @@ -0,0 +1,30 @@ +/* file TaggedRcptr.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "TaggedRcptr.hpp" +#include "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/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp new file mode 100644 index 0000000..564fec1 --- /dev/null +++ b/src/reflect/TypeDescr.cpp @@ -0,0 +1,194 @@ +/* @file TypeDescr.cpp */ + +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" +#include "TypeDescrExtra.hpp" +#include "atomic/AtomicTdx.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::xtag; + using xo::tostr; + + namespace reflect { + uint32_t + TypeId::s_next_id = 1; + + std::unordered_map> + TypeDescrBase::s_type_table_map; + + std::unordered_map + TypeDescrBase::s_coalesced_type_table_map; + + std::vector + TypeDescrBase::s_type_table_v; + + TypeDescrW + TypeDescrBase::require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + { + /* 1. lookup by tinfo hash_code in s_type_table_map */ + { + auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); + + if ((ix != s_type_table_map.end()) && ix->second) + return ix->second.get(); + } + + /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ + { + auto ix = s_coalesced_type_table_map.find(TypeInfoRef(tinfo)); + + if ((ix != s_coalesced_type_table_map.end()) && ix->second) + return ix->second; + } + + /* 3. O(n) lookup by canonical_name, before we create a new slot. + * + * Have to accept that on clang type_info objects aren't always unique (!$@#!!) + * + * TODO: lookup table keyed by canonical_name + */ + for (TypeDescrBase * x : s_type_table_v) { + if (x && (x->canonical_name() == canonical_name)) { + /* 1. assume *x represents the type associated with tinfo. + * 2. *do* store tinfo in s_coalesced_type_table_map[], + * for faster lookup next time + */ + s_coalesced_type_table_map[TypeInfoRef(tinfo)] = x; + + return x; + } + } + + TypeId id = TypeId::allocate(); + + std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; + + slot.reset(new TypeDescrBase(id, + tinfo, + canonical_name, + std::move(tdextra))); + + if (s_type_table_v.size() <= id.id()) + s_type_table_v.resize(id.id() + 1); + + s_type_table_v[id.id()] = slot.get(); + + return slot.get(); + } /*require*/ + + 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, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + : id_{std::move(id)}, + typeinfo_{tinfo}, + canonical_name_{std::move(canonical_name)}, + short_name_{unqualified_name(canonical_name_)}, + 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*/ + + 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*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescr.cpp */ diff --git a/src/reflect/TypeDescrExtra.cpp b/src/reflect/TypeDescrExtra.cpp new file mode 100644 index 0000000..e53a216 --- /dev/null +++ b/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/src/reflect/atomic/AtomicTdx.cpp b/src/reflect/atomic/AtomicTdx.cpp new file mode 100644 index 0000000..42ca9e2 --- /dev/null +++ b/src/reflect/atomic/AtomicTdx.cpp @@ -0,0 +1,24 @@ +/* @file AtomicTdx.cpp */ + +#include "atomic/AtomicTdx.hpp" +#include "TaggedPtr.hpp" + +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*/ + + 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/src/reflect/init_reflect.cpp b/src/reflect/init_reflect.cpp new file mode 100644 index 0000000..20dd144 --- /dev/null +++ b/src/reflect/init_reflect.cpp @@ -0,0 +1,23 @@ +/* file init_reflect.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_reflect.hpp" +#include "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/src/reflect/pointer/PointerTdx.cpp b/src/reflect/pointer/PointerTdx.cpp new file mode 100644 index 0000000..cc81e39 --- /dev/null +++ b/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/src/reflect/struct/StructMember.cpp b/src/reflect/struct/StructMember.cpp new file mode 100644 index 0000000..59d6905 --- /dev/null +++ b/src/reflect/struct/StructMember.cpp @@ -0,0 +1,36 @@ +/* file StructMember.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "struct/StructMember.hpp" +#include "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/src/reflect/struct/StructTdx.cpp b/src/reflect/struct/StructTdx.cpp new file mode 100644 index 0000000..593ad38 --- /dev/null +++ b/src/reflect/struct/StructTdx.cpp @@ -0,0 +1,55 @@ +/* @file StructTdx.cpp */ + +#include "struct/StructTdx.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(); + } + + StructMember const & member_info = this->member_v_[i]; + + return member_info.get_member_tp(object); + + } /*get_child*/ + + 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/src/reflect/vector/VectorTdx.cpp b/src/reflect/vector/VectorTdx.cpp new file mode 100644 index 0000000..fd2e40e --- /dev/null +++ b/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/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 0000000..80ce49a --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,57 @@ +# 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) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_include_options(${SELF_EXECUTABLE_NAME}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "indentlog/scope.hpp" +# from anywhere in the project +# PROJECT_BINARY_DIR: +# since version file will be in build directory, need that directory +# to also be included in compiler's include path +# +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC reflect) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/StructReflector.test.cpp b/utest/StructReflector.test.cpp new file mode 100644 index 0000000..5bf87ad --- /dev/null +++ b/utest/StructReflector.test.cpp @@ -0,0 +1,142 @@ +/* file StructReflector.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "reflect/Reflect.hpp" +#include "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/utest/StructTdx.test.cpp b/utest/StructTdx.test.cpp new file mode 100644 index 0000000..c20e213 --- /dev/null +++ b/utest/StructTdx.test.cpp @@ -0,0 +1,59 @@ +/* file StructTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "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); + //TypeDescr td = Reflect::require>(); + + 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 VectorTdx.test.cpp */ diff --git a/utest/VectorTdx.test.cpp b/utest/VectorTdx.test.cpp new file mode 100644 index 0000000..3836b4f --- /dev/null +++ b/utest/VectorTdx.test.cpp @@ -0,0 +1,181 @@ +/* file VectorTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "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/utest/reflect_utest_main.cpp b/utest/reflect_utest_main.cpp new file mode 100644 index 0000000..91c62d0 --- /dev/null +++ b/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 */