reflect: initial implementation
This commit is contained in:
parent
2e63293139
commit
fdb4ca37f4
39 changed files with 3662 additions and 0 deletions
184
CMakeLists.txt
Normal file
184
CMakeLists.txt
Normal file
|
|
@ -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=<INSTALL_DIR> -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
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=<INSTALL_DIR> -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
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=<INSTALL_DIR> -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
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)
|
||||
1
FILESYSTEM
Normal file
1
FILESYSTEM
Normal file
|
|
@ -0,0 +1 @@
|
|||
repo -- git submoduules here
|
||||
678
cmake/code-coverage.cmake
Normal file
678
cmake/code-coverage.cmake
Normal file
|
|
@ -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(<TARGET_NAME>)`, 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(<TARGET_NAME>)` 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 <PATTERNS> - 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 <TARGETS> - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output
|
||||
# ARGS <ARGUMENTS> - 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=$<TARGET_FILE:${TARGET_NAME}>" >>
|
||||
${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=$<TARGET_FILE:${SO_TARGET}>)
|
||||
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_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>"
|
||||
${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 $<TARGET_FILE:${TARGET_NAME}> ${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 $<TARGET_FILE:${TARGET_NAME}> ${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 $<TARGET_FILE:${TARGET_NAME}> ${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 $<TARGET_FILE:${TARGET_NAME}> ${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_FILE:${TARGET_NAME}> ${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_FILE:${TARGET_NAME}> ${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_FILE:${TARGET_NAME}> ${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 <PATTERNS> - 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()
|
||||
98
cmake/cxx.cmake
Normal file
98
cmake/cxx.cmake
Normal file
|
|
@ -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
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> # e.g. for #include "indentlog/scope.hpp"
|
||||
$<INSTALL_INTERFACE:include>
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/${target}> # e.g. for #include "Refcounted.hpp" in refcnt/src
|
||||
$<INSTALL_INTERFACE:include/${target}>
|
||||
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}> # 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 <built-in>:460:
|
||||
# <command line>: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()
|
||||
4
cmake/reflectConfig.cmake.in
Normal file
4
cmake/reflectConfig.cmake.in
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
8
cmake/run-external-ctest
Executable file
8
cmake/run-external-ctest
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# $1 = build directory
|
||||
|
||||
cd $1
|
||||
shift
|
||||
|
||||
ctest "${@}"
|
||||
32
include/reflect/CMakeLists.txt
Normal file
32
include/reflect/CMakeLists.txt
Normal file
|
|
@ -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
|
||||
62
include/reflect/EstablishTypeDescr.hpp
Normal file
62
include/reflect/EstablishTypeDescr.hpp
Normal file
|
|
@ -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<typename T>
|
||||
static TaggedPtr establish_tp(T * x) { return TaggedPtr(establish<T>(), x); }
|
||||
#endif
|
||||
template<typename T>
|
||||
static TaggedPtr establish_most_derived_tp(T * x) { return establish<T>()->most_derived_self_tp(x); }
|
||||
|
||||
template<typename T>
|
||||
static TypeDescrW establish() {
|
||||
TypeDescrW td = TypeDescrBase::require(&typeid(T),
|
||||
type_name<T>(),
|
||||
nullptr);
|
||||
|
||||
#ifdef NOT_USING
|
||||
std::function<TaggedPtr (void *)> to_self_tp;
|
||||
|
||||
if (std::is_base_of_v<SelfTagging, T>) {
|
||||
/* T is a descendant of SelfTagging (or T = SelfTagging);
|
||||
* use SelfTagging.self_tp()
|
||||
*/
|
||||
to_self_tp = [](void * x) { return reinterpret_cast<T *>(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<typename T>
|
||||
inline TaggedPtr establish_most_derived_tp(T * x) {
|
||||
return EstablishTypeDescr::establish_most_derived_tp<T>(x);
|
||||
}
|
||||
} /*namespace reflect*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end EstablishTypeDescr.hpp */
|
||||
38
include/reflect/Metatype.hpp
Normal file
38
include/reflect/Metatype.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* @file Metatype.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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 */
|
||||
235
include/reflect/Reflect.hpp
Normal file
235
include/reflect/Reflect.hpp
Normal file
|
|
@ -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 <vector>
|
||||
#include <array>
|
||||
#include <utility> // for std::pair<>
|
||||
|
||||
namespace xo {
|
||||
namespace reflect {
|
||||
template<typename T>
|
||||
class EstablishTdx {
|
||||
public:
|
||||
static std::unique_ptr<TypeDescrExtra> make() { return AtomicTdx::make(); }
|
||||
}; /*EstablishTdx*/
|
||||
|
||||
// ----- xo::ref::rp<Object> -----
|
||||
|
||||
/* definition provide after decl for Reflect {} below */
|
||||
template<typename Object>
|
||||
class EstablishTdx<xo::ref::rp<Object>> {
|
||||
public:
|
||||
static std::unique_ptr<TypeDescrExtra> make();
|
||||
}; /*EstablishTdx*/
|
||||
|
||||
// ----- std::array<Element, N> -----
|
||||
|
||||
/* definition provide after decl for Reflect {} below */
|
||||
template<typename Element, std::size_t N>
|
||||
class EstablishTdx<std::array<Element, N>> {
|
||||
public:
|
||||
static std::unique_ptr<TypeDescrExtra> make();
|
||||
}; /*EstablishTdx*/
|
||||
|
||||
// ----- std::vector<Element> -----
|
||||
|
||||
/* definition provide after decl for Reflect {} below */
|
||||
template<typename Element>
|
||||
class EstablishTdx<std::vector<Element>> {
|
||||
public:
|
||||
static std::unique_ptr<TypeDescrExtra> make();
|
||||
}; /*EstablishTdx*/
|
||||
|
||||
// ----- std::pair<Lhs, Rhs> -----
|
||||
|
||||
/* definition provide after decl for Reflect {} below */
|
||||
template<typename Lhs, typename Rhs>
|
||||
class EstablishTdx<std::pair<Lhs, Rhs>> {
|
||||
public:
|
||||
static std::unique_ptr<TypeDescrExtra> make();
|
||||
}; /*EstablishTdx*/
|
||||
|
||||
// ----- MakeTagged -----
|
||||
|
||||
template<typename T>
|
||||
class TaggedPtrMaker {
|
||||
public:
|
||||
static TaggedPtr make_tp(T * x);
|
||||
static TaggedRcptr make_rctp(T * x);
|
||||
};
|
||||
|
||||
template<>
|
||||
class TaggedPtrMaker<SelfTagging> {
|
||||
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<mytype>()) { ... }
|
||||
*/
|
||||
template<typename T>
|
||||
static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); }
|
||||
|
||||
/* Use:
|
||||
* using mytype = ...;
|
||||
* TypeDescrW td = Reflect::require<mytype>();
|
||||
*
|
||||
* Note:
|
||||
* To avoid cyclic header dependencies
|
||||
* (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.},
|
||||
* we use a 2-stage setup process:
|
||||
*
|
||||
* 1. EstablishTypeDescr::establish<T>() creates a TypeDescr* object
|
||||
* with lowest-common-denominator .tdextra AtomicTdx.
|
||||
* (see [reflect/EstablishTypeDescr.hpp])
|
||||
*
|
||||
* 2. Reflect::require<T>() 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<T>),
|
||||
* 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<typename T>
|
||||
static TypeDescrW require() {
|
||||
TypeDescrW retval_td = EstablishTypeDescr::establish<T>();
|
||||
|
||||
/* 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<T *>;
|
||||
*
|
||||
*/
|
||||
if (retval_td->mark_complete()) {
|
||||
/* control here on 2nd+later calls to require<T>().
|
||||
* in principle can immediately short-circuit.
|
||||
*/
|
||||
} else {
|
||||
/* control comes here the first time require<T>() runs */
|
||||
|
||||
auto final_tdx = EstablishTdx<T>::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<typename T>
|
||||
static TaggedPtr make_tp(T * x) { return TaggedPtrMaker<T>::make_tp(x); }
|
||||
|
||||
template<typename T>
|
||||
static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker<T>::make_rctp(x); }
|
||||
}; /*Reflect*/
|
||||
|
||||
// ----- MakeTagged -----
|
||||
|
||||
template<typename T>
|
||||
TaggedPtr
|
||||
TaggedPtrMaker<T>::make_tp(T * x) {
|
||||
return TaggedPtr(Reflect::require<T>(), x);
|
||||
} /*make_tp*/
|
||||
|
||||
template<typename T>
|
||||
TaggedRcptr
|
||||
TaggedPtrMaker<T>::make_rctp(T * x) {
|
||||
return TaggedRcptr(Reflect::require<T>(), x);
|
||||
} /*make_rctp*/
|
||||
|
||||
// ----- xo::ref::rp<Object> -----
|
||||
|
||||
/* declared above before
|
||||
* class Reflect { .. }
|
||||
*/
|
||||
template<typename Object>
|
||||
std::unique_ptr<TypeDescrExtra>
|
||||
EstablishTdx<xo::ref::rp<Object>>::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<Object>();
|
||||
|
||||
return RefPointerTdx<xo::ref::rp<Object>>::make();
|
||||
} /*make*/
|
||||
|
||||
// ----- std::array<Element, N> -----
|
||||
|
||||
/* declared above before
|
||||
* class Reflect { .. }
|
||||
*/
|
||||
template<typename Element, std::size_t N>
|
||||
std::unique_ptr<TypeDescrExtra>
|
||||
EstablishTdx<std::array<Element, N>>::make() {
|
||||
/* need to ensure Element is properly reflected */
|
||||
Reflect::require<Element>();
|
||||
|
||||
return StdArrayTdx<Element, N>::make();
|
||||
} /*make*/
|
||||
|
||||
// ----- std::vector<Element> -----
|
||||
|
||||
/* declared above before
|
||||
* class Reflect { .. }
|
||||
*/
|
||||
template<typename Element>
|
||||
std::unique_ptr<TypeDescrExtra>
|
||||
EstablishTdx<std::vector<Element>>::make() {
|
||||
/* need to ensure Element is properly reflected */
|
||||
Reflect::require<Element>();
|
||||
|
||||
return StdVectorTdx<Element>::make();
|
||||
} /*make*/
|
||||
|
||||
// ----- std::pair<Lhs, Rhs> -----
|
||||
|
||||
/* declared above before
|
||||
* class Reflect { .. }
|
||||
*/
|
||||
template<typename Lhs, typename Rhs>
|
||||
std::unique_ptr<TypeDescrExtra>
|
||||
EstablishTdx<std::pair<Lhs, Rhs>>::make() {
|
||||
/* need to ensure Lhs, Rhs are properly reflected */
|
||||
Reflect::require<Lhs>();
|
||||
Reflect::require<Rhs>();
|
||||
|
||||
return StructTdx::pair<Lhs, Rhs>();
|
||||
} /*make*/
|
||||
} /*namespace reflect*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end Reflect.hpp */
|
||||
31
include/reflect/SelfTagging.hpp
Normal file
31
include/reflect/SelfTagging.hpp
Normal file
|
|
@ -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 */
|
||||
161
include/reflect/StructReflector.hpp
Normal file
161
include/reflect/StructReflector.hpp
Normal file
|
|
@ -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 <vector>
|
||||
|
||||
namespace xo {
|
||||
namespace reflect {
|
||||
template<typename StructT, bool IsSelfTaggingDescendant>
|
||||
class SelfTagger {};
|
||||
|
||||
template<typename StructT>
|
||||
struct SelfTagger<StructT, true> {
|
||||
static TaggedPtr self_tp(void * object) {
|
||||
return (reinterpret_cast<StructT *>(object))->self_tp();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename StructT>
|
||||
struct SelfTagger<StructT, false> {
|
||||
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<Foo> 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<typename StructT>
|
||||
class StructReflector {
|
||||
public:
|
||||
using struct_t = StructT;
|
||||
|
||||
public:
|
||||
StructReflector() : td_{EstablishTypeDescr::establish<StructT>()} {}
|
||||
~StructReflector() {
|
||||
this->require_complete();
|
||||
}
|
||||
|
||||
bool is_complete() const { return s_reflected_flag; }
|
||||
bool is_incomplete() const { return !s_reflected_flag; }
|
||||
|
||||
template<typename OwnerT, typename MemberT>
|
||||
void reflect_member(std::string const & member_name,
|
||||
MemberT OwnerT::* member_addr) {
|
||||
|
||||
auto accessor
|
||||
(GeneralStructMemberAccessor<StructT, OwnerT, MemberT>::make(member_addr));
|
||||
|
||||
/* used to do this in GeneralStructMemberAccessor<> ctor,
|
||||
* but that introduces #include cycle
|
||||
*/
|
||||
Reflect::require<MemberT>();
|
||||
|
||||
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<SelfTagging, StructT>;
|
||||
|
||||
/* if self-tagging, can use .self_tp() to get most-derived tagged pointer */
|
||||
auto to_self_tp_fn
|
||||
= ([](void * object)
|
||||
{
|
||||
return SelfTagger<StructT, have_to_self_tp>::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<typename AncestorT>
|
||||
void adopt_ancestors() {
|
||||
assert(Reflect::is_reflected<AncestorT>());
|
||||
|
||||
TypeDescr ancestor_td = Reflect::require<AncestorT>();
|
||||
|
||||
/* 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<StructT, AncestorT>());
|
||||
}
|
||||
} /*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<StructMember> member_v_;
|
||||
}; /*StructReflector*/
|
||||
|
||||
template<typename StructT>
|
||||
bool StructReflector<StructT>::s_reflected_flag = false;
|
||||
} /*namespace reflect*/
|
||||
|
||||
/* e.g.
|
||||
* struct Foo { int bar_; };
|
||||
* struct Bar : public Foo { .. };
|
||||
*
|
||||
* StructReflector<Bar> 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<Foo> sr;
|
||||
* REFLECT_LITERAL_MEMBER(sr, bar_);
|
||||
*
|
||||
* then REFLECT_LITERAL_MEMBER() expands to something like:
|
||||
* sr.reflect_member("bar_", &StructReflector<Foo>::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<Foo> 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*/
|
||||
125
include/reflect/TaggedPtr.hpp
Normal file
125
include/reflect/TaggedPtr.hpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/* @file TaggedPtr.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "reflect/TypeDescr.hpp"
|
||||
//#include "reflect/EstablishTypeDescr.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
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<typename T>
|
||||
static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require<T>(), 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<typename Fn>
|
||||
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<typename Fn>
|
||||
static void visit_graph(TaggedPtr tp, Fn && visit_fn) {
|
||||
std::unordered_set<void *> 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<typename T>
|
||||
T * recover_native() const { return this->td_->recover_native<T>(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<typename Fn>
|
||||
static void visit_graph_aux(TaggedPtr tp,
|
||||
Fn && visit_fn,
|
||||
std::unordered_set<void *> * 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 */
|
||||
|
||||
|
||||
88
include/reflect/TaggedRcptr.hpp
Normal file
88
include/reflect/TaggedRcptr.hpp
Normal file
|
|
@ -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<typename T>
|
||||
static TaggedRcptr make(T * x) { return TaggedRcptr(Reflect::require<T>(), x); }
|
||||
#endif
|
||||
|
||||
Refcount * rc_address() const {
|
||||
return reinterpret_cast<Refcount *>(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 */
|
||||
302
include/reflect/TypeDescr.hpp
Normal file
302
include/reflect/TypeDescr.hpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/* @file TypeDescr.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include "reflect/atomic/AtomicTdx.hpp"
|
||||
#include "reflect/TypeDescrExtra.hpp"
|
||||
#include "cxxutil/demangle.hpp"
|
||||
#include <iostream>
|
||||
#include <typeinfo>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
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<T>();
|
||||
*/
|
||||
template<typename T>
|
||||
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<xo::reflect::TypeInfoRef> {
|
||||
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<Foo<*>> fails]
|
||||
*/
|
||||
static TypeDescrW require(std::type_info const * tinfo,
|
||||
std::string_view canonical_name,
|
||||
std::unique_ptr<TypeDescrExtra> 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<typename T>
|
||||
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<typename T>
|
||||
T * recover_native(void * address) const {
|
||||
if (this->is_native<T>()) {
|
||||
return reinterpret_cast<T *>(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<T>, std::array<T,N>, ..):
|
||||
* .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<TypeDescrExtra> tdx);
|
||||
|
||||
private:
|
||||
TypeDescrBase(TypeId id,
|
||||
std::type_info const * tinfo,
|
||||
std::string_view canonical_name,
|
||||
std::unique_ptr<TypeDescrExtra> 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<TypeInfoRef, std::unique_ptr<TypeDescrBase>> 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<TypeInfoRef, TypeDescrBase *> s_coalesced_type_table_map;
|
||||
|
||||
/* vector of all TypeDescr instances. singleton. */
|
||||
static std::vector<TypeDescrBase *> 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<T>())
|
||||
* 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<TypeDescrExtra> 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 */
|
||||
65
include/reflect/TypeDescrExtra.hpp
Normal file
65
include/reflect/TypeDescrExtra.hpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* @file TypeDescrExtra.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "reflect/Metatype.hpp"
|
||||
#include <string>
|
||||
/* note: this file #include'd into TypeDescr.hpp */
|
||||
#include <cstdint>
|
||||
|
||||
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<int>
|
||||
* and
|
||||
* std::list<std::string>
|
||||
* 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 */
|
||||
47
include/reflect/TypeDrivenMap.hpp
Normal file
47
include/reflect/TypeDrivenMap.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/* @file TypeDrivenMap.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "reflect/TypeDescr.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace xo {
|
||||
namespace reflect {
|
||||
/* represents a map :: TypeId -> Value */
|
||||
template<typename Value>
|
||||
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<Value> contents_v_;
|
||||
}; /*TypeDrivenMap*/
|
||||
} /*namespace reflect*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end TypeDrivenMap.hpp */
|
||||
37
include/reflect/atomic/AtomicTdx.hpp
Normal file
37
include/reflect/atomic/AtomicTdx.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* @file AtomicTdx.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "reflect/TypeDescrExtra.hpp"
|
||||
//#include "reflect/TaggedPtr.hpp"
|
||||
#include <memory>
|
||||
|
||||
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<AtomicTdx> 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 */
|
||||
22
include/reflect/init_reflect.hpp
Normal file
22
include/reflect/init_reflect.hpp
Normal file
|
|
@ -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<S_reflect_tag> {
|
||||
static void init();
|
||||
static InitEvidence require();
|
||||
};
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end init_reflect.hpp */
|
||||
76
include/reflect/pointer/PointerTdx.hpp
Normal file
76
include/reflect/pointer/PointerTdx.hpp
Normal file
|
|
@ -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<T> for some T */
|
||||
template<typename Pointer>
|
||||
class RefPointerTdx : public PointerTdx {
|
||||
public:
|
||||
using target_t = Pointer;
|
||||
|
||||
static std::unique_ptr<RefPointerTdx> make() {
|
||||
return std::unique_ptr<RefPointerTdx>(new RefPointerTdx());
|
||||
} /*make*/
|
||||
|
||||
virtual uint32_t n_child(void * object) const override {
|
||||
/* e.g:
|
||||
* target_t = ref::rp<filter::KalmanFilterState>
|
||||
*/
|
||||
target_t * ptr = reinterpret_cast<target_t *>(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<target_t *>(object);
|
||||
|
||||
if (i > 0) {
|
||||
throw std::runtime_error(tostr("RefPointerTdx<T>::child_tp"
|
||||
": attempt to fetch child #i from a ref::rp<T>",
|
||||
xtag("T", type_name<target_t>()),
|
||||
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 */
|
||||
236
include/reflect/struct/StructMember.hpp
Normal file
236
include/reflect/struct/StructMember.hpp
Normal file
|
|
@ -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 <string>
|
||||
#include <memory>
|
||||
|
||||
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<int>();
|
||||
*/
|
||||
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<AbstractStructMemberAccessor> 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 <typename StructT, typename OwnerT, typename MemberT>
|
||||
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<MemberT>()},
|
||||
memptr_{memptr} {}
|
||||
GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default;
|
||||
virtual ~GeneralStructMemberAccessor() = default;
|
||||
|
||||
static std::unique_ptr<GeneralStructMemberAccessor> make(Memptr memptr) {
|
||||
return std::unique_ptr<GeneralStructMemberAccessor>(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<StructT>(); }
|
||||
|
||||
virtual TypeDescr member_td() const override { return this->member_td_; }
|
||||
|
||||
virtual void * address(void * struct_addr) const override {
|
||||
return this->address_impl(reinterpret_cast<StructT *>(struct_addr));
|
||||
} /*address*/
|
||||
|
||||
virtual std::unique_ptr<AbstractStructMemberAccessor> clone() const override {
|
||||
return std::unique_ptr<AbstractStructMemberAccessor>
|
||||
(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<Bar, Foo, int>::make(&Foo::x_);
|
||||
*
|
||||
* or equivalently:
|
||||
* auto foo_x_access = GeneralStructMemberAccessor<Foo, Foo, int>::make(&Foo::x_);
|
||||
* auto bar_x_access = AncestorStructMemberAccessor<Bar, Foo>::adopt(foo_x_access);
|
||||
*
|
||||
* can use the 2nd form to adopt accessors from an already-reflected ancestor class
|
||||
*
|
||||
* Require:
|
||||
* - StructT -isa-> AncestorT
|
||||
*/
|
||||
template <typename StructT, typename AncestorT>
|
||||
class AncestorStructMemberAccessor : public AbstractStructMemberAccessor {
|
||||
public:
|
||||
AncestorStructMemberAccessor(std::unique_ptr<AbstractStructMemberAccessor> ancestor_accessor)
|
||||
: ancestor_accessor_{std::move(ancestor_accessor)} {}
|
||||
AncestorStructMemberAccessor(AncestorStructMemberAccessor const & x) = default;
|
||||
virtual ~AncestorStructMemberAccessor() = default;
|
||||
|
||||
static std::unique_ptr<AncestorStructMemberAccessor>
|
||||
adopt(std::unique_ptr<AbstractStructMemberAccessor> ancestor_accessor) {
|
||||
return std::unique_ptr<AncestorStructMemberAccessor>
|
||||
(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<StructT *>(struct_addr);
|
||||
|
||||
return this->ancestor_accessor_->member_tp(ancestor_addr);
|
||||
} /*member_tp*/
|
||||
#endif
|
||||
|
||||
virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish<StructT>(); }
|
||||
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<StructT *>(struct_addr));
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<AbstractStructMemberAccessor> clone() const override {
|
||||
return std::unique_ptr<AbstractStructMemberAccessor>
|
||||
(new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone())));
|
||||
} /*clone*/
|
||||
|
||||
private:
|
||||
/* .ancestor_accessor fetches some particular member of AncestorT */
|
||||
std::unique_ptr<AbstractStructMemberAccessor> 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<AbstractStructMemberAccessor> 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 <typename DescendantT, typename StructT>
|
||||
StructMember for_descendant() const {
|
||||
assert(EstablishTypeDescr::establish<StructT>() == this->get_struct_td());
|
||||
|
||||
return StructMember(this->member_name(),
|
||||
std::move(AncestorStructMemberAccessor<DescendantT, StructT>::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<AbstractStructMemberAccessor> accessor_;
|
||||
}; /*StructMember*/
|
||||
} /*namespace reflect*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end StructMember.hpp */
|
||||
94
include/reflect/struct/StructTdx.hpp
Normal file
94
include/reflect/struct/StructTdx.hpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/* @file StructTdx.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "reflect/TypeDescrExtra.hpp"
|
||||
#include "reflect/TaggedPtr.hpp"
|
||||
#include "reflect/struct/StructMember.hpp"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
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<StructTdx> make(std::vector<StructMember> member_v,
|
||||
bool have_to_self_tp,
|
||||
std::function<TaggedPtr (void *)> to_self_tp);
|
||||
|
||||
/* specialization for std::pair<Lhs, Rhs>
|
||||
* coordinates with [reflect/Reflect.hpp]
|
||||
*/
|
||||
template<typename Lhs, typename Rhs>
|
||||
static std::unique_ptr<StructTdx> pair() {
|
||||
using struct_t = std::pair<Lhs, Rhs>;
|
||||
|
||||
std::vector<StructMember> mv;
|
||||
{
|
||||
auto lhs_access
|
||||
(GeneralStructMemberAccessor<struct_t, struct_t, Lhs>::make
|
||||
(&struct_t::first));
|
||||
|
||||
mv.push_back(StructMember("first", std::move(lhs_access)));
|
||||
}
|
||||
{
|
||||
auto rhs_access
|
||||
(GeneralStructMemberAccessor<struct_t, struct_t, Rhs>::make
|
||||
(&struct_t::second));
|
||||
|
||||
mv.push_back(StructMember("second", std::move(rhs_access)));
|
||||
}
|
||||
|
||||
std::function<TaggedPtr (void *)> 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<StructMember> member_v,
|
||||
bool have_to_self_tp,
|
||||
std::function<TaggedPtr (void*)> 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<StructMember> 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<TaggedPtr (void *)> to_self_tp_;
|
||||
}; /*StructTdx*/
|
||||
|
||||
} /*namespace reflect*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end StructTdx.hpp */
|
||||
100
include/reflect/vector/VectorTdx.hpp
Normal file
100
include/reflect/vector/VectorTdx.hpp
Normal file
|
|
@ -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 <vector>
|
||||
//#include <memory>
|
||||
|
||||
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<VectorTdx> 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<typename VectorT>
|
||||
class StlVectorTdx : public VectorTdx {
|
||||
public:
|
||||
using target_t = VectorT;
|
||||
|
||||
static std::unique_ptr<StlVectorTdx> make() {
|
||||
return std::unique_ptr<StlVectorTdx>(new StlVectorTdx());
|
||||
} /*make*/
|
||||
|
||||
virtual uint32_t n_child(void * object) const override {
|
||||
target_t * vec = reinterpret_cast<target_t *>(object);
|
||||
|
||||
return vec->size();
|
||||
} /*n_child*/
|
||||
|
||||
virtual TaggedPtr child_tp(uint32_t i, void * object) const override {
|
||||
target_t * vec = reinterpret_cast<target_t *>(object);
|
||||
|
||||
return establish_most_derived_tp(&((*vec)[i]));
|
||||
} /*child_tp*/
|
||||
}; /*StlVectorTdx*/
|
||||
|
||||
// ----- std::array<Element, N> -----
|
||||
|
||||
/* coordinates with EstablishTdx<std::array<Element, N>>::make(),
|
||||
* see [reflect/Reflect.hpp]
|
||||
*/
|
||||
|
||||
template<typename Element, std::size_t N>
|
||||
using StdArrayTdx = StlVectorTdx<std::array<Element, N>>;
|
||||
|
||||
// ----- std::vector<Element> -----
|
||||
|
||||
/* coordinates with EstablishTdx<std::vector<Element>>::make()
|
||||
* see [reflect/Reflect.hpp]
|
||||
*/
|
||||
template<typename Element>
|
||||
class StdVectorTdx : public VectorTdx {
|
||||
public:
|
||||
using target_t = std::vector<Element>;
|
||||
|
||||
static std::unique_ptr<StdVectorTdx> make() {
|
||||
return std::unique_ptr<StdVectorTdx>(new StdVectorTdx());
|
||||
} /*make*/
|
||||
|
||||
virtual uint32_t n_child(void * object) const override {
|
||||
target_t * vec = reinterpret_cast<target_t *>(object);
|
||||
|
||||
return vec->size();
|
||||
} /*n_child*/
|
||||
|
||||
virtual TaggedPtr child_tp(uint32_t i, void * object) const override {
|
||||
target_t * vec = reinterpret_cast<target_t *>(object);
|
||||
|
||||
return establish_most_derived_tp(&((*vec)[i]));
|
||||
}
|
||||
}; /*StdVectorTdx*/
|
||||
} /*namespace reflect*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end VectorTdx.hpp */
|
||||
5
repo/README.md
Normal file
5
repo/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
```
|
||||
cd reflect/repo
|
||||
git submodule add git@github.com/someusername/someproject.git
|
||||
```
|
||||
52
src/reflect/CMakeLists.txt
Normal file
52
src/reflect/CMakeLists.txt
Normal file
|
|
@ -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
|
||||
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/local/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
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
|
||||
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/local/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3rd party dependency: boost:
|
||||
|
||||
#xo_boost_dependency(${SELF_LIBRARY_NAME})
|
||||
|
||||
# end CMakeLists.txt
|
||||
30
src/reflect/TaggedRcptr.cpp
Normal file
30
src/reflect/TaggedRcptr.cpp
Normal file
|
|
@ -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 << "<TaggedRcptr"
|
||||
<< xtag("type", this->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 */
|
||||
194
src/reflect/TypeDescr.cpp
Normal file
194
src/reflect/TypeDescr.cpp
Normal file
|
|
@ -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<TypeInfoRef, std::unique_ptr<TypeDescrBase>>
|
||||
TypeDescrBase::s_type_table_map;
|
||||
|
||||
std::unordered_map<TypeInfoRef, TypeDescrBase*>
|
||||
TypeDescrBase::s_coalesced_type_table_map;
|
||||
|
||||
std::vector<TypeDescrW>
|
||||
TypeDescrBase::s_type_table_v;
|
||||
|
||||
TypeDescrW
|
||||
TypeDescrBase::require(std::type_info const * tinfo,
|
||||
std::string_view canonical_name,
|
||||
std::unique_ptr<TypeDescrExtra> 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<TypeDescrBase> & 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 << "<type_table_v[" << s_type_table_v.size() << "]:";
|
||||
|
||||
for (TypeDescrBase * td : s_type_table_v) {
|
||||
os << "\n ";
|
||||
if (td) {
|
||||
td->display(os);
|
||||
}
|
||||
}
|
||||
|
||||
os << ">\n";
|
||||
} /*print_reflected_types*/
|
||||
|
||||
namespace {
|
||||
/* readability hack:
|
||||
* foo::bar::Quux ==> Quux
|
||||
* but lookout for template names:
|
||||
* std::pair<std::bar, std::foo> ==> pair<std::bar, std::foo>
|
||||
*/
|
||||
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<TypeDescrExtra> 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 << "<TypeDescr"
|
||||
<< xtag("id", id_)
|
||||
<< xtag("canonical_name", canonical_name_)
|
||||
<< xtag("complete", complete_flag_)
|
||||
<< xtag("metatype", this->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<TypeDescrExtra> 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 */
|
||||
37
src/reflect/TypeDescrExtra.cpp
Normal file
37
src/reflect/TypeDescrExtra.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* file TypeDescrExtra.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2022
|
||||
*/
|
||||
|
||||
#include "TypeDescrExtra.hpp"
|
||||
#include "TypeDescr.hpp"
|
||||
#include "TaggedPtr.hpp"
|
||||
#include <cassert>
|
||||
|
||||
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 */
|
||||
24
src/reflect/atomic/AtomicTdx.cpp
Normal file
24
src/reflect/atomic/AtomicTdx.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* @file AtomicTdx.cpp */
|
||||
|
||||
#include "atomic/AtomicTdx.hpp"
|
||||
#include "TaggedPtr.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace reflect {
|
||||
std::unique_ptr<AtomicTdx> AtomicTdx::make() {
|
||||
return std::unique_ptr<AtomicTdx>(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 */
|
||||
23
src/reflect/init_reflect.cpp
Normal file
23
src/reflect/init_reflect.cpp
Normal file
|
|
@ -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<S_reflect_tag>::init()
|
||||
{
|
||||
/* placeholder -- expecting there to be non-trivial content soon */
|
||||
} /*init*/
|
||||
|
||||
InitEvidence
|
||||
InitSubsys<S_reflect_tag>::require()
|
||||
{
|
||||
return Subsystem::provide<S_reflect_tag>("reflect", &init);
|
||||
} /*require*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end init_reflect.cpp */
|
||||
17
src/reflect/pointer/PointerTdx.cpp
Normal file
17
src/reflect/pointer/PointerTdx.cpp
Normal file
|
|
@ -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 */
|
||||
36
src/reflect/struct/StructMember.cpp
Normal file
36
src/reflect/struct/StructMember.cpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* file StructMember.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2022
|
||||
*/
|
||||
|
||||
#include "struct/StructMember.hpp"
|
||||
#include "indentlog/scope.hpp"
|
||||
#include <type_traits>
|
||||
|
||||
namespace xo {
|
||||
using xo::scope;
|
||||
using xo::xtag;
|
||||
|
||||
namespace reflect {
|
||||
static_assert(std::is_move_constructible_v<StructMember>);
|
||||
|
||||
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 */
|
||||
55
src/reflect/struct/StructTdx.cpp
Normal file
55
src/reflect/struct/StructTdx.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* @file StructTdx.cpp */
|
||||
|
||||
#include "struct/StructTdx.hpp"
|
||||
|
||||
namespace xo {
|
||||
using std::uint32_t;
|
||||
|
||||
namespace reflect {
|
||||
std::unique_ptr<StructTdx>
|
||||
StructTdx::make(std::vector<StructMember> member_v,
|
||||
bool have_to_self_tp,
|
||||
std::function<TaggedPtr (void*)> to_self_tp)
|
||||
{
|
||||
return std::unique_ptr<StructTdx>(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 */
|
||||
20
src/reflect/vector/VectorTdx.cpp
Normal file
20
src/reflect/vector/VectorTdx.cpp
Normal file
|
|
@ -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 */
|
||||
57
utest/CMakeLists.txt
Normal file
57
utest/CMakeLists.txt
Normal file
|
|
@ -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
|
||||
142
utest/StructReflector.test.cpp
Normal file
142
utest/StructReflector.test.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/* file StructReflector.test.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2022
|
||||
*/
|
||||
|
||||
#include "reflect/Reflect.hpp"
|
||||
#include "reflect/StructReflector.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#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<TestStruct1> sr;
|
||||
|
||||
REQUIRE(Reflect::is_reflected<TestStruct0>() == false);
|
||||
REQUIRE(Reflect::is_reflected<TestStruct1>() == true);
|
||||
|
||||
TestStruct0 recd0;
|
||||
TaggedPtr tp = Reflect::make_tp(&recd0);
|
||||
|
||||
REQUIRE(tp.address() == &recd0);
|
||||
REQUIRE(tp.td() == Reflect::require<TestStruct0>());
|
||||
|
||||
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<TestStructS1> sr;
|
||||
|
||||
REQUIRE(Reflect::is_reflected<TestStructS1>() == true);
|
||||
|
||||
//sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_);
|
||||
REFLECT_LITERAL_MEMBER(sr, x_);
|
||||
|
||||
REQUIRE(!Reflect::require<TestStructS1>()->is_struct());
|
||||
|
||||
sr.require_complete();
|
||||
|
||||
REQUIRE(Reflect::require<TestStructS1>()->is_struct());
|
||||
} /*TEST_CASE(struct-reflect-s1)*/
|
||||
|
||||
namespace {
|
||||
struct TestStructS2 { int x_; };
|
||||
}
|
||||
|
||||
TEST_CASE("struct-reflect-s2", "[reflect]") {
|
||||
StructReflector<TestStructS2> sr;
|
||||
|
||||
REQUIRE(Reflect::is_reflected<TestStructS2>() == true);
|
||||
|
||||
//sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_);
|
||||
REFLECT_MEMBER(sr, x);
|
||||
|
||||
REQUIRE(!Reflect::require<TestStructS2>()->is_struct());
|
||||
|
||||
sr.require_complete();
|
||||
|
||||
REQUIRE(Reflect::require<TestStructS2>()->is_struct());
|
||||
|
||||
TestStructS2 recd1{666};
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&recd1);
|
||||
|
||||
REQUIRE(tp.address() == &recd1);
|
||||
REQUIRE(tp.td() == Reflect::require<TestStructS2>());
|
||||
|
||||
REQUIRE(tp.n_child() == 1);
|
||||
|
||||
REQUIRE(tp.get_child(0).td() == Reflect::require<int>());
|
||||
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<TestStructS3> sr;
|
||||
|
||||
REQUIRE(Reflect::is_reflected<TestStructS3>() == true);
|
||||
|
||||
REFLECT_MEMBER(sr, x);
|
||||
REFLECT_MEMBER(sr, y);
|
||||
REFLECT_MEMBER(sr, z);
|
||||
|
||||
REQUIRE(!Reflect::require<TestStructS3>()->is_struct());
|
||||
|
||||
sr.require_complete();
|
||||
|
||||
REQUIRE(Reflect::require<TestStructS3>()->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<TestStructS3>());
|
||||
|
||||
REQUIRE(tp.n_child() == 3);
|
||||
|
||||
REQUIRE(tp.get_child(0).td() == Reflect::require<int>());
|
||||
REQUIRE(tp.get_child(0).address() == &(recd1.x_));
|
||||
|
||||
REQUIRE(tp.get_child(1).td() == Reflect::require<char>());
|
||||
REQUIRE(tp.get_child(1).address() == &(recd1.y_));
|
||||
|
||||
REQUIRE(tp.get_child(2).td() == Reflect::require<double>());
|
||||
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 */
|
||||
59
utest/StructTdx.test.cpp
Normal file
59
utest/StructTdx.test.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/* file StructTdx.test.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2022
|
||||
*/
|
||||
|
||||
#include "reflect/Reflect.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
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<int, double> p;
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&p);
|
||||
//TypeDescr td = Reflect::require<std::vector<double>>();
|
||||
|
||||
REQUIRE(Reflect::is_reflected<std::pair<int, double>>() == 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<std::pair<int, double>>() == &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<int>() == &(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<double>() == &(p.second));
|
||||
REQUIRE(tp1.n_child() == 0);
|
||||
|
||||
} /*TEST_CASE(std-pair-reflect)*/
|
||||
|
||||
} /*namespace ut*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end VectorTdx.test.cpp */
|
||||
181
utest/VectorTdx.test.cpp
Normal file
181
utest/VectorTdx.test.cpp
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/* file VectorTdx.test.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2022
|
||||
*/
|
||||
|
||||
#include "reflect/Reflect.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
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<double> v;
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&v);
|
||||
//TypeDescr td = Reflect::require<std::vector<double>>();
|
||||
|
||||
REQUIRE(Reflect::is_reflected<std::vector<double>>() == 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<std::vector<double>>() == &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<double> v = { 1.123 };
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&v);
|
||||
|
||||
REQUIRE(Reflect::is_reflected<std::vector<double>>() == 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<std::vector<double>>() == &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<double>() == &(v[0]));
|
||||
REQUIRE(tp0.n_child() == 0);
|
||||
} /*TEST_CASE(std-vector-reflect-one)*/
|
||||
|
||||
TEST_CASE("std-vector-reflect-two", "[reflect]") {
|
||||
std::vector<double> v = { 1.123, 2.234 };
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&v);
|
||||
|
||||
REQUIRE(Reflect::is_reflected<std::vector<double>>() == 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<std::vector<double>>() == &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<double>() == &(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<double>() == &(v[1]));
|
||||
REQUIRE(tp1.n_child() == 0);
|
||||
} /*TEST(std-vector-reflect-two)*/
|
||||
|
||||
// ----- std::array -----
|
||||
|
||||
TEST_CASE("std-array-reflect-empty", "[reflect]") {
|
||||
std::array<double, 0> v;
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&v);
|
||||
//TypeDescr td = Reflect::require<std::array<double, xx>>();
|
||||
|
||||
REQUIRE(Reflect::is_reflected<std::array<double, 0>>() == 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<std::array<double, 0>>() == &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<double, 1> v = { 1.123 };
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&v);
|
||||
|
||||
REQUIRE(Reflect::is_reflected<std::array<double, 1>>() == 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<std::array<double, 1>>() == &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<double>() == &(v[0]));
|
||||
REQUIRE(tp0.n_child() == 0);
|
||||
} /*TEST_CASE(std-array-reflect-one)*/
|
||||
|
||||
TEST_CASE("std-array-reflect-two", "[reflect]") {
|
||||
std::array<double, 2> v = { 1.123, 2.234 };
|
||||
|
||||
TaggedPtr tp = Reflect::make_tp(&v);
|
||||
|
||||
REQUIRE(Reflect::is_reflected<std::array<double, 2>>() == 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<std::array<double, 2>>() == &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<double>() == &(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<double>() == &(v[1]));
|
||||
REQUIRE(tp1.n_child() == 0);
|
||||
} /*TEST(std-array-reflect-two)*/
|
||||
|
||||
} /*namespace ut*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end VectorTdx.test.cpp */
|
||||
6
utest/reflect_utest_main.cpp
Normal file
6
utest/reflect_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/* file reflect_utest_main.cpp */
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
/* end reflect_utest_main.cpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue